赞
踩
本文以Board JZ2440(SMDK2410)的启动过程进行说明,u-boot选用u-boot-2015
CPU初始刚上电的状态。需要小心的设置好很多状态,包括cpu状态、中断状态、MMU状态等等。其次,就是要根据硬件资源进行板级的初始化,代码重定向等等。最后,就是进入命令行状态,等待处理命令。
在armv7架构的uboot,主要需要做如下事情
(1)arch级的初始化
(2)板级的初始化
(3)进入命令行状态,等待终端输入命令以及对命令进行处理
上述工作,也就是uboot流程的核心。
在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 确定链接脚本文件:
uboot根目录下Makefile中的LDSCRIPT宏值,就是指定链接脚本(如:arch/arm/cpu/u-boot.lds)路径用的。
2 从脚本文件找入口:
在链接脚本中可以看到ENTRY()指定的入口,如:ENTRY(_start), _start就是入口(arch/arm/lib/vectors.S)
3 链接脚本简要分析:
- /arch/arm/cpu/u-boot.lds
-
- #include <config.h>
-
- OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")@指定输出可执行文件是elf格式,32位ARM指令,小端
- OUTPUT_ARCH(arm)@指定输出可执行文件的平台为ARM
- ENTRY(_start)@入口函数 ,指定输出可执行文件的起始代码段为_start
- SECTIONS
- {
- @指定可执行image文件的全局入口点,通常这个地址都放在ROM(flash)0x0位置。必须使编译器知道这个地址,通常都是修改此处来完成
- . = 0x00000000;@起始地址 从0x0位置开始
-
- . = ALIGN(4);@代码以4字节对齐
- .text :@文本段
- {
- *(.__image_copy_start)@变量__image_copy_start 映像文件复制起始地址
- *(.vectors)@异常向量表 .vectors标记的代码段
- CPUDIR/start.o (.text*)@启动函数
- *(.text*)@剩余的文本段
- }
-
- ......@安全相关
-
- . = ALIGN(4);
- .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }@ro数据段
-
- . = ALIGN(4);
- .data : {@RW数据段
- *(.data*)
- }
-
- . = ALIGN(4);
-
- . = .;
-
- . = ALIGN(4);
- .u_boot_list : {@????
- KEEP(*(SORT(.u_boot_list*)));
- }
-
- . = ALIGN(4);
-
- .image_copy_end :
- {
- *(.__image_copy_end)@变量__image_copy_end
- }
-
- .rel_dyn_start :
- {
- *(.__rel_dyn_start)
- }
-
- .rel.dyn : {
- *(.rel*)
- }
-
- .rel_dyn_end :
- {
- *(.__rel_dyn_end)
- }
-
- .end :
- {
- *(.__end)
- }
- _image_binary_end = .;
-
- /*
- * Deprecated: this MMU section is used by pxa at present but
- * should not be used by new boards/CPUs.
- */
- . = ALIGN(4096);
- .mmutable : {
- *(.mmutable)
- }
-
- /*
- * Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
- * __bss_base and __bss_limit are for linker only (overlay ordering)
- */
- .bss_start __rel_dyn_start (OVERLAY) : {
- KEEP(*(.__bss_start));
- __bss_base = .;
- }
-
- .bss __bss_base (OVERLAY) : {
- *(.bss*)
- . = ALIGN(4);
- __bss_limit = .;
- }
-
- .bss_end __bss_limit (OVERLAY) : {
- KEEP(*(.__bss_end));
- }
-
- .dynsym _image_binary_end : { *(.dynsym) }
- .dynbss : { *(.dynbss) }
- .dynstr : { *(.dynstr*) }
- .dynamic : { *(.dynamic*) }
- .plt : { *(.plt*) }
- .interp : { *(.interp*) }
- .gnu.hash : { *(.gnu.hash) }
- .gnu : { *(.gnu*) }
- .ARM.exidx : { *(.ARM.exidx*) }
- .gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
- }
注意几个变量:
_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中使用,定义一些全局变量用于只是各个段的起始地址
- //C文件中利用这种方式把一个变量或者函数标记到指定段。
- char __bss_start[0] __attribute__((section(".__bss_start")));
- char __bss_end[0] __attribute__((section(".__bss_end")));
- char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
- char __image_copy_end[0] __attribute__((section(".__image_copy_end")));
- char __rel_dyn_start[0] __attribute__((section(".__rel_dyn_start")));
- char __rel_dyn_end[0] __attribute__((section(".__rel_dyn_end")));
- char __secure_start[0] __attribute__((section(".__secure_start")));
- char __secure_end[0] __attribute__((section(".__secure_end")));
- char _end[0] __attribute__((section(".__end")));
在文件common/board_r.c中被调用:
- static int initr_reloc_global_data(void)
- {
- #ifdef __ARM__
- monitor_flash_len = _end - __image_copy_start;
- 在arch级初始化
- _start———–>reset————–>关闭中断
- ………………………………|
- ………………………………———->cpu_init_cp15———–>关闭MMU,TLB
- ………………………………|
- ………………………………———->cpu_init_crit————->lowlevel_init————->关键寄存器的配置和初始化
- ………………………………|
- ………………………………———->_main————–>进入板级初始化,具体看下面
- 2、板级初始化的流程
-
- _main————–>board_init_f_alloc_reserve —————>堆栈、GD、early malloc空间的分配
- …………|
- …………————->board_init_f_init_reserve —————>堆栈、GD、early malloc空间的初始化
- …………|
- …………————->board_init_f —————>uboot relocate前的板级初始化以及relocate的区域规划
- …………|
- …………————->relocate_code、relocate_vectors —————>进行uboot和异常中断向量表的重定向
- …………|
- …………————->旧堆栈的清空
- …………|
- …………————->board_init_r —————>uboot relocate后的板级初始化
- …………|
- …………————->run_main_loop —————>进入命令行状态,等待终端输入命令以及对命令进行处理
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执行流程图如下:
分析文件:arch/arm/lib/vectors.S
- .globl _start /*声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同*/
- _start:
- b reset /* b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号处执行程序*/
- ldr pc, _undefined_instruction /*未定义指令异常向量,ldr的作用是,将符号_undefined_instruction指向的地址的内容加载到pc*/
- ldr pc, _software_interrupt /*软件中断向量*/
- ldr pc, _prefetch_abort /*预取指令异常向量*/
- ldr pc, _data_abort /*数据操作异常向量*/
- ldr pc, _not_used /*未使用*/
- ldr pc, _irq /*irq中断向量*/
- ldr pc, _fiq /*fiq中断向量*/
- .globl _undefined_instruction
- .globl _software_interrupt
- .globl _prefetch_abort
- .globl _data_abort
- .globl _not_used
- .globl _irq
- .globl _fiq
- _undefined_instruction: .word undefined_instruction
- _software_interrupt: .word software_interrupt
- _prefetch_abort: .word prefetch_abort
- _data_abort: .word data_abort
- _not_used: .word not_used
- _irq: .word irq
- _fiq: .word fiq
- .balignl 16,0xdeadbeef /*下面的代码开始16字节对齐,即当上段的代码运行完后不是16字节对齐,就填充0xdeadbeef,直到使下段的代码开始处16字节对齐。*/
- //下面是占坑操作
- /* IRQ stack memory (calculated at run-time) + 8 bytes, 不能确定堆栈指针地址,所以填充无效数字,当堆栈指针确定以后,将其加8填充到这里*/
- .globl IRQ_STACK_START_IN
- IRQ_STACK_START_IN:
- .word 0x0badc0de
-
- /*下面是IRQ中断,放了一个 IRQ_STACK_START 标号,标号内容为: 0x0badc0de ,此内容没有意义,只是为了占一个坑。程序刚刚运行,还没有进入初始化,根本不知道堆栈指针现在应该放在什么地方,所以一开始,作者做了一个小技巧,填充一个无效的数字,等初始化以后堆栈指针建立好后,再将实际的堆栈指针写到这里。*/
- #ifdef CONFIG_USE_IRQ
- /* IRQ stack memory (calculated at run-time) */
- .globl IRQ_STACK_START
- IRQ_STACK_START:
- .word 0x0badc0de
- /* IRQ stack memory (calculated at run-time) */
- .globl FIQ_STACK_START
- FIQ_STACK_START:
- .word 0x0badc0de
- #endif /* CONFIG_USE_IRQ */
文件位置:arch/arm/cpu/arm920t/start.S
功能:(1)设置SVC模式;(2)关看门狗,中断;(3)设置PLL
vectors.S中第一条指令
_start: b reset
- arch/arm/cpu/arm920t/start.S
-
- /*
- *************************************************************************
- *
- * Startup Code (called from the ARM reset exception vector)
- *
- * do important init only if we don't start from memory!
- * relocate armboot to ram
- * setup stack
- * jump to second stage
- *
- *************************************************************************
- */
- .globl reset
-
- reset:
- /*
- * set the cpu to SVC32 mode
- */
- mrs r0, cpsr @将cpsr寄存器的内容传送到r0寄存器
- bic r0, r0, #0x1f @工作模式位清零
- orr r0, r0, #0xd3 @设置为SVC 模式
- msr cpsr, r0 @将r0的值赋给cpsr
-
-
- #ifdef CONFIG_S3C24X0
- /* turn off the watchdog */
-
- # if defined(CONFIG_S3C2400)
- # define pWTCON 0x15300000
- # define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
- # define CLKDIVN 0x14800014 /* clock divisor register */
- #else
- # define pWTCON 0x53000000
- # define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
- # define INTSUBMSK 0x4A00001C
- # define CLKDIVN 0x4C000014 /* clock divisor register */
- # endif
-
- ldr r0, =pWTCON
- mov r1, #0x0
- str r1, [r0]
-
- /*
- * mask all IRQs by setting all bits in the INTMR - default
- */
- mov r1, #0xffffffff
- ldr r0, =INTMSK
- str r1, [r0]
- # if defined(CONFIG_S3C2410)
- ldr r1, =0x3ff
- ldr r0, =INTSUBMSK
- str r1, [r0]
- # endif
-
- /* FCLK:HCLK:PCLK = 1:2:4 */
- /* default FCLK is 120 MHz ! */
- ldr r0, =CLKDIVN
- mov r1, #3
- str r1, [r0]
- #endif /* CONFIG_S3C24X0 */
-
- /*
- * we do sys-critical inits only at reboot,
- * not when booting from ram!
- */
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_crit
- #endif
-
- bl _main
-
- /*------------------------------------------------------------------------------*/
-
- .globl c_runtime_cpu_setup
- c_runtime_cpu_setup:
-
- mov pc, lr
文件位置:arch/arm/cpu/arm920t/start.S
功能:(1)清空I-Cache、D-Cache;(2)关闭MMU Stuff及cachei;(3)跳转到lowlevel_init;
- /*
- *************************************************************************
- *
- * CPU_init_critical registers
- *
- * setup important registers
- * setup memory timing
- *
- *************************************************************************
- */
-
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- cpu_init_crit:
- /*
- * flush v4 I/D caches
- */
- mov r0, #0
- mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
- mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
-
- /*
- * disable MMU stuff and caches
- */
- mrc p15, 0, r0, c1, c0, 0
- bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
- bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
- orr r0, r0, #0x00000002 @ set bit 2 (A) Align
- orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
- mcr p15, 0, r0, c1, c0, 0
-
- /*
- * before relocating, we have to setup RAM timing
- * because memory timing is board-dependend, you will
- * find a lowlevel_init.S in your board directory.
- */
- mov ip, lr
-
- bl lowlevel_init
-
- mov lr, ip
- mov pc, lr
- #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
lowlevel_init标号对应的源码:
(这个代码要初始化内存等,是和具体平台有关的,所以应该去对应平台的目录下找lowlevel_init.S文件board/samsung/smdk2410/lowlevel_init.S)
- .globl lowlevel_init
- lowlevel_init:
- /* memory control configuration */
- /* make r0 relative the current location so that it */
- /* reads SMRDATA out of FLASH rather than memory ! */
- ldr r0, =SMRDATA
- ldr r1, =CONFIG_SYS_TEXT_BASE
- sub r0, r0, r1
- ldr r1, =BWSCON /* Bus Width Status Controller */
- add r2, r0, #13*4
- 0:
- ldr r3, [r0], #4
- str r3, [r1], #4
- cmp r2, r0
- bne 0b
-
- /* everything is fine now */
- mov pc, lr
-
- .ltorg
- /* the literal pools origin */
-
- SMRDATA:
- .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
- .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
- .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
- .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
- .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
- .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
- .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
- .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
- .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
- .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
- .word 0x32
- .word 0x30
- .word 0x30
文件: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
- /*
- * This file handles the target-independent stages of the U-Boot
- * start-up where a C runtime environment is needed. Its entry point
- * is _main and is branched into from the target's start.S file.
- *
- * _main execution sequence is:
- *
- * 1. Set up initial environment for calling board_init_f().
- * This environment only provides a stack and a place to store
- * the GD ('global data') structure, both located in some readily
- * available RAM (SRAM, locked cache...). In this context, VARIABLE
- * global data, initialized or not (BSS), are UNAVAILABLE; only
- * CONSTANT initialized data are available.
- *
- * 2. Call board_init_f(). This function prepares the hardware for
- * execution from system RAM (DRAM, DDR...) As system RAM may not
- * be available yet, , board_init_f() must use the current GD to
- * store any data which must be passed on to later stages. These
- * data include the relocation destination, the future stack, and
- * the future GD location.
- *
- * (the following applies only to non-SPL builds)
- *
- * 3. Set up intermediate environment where the stack and GD are the
- * ones allocated by board_init_f() in system RAM, but BSS and
- * initialized non-const data are still not available.
- *
- * 4. Call relocate_code(). This function relocates U-Boot from its
- * current location into the relocation destination computed by
- * board_init_f().
- *
- * 5. Set up final environment for calling board_init_r(). This
- * environment has BSS (initialized to 0), initialized non-const
- * data (initialized to their intended value), and stack in system
- * RAM. GD has retained values set by board_init_f(). Some CPUs
- * have some work left to do at this point regarding memory, so
- * call c_runtime_cpu_setup.
- *
- * 6. Branch to board_init_r().
- */
- ENTRY(_main)
- /*
- * Set up initial C runtime environment and call board_init_f(0).
- */
- /*
- 这里首先为调用board_init_f准备一个临时堆栈,CONFIG_SYS_INIT_SP_ADDR 这个宏就是cpu片上内存的高地址(片上内存的 大小减去GD_SIZE)。然后将堆栈初始的地址保存在r9,所以r9就是gd的起始地址,后面需要靠r9访问gd的成员。然后将r0赋值成0,r0就是要调用的board_init_f函数的第一个参数!
- CONFIG_SYS_INIT_SP_ADDR = IRAM大小 - sizeof(GD)
- */
- #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
- ldr sp, =(CONFIG_SPL_STACK)
- #else
- ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
- #endif
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- sub sp, sp, #GD_SIZE /* allocate one GD above SP */
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- mov r9, sp /* GD is above SP */
- mov r0, #0
- bl board_init_f
- #if ! defined(CONFIG_SPL_BUILD)
- /*
- * Set up intermediate environment (new sp and gd) and call
- * relocate_code(addr_moni). Trick here is that we'll return
- * 'here' but relocated.
- */
- /*
- *这段代码的主要功能就是将uboot搬移到内存的高地址去执行,为kernel腾出低端空间,防止kernel解压覆盖uboot。
- • adr lr, here
- • ldr r0, [r9, #GD_RELOC_OFF]
- • add lr, lr, r0
- • 功能就是,将relocate后的here标号的地址保存到lr寄存器,这样等到relocate完成后,就可以直接跳到relocate后的here标号去执行了。
- *relocate_code函数的原理及流程,是 uboot 的重要代码,下面详解!
- */
- ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
- sub r9, r9, #GD_SIZE /* new GD is below bd */
- adr lr, here
- ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
- add lr, lr, r0
- ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- b relocate_code
- here:
-
- /*
- *relocate完成后,uboot的代码被搬到了内存的顶部,所以必须重新设置异常向量表的
- *地址,c_runtime_cpu_setup这个函数的主要功能就是重新设置异常向量表的地址。
- */
- bl relocate_vectors
-
- /* Set up final (full) environment */
- bl c_runtime_cpu_setup /* we still call old routine here */
-
-
- /*
- *清空bss段。
- */
- /*
- *在relocate的过程中,并没有去搬移bss段。bss段是auto-relocated的!为什么?
- *可以自己思考一下,又或许看完我后面介绍的relocate的原理后你会明白!
- */
- ldr r0, =__bss_start /* this is auto-relocated! */
- ldr r1, =__bss_end /* this is auto-relocated! */
- mov r2, #0x00000000 /* prepare zero to clear BSS */
-
- clbss_l:
- cmp r0, r1 /* while not at end of BSS */
- strlo r2, [r0] /* clear 32-bit BSS word */
- addlo r0, r0, #4 /* move to next */
- blo clbss_l
-
-
- /*
- *这两行代码无视之,点灯什么的,和这里要讲的uboot的原理及过程没有半毛钱关系。
- */
- bl coloured_LED_init
- bl red_led_on
-
-
- /*
- *将relocate后的gd的地址保存到r1,然后调用board_init_r函数,进入uboot的新天地!
- */
- /* call board_init_r(gd_t *id, ulong dest_addr) */
- mov r0, r9 /* gd_t */
- ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
- /* call board_init_r */
- ldr pc, =board_init_r /* this is auto-relocated! */
- /* we should not return here. */
- #endif
- ENDPROC(_main)
功能:第一阶段启动
文件:common/board_f.c
- void board_init_f(ulong boot_flags)
- {
- gd->flags = boot_flags;
- gd->have_console = 0;
- // 设置global_data里面的一些标志位
-
- if (initcall_run_list(init_sequence_f))
- hang();
- // 调用initcall_run_list依次执行init_sequence_f函数数组里面的函数,initcall_run_list这里不深究
- // 一旦init_sequence_f的函数出错,会导致initcall_run_list返回不为0,而从卡掉
- }
打开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中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:
-
- static init_fnc_t init_sequence_f[] = {
- setup_mon_len,
- // 计算整个镜像的长度gd->mon_len
- initf_malloc,
- // early malloc的内存池的设定
- initf_console_record,
- // console的log的缓存
- arch_cpu_init, /* basic arch cpu dependent setup */
- // cpu的一些特殊的初始化
- initf_dm,
- arch_cpu_init_dm,
- mark_bootstage, /* need timer, go after init dm */
- /* TODO: can any of this go into arch_cpu_init()? */
- env_init, /* initialize environment */
- // 环境变量的初始化,后续会专门研究一下关于环境变量的内容
- init_baud_rate, /* initialze baudrate settings */
- // 波特率的初始化
- serial_init, /* serial communications setup */
- // 串口的初始化
- console_init_f, /* stage 1 init of console */
- // console的初始化
- print_cpuinfo, /* display cpu info (and speed) */
- // 打印CPU的信息
- init_func_i2c,
- init_func_spi,
- // i2c和spi的初始化
-
- dram_init, /* configure available RAM banks */
- // ddr的初始化,最重要的是ddr ram size的设置!!!!gd->ram_size
- // 如果说uboot是在ROM、flash中运行的话,那么这里就必须要对DDR进行初始化
- //========================================
- setup_dest_addr,
- reserve_round_4k,
- reserve_trace,
- setup_machine,
- reserve_global_data,
- reserve_fdt,
- reserve_arch,
- reserve_stacks,
- // ==以上部分是对relocate区域的规划,具体参考《[uboot] (番外篇)uboot relocation介绍》
- setup_dram_config,
- show_dram_config,
- display_new_sp,
- reloc_fdt,
- setup_reloc,
- // relocation之后gd一些成员的设置
- NULL,
- };
注意,必须保证上述的函数都正确地返回0值,否则会导致hang。
文件:arch/arm/lib/relocate.S
几点解释:
(1)uboot image在编译链接时已经链接到固定的地址处;
(2)image首先从flash上被copy到0地址处,然后根据链接地址重新将code搬移到链接时制定的DDR地址处;
(3)code搬移完成之后,PC会跳转到下一条指令地址处(已经是搬完code之后的新的地址),继续执行;
- /*
- * void relocate_code(addr_moni)
- *
- * This function relocates the monitor code.
- *
- * NOTE:
- * To prevent the code below from containing references with an R_ARM_ABS32
- * relocation record type, we never refer to linker-defined symbols directly.
- * Instead, we declare literals which contain their relative location with
- * respect to relocate_code, and at run time, add relocate_code back to them.
- */
- ENTRY(relocate_code)
- ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
- subs r4, r0, r1 /* r4 <- relocation offset */
- beq relocate_done /* skip relocation */
- ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
-
- copy_loop:
- ldmia r1!, {r10-r11} /* copy from source address [r1] */
- stmia r0!, {r10-r11} /* copy to target address [r0] */
- cmp r1, r2 /* until source end address [r2] */
- blo copy_loop
-
- /*
- * fix .rel.dyn relocations
- */
- ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
- ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
- fixloop:
- ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
- and r1, r1, #0xff
- cmp r1, #23 /* relative fixup? */
- bne fixnext
-
- /* relative fix: increase location by offset */
- add r0, r0, r4
- ldr r1, [r0]
- add r1, r1, r4
- str r1, [r0]
- fixnext:
- cmp r2, r3
- blo fixloop
-
- relocate_done:
-
- #ifdef __XSCALE__
- /*
- * On xscale, icache must be invalidated and write buffers drained,
- * even with cache disabled - 4.2.7 of xscale core developer's manual
- */
- mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
- mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
- #endif
- /* ARMv4- don't know bx lr but the assembler fails to see that */
-
- #ifdef __ARM_ARCH_4__
- mov pc, lr
- #else
- bx lr
- #endif
-
- ENDPROC(relocate_code)
文件:arch/arm/lib/relocate.S
说明:将uboot image开始的8个DWORD(即中断向量表)重新拷贝到0x0 或 VBAR
- /*
- * Default/weak exception vectors relocation routine
- *
- * This routine covers the standard ARM cases: normal (0x00000000),
- * high (0xffff0000) and VBAR. SoCs which do not comply with any of
- * the standard cases must provide their own, strong, version.
- */
- .section .text.relocate_vectors,"ax",%progbits
- .weak relocate_vectors
-
- ENTRY(relocate_vectors)
-
- #ifdef CONFIG_CPU_V7M
- /*
- * On ARMv7-M we only have to write the new vector address
- * to VTOR register.
- */
- ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- ldr r1, =V7M_SCB_BASE
- str r0, [r1, V7M_SCB_VTOR]
- #else
- #ifdef CONFIG_HAS_VBAR
- /*
- * If the ARM processor has the security extensions,
- * use VBAR to relocate the exception vectors.
- */
- ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
- #else
- /*
- * Copy the relocated exception vectors to the
- * correct address
- * CP15 c1 V bit gives us the location of the vectors:
- * 0x00000000 or 0xFFFF0000.
- */
- ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
- mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
- ands r2, r2, #(1 << 13)
- ldreq r1, =0x00000000 /* If V=0 */
- ldrne r1, =0xFFFF0000 /* If V=1 */
- ldmia r0!, {r2-r8,r10}
- stmia r1!, {r2-r8,r10}
- ldmia r0!, {r2-r8,r10}
- stmia r1!, {r2-r8,r10}
- #endif
- #endif
- bx lr
-
- ENDPROC(relocate_vectors)
第二阶段启动
文件:common/board_r.c
- void board_init_r(gd_t *new_gd, ulong dest_addr)
- {
- if (initcall_run_list(init_sequence_r))
- hang();
- // 调用initcall_run_list依次执行init_sequence_r函数数组里面的函数,initcall_run_list这里不深究
- // 一旦init_sequence_r的函数出错,会导致initcall_run_list返回不为0,而从卡掉
-
- /* NOTREACHED - run_main_loop() does not return */
- hang();
- // uboot要求在这个函数里面终止一切工作,或者进入死循环,一旦试图返回,则直接hang。
- }
所以uboot relocate之后的板级初始化的核心就是init_sequence_r中定义的函数了。
如下,这里只做简单的说明,需要的时候再具体分析:
common/board_r.c
- init_fnc_t init_sequence_r[] = {
- initr_trace,
- // trace相关的初始化
- initr_reloc,
- // gd中一些关于relocate的标识的设置
- initr_reloc_global_data,
- // relocate之后,gd中一些的成员的重新设置
- initr_malloc,
- // malloc内存池的设置
- initr_console_record,
- bootstage_relocate,
- initr_bootstage,
- #if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
- board_init, /* Setup chipselects */
- // 板级自己需要的特殊的初始化函数,如board/samsung/tiny210/board.c中定义了board_init这个函数
- #endif
- stdio_init_tables,
- initr_serial,
- // 串口初始化
- initr_announce,
- // 打印uboot运行位置的log
- initr_logbuffer,
- // logbuffer的初始化
- power_init_board,
- #ifdef CONFIG_CMD_NAND
- initr_nand,
- // 如果使用nand flash,那么这里需要对nand进行初始化
- #endif
- #ifdef CONFIG_GENERIC_MMC
- initr_mmc,
- // 如果使用emmc,那么这里需要对nand进行初始化
- #endif
- initr_env,
- // 初始化环境变量
- initr_secondary_cpu,
- stdio_add_devices,
- initr_jumptable,
- console_init_r, /* fully init console as a device */
- interrupt_init,
- // 初始化中断
- #if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
- initr_enable_interrupts,
- // 使能中断
- #endif
- run_main_loop,
- // 进入一个死循环,在死循环里面处理终端命令。
- };
最终,uboot运行到了run_main_loop,并且在run_main_loop进入命令行状态,等待终端输入命令以及对命令进行处理。
cli---command line interface 命令行界面 命令行界面
- static int run_main_loop(void)
- {
- /* initialize uboot log */
- init_write_log();
-
- /* main_loop() can return to retry autoboot, if so just run it again */
- for (;;)
- main_loop();
- return 0;
- }
main_loop函数基本框架
- main_loop()
- >>>s = bootdelay_process();
- >>>s = getenv("bootcmd");
- >>>from GD
- >>>autoboot_command(s)
- >>>run_command_list(s, -1, 0);
- >>>cli_simple_run_command_list()
- while (*next) {
- >>>cli_simple_run_command()
- >>>cmd_process()
- >>>cmd_call()
- >>>result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
- cmd_tbl_t/U_BOOT_CMD -----> do_cboot()
- }
- >>>cli_loop(); #进入命令行模式
- >>> cli_simple_loop()
- >>>run_command_repeatable()
- >>>cli_simple_run_command()
- >>>cmd_process()
到此,uboot流程也就完成了,main_loop函数的主要功能是实现命令的处理。
(1)uboot在执行完所有初始化程序之后,调用run_main_loop进入主循环,通过主循环进入了命令行模式。
(2)uboot把所有命令的数据结构都放在一个表格中,我们后续称之为命令表。表中的每一项代表着一个命令,其项的类型是cmd_tbl_t,这些命令在链接时会被链接到指定的段中,命令表是被定义在这个段中。
(3)命令行模式有两种简单的方式。正常模式是简单地获取串口数据、解析和处理命令。hush模式则是指命令的接收和解析使用busybox的hush工具,对应代码是hush.c
文件:common/cmd_bootm.c
- U_BOOT_CMD(
- bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
- "boot application image from memory", bootm_help_text
- );
- // bootm就是我们的命令字符串
- // 在smdk2410.h中定义了最大参数数量是64
- // #define CONFIG_SYS_MAXARGS 64 /* max number of command args */
- // 1表示重复一次
- // 对应命令处理函数是do_bootm
- // usage字符串是对该命令的简短说明
- // help字符串是bootm_help_text定义的字符串。
功能:定义一个cmd_tbl_t类型的变量,并在程序编译时链接到指定的段中。
文件:include/command.h
- /*
- * Command Flags:
- */
- #define CMD_FLAG_REPEAT 0x0001 /* repeat last command */
- #define CMD_FLAG_BOOTD 0x0002 /* command is from bootd */
- #define CMD_FLAG_ENV 0x0004 /* command is from the environment */
-
- #ifdef CONFIG_AUTO_COMPLETE
- # define _CMD_COMPLETE(x) x,
- #else
- # define _CMD_COMPLETE(x)
- #endif
- #ifdef CONFIG_SYS_LONGHELP
- # define _CMD_HELP(x) x,
- #else
- # define _CMD_HELP(x)
- #endif
-
- #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
- _usage, _help, _comp) \
- { #_name, _maxargs, _rep, _cmd, _usage, \
- _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
-
- #define U_BOOT_CMD_MKENT(_name, _maxargs, _rep, _cmd, _usage, _help) \
- U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
- _usage, _help, NULL)
-
- #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
- ll_entry_declare(cmd_tbl_t, _name, cmd) = \
- U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
- _usage, _help, _comp);
-
- #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
- U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
其中最关键的函数ll_entry_declare 数据结构体类型:cmd_tbl_t
- #define ll_entry_declare(_type, _name, _list) \
- _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
- __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_2_"#_name)))
- 文件:include/command.h
- /*
- * Monitor Command Table
- */
- struct cmd_tbl_s {
- char *name; /* Command Name */
- int maxargs; /* maximum number of arguments */
- int repeatable; /* autorepeat allowed? */
- /* Implementation function */
- int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
- char *usage; /* Usage message (short) */
- #ifdef CONFIG_SYS_LONGHELP
- char *help; /* Help message (long) */
- #endif
- #ifdef CONFIG_AUTO_COMPLETE
- /* do auto completion on the arguments */
- int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
- #endif
- };
-
- typedef struct cmd_tbl_s cmd_tbl_t;
展开后的变量定义如下:
- ll_entry_declare(cmd_tbl_t, _name, cmd)
- 以bootm为例
- _type=cmd_tbl_t
- _name=bootm
- _list=cmd
- 这里最终会转化为如下数据结构
-
- cmd_tbl_t _u_boot_list_2_cmd_2_bootm=
- {
- _name=bootm,
- _maxargs=CONFIG_SYS_MAXARGS,
- _rep=1,
- _cmd=do_bootm,
- _usage="boot application image from memory",
- _help=bootm_help_text,
- _comp=NULL,
- }
- 并且这个数据结构给存放到了 .u_boot_list_2_cmd_2_bootm段中!!!和我们上述的完全一致。
具体解析见下节uboot boot kernel
假设传进来的命令是cmd_name。
(1)获取命令表;
(2)从命令表中搜索和cmd_name匹配的项;
(3)执行对应项中的命令;
通过find_cmd可以获取命令对应的命令表项cmd_tbl_t 。在命令表中(对应的段中)寻找与cmd_name相匹配的命令表项;
- 文件:common/command.c
-
- enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
- int *repeatable, ulong *ticks)
- {
- enum command_ret_t rc = CMD_RET_SUCCESS;
- cmd_tbl_t *cmdtp;
-
- /* Look up command in command table */
- cmdtp = find_cmd(argv[0]); // 第一个参数argv[0]表示命令,调用find_cmd获取命令对应的表项cmd_tbl_t。
- if (cmdtp == NULL) {
- printf("Unknown command '%s' - try 'help'\n", argv[0]);
- return 1;
- }
-
- /* found - check max args */
- if (argc > cmdtp->maxargs)
- rc = CMD_RET_USAGE; // 检测参数是否正常
-
- /* If OK so far, then do the command */
- if (!rc) {
- if (ticks)
- *ticks = get_timer(0);
- rc = cmd_call(cmdtp, flag, argc, argv); // 调用cmd_call执行命令表项中的命令,成功的话需要返回0值
- if (ticks)
- *ticks = get_timer(*ticks); // 判断命令执行的时间
- *repeatable &= cmdtp->repeatable; // 这个命令执行的重复次数存放在repeatable中的
- }
- if (rc == CMD_RET_USAGE)
- rc = cmd_usage(cmdtp); // 命令格式有问题,打印帮助信息
- return rc;
- }
-
-
- 返回0表示执行成功,返回非0值表示执行失败。
- 后续需要执行一个命令的时候,直接调用cmd_process即可。
后续我们我们分成“查找cmd对应的表项”、“执行对应表项中的命令”两部分进行说明.
1 查找cmd对应的表项 --- find_cmd
- 文件:common/command.c
- cmd_tbl_t *find_cmd(const char *cmd)
- {
- cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
- // 获取命令表的地址,start表示指向命令表的指针,具体实现看后面
- const int len = ll_entry_count(cmd_tbl_t, cmd);
- // 获取命令表的长度,具体实现看后面
- return find_cmd_tbl(cmd, start, len);
- // 以命令表的指针和命令表的长度为参数,查找和cmd匹配的表项,也就是cmd_tbl_t结构,并返回给调用者。
- }
-
- 文件:include/linker_lists.h
- // 获取命令表的起始地址,也就是.u_boot_list_2_cmd_1的地址,
- #define ll_entry_start(_type, _list) \
- ({ \
- static char start[0] __aligned(4) __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_1"))); \
- (_type *)&start; \
- })
-
-
- // 获取命令表的结束地址,也就是.u_boot_list_2_cmd_3的地址,
- #define ll_entry_end(_type, _list) \
- ({ \
- static char end[0] __aligned(4) __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_3"))); \
- (_type *)&end; \
- })
-
-
- // 计算命令表的长度
- #define ll_entry_count(_type, _list) \
- ({ \
- _type *start = ll_entry_start(_type, _list); \
- _type *end = ll_entry_end(_type, _list); \
- unsigned int _ll_result = end - start; \
- _ll_result; \
- })
-
-
- /* find command table entry for a command */
- cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
- {
- #ifdef CONFIG_CMDLINE
- cmd_tbl_t *cmdtp;
- cmd_tbl_t *cmdtp_temp = table; /* Init value */
- const char *p;
- int len;
- int n_found = 0;
-
- if (!cmd)
- return NULL;
- /*
- * Some commands allow length modifiers (like "cp.b");
- * compare command name only until first dot.
- */
- len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);
-
- for (cmdtp = table; cmdtp != table + table_len; cmdtp++) {
- // 通过指针递增的方式,查找table中的每一个cmd_tbl_t
- if (strncmp(cmd, cmdtp->name, len) == 0) {
- if (len == strlen(cmdtp->name))
- return cmdtp; /* full match */
- // 如果是命令字符串和表项中的name完全匹配,包括长度一致的,则直接返回
-
- cmdtp_temp = cmdtp; /* abbreviated command ? */
- n_found++;
- // 如果命令字符串和表项中的name的前面部分匹配的话(我们称为部分匹配),暂时保存下来,并且记录有几个这样的表项,主要是为了支持自动补全的功能
- }
- }
- if (n_found == 1) { /* exactly one match */
- return cmdtp_temp;
- // 如果部分匹配的表项是唯一的话,则可以将这个表项返回,主要是为了支持自动补全的功能
- }
- #endif /* CONFIG_CMDLINE */
-
- return NULL; /* not found or ambiguous command */
- }
2 执行对应表项中的命令——cmd_call
通过调用cmd_call可以执行命令表项cmd_tbl_t 中的命令
文件:common/command.c
- static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
- {
- int result;
-
- result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
- // 直接执行命令表项cmd_tbl_t 中的cmd命令处理函数
- if (result)
- debug("Command failed, result=%d\n", result);
- // 命令返回非0值时,报错
- return result;
- }
文件:common/cmd_bootm.c
- U_BOOT_CMD(
- bootm, CONFIG_SYS_MAXARGS, 1, do_bootm,
- "boot application image from memory", bootm_help_text
- );
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
- /*******************************************************************/
- /* bootm - boot application image from image in memory */
- /*******************************************************************/
-
- int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
- {
- /* determine if we have a sub command */
- argc--; argv++;
- if (argc > 0) {
- char *endp;
-
- simple_strtoul(argv[0], &endp, 16);
- /* endp pointing to NULL means that argv[0] was just a
- * valid number, pass it along to the normal bootm processing
- *
- * If endp is ':' or '#' assume a FIT identifier so pass
- * along for normal processing.
- *
- * Right now we assume the first arg should never be '-'
- */
- if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
- return do_bootm_subcommand(cmdtp, flag, argc, argv);
- }
- // 以上会判断是否有子命令,这里我们不管
- // 到这里,参数中的bootm参数会被去掉,
- // 也就是当'bootm 0x20008000 0x21000000 0x22000000'时
- // argc=3, argv[0]=0x20008000 , argv[1]=0x21000000, argv[2]=0x22000000
- // 当‘bootm 0x30000000’时
- // argc=1, argv[0]=0x30000000
-
- return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |
- BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |
- BOOTM_STATE_LOADOS |
- BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
- BOOTM_STATE_OS_GO, &images, 1);
- // 最终对调用到do_bootm_states,在do_bootm_states中执行的操作如states标识所示:
- // BOOTM_STATE_START
- // BOOTM_STATE_FINDOS
- // BOOTM_STATE_FINDOTHER
- // BOOTM_STATE_LOADOS
- // BOOTM_STATE_OS_PREP
- // BOOTM_STATE_OS_FAKE_GO
- // BOOTM_STATE_OS_GO
- }
- 所以bootm的核心是do_bootm_states,以全局变量bootm_headers_t images作为do_bootm_states的参数。
- do_bootm_states
- >>>bootm_start(cmdtp, flag, argc, argv)//填充image中的verify和lmb
- >>>bootm_find_os(cmdtp, flag, argc, argv)//填充image中的os和ep,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
- >>>bootm_find_other(cmdtp, flag, argc, argv)//实现rd_start, rd_end,ft_addr和initrd_end,不管是Legacy-uImage还是FIT-uImage,最终解析出来要得到的都是这两个成员
- >>>ret = bootm_load_os(images, &load_end, 0);
- >>>bootm_os_get_boot_func()//用于获取到对应操作系统的启动函数,被存储到boot_fn 中
- >>>boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
- >>>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这个数据结构。
- typedef struct bootm_headers {
- image_info_t os; /* os image info */
- ulong ep; /* entry point of OS */
-
- ulong rd_start, rd_end;/* ramdisk start/end */
-
- char *ft_addr; /* flat dev tree address */
- ulong ft_len; /* length of flat device tree */
-
- ulong initrd_start;
- ulong initrd_end;
- ulong cmdline_start;
- ulong cmdline_end;
- bd_t *kbd;
- int verify; /* getenv("verify")[0] != 'n' */
- #ifdef CONFIG_LMB
- struct lmb lmb; /* for memory mgmt */
- #endif
- }
(2)bootm_load_os
在bootm_load_os中,会对kernel镜像进行load到对应的位置上,并且如果kernel镜像是被mkimage压缩过的,那么会先经过解压之后再进行load。(这里要注意,这里的压缩和Image压缩成zImage并不是同一个,而是uboot在Image或者zImage的基础上进行的压缩!!!)
- static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,
- int boot_progress)
- {
- image_info_t os = images->os;
- ulong load = os.load; // kernel要加载的地址
- ulong blob_start = os.start;
- ulong blob_end = os.end;
- ulong image_start = os.image_start; // kernel实际存在的位置
- ulong image_len = os.image_len; // kernel的长度
- bool no_overlap;
- void *load_buf, *image_buf;
- int err;
-
- load_buf = map_sysmem(load, 0);
- image_buf = map_sysmem(os.image_start, image_len);
-
- // 调用bootm_decomp_image,对image_buf的镜像进行解压缩,并load到load_buf上
- err = bootm_decomp_image(os.comp, load, os.image_start, os.type,
- load_buf, image_buf, image_len,
- CONFIG_SYS_BOOTM_LEN, load_end);
- 。。。
- }
结果上述步骤之后,kernel镜像就被load到对应位置上了。
(3)bootm_os_get_boot_func
bootm_os_get_boot_func用于获取到对应操作系统的启动函数,被存储到boot_fn 中。
- int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
- int states, bootm_headers_t *images, int boot_progress)
- {
- ...
- boot_fn = bootm_os_get_boot_func(images->os.os);
- ...
- }
-
- boot_os_fn *bootm_os_get_boot_func(int os)
- {
- return boot_os[os];
- // 根据操作系统类型获得到对应的操作函数
- }
-
- static boot_os_fn *boot_os[] = {
- ...
- #ifdef CONFIG_BOOTM_LINUX
- [IH_OS_LINUX] = do_bootm_linux,
- #endif
- }
-
可以看出最终启动linux的核心函数是do_bootm_linux。
(4)boot_selected_os及其它
另外几个函数最终也是调用到boot_fn,对应linux也就是do_bootm_linux,所以这里不在说明了。
文件:arch/arm/lib/bootm.c
- int do_bootm_linux(int flag, int argc, char * const argv[],
- bootm_headers_t *images)
- {
- /* No need for those on ARM */
- if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
- return -1;
-
- // 当flag为BOOTM_STATE_OS_PREP,则说明只需要做准备动作boot_prep_linux
- if (flag & BOOTM_STATE_OS_PREP) {
- boot_prep_linux(images);
- return 0;
- }
-
- // 当flag为BOOTM_STATE_OS_GO ,则说明只需要做跳转动作
- if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
- boot_jump_linux(images, flag);
- return 0;
- }
-
- boot_prep_linux(images); // 以全局变量bootm_headers_t images为参数传递给boot_prep_linux
- boot_jump_linux(images, flag);// 以全局变量bootm_headers_t images为参数传递给 boot_jump_linux
- return 0;
- }
boot_prep_linux用于实现跳转到linux前的准备动作;boot_jump_linux用于跳转到linux中。
都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。
首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。
boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。
实现如下:
- static void boot_prep_linux(bootm_headers_t *images)
- {
- char *commandline = getenv("bootargs");
-
- if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
- #ifdef CONFIG_OF_LIBFDT
- debug("using: FDT\n");
- if (image_setup_linux(images)) {
- printf("FDT creation failed! hanging...");
- hang();
- }
- #endif
- }
这里没有深入学习image_setup_linux,等后续有需要的话再进行深入。
文件:arch/arm/lib/bootm.c
- static void boot_jump_linux(bootm_headers_t *images, int flag)
- {
- unsigned long machid = gd->bd->bi_arch_number; // 从bd中获取machine-id,
- char *s;
- void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函数,也就是kernel的入口地址,对应kernel的_start地址。
- unsigned long r2;
- int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 伪跳转,并不真正地跳转到kernel中
-
- kernel_entry = (void (*)(int, int, uint))images->ep;
- // 将kernel_entry设置为images中的ep(kernel的入口地址),后面直接执行kernel_entry也就跳转到了kernel中了
- // 这里要注意这种跳转的方法
-
- debug("## Transferring control to Linux (at address %08lx)" \
- "...\n", (ulong) kernel_entry);
- bootstage_mark(BOOTSTAGE_ID_RUN_OS);
- announce_and_cleanup(fake);
-
- // 把images->ft_addr(fdt的地址)放在r2寄存器中
- if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
- r2 = (unsigned long)images->ft_addr;
- else
- r2 = gd->bd->bi_boot_params;
-
- if (!fake) {
- kernel_entry(0, machid, r2);
- // 这里通过调用kernel_entry,就跳转到了images->ep中了,也就是跳转到kernel中了,具体则是kernel的_start符号的地址。
- // 参数0则传入到r0寄存器中,参数machid传入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中
- // 满足了kernel启动的硬件要求
- }
- }
到这里,经过kernel_entry之后就跳转到kernel环境中了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。