本文以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
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环境中了。