前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >最简 bootloader

最简 bootloader

作者头像
IOT物联网小镇
发布2021-05-13 11:36:03
7970
发布2021-05-13 11:36:03
举报
文章被收录于专栏:IOT物联网小镇

文章主题

bootloader 是什么?如果你看到了这篇文章,肯定已经知道答案了,所以这里就不赘述了。这篇文章主要是根据韦东山老师的视频,从零开始写一个最简单的 bootloader,每一行代码都是手动输入。虽然直接看一遍视频,也能够理解其中的步骤或者原理,但是根据视频敲一遍之后,印象才是最深刻的。

内容导航

  • 测试平台
  • 文件目录结构
  • 代码详解
  • 一些操作指令和流程

测试平台

开发环境:电脑 MAC Pro,安装2个虚拟机 Windows7 和Ubuntu14.04。

  • Win7: 用于代码编辑(SourceInsight)和烧录裸板程序(oflash)。需要安装驱动程序:OpenJTAG 驱动,PL2303 USB 转串口驱动。
  • Ubuntu14.04:用于交叉编译,交叉编译工具链直接使用光盘里提供的 arm-linux-gcc 即可。
  • 文件传送:Win7 与 Ubuntu 之间的文件复制使用 SecureCRT。

开发板:JZ2440-V3,这块板子上已经集成了 USB转串口芯片。

烧录工具:OpenJTAG,连接开发板的 JTAG 接口即可。

测试工具:串口,直接连接 MicroUSB 口到 PC 的 USB 口,串口工具 SecureCRT。

文件目录结构

start.S:第一个启动的程序,完成的功能是:

init.c:串口的初始化和发送数据,NandFlash 的初始化和读取数据,BSS 段初始化。

setup.h:主要是定义了向内核传递参数时所需要的 tag 结构体。

boot.c:main 函数,设置 bootloader 向内核传递的启动参数。

boot.lds:连接脚本。

Makefile:make 编译指令。

代码详解

我比较赞成“代码即注释”这样的编程风格,只要看代码中的注释,就能明白其中的逻辑。所以,这里只贴出代码,不再进行进一步解释说明了。

1、start.S

代码语言:javascript
复制
  1. #define S3C2440_MPLL_200MHZ ((0x5c<<12) | (0x01<<4) | (0x02))
  2. #define MEM_CTL_BASE 0x48000000
  3. .text
  4. .global _start
  5. _start:
  6. /* 1. 关看门狗 */
  7. ldr r0, =0x53000000
  8. mov r1, #0
  9. str r1, [r0]
  10. /* 2. 设置时钟 */
  11. ldr r0, =0x4c000014
  12. mov r1, #0x03 /* FLCK:HCLK:PCLK = 1:2:4, HDIVN=1, PDIVN=1*/
  13. str r1, [r0]
  14. /* 如果 HDIVN 非0, CPU 的总线模式应该从 "fast bus mode" 改为 "asynchronous bus mod"*/
  15. mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
  16. orr r1, r1, #0xc0000000 /* 设置为 "asynchronous bus mod" */
  17. mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
  18. /* 设置 MPLLCON */
  19. ldr r0, =0x4c000004
  20. ldr r1, =S3C2440_MPLL_200MHZ
  21. str r1, [r0]
  22. /* 3. 初始化SDRAM */
  23. ldr r0, =MEM_CTL_BASE
  24. adr r1, sdram_config
  25. add r3, r0, #(13*4)
  26. 1:
  27. ldr r2, [r1], #4
  28. str r2, [r0], #4
  29. cmp r0, r3
  30. bne 1b /* b表示向前跳转到标号1的地方*/
  31. /* 4. 重定位:把bootloader本身的代码,从flash复制到它的链接地址上去 */
  32. ldr sp, =0x34000000 /* 把堆栈设置为 SDRAM 的最顶端,因为是向下增长的 */
  33. bl nand_init /* 初始化 NAND Flash */
  34. mov r0, #0 /* 第一个参数:源地址 */
  35. ldr r1, =_start /* 第二个参数:目标地址,即链接地址*/
  36. ldr r2, =__bss_start
  37. sub r2, r2, r1 /* 第三个参数:代码长度*/
  38. bl copy_code_to_sdram /* 调用代码拷贝函数 */
  39. bl clean_bss /* 调用 bss 段清零函数 */
  40. /* 5. 执行 main */
  41. ldr lr, =halt
  42. ldr pc, =main
  43. halt:
  44. b halt
  45. /* SDRAM 13 个控制器的值 */
  46. sdram_config:
  47. .long 0x22011110 // BWSCON
  48. .long 0x00000700 // BANKCON0
  49. .long 0x00000700 // BANKCON1
  50. .long 0x00000700 // BANKCON2
  51. .long 0x00000700 // BANKCON3
  52. .long 0x00000700 // BANKCON4
  53. .long 0x00000700 // BANKCON5
  54. .long 0x00018005 // BANKCON6
  55. .long 0x00018005 // BANKCON7
  56. .long 0x008c04f4 // REFRESH
  57. .long 0x000000b1 // BANKSIZE
  58. .long 0x00000030 // MRSRB6
  59. .long 0x00000030 // MRSRB7

