当前位置:   article > 正文

uboot启动 -- uboot基本启动流程_uboot spl初始化emmc

uboot spl初始化emmc

本文以Board JZ2440(SMDK2410)的启动过程进行说明,u-boot选用u-boot-2015

1 概述

1.1 概述

CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,等待处理命令。
在armv7架构的uboot,主要需要做如下事情
(1)arch级的初始化

  • 关闭中断,设置svc模式
  • 禁用MMU、TLB
  • 关键寄存器的设置,包括时钟、看门狗的寄存器


(2)板级的初始化

  • 堆栈环境的设置
  • 代码重定向之前的板级初始化,包括串口、定时器、环境变量、I2C\SPI等等的初始化
  • 进行代码重定向
  • 代码重定向之后的板级初始化,包括板级代码中定义的初始化操作、emmc、nand flash、网络、中断等等的初始化。

(3)进入命令行状态,等待终端输入命令以及对命令进行处理

上述工作,也就是uboot流程的核心。


1.2 疑问


在spl的阶段中已经对arch级进行了初始化了,为什么uboot里面还要对arch再初始化一遍?
spl对于启动uboot来说并不是必须的,在某些情况下,上电之后uboot可能在ROM上或者flash上开始执行而并没有使用spl。这些都是取决于平台的启动机制。因此uboot并不会考虑spl是否已经对arch进行了初始化操作,uboot会完整的做一遍初始化动作,以保证cpu处于所要求的状态下。

和spl在启动过程的差异在哪里?
前期arch的初始化流程基本上是一致的,出现本质区别的是在board_init_f开始的。
spl的board_init_f是由board自己实现相应的功能。其主要实现了复制uboot到ddr中,并且跳转到uboot的对应位置上。一般spl在这里就可以完成自己的工作了。
uboot的board_init_f是在common下实现的,其主要实现uboot relocate前的板级初始化以及relocate的区域规划,其还需要往下走其他初始化流程。
 

1.3 编译说明

1 确定链接脚本文件:
uboot根目录下Makefile中的LDSCRIPT宏值,就是指定链接脚本(如:arch/arm/cpu/u-boot.lds)路径用的。
2 从脚本文件找入口:
在链接脚本中可以看到ENTRY()指定的入口,如:ENTRY(_start), _start就是入口(arch/arm/lib/vectors.S)


3 链接脚本简要分析:

  1. /arch/arm/cpu/u-boot.lds
  2. #include <config.h>
  3. OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")@指定输出可执行文件是elf格式,32位ARM指令,小端
  4. OUTPUT_ARCH(arm)@指定输出可执行文件的平台为ARM
  5. ENTRY(_start)@入口函数 ,指定输出可执行文件的起始代码段为_start
  6. SECTIONS
  7. {
  8. @指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成
  9. . = 0x00000000;@起始地址 从0x0位置开始
  10. . = ALIGN(4);@代码以4字节对齐
  11. .text :@文本段
  12. {
  13. *(.__image_copy_start)@变量__image_copy_start 映像文件复制起始地址
  14. *(.vectors)@异常向量表 .vectors标记的代码段
  15. CPUDIR/start.o (.text*)@启动函数
  16. *(.text*)@剩余的文本段
  17. }
  18. ......@安全相关
  19. . = ALIGN(4);
  20. .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }@ro数据段
  21. . = ALIGN(4);
  22. .data : {@RW数据段
  23. *(.data*)
  24. }
  25. . = ALIGN(4);
  26. . = .;
  27. . = ALIGN(4);
  28. .u_boot_list : {@????
  29. KEEP(*(SORT(.u_boot_list*)));
  30. }
  31. . = ALIGN(4);
  32. .image_copy_end :
  33. {
  34. *(.__image_copy_end)@变量__image_copy_end
  35. }
  36. .rel_dyn_start :
  37. {
  38. *(.__rel_dyn_start)
  39. }
  40. .rel.dyn : {
  41. *(.rel*)
  42. }
  43. .rel_dyn_end :
  44. {
  45. *(.__rel_dyn_end)
  46. }
  47. .end :
  48. {
  49. *(.__end)
  50. }
  51. _image_binary_end = .;
  52. /*
  53. * Deprecated: this MMU section is used by pxa at present but
  54. * should not be used by new boards/CPUs.
  55. */
  56. . = ALIGN(4096);
  57. .mmutable : {
  58. *(.mmutable)
  59. }
  60. /*
  61. * Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
  62. * __bss_base and __bss_limit are for linker only (overlay ordering)
  63. */
  64. .bss_start __rel_dyn_start (OVERLAY) : {
  65. KEEP(*(.__bss_start));
  66. __bss_base = .;
  67. }
  68. .bss __bss_base (OVERLAY) : {
  69. *(.bss*)
  70. . = ALIGN(4);
  71. __bss_limit = .;
  72. }
  73. .bss_end __bss_limit (OVERLAY) : {
  74. KEEP(*(.__bss_end));
  75. }
  76. .dynsym _image_binary_end : { *(.dynsym) }
  77. .dynbss : { *(.dynbss) }
  78. .dynstr : { *(.dynstr*) }
  79. .dynamic : { *(.dynamic*) }
  80. .plt : { *(.plt*) }
  81. .interp : { *(.interp*) }
  82. .gnu.hash : { *(.gnu.hash) }
  83. .gnu : { *(.gnu*) }
  84. .ARM.exidx : { *(.ARM.exidx*) }
  85. .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
  86. }

注意几个变量:

_start:程序启动入口地址处

__image_copy_start、__image_copy_end用于u-boot搬移本身image到指定的ddr地址处;

__rel_dyn_start、__rel_dyn_end用于重定位代码

__bss_start、__bss_end是bss段的开始、结束地址

上面的这几个变量会在arch\arm\lib\sections.c中使用,定义一些全局变量用于只是各个段的起始地址

  1. //C文件中利用这种方式把一个变量或者函数标记到指定段。
  2. char __bss_start[0] __attribute__((section(".__bss_start")));
  3. char __bss_end[0] __attribute__((section(".__bss_end")));
  4. char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
  5. char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
  6. char __rel_dyn_start[0] __attribute__((section(".__rel_dyn_start")));
  7. char __rel_dyn_end[0] __attribute__((section(".__rel_dyn_end")));
  8. char __secure_start[0] __attribute__((section(".__secure_start")));
  9. char __secure_end[0] __attribute__((section(".__secure_end")));
  10. char _end[0] __attribute__((section(".__end")));

