最简 bootloader

2021-05-13 11:36:03 浏览数 (1)

文章主题

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 48;
  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 ) != '');
  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 rn");
  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 rn");
  57. nand_read(0x00060000 64, (unsigned char *)0x30008000, 0x00200000);
  58. /* 2. 设置参数 */
  59. puts("Sewain>> Set boot params rn");
  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 rn");
  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!! rn");
  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物联网小镇】。

0 人点赞