2、init.c

代码语言:javascript
复制
  1. /* Nand Flash 相关寄存器 */
  2. #define NFCONF (*((volatile unsigned long *)0x4E000000))
  3. #define NFCONT (*((volatile unsigned long *)0x4E000004))
  4. #define NFCMMD (*((volatile unsigned char *)0x4E000008))
  5. #define NFADDR (*((volatile unsigned char *)0x4E00000C))
  6. #define NFDATA (*((volatile unsigned char *)0x4E000010))
  7. #define NFSTAT (*((volatile unsigned char *)0x4E000020))
  8. /* 串口 GPIO 配置*/
  9. #define GPHCON (*((volatile unsigned char *)0x56000070))
  10. #define GPHUP (*((volatile unsigned char *)0x56000078))
  11. /* 串口寄存器 */
  12. #define ULCON0 (*(volatile unsigned long *)0x50000000)
  13. #define UCON0 (*(volatile unsigned long *)0x50000004)
  14. #define UFCON0 (*(volatile unsigned long *)0x50000008)
  15. #define UMCON0 (*(volatile unsigned long *)0x5000000c)
  16. #define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
  17. #define UTXH0 (*(volatile unsigned char *)0x50000020)
  18. #define URXH0 (*(volatile unsigned char *)0x50000024)
  19. #define UBRDIV0 (*(volatile unsigned long *)0x50000028)
  20. #define PCLK 50000000 // init.c
  21. #define UART_CLK PCLK // UART0 的时钟源设置为 PCLK
  22. #define UART_BAUD_RATE 115200 // 波特率
  23. #define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
  24. #define TXD0READY (1<<2)
  25. #define RXD0READY (1)
  26. extern void nand_read(unsigned int addr, unsigned char*buf, unsigned int len);
  27. /* UART0 初始化 */
  28. void uart0_init(void)
  29. {
  30. GPHCON |= 0xa0; // GPH2,GPH3 用作 TXD0,RXD0
  31. GPHUP = 0x0c; // GPH2,GPH3内部上拉
  32. ULCON0 = 0x03; // 8N1
  33. UCON0 = 0x05; //查询方式,UART时钟源为 PCLK
  34. UFCON0 = 0x00; // 不适用FIFO
  35. UMCON0 = 0x00; // 不使用流控
  36. UBRDIV0 = UART_BRD; // 波特率115200
  37. }
  38. /*
  39. * 发送一个字符
  40. */
  41. void putc(unsigned char c)
  42. {
  43. /* 等待,直到发送缓冲区中的数据已经全部发送出去 */
  44. while (!(UTRSTAT0 & TXD0READY));
  45. /* 向 UTXH0 寄存器中写入数据,UART即自动将它发送出去 */
  46. UTXH0 = c;
  47. }
  48. unsigned char getc(void)
  49. {
  50. /* 等待,直到接受缓冲区有数据 */
  51. while (!(UTRSTAT0 & RXD0READY));
  52. /* 直接读取 URXH0 寄存器,即可获得接收到的数据 */
  53. return URXH0;
  54. }
  55. void puts(char *str)
  56. {
  57. int i = 0;
  58. while(str[i])
  59. {
  60. putc(str[i]);
  61. i++;
  62. }
  63. }
  64. /*
  65. * Check if boot from Nor Flash
  66. * Return: 1-Yes, 0-No
  67. */
  68. int isbootFromNorFlash(void)
  69. {
  70. volatile int *p = (volatile int *)0;
  71. int val;
  72. val = *p;
  73. *p = 0x12345678;
  74. if (*p == 0x12345678)
  75. {
  76. /* write data success, boot form Nand Flash */
  77. *p = val;
  78. return 0;
  79. }
  80. else
  81. {
  82. /* write data failed, boot from Nor Flash */
  83. return 1;
  84. }
  85. }
  86. /*
  87. * Init Nand Flash Device
  88. * Return: None
  89. */
  90. void nand_init(void)
  91. {
  92. /* NandFlash 的读写需要满足一定的时序,
  93. 首先查找 NAND FLASH 手册,确定每个信号需要持续的时间,以及两个信号之间需要等待的时间。
  94. 然后查找 2440 手册,确定这些时间要设置的是哪个寄存器的哪些bit位,然后通过公式计算出应该设置的值*/
  95. #define TACLS 0
  96. #define TWRPH0 3
  97. #define TWRPH1 0
  98. /* 设置时序*/
  99. NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
  100. /* 使能 NAND FLASH 控制器,初始化 ECC, 禁止片选 */
  101. NFCONT = (1<<4)|(1<<1)|(1<<0);
  102. }
  103. void nand_select(void)
  104. {
  105. NFCONT &= ~(1 << 1);
  106. }
  107. void nand_deselect(void)
  108. {
  109. NFCONT |= (1 << 1);
  110. }
  111. void nand_cmd(unsigned char cmd)
  112. {
  113. unsigned int i;
  114. NFCMMD = cmd;
  115. for (i = 0;i < 10; i++);
  116. }
  117. void nand_addr(unsigned int addr)
  118. {
  119. volatile unsigned int i;
  120. unsigned int col = addr % 2048;
  121. unsigned int page = addr / 2048;
  122. /* 发出列地址 */
  123. NFADDR = col & 0xFF;
  124. for (i = 0;i < 10; i++);
  125. NFADDR = (col >> 8) & 0xFF;
  126. for (i = 0;i < 10; i++);
  127. /* 发出行地址,也就是页地址 */
  128. NFADDR = page & 0xFF;
  129. for (i = 0;i < 10; i++);
  130. NFADDR = (page >> 8) & 0xFF;
  131. for (i = 0;i < 10; i++);
  132. NFADDR = (page >> 16) & 0xFF;
  133. for (i = 0;i < 10; i++);
  134. }
  135. void nand_wait_ready(void)
  136. {
  137. while (!(NFSTAT & 1));
  138. }
  139. char nand_data(void)
  140. {
  141. return NFDATA;
  142. }
  143. /*
  144. * Read data from Nand Flash
  145. * Return: None
  146. */
  147. void nand_read(unsigned int addr, unsigned char*buf, unsigned int len)
  148. {
  149. unsigned int i = 0;
  150. int col = addr%2048;
  151. /* 1. 选中 */
  152. nand_select();
  153. while (i < len)
  154. {
  155. /* 2. 发出读命令 0x00 */
  156. nand_cmd(0x00);
  157. /* 3. 发出地址(分5步发出) */
  158. nand_addr(addr);
  159. /* 4. 发出读命令 0x30 */
  160. nand_cmd(0x30);
  161. /* 5. 判断状态 */
  162. nand_wait_ready();
  163. /* 6. 读数据 */
  164. for (; (col < 2048) && (i < len); col++)
  165. {
  166. buf[i] = nand_data();
  167. i++;
  168. addr++;
  169. }
  170. col = 0;
  171. }
  172. /* 7. 取消选中 */
  173. nand_deselect();
  174. }
  175. /*
  176. * Copy code from NorFlash or NandFlash to SDRAM
  177. * Return: None
  178. */
  179. void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
  180. {
  181. unsigned int i = 0;
  182. if (isbootFromNorFlash())
  183. {
  184. while (i < len)
  185. {
  186. dest[i] = src[i];
  187. i++;
  188. }
  189. }
  190. else
  191. {
  192. nand_read((unsigned int)src, dest, len);
  193. }
  194. }
  195. /*
  196. * Clear BSS Section
  197. * Return: None
  198. */
  199. void clean_bss(void)
  200. {
  201. extern int __bss_start, __bss_end;
  202. int *p = &__bss_start;
  203. for (; p < &__bss_end; p++)
  204. *p = 0;
  205. }