在文件common/board_r.c中被调用:

  1. static int initr_reloc_global_data(void)
  2. {
  3. #ifdef __ARM__
  4. monitor_flash_len = _end - __image_copy_start;

2  总体启动流程

2.1 总体流程

  1. 在arch级初始化
  2. _start———–>reset————–>关闭中断
  3. ………………………………|
  4. ………………………………———->cpu_init_cp15———–>关闭MMU,TLB
  5. ………………………………|
  6. ………………………………———->cpu_init_crit————->lowlevel_init————->关键寄存器的配置和初始化
  7. ………………………………|
  8. ………………………………———->_main————–>进入板级初始化,具体看下面
  9. 2、板级初始化的流程
  10. _main————–>board_init_f_alloc_reserve —————>堆栈、GD、early malloc空间的分配
  11. …………|
  12. …………————->board_init_f_init_reserve —————>堆栈、GD、early malloc空间的初始化
  13. …………|
  14. …………————->board_init_f —————>uboot relocate前的板级初始化以及relocate的区域规划
  15. …………|
  16. …………————->relocate_code、relocate_vectors —————>进行uboot和异常中断向量表的重定向
  17. …………|
  18. …………————->旧堆栈的清空
  19. …………|
  20. …………————->board_init_r —————>uboot relocate后的板级初始化
  21. …………|
  22. …………————->run_main_loop —————>进入命令行状态,等待终端输入命令以及对命令进行处理

2.2 启动流程主主干说明


1 vectors.S中第一条指令(分析文件arch/arm/lib/vectors.S)
_start: b reset
2 reset定义在文件arch/arm/cpu/arm920t/start.S中
start.S的执行流程如下图:

3 bl _main //调用c代码, _main实现在 /arch/arm/lib/crt0.S
_main执行流程图如下:

 

3 启动流程详解


3.1 中断向量表

分析文件:arch/arm/lib/vectors.S

  1. .globl _start  /*声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同*/
  2. _start:
  3. b reset  /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/
  4. ldr pc, _undefined_instruction   /*未定义指令异常向量,ldr的作用是,将符号_undefined_instruction指向的地址的内容加载到pc*/
  5. ldr pc, _software_interrupt   /*软件中断向量*/
  6. ldr pc, _prefetch_abort       /*预取指令异常向量*/
  7. ldr pc, _data_abort           /*数据操作异常向量*/
  8. ldr pc, _not_used             /*未使用*/
  9. ldr pc, _irq    /*irq中断向量*/
  10. ldr pc, _fiq    /*fiq中断向量*/
  11. .globl _undefined_instruction
  12. .globl _software_interrupt
  13. .globl _prefetch_abort
  14. .globl _data_abort
  15. .globl _not_used
  16. .globl _irq
  17. .globl _fiq
  18. _undefined_instruction: .word undefined_instruction
  19. _software_interrupt: .word software_interrupt
  20. _prefetch_abort: .word prefetch_abort
  21. _data_abort: .word data_abort
  22. _not_used: .word not_used
  23. _irq: .word irq
  24. _fiq: .word fiq
  25. .balignl 16,0xdeadbeef /*下面的代码开始16字节对齐,即当上段的代码运行完后不是16字节对齐,就填充0xdeadbeef,直到使下段的代码开始处16字节对齐。*/
  26. //下面是占坑操作
  27. /* IRQ stack memory (calculated at run-time) + 8 bytes, 不能确定堆栈指针地址,所以填充无效数字,当堆栈指针确定以后,将其加8填充到这里*/
  28. .globl IRQ_STACK_START_IN
  29. IRQ_STACK_START_IN:
  30. .word 0x0badc0de
  31. /*下面是IRQ中断,放了一个 IRQ_STACK_START 标号,标号内容为: 0x0badc0de ,此内容没有意义,只是为了占一个坑。程序刚刚运行,还没有进入初始化,根本不知道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,等初始化以后堆栈指针建立好后,再将实际的堆栈指针写到这里。*/
  32. #ifdef CONFIG_USE_IRQ
  33. /* IRQ stack memory (calculated at run-time) */
  34. .globl IRQ_STACK_START
  35. IRQ_STACK_START:
  36. .word 0x0badc0de
  37. /* IRQ stack memory (calculated at run-time) */
  38. .globl FIQ_STACK_START
  39. FIQ_STACK_START:
  40. .word 0x0badc0de
  41. #endif /* CONFIG_USE_IRQ */

3.2 关中断(FIQ IRQ),设置CPU为SVC32模式

文件位置:arch/arm/cpu/arm920t/start.S

功能:(1)设置SVC模式;(2)关看门狗,中断;(3)设置PLL

vectors.S中第一条指令
_start: b reset

  1. arch/arm/cpu/arm920t/start.S
  2. /*
  3. *************************************************************************
  4. *
  5. * Startup Code (called from the ARM reset exception vector)
  6. *
  7. * do important init only if we don't start from memory!
  8. * relocate armboot to ram
  9. * setup stack
  10. * jump to second stage
  11. *
  12. *************************************************************************
  13. */
  14. .globl reset
  15. reset:
  16. /*
  17. * set the cpu to SVC32 mode
  18. */
  19. mrs r0, cpsr @将cpsr寄存器的内容传送到r0寄存器
  20. bic r0, r0, #0x1f @工作模式位清零
  21. orr r0, r0, #0xd3 @设置为SVC 模式
  22. msr cpsr, r0 @将r0的值赋给cpsr
  23. #ifdef CONFIG_S3C24X0
  24. /* turn off the watchdog */
  25. # if defined(CONFIG_S3C2400)
  26. # define pWTCON 0x15300000
  27. # define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
  28. # define CLKDIVN 0x14800014 /* clock divisor register */
  29. #else
  30. # define pWTCON 0x53000000
  31. # define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
  32. # define INTSUBMSK 0x4A00001C
  33. # define CLKDIVN 0x4C000014 /* clock divisor register */
  34. # endif
  35. ldr r0, =pWTCON
  36. mov r1, #0x0
  37. str r1, [r0]
  38. /*
  39. * mask all IRQs by setting all bits in the INTMR - default
  40. */
  41. mov r1, #0xffffffff
  42. ldr r0, =INTMSK
  43. str r1, [r0]
  44. # if defined(CONFIG_S3C2410)
  45. ldr r1, =0x3ff
  46. ldr r0, =INTSUBMSK
  47. str r1, [r0]
  48. # endif
  49. /* FCLK:HCLK:PCLK = 1:2:4 */
  50. /* default FCLK is 120 MHz ! */
  51. ldr r0, =CLKDIVN
  52. mov r1, #3
  53. str r1, [r0]
  54. #endif /* CONFIG_S3C24X0 */
  55. /*
  56. * we do sys-critical inits only at reboot,
  57. * not when booting from ram!
  58. */
  59. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  60. bl cpu_init_crit
  61. #endif
  62. bl _main
  63. /*------------------------------------------------------------------------------*/
  64. .globl c_runtime_cpu_setup
  65. c_runtime_cpu_setup:
  66. mov pc, lr



3.3 跳转到cpu_init_crit

文件位置:arch/arm/cpu/arm920t/start.S

功能:(1)清空I-Cache、D-Cache;(2)关闭MMU Stuff及cachei;(3)跳转到lowlevel_init;

  1. /*
  2. *************************************************************************
  3. *
  4. * CPU_init_critical registers
  5. *
  6. * setup important registers
  7. * setup memory timing
  8. *
  9. *************************************************************************
  10. */
  11. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  12. cpu_init_crit:
  13. /*
  14. * flush v4 I/D caches
  15. */
  16. mov r0, #0
  17. mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
  18. mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
  19. /*
  20. * disable MMU stuff and caches
  21. */
  22. mrc p15, 0, r0, c1, c0, 0
  23. bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
  24. bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
  25. orr r0, r0, #0x00000002 @ set bit 2 (A) Align
  26. orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
  27. mcr p15, 0, r0, c1, c0, 0
  28. /*
  29. * before relocating, we have to setup RAM timing
  30. * because memory timing is board-dependend, you will
  31. * find a lowlevel_init.S in your board directory.
  32. */
  33. mov ip, lr
  34. bl lowlevel_init
  35. mov lr, ip
  36. mov pc, lr
  37. #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

lowlevel_init标号对应的源码:
(这个代码要初始化内存等,是和具体平台有关的,所以应该去对应平台的目录下找lowlevel_init.S文件board/samsung/smdk2410/lowlevel_init.S)

  1. .globl lowlevel_init
  2. lowlevel_init:
  3. /* memory control configuration */
  4. /* make r0 relative the current location so that it */
  5. /* reads SMRDATA out of FLASH rather than memory ! */
  6. ldr r0, =SMRDATA
  7. ldr r1, =CONFIG_SYS_TEXT_BASE
  8. sub r0, r0, r1
  9. ldr r1, =BWSCON /* Bus Width Status Controller */
  10. add r2, r0, #13*4
  11. 0:
  12. ldr r3, [r0], #4
  13. str r3, [r1], #4
  14. cmp r2, r0
  15. bne 0b
  16. /* everything is fine now */
  17. mov pc, lr
  18. .ltorg
  19. /* the literal pools origin */
  20. SMRDATA:
  21. .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
  22. .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
  23. .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
  24. .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
  25. .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
  26. .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
  27. .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
  28. .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
  29. .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
  30. .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
  31. .word 0x32
  32. .word 0x30
  33. .word 0x30



3.4 跳转到_main

文件:arch/arm/lib/crt0.S

设置并初始化C运行环境

(1)设置SP,C运行时会用到

(2)board_init_f(0)

(3)relocate_code/relocate_vectors/c_runtime_cpu_setup

(4)清空BSS

(5)board_init_r

  1. /*
  2. * This file handles the target-independent stages of the U-Boot
  3. * start-up where a C runtime environment is needed. Its entry point
  4. * is _main and is branched into from the target's start.S file.
  5. *
  6. * _main execution sequence is:
  7. *
  8. * 1. Set up initial environment for calling board_init_f().
  9. * This environment only provides a stack and a place to store
  10. * the GD ('global data') structure, both located in some readily
  11. * available RAM (SRAM, locked cache...). In this context, VARIABLE
  12. * global data, initialized or not (BSS), are UNAVAILABLE; only
  13. * CONSTANT initialized data are available.
  14. *
  15. * 2. Call board_init_f(). This function prepares the hardware for
  16. * execution from system RAM (DRAM, DDR...) As system RAM may not
  17. * be available yet, , board_init_f() must use the current GD to
  18. * store any data which must be passed on to later stages. These
  19. * data include the relocation destination, the future stack, and
  20. * the future GD location.
  21. *
  22. * (the following applies only to non-SPL builds)
  23. *
  24. * 3. Set up intermediate environment where the stack and GD are the
  25. * ones allocated by board_init_f() in system RAM, but BSS and
  26. * initialized non-const data are still not available.
  27. *
  28. * 4. Call relocate_code(). This function relocates U-Boot from its
  29. * current location into the relocation destination computed by
  30. * board_init_f().
  31. *
  32. * 5. Set up final environment for calling board_init_r(). This
  33. * environment has BSS (initialized to 0), initialized non-const
  34. * data (initialized to their intended value), and stack in system
  35. * RAM. GD has retained values set by board_init_f(). Some CPUs
  36. * have some work left to do at this point regarding memory, so
  37. * call c_runtime_cpu_setup.
  38. *
  39. * 6. Branch to board_init_r().
  40. */
  41. ENTRY(_main)
  42. /*
  43. * Set up initial C runtime environment and call board_init_f(0).
  44. */
  45. /*
  46. 这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR 这个宏就是cpu片上内存的高地址(片上内存的 大小减去GD_SIZE)。然后将堆栈初始的地址保存在r9,所以r9就是gd的起始地址,后面需要靠r9访问gd的成员。然后将r0赋值成0,r0就是要调用的board_init_f函数的第一个参数!
  47. CONFIG_SYS_INIT_SP_ADDR = IRAM大小 - sizeof(GD)
  48. */
  49. #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
  50.   ldr sp, =(CONFIG_SPL_STACK)
  51. #else
  52.   ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
  53. #endif
  54.   bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  55.   sub sp, sp, #GD_SIZE /* allocate one GD above SP */
  56.   bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  57.   mov r9, sp /* GD is above SP */
  58.   mov r0, #0
  59.   bl board_init_f
  60. #if ! defined(CONFIG_SPL_BUILD)
  61. /*
  62. * Set up intermediate environment (new sp and gd) and call
  63. * relocate_code(addr_moni). Trick here is that we'll return
  64. * 'here' but relocated.
  65. */
  66. /*
  67. *这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间,防止kernel解压覆盖uboot。
  68. • adr lr, here
  69. • ldr r0, [r9, #GD_RELOC_OFF]
  70. add lr, lr, r0
  71. • 功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate完成后,就可以直接跳到relocate后的here标号去执行了。
  72. *relocate_code函数的原理及流程,是 uboot 的重要代码,下面详解!
  73. */
  74. ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
  75. bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  76. ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
  77. sub r9, r9, #GD_SIZE /* new GD is below bd */
  78. adr lr, here
  79. ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
  80. add lr, lr, r0
  81. ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  82. b relocate_code
  83. here:
  84. /*
  85. *relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的
  86. *地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。
  87. */
  88. bl relocate_vectors
  89. /* Set up final (full) environment */
  90. bl c_runtime_cpu_setup /* we still call old routine here */
  91. /*
  92. *清空bss段。
  93. */
  94. /*
  95. *在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么?
  96. *可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白!
  97. */
  98. ldr r0, =__bss_start /* this is auto-relocated! */
  99. ldr r1, =__bss_end /* this is auto-relocated! */
  100. mov r2, #0x00000000 /* prepare zero to clear BSS */
  101. clbss_l:
  102. cmp r0, r1 /* while not at end of BSS */
  103. strlo r2, [r0] /* clear 32-bit BSS word */
  104. addlo r0, r0, #4 /* move to next */
  105. blo clbss_l
  106. /*
  107. *这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。
  108. */
  109. bl coloured_LED_init
  110. bl red_led_on
  111. /*
  112. *将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地!
  113. */
  114. /* call board_init_r(gd_t *id, ulong dest_addr) */
  115. mov r0, r9 /* gd_t */
  116. ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
  117. /* call board_init_r */
  118. ldr pc, =board_init_r /* this is auto-relocated! */
  119. /* we should not return here. */
  120. #endif
  121. ENDPROC(_main)


3.4.1 跳转到board_init_f(0)

功能:第一阶段启动
文件:common/board_f.c

  1. void board_init_f(ulong boot_flags)
  2. {
  3. gd->flags = boot_flags;
  4. gd->have_console = 0;
  5. // 设置global_data里面的一些标志位
  6. if (initcall_run_list(init_sequence_f))
  7. hang();
  8. // 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数,initcall_run_list这里不深究
  9. // 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉
  10. }

打开DEBUG宏之后,可以通过log观察哪些init函数被调用:
uboot log中有如下log:
initcall: 23e005a4
根据u-boot.map可以发现对应
.text.print_cpuinfo
0x23e005a4 0x8 arch/arm/cpu/armv7/built-in.o
0x23e005a4 print_cpuinfo
也就是说print_cpuinfo被initcall调用了。

所以uboot relocate之前的arch级初始化的核心就是init_sequence_f中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:

  1. static init_fnc_t init_sequence_f[] = {
  2. setup_mon_len,
  3. // 计算整个镜像的长度gd->mon_len
  4. initf_malloc,
  5. // early malloc的内存池的设定
  6. initf_console_record,
  7. // console的log的缓存
  8. arch_cpu_init, /* basic arch cpu dependent setup */
  9. // cpu的一些特殊的初始化
  10. initf_dm,
  11. arch_cpu_init_dm,
  12. mark_bootstage, /* need timer, go after init dm */
  13. /* TODO: can any of this go into arch_cpu_init()? */
  14. env_init, /* initialize environment */
  15. // 环境变量的初始化,后续会专门研究一下关于环境变量的内容
  16. init_baud_rate, /* initialze baudrate settings */
  17. // 波特率的初始化
  18. serial_init, /* serial communications setup */
  19. // 串口的初始化
  20. console_init_f, /* stage 1 init of console */
  21. // console的初始化
  22. print_cpuinfo, /* display cpu info (and speed) */
  23. // 打印CPU的信息
  24. init_func_i2c,
  25. init_func_spi,
  26. // i2c和spi的初始化
  27. dram_init, /* configure available RAM banks */
  28. // ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
  29. // 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
  30. //========================================
  31. setup_dest_addr,
  32. reserve_round_4k,
  33. reserve_trace,
  34. setup_machine,
  35. reserve_global_data,
  36. reserve_fdt,
  37. reserve_arch,
  38. reserve_stacks,
  39. // ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》
  40. setup_dram_config,
  41. show_dram_config,
  42. display_new_sp,
  43. reloc_fdt,
  44. setup_reloc,
  45. // relocation之后gd一些成员的设置
  46. NULL,
  47. };

注意,必须保证上述的函数都正确地返回0值,否则会导致hang。

3.4.2 跳转 relocate_code

文件:arch/arm/lib/relocate.S

几点解释:

(1)uboot image在编译链接时已经链接到固定的地址处;

(2)image首先从flash上被copy到0地址处,然后根据链接地址重新将code搬移到链接时制定的DDR地址处;

(3)code搬移完成之后,PC会跳转到下一条指令地址处(已经是搬完code之后的新的地址),继续执行;

  1. /*
  2. * void relocate_code(addr_moni)
  3. *
  4. * This function relocates the monitor code.
  5. *
  6. * NOTE:
  7. * To prevent the code below from containing references with an R_ARM_ABS32
  8. * relocation record type, we never refer to linker-defined symbols directly.
  9. * Instead, we declare literals which contain their relative location with
  10. * respect to relocate_code, and at run time, add relocate_code back to them.
  11. */
  12. ENTRY(relocate_code)
  13. ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
  14. subs r4, r0, r1 /* r4 <- relocation offset */
  15. beq relocate_done /* skip relocation */
  16. ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
  17. copy_loop:
  18. ldmia r1!, {r10-r11} /* copy from source address [r1] */
  19. stmia r0!, {r10-r11} /* copy to target address [r0] */
  20. cmp r1, r2 /* until source end address [r2] */
  21. blo copy_loop
  22. /*
  23. * fix .rel.dyn relocations
  24. */
  25. ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
  26. ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
  27. fixloop:
  28. ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
  29. and r1, r1, #0xff
  30. cmp r1, #23 /* relative fixup? */
  31. bne fixnext
  32. /* relative fix: increase location by offset */
  33. add r0, r0, r4
  34. ldr r1, [r0]
  35. add r1, r1, r4
  36. str r1, [r0]
  37. fixnext:
  38. cmp r2, r3
  39. blo fixloop
  40. relocate_done:
  41. #ifdef __XSCALE__
  42. /*
  43. * On xscale, icache must be invalidated and write buffers drained,
  44. * even with cache disabled - 4.2.7 of xscale core developer's manual
  45. */
  46. mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
  47. mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
  48. #endif
  49. /* ARMv4- don't know bx lr but the assembler fails to see that */
  50. #ifdef __ARM_ARCH_4__
  51. mov pc, lr
  52. #else
  53. bx lr
  54. #endif
  55. ENDPROC(relocate_code)

3.4.3 relocate_vectors

文件:arch/arm/lib/relocate.S

说明:将uboot image开始的8个DWORD(即中断向量表)重新拷贝到0x0 或 VBAR

  1. /*
  2. * Default/weak exception vectors relocation routine
  3. *
  4. * This routine covers the standard ARM cases: normal (0x00000000),
  5. * high (0xffff0000) and VBAR. SoCs which do not comply with any of
  6. * the standard cases must provide their own, strong, version.
  7. */
  8. .section .text.relocate_vectors,"ax",%progbits
  9. .weak relocate_vectors
  10. ENTRY(relocate_vectors)
  11. #ifdef CONFIG_CPU_V7M
  12. /*
  13. * On ARMv7-M we only have to write the new vector address
  14. * to VTOR register.
  15. */
  16. ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  17. ldr r1, =V7M_SCB_BASE
  18. str r0, [r1, V7M_SCB_VTOR]
  19. #else
  20. #ifdef CONFIG_HAS_VBAR
  21. /*
  22. * If the ARM processor has the security extensions,
  23. * use VBAR to relocate the exception vectors.
  24. */
  25. ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  26. mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
  27. #else
  28. /*
  29. * Copy the relocated exception vectors to the
  30. * correct address
  31. * CP15 c1 V bit gives us the location of the vectors:
  32. * 0x00000000 or 0xFFFF0000.
  33. */
  34. ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
  35. mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
  36. ands r2, r2, #(1 << 13)
  37. ldreq r1, =0x00000000 /* If V=0 */
  38. ldrne r1, =0xFFFF0000 /* If V=1 */
  39. ldmia r0!, {r2-r8,r10}
  40. stmia r1!, {r2-r8,r10}
  41. ldmia r0!, {r2-r8,r10}
  42. stmia r1!, {r2-r8,r10}
  43. #endif
  44. #endif
  45. bx lr
  46. ENDPROC(relocate_vectors)

3.4.4 board_init_r

第二阶段启动
文件:common/board_r.c

  1. void board_init_r(gd_t *new_gd, ulong dest_addr)
  2. {
  3. if (initcall_run_list(init_sequence_r))
  4. hang();
  5. // 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究
  6. // 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉
  7. /* NOTREACHED - run_main_loop() does not return */
  8. hang();
  9. // uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。
  10. }

所以uboot relocate之后的板级初始化的核心就是init_sequence_r中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:
common/board_r.c

  1. init_fnc_t init_sequence_r[] = {
  2. initr_trace,
  3. // trace相关的初始化
  4. initr_reloc,
  5. // gd中一些关于relocate的标识的设置
  6. initr_reloc_global_data,
  7. // relocate之后,gd中一些的成员的重新设置
  8. initr_malloc,
  9. // malloc内存池的设置
  10. initr_console_record,
  11. bootstage_relocate,
  12. initr_bootstage,
  13. #if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
  14. board_init, /* Setup chipselects */
  15. // 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数
  16. #endif
  17. stdio_init_tables,
  18. initr_serial,
  19. // 串口初始化
  20. initr_announce,
  21. // 打印uboot运行位置的log
  22. initr_logbuffer,
  23. // logbuffer的初始化
  24. power_init_board,
  25. #ifdef CONFIG_CMD_NAND
  26. initr_nand,
  27. // 如果使用nand flash,那么这里需要对nand进行初始化
  28. #endif
  29. #ifdef CONFIG_GENERIC_MMC
  30. initr_mmc,
  31. // 如果使用emmc,那么这里需要对nand进行初始化
  32. #endif
  33. initr_env,
  34. // 初始化环境变量
  35. initr_secondary_cpu,
  36. stdio_add_devices,
  37. initr_jumptable,
  38. console_init_r, /* fully init console as a device */
  39. interrupt_init,
  40. // 初始化中断
  41. #if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
  42. initr_enable_interrupts,
  43. // 使能中断
  44. #endif
  45. run_main_loop,
  46. // 进入一个死循环,在死循环里面处理终端命令。
  47. };

 

最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
cli---command line interface 命令行界面 命令行界面

4 run_main_loop函数

  1. static int run_main_loop(void)
  2. {
  3. /* initialize uboot log */
  4. init_write_log();
  5. /* main_loop() can return to retry autoboot, if so just run it again */
  6. for (;;)
  7. main_loop();
  8. return 0;
  9. }

main_loop函数基本框架

  1. main_loop()
  2.     >>>s = bootdelay_process();
  3.            >>>s = getenv("bootcmd");
  4.                   >>>from GD
  5.      >>>autoboot_command(s)
  6.             >>>run_command_list(s, -1, 0);
  7.                     >>>cli_simple_run_command_list()
  8.                            while (*next) {
  9.                                      >>>cli_simple_run_command()
  10.                                      >>>cmd_process()
  11.                                      >>>cmd_call()
  12.                                      >>>result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
  13.                                               cmd_tbl_t/U_BOOT_CMD -----> do_cboot()
  14.                              }
  15.               >>>cli_loop(); #进入命令行模式
  16.               >>> cli_simple_loop()
  17.               >>>run_command_repeatable()
  18.               >>>cli_simple_run_command()
  19.               >>>cmd_process()


到此,uboot流程也就完成了,main_loop函数的主要功能是实现命令的处理。

5 命令行的处理

5.1 概述

(1)uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环,通过主循环进入了命令行模式。

(2)uboot把所有命令的数据结构都放在一个表格中,我们后续称之为命令表。表中的每一项代表着一个命令,其项的类型是cmd_tbl_t,这些命令在链接时会被链接到指定的段中,命令表是被定义在这个段中。

(3)命令行模式有两种简单的方式。正常模式是简单地获取串口数据、解析和处理命令。hush模式则是指命令的接收和解析使用busybox的hush工具,对应代码是hush.c

 

5.2 定义一个命令

文件:common/cmd_bootm.c

  1. U_BOOT_CMD(
  2. bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
  3. "boot application image from memory", bootm_help_text
  4. );
  5. // bootm就是我们的命令字符串
  6. // 在smdk2410.h中定义了最大参数数量是64
  7. // #define CONFIG_SYS_MAXARGS 64 /* max number of command args */
  8. // 1表示重复一次
  9. // 对应命令处理函数是do_bootm
  10. // usage字符串是对该命令的简短说明
  11. // help字符串是bootm_help_text定义的字符串。

5.2.1 U_BOOT_CMD实现

功能:定义一个cmd_tbl_t类型的变量,并在程序编译时链接到指定的段中。

文件:include/command.h

  1. /*
  2. * Command Flags:
  3. */
  4. #define CMD_FLAG_REPEAT 0x0001 /* repeat last command */
  5. #define CMD_FLAG_BOOTD 0x0002 /* command is from bootd */
  6. #define CMD_FLAG_ENV 0x0004 /* command is from the environment */
  7. #ifdef CONFIG_AUTO_COMPLETE
  8. # define _CMD_COMPLETE(x) x,
  9. #else
  10. # define _CMD_COMPLETE(x)
  11. #endif
  12. #ifdef CONFIG_SYS_LONGHELP
  13. # define _CMD_HELP(x) x,
  14. #else
  15. # define _CMD_HELP(x)
  16. #endif
  17. #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
  18. _usage, _help, _comp) \
  19. { #_name, _maxargs, _rep, _cmd, _usage, \
  20. _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
  21. #define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help) \
  22. U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
  23. _usage, _help, NULL)
  24. #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
  25. ll_entry_declare(cmd_tbl_t, _name, cmd) = \
  26. U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
  27. _usage, _help, _comp);
  28. #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
  29. U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