3、setup.h

主要是 tag 数据结构,这里就不贴全部代码了,如果需要可以直接从 uboot 的源码里找到这个文件。

代码语言:javascript
复制
  1. struct tag {
  2. struct tag_header hdr;
  3. union {
  4. struct tag_core core;
  5. struct tag_mem32 mem;
  6. struct tag_videotext videotext;
  7. struct tag_ramdisk ramdisk;
  8. struct tag_initrd initrd;
  9. struct tag_serialnr serialnr;
  10. struct tag_revision revision;
  11. struct tag_videolfb videolfb;
  12. struct tag_cmdline cmdline;
  13. /*
  14. * Acorn specific
  15. */
  16. struct tag_acorn acorn;
  17. /*
  18. * DC21285 specific
  19. */
  20. struct tag_memclk memclk;
  21. } u;
  22. };

4、boot.c

代码语言:javascript
复制
  1. #include "setup.h"
  2. static struct tag *params;
  3. void setup_start_tag(void)
  4. {
  5. /* uboot与kernel约定好:在地址 0x30000100 的地方开始存放参数 */
  6. params = (struct tag *)0x30000100;
  7. params->hdr.tag = ATAG_CORE;
  8. params->hdr.size = tag_size(tag_core);
  9. params->u.core.flags = 0;
  10. params->u.core.pagesize = 0;
  11. params->u.core.rootdev = 0;
  12. params = tag_next(params);
  13. }
  14. void setup_memory_tag(void)
  15. {
  16. params->hdr.tag = ATAG_MEM;
  17. params->hdr.size = tag_size(tag_mem32);
  18. params->u.mem.start = 0x30000000; // SDRAM 开始地址
  19. params->u.mem.size = 64 * 1024 * 1024; // SDRAM 容量
  20. params = tag_next(params);
  21. }
  22. int strlen(char *str)
  23. {
  24. int i = 0;
  25. while (str[i]) i++;
  26. return i;
  27. }
  28. void strcpy(char *dest, char *src)
  29. {
  30. while((*dest++ = *src++) != '\0');
  31. }
  32. void setup_commandline_tag(char *cmdline)
  33. {
  34. int len = strlen(cmdline) + 1;
  35. params->hdr.tag = ATAG_CMDLINE;
  36. params->hdr.size = (sizeof(struct tag_header) + len + 3)>>2;
  37. strcpy(params->u.cmdline.cmdline, cmdline);
  38. params = tag_next(params);
  39. }
  40. void setup_end_tag(void)
  41. {
  42. params->hdr.tag = ATAG_NONE;
  43. params->hdr.size =0;
  44. }
  45. int main(void)
  46. {
  47. void (*theKernel)(int zero, int arch, unsigned int params);
  48. /* 帮内核设置串口: 内核启动时,会从串口打印一些信息,但是内核一开始没有初始化串口 */
  49. uart0_init();
  50. puts("Sewain>> Init UART0 success \r\n");
  51. /* 1. 从 NAD FLASH 里把内核读入内存。
  52. 开发板中内核 uImage = Header(64B) + zImage(真正的内核)。
  53. 参数1:uImage 的地址位于 0x00060000, 所以从这个地址之后的 64 字节,才是真正的内核文件。
  54. 参数2:内核在编译时就固定了加载地址和运行地址都为 0x30008000(是距离 SDRAM 底部32K 的位置),所以第二个参数是写入这个地址。
  55. 参数3:内核文件大概是 1.8M,这里直接读取 2M 的数据 */
  56. puts("Sewain>> Copy kernel from nand flash \r\n");
  57. nand_read(0x00060000 + 64, (unsigned char *)0x30008000, 0x00200000);
  58. /* 2. 设置参数 */
  59. puts("Sewain>> Set boot params \r\n");
  60. setup_start_tag();
  61. setup_memory_tag();
  62. setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
  63. setup_end_tag();
  64. /* 3. 跳转执行 */
  65. puts("Sewain>> Boot kernel \r\n");
  66. theKernel= (void (*)(int, int, unsigned int))0x30008000;
  67. theKernel(0, 362, 0X3000100); /* 相当于: mov pc, #0x30008000 */
  68. /* 如果一切正常,不应该执行到这里 */
  69. puts("Sewain>> Error! You should NOT come here!! \r\n");
  70. return -1;
  71. }