其中最关键的函数ll_entry_declare  数据结构体类型:cmd_tbl_t

  1. #define ll_entry_declare(_type, _name, _list) \
  2. _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
  3. __attribute__((unused, \
  4. section(".u_boot_list_2_"#_list"_2_"#_name)))
  1. 文件:include/command.h
  2. /*
  3. * Monitor Command Table
  4. */
  5. struct cmd_tbl_s {
  6. char *name; /* Command Name */
  7. int maxargs; /* maximum number of arguments */
  8. int repeatable; /* autorepeat allowed? */
  9. /* Implementation function */
  10. int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
  11. char *usage; /* Usage message (short) */
  12. #ifdef CONFIG_SYS_LONGHELP
  13. char *help; /* Help message (long) */
  14. #endif
  15. #ifdef CONFIG_AUTO_COMPLETE
  16. /* do auto completion on the arguments */
  17. int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
  18. #endif
  19. };
  20. typedef struct cmd_tbl_s cmd_tbl_t;

展开后的变量定义如下:

  1. ll_entry_declare(cmd_tbl_t, _name, cmd)
  2. 以bootm为例
  3. _type=cmd_tbl_t
  4. _name=bootm
  5. _list=cmd
  6. 这里最终会转化为如下数据结构
  7. cmd_tbl_t _u_boot_list_2_cmd_2_bootm=
  8. {
  9. _name=bootm,
  10. _maxargs=CONFIG_SYS_MAXARGS,
  11. _rep=1
  12. _cmd=do_bootm,
  13. _usage="boot application image from memory"
  14. _help=bootm_help_text,
  15. _comp=NULL
  16. }
  17. 并且这个数据结构给存放到了 .u_boot_list_2_cmd_2_bootm段中!!!和我们上述的完全一致。

 

 

5.2.2 do_bootm

具体解析见下节uboot boot kernel

 

5.3 命令的处理

5.3.1 基本流程

假设传进来的命令是cmd_name。
(1)获取命令表;
(2)从命令表中搜索和cmd_name匹配的项;
(3)执行对应项中的命令;

5.3.2 代码详解

通过find_cmd可以获取命令对应的命令表项cmd_tbl_t 。在命令表中(对应的段中)寻找与cmd_name相匹配的命令表项;

  1. 文件:common/command.c
  2. enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
  3. int *repeatable, ulong *ticks)
  4. {
  5. enum command_ret_t rc = CMD_RET_SUCCESS;
  6. cmd_tbl_t *cmdtp;
  7. /* Look up command in command table */
  8. cmdtp = find_cmd(argv[0]); // 第一个参数argv[0]表示命令,调用find_cmd获取命令对应的表项cmd_tbl_t。
  9. if (cmdtp == NULL) {
  10. printf("Unknown command '%s' - try 'help'\n", argv[0]);
  11. return 1;
  12. }
  13. /* found - check max args */
  14. if (argc > cmdtp->maxargs)
  15. rc = CMD_RET_USAGE; // 检测参数是否正常
  16. /* If OK so far, then do the command */
  17. if (!rc) {
  18. if (ticks)
  19. *ticks = get_timer(0);
  20. rc = cmd_call(cmdtp, flag, argc, argv); // 调用cmd_call执行命令表项中的命令,成功的话需要返回0
  21. if (ticks)
  22. *ticks = get_timer(*ticks); // 判断命令执行的时间
  23. *repeatable &= cmdtp->repeatable; // 这个命令执行的重复次数存放在repeatable中的
  24. }
  25. if (rc == CMD_RET_USAGE)
  26. rc = cmd_usage(cmdtp); // 命令格式有问题,打印帮助信息
  27. return rc;
  28. }
  29. 返回0表示执行成功,返回非0值表示执行失败。
  30. 后续需要执行一个命令的时候,直接调用cmd_process即可。

后续我们我们分成“查找cmd对应的表项”、“执行对应表项中的命令”两部分进行说明.

 

 1 查找cmd对应的表项 --- find_cmd

  1. 文件:common/command.c
  2. cmd_tbl_t *find_cmd(const char *cmd)
  3. {
  4. cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
  5. // 获取命令表的地址,start表示指向命令表的指针,具体实现看后面
  6. const int len = ll_entry_count(cmd_tbl_t, cmd);
  7. // 获取命令表的长度,具体实现看后面
  8. return find_cmd_tbl(cmd, start, len);
  9. // 以命令表的指针和命令表的长度为参数,查找和cmd匹配的表项,也就是cmd_tbl_t结构,并返回给调用者。
  10. }
  11. 文件:include/linker_lists.h
  12. // 获取命令表的起始地址,也就是.u_boot_list_2_cmd_1的地址,
  13. #define ll_entry_start(_type, _list) \
  14. ({ \
  15. static char start[0] __aligned(4) __attribute__((unused, \
  16. section(".u_boot_list_2_"#_list"_1"))); \
  17. (_type *)&start; \
  18. })
  19. // 获取命令表的结束地址,也就是.u_boot_list_2_cmd_3的地址,
  20. #define ll_entry_end(_type, _list) \
  21. ({ \
  22. static char end[0] __aligned(4) __attribute__((unused, \
  23. section(".u_boot_list_2_"#_list"_3"))); \
  24. (_type *)&end; \
  25. })
  26. // 计算命令表的长度
  27. #define ll_entry_count(_type, _list) \
  28. ({ \
  29. _type *start = ll_entry_start(_type, _list); \
  30. _type *end = ll_entry_end(_type, _list); \
  31. unsigned int _ll_result = end - start; \
  32. _ll_result; \
  33. })
  34. /* find command table entry for a command */
  35. cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
  36. {
  37. #ifdef CONFIG_CMDLINE
  38. cmd_tbl_t *cmdtp;
  39. cmd_tbl_t *cmdtp_temp = table; /* Init value */
  40. const char *p;
  41. int len;
  42. int n_found = 0;
  43. if (!cmd)
  44. return NULL;
  45. /*
  46. * Some commands allow length modifiers (like "cp.b");
  47. * compare command name only until first dot.
  48. */
  49. len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
  50. for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {
  51. // 通过指针递增的方式,查找table中的每一个cmd_tbl_t
  52. if (strncmp(cmd, cmdtp->name, len) == 0) {
  53. if (len == strlen(cmdtp->name))
  54. return cmdtp; /* full match */
  55. // 如果是命令字符串和表项中的name完全匹配,包括长度一致的,则直接返回
  56. cmdtp_temp = cmdtp; /* abbreviated command ? */
  57. n_found++;
  58. // 如果命令字符串和表项中的name的前面部分匹配的话(我们称为部分匹配),暂时保存下来,并且记录有几个这样的表项,主要是为了支持自动补全的功能
  59. }
  60. }
  61. if (n_found == 1) { /* exactly one match */
  62. return cmdtp_temp;
  63. // 如果部分匹配的表项是唯一的话,则可以将这个表项返回,主要是为了支持自动补全的功能
  64. }
  65. #endif /* CONFIG_CMDLINE */
  66. return NULL; /* not found or ambiguous command */
  67. }


2 执行对应表项中的命令——cmd_call

通过调用cmd_call可以执行命令表项cmd_tbl_t 中的命令
文件:common/command.c
 

  1. static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  2. {
  3. int result;
  4. result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
  5. // 直接执行命令表项cmd_tbl_t 中的cmd命令处理函数
  6. if (result)
  7. debug("Command failed, result=%d\n", result);
  8. // 命令返回非0值时,报错
  9. return result;
  10. }

 

 

6 uboot boot kernel

文件:common/cmd_bootm.c

  1. U_BOOT_CMD(
  2. bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
  3. "boot application image from memory", bootm_help_text
  4. );

do_bootm参数说明:
当执行‘bootm 0x20008000 0x21000000 0x22000000’命令时,do_bootm会被调用
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
cmdtp:传递的是bootm的命令表项指针,也就是_u_boot_list_2_cmd_2_bootm的指针
argc=4,argv[0]="bootm", argv[1]=0x20008000, arv[2]=0x21000000, argv[3]=0x22000000
 

  1. /*******************************************************************/
  2. /* bootm - boot application image from image in memory */
  3. /*******************************************************************/
  4. int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
  5. {
  6. /* determine if we have a sub command */
  7. argc--; argv++;
  8. if (argc > 0) {
  9. char *endp;
  10. simple_strtoul(argv[0], &endp, 16);
  11. /* endp pointing to NULL means that argv[0] was just a
  12. * valid number, pass it along to the normal bootm processing
  13. *
  14. * If endp is ':' or '#' assume a FIT identifier so pass
  15. * along for normal processing.
  16. *
  17. * Right now we assume the first arg should never be '-'
  18. */
  19. if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
  20. return do_bootm_subcommand(cmdtp, flag, argc, argv);
  21. }
  22. // 以上会判断是否有子命令,这里我们不管
  23. // 到这里,参数中的bootm参数会被去掉,
  24. // 也就是当'bootm 0x20008000 0x21000000 0x22000000'
  25. // argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000
  26. // 当‘bootm 0x30000000’时
  27. // argc=1, argv[0]=0x30000000
  28. return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
  29. BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
  30. BOOTM_STATE_LOADOS |
  31. BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
  32. BOOTM_STATE_OS_GO, &images, 1);
  33. // 最终对调用到do_bootm_states,在do_bootm_states中执行的操作如states标识所示:
  34. // BOOTM_STATE_START
  35. // BOOTM_STATE_FINDOS
  36. // BOOTM_STATE_FINDOTHER
  37. // BOOTM_STATE_LOADOS
  38. // BOOTM_STATE_OS_PREP
  39. // BOOTM_STATE_OS_FAKE_GO
  40. // BOOTM_STATE_OS_GO
  41. }
  42. 所以bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。

6.1 do_bootm_states函数

  1. do_bootm_states
  2. >>>bootm_start(cmdtp, flag, argc, argv)//填充image中的verify和lmb
  3. >>>bootm_find_os(cmdtp, flag, argc, argv)//填充image中的os和ep,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
  4. >>>bootm_find_other(cmdtp, flag, argc, argv)//实现rd_start, rd_end,ft_addr和initrd_end,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
  5. >>>ret = bootm_load_os(images, &load_end, 0);
  6. >>>bootm_os_get_boot_func()//用于获取到对应操作系统的启动函数,被存储到boot_fn 中
  7. >>>boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
  8. >>>boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,images, boot_fn);

下面对着几个函数的功能做一个简要的说明:

(1)bootm_start & bootm_find_os & bootm_find_other
主要负责解析环境变量、参数、uImage,来填充bootm_headers_t images这个数据结构。

  1. typedef struct bootm_headers {
  2. image_info_t os; /* os image info */
  3. ulong ep; /* entry point of OS */
  4. ulong rd_start, rd_end;/* ramdisk start/end */
  5. char *ft_addr; /* flat dev tree address */
  6. ulong ft_len; /* length of flat device tree */
  7. ulong initrd_start;
  8. ulong initrd_end;
  9. ulong cmdline_start;
  10. ulong cmdline_end;
  11. bd_t *kbd;
  12. int verify; /* getenv("verify")[0] != 'n' */
  13. #ifdef CONFIG_LMB
  14. struct lmb lmb; /* for memory mgmt */
  15. #endif
  16. }

(2)bootm_load_os

在bootm_load_os中,会对kernel镜像进行load到对应的位置上,并且如果kernel镜像是被mkimage压缩过的,那么会先经过解压之后再进行load。(这里要注意,这里的压缩和Image压缩成zImage并不是同一个,而是uboot在Image或者zImage的基础上进行的压缩!!!)

  1. static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
  2. int boot_progress)
  3. {
  4. image_info_t os = images->os;
  5. ulong load = os.load; // kernel要加载的地址
  6. ulong blob_start = os.start;
  7. ulong blob_end = os.end;
  8. ulong image_start = os.image_start; // kernel实际存在的位置
  9. ulong image_len = os.image_len; // kernel的长度
  10. bool no_overlap;
  11. void *load_buf, *image_buf;
  12. int err;
  13. load_buf = map_sysmem(load, 0);
  14. image_buf = map_sysmem(os.image_start, image_len);
  15. // 调用bootm_decomp_image,对image_buf的镜像进行解压缩,并load到load_buf上
  16. err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
  17. load_buf, image_buf, image_len,
  18. CONFIG_SYS_BOOTM_LEN, load_end);
  19. 。。。
  20. }

结果上述步骤之后,kernel镜像就被load到对应位置上了。

(3)bootm_os_get_boot_func

bootm_os_get_boot_func用于获取到对应操作系统的启动函数,被存储到boot_fn 中。

  1. int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
  2. int states, bootm_headers_t *images, int boot_progress)
  3. {
  4. ...
  5. boot_fn = bootm_os_get_boot_func(images->os.os);
  6. ...
  7. }
  8. boot_os_fn *bootm_os_get_boot_func(int os)
  9. {
  10. return boot_os[os];
  11. // 根据操作系统类型获得到对应的操作函数
  12. }
  13. static boot_os_fn *boot_os[] = {
  14. ...
  15. #ifdef CONFIG_BOOTM_LINUX
  16. [IH_OS_LINUX] = do_bootm_linux,
  17. #endif
  18. }

可以看出最终启动linux的核心函数是do_bootm_linux。

(4)boot_selected_os及其它

另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。

6.2 do_bootm_linux函数

文件:arch/arm/lib/bootm.c

  1. int do_bootm_linux(int flag, int argc, char * const argv[],
  2. bootm_headers_t *images)
  3. {
  4. /* No need for those on ARM */
  5. if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
  6. return -1;
  7. // 当flag为BOOTM_STATE_OS_PREP,则说明只需要做准备动作boot_prep_linux
  8. if (flag & BOOTM_STATE_OS_PREP) {
  9. boot_prep_linux(images);
  10. return 0;
  11. }
  12. // 当flag为BOOTM_STATE_OS_GO ,则说明只需要做跳转动作
  13. if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
  14. boot_jump_linux(images, flag);
  15. return 0;
  16. }
  17. boot_prep_linux(images); // 以全局变量bootm_headers_t images为参数传递给boot_prep_linux
  18. boot_jump_linux(images, flag);// 以全局变量bootm_headers_t images为参数传递给 boot_jump_linux
  19. return 0;
  20. }

boot_prep_linux用于实现跳转到linux前的准备动作;boot_jump_linux用于跳转到linux中。
都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。


6.2.1 boot_prep_linux函数


首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。
boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。
实现如下:

  1. static void boot_prep_linux(bootm_headers_t *images)
  2. {
  3. char *commandline = getenv("bootargs");
  4. if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
  5. #ifdef CONFIG_OF_LIBFDT
  6. debug("using: FDT\n");
  7. if (image_setup_linux(images)) {
  8. printf("FDT creation failed! hanging...");
  9. hang();
  10. }
  11. #endif
  12. }

这里没有深入学习image_setup_linux,等后续有需要的话再进行深入。


6.2.2 boot_jump_linux函数


文件:arch/arm/lib/bootm.c

  1. static void boot_jump_linux(bootm_headers_t *images, int flag)
  2. {
  3. unsigned long machid = gd->bd->bi_arch_number; // 从bd中获取machine-id,
  4. char *s;
  5. void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函数,也就是kernel的入口地址,对应kernel的_start地址。
  6. unsigned long r2;
  7. int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 伪跳转,并不真正地跳转到kernel中
  8. kernel_entry = (void (*)(int, int, uint))images->ep;
  9. // 将kernel_entry设置为images中的ep(kernel的入口地址),后面直接执行kernel_entry也就跳转到了kernel中了
  10. // 这里要注意这种跳转的方法
  11. debug("## Transferring control to Linux (at address %08lx)" \
  12. "...\n", (ulong) kernel_entry);
  13. bootstage_mark(BOOTSTAGE_ID_RUN_OS);
  14. announce_and_cleanup(fake);
  15. // 把images->ft_addr(fdt的地址)放在r2寄存器中
  16. if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
  17. r2 = (unsigned long)images->ft_addr;
  18. else
  19. r2 = gd->bd->bi_boot_params;
  20. if (!fake) {
  21. kernel_entry(0, machid, r2);
  22. // 这里通过调用kernel_entry,就跳转到了images->ep中了,也就是跳转到kernel中了,具体则是kernel的_start符号的地址。
  23. // 参数0则传入到r0寄存器中,参数machid传入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中
  24. // 满足了kernel启动的硬件要求
  25. }
  26. }


到这里,经过kernel_entry之后就跳转到kernel环境中了。

 

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号