5、boot.lds

这里的 0x33f80000 是位于开发板上 SDRAM 上的空间,距离底部 15M+512K 的地址,意思就是编译出来的程序的加载地址是 0x33f80000。

代码语言:javascript
复制
  1. SECTIONS {
  2. . = 0x33f80000;
  3. .text : { *(.text) }
  4. . = ALIGN(4);
  5. .rodata : { *(.rodata*) }
  6. . = ALIGN(4);
  7. .data : { *(.data) }
  8. . = ALIGN(4);
  9. __bss_start = .;
  10. .bss : { *(.bss) *(COMMON) }
  11. __bss_end = .;
  12. }

6、Makefile

代码语言:javascript
复制
  1. CC = arm-linux-gcc
  2. LD = arm-linux-ld
  3. AR = arm-linux-ar
  4. OBJCOPY = arm-linux-objcopy
  5. OBJDUMP = arm-linux-objdump
  6. INCLUDEDIR := $(shell pwd)/include
  7. CFLAGS := -Wall -O2
  8. CPPFLAGS := -nostdinc -nostdlib -fno-builtin
  9. objs := start.o boot.o init.o
  10. boot.bin: $(objs)
  11. {LD} -Tboot.lds -o boot.elf ^
  12. {OBJCOPY} -O binary -S boot.elf @
  13. ${OBJDUMP} -D -m arm boot.elf > boot.dis
  14. %.o: %.c
  15. {CC} {CFLAGS} {CPPFLAGS} -c -o @
  16. %.o: %.S
  17. {CC} {CFLAGS} {CPPFLAGS} -c -o @
  18. clean:
  19. rm -f *.o *.bin *.elf *.dis

一些操作指令和流程

1、编译

通过 SecureCRT 把编辑好的代码复制到 Ubuntu 中,然后执行 make clean & make 编译,得到三个文件:boot.elf, boot.bin, boot.dis。

2. 烧写程序

把 boot.bin 复制到 Win7 中,然后通过命令行执行烧写工具 oflash boot.bin,根据提示符选择烧录到 Nand Flash 中。这期间会让你多次选择一些指令,看一下就明白了。

3. 验证

把开发板上的启动开关选择为 NAND,然后用串口工具 SecureCRT 连接到开发板,上电,可以看到一些信息,就是在 main 函数中写的那些打印信息。

总结

如果你需要上面的源码包,请联系我,非常乐意分享。

当然了,还是强烈建议你去看一下韦东山老师的视频教程,自己动手操作一遍之后,理解会更深刻。比如:如何配系统时钟、如何初始化串口、NandFlash 的存储和读写机制是怎么处理的等等。


  1. 欢迎转载,请尊重版权,保留全部内容并注明来源。
  2. 如果这篇文章侵犯了您的权益,请在此处或微信公众号留言,我会及时处理。
  3. 如果需要一块探讨,请联系下面微信公众号【IOT物联网小镇】。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IOT物联网小镇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档