典型的Uboot启动分为两个阶段,bootrom
->SPL(Secondary Program Loader)
->ATF
->OPTEE
(可选)->Uboot
。其中SPL
为BL2
,ATF
为BL31
,OPTEE
为BL32
,Uboot
为BL33
。其中bootrom是固化在芯片内部的代码,负责从各种外(sdcard、mmc、flash)中加载spl到芯片内部的SRAM;SPL
的主要工作是初始化板载的DRAM和核心硬件;Uboot
最主要的功能就是加载启动kernel。
SPL代码是Uboot源码中一部分特殊的代码,其编译后的大小足以在片上RAM中运行。下面我们通过SPL的反汇编代码来了解实际的代码执行过程。
_start
标签是程序的入口点。第一条指令 b 28
是一个无条件分支指令,将控制流跳转到 reset
标签处。reset函数中又直接跳转到了save_boot_params
。对于i.MX平台, i.MX8各个系列的save_boot_params
定义都不一样。下面我们将以i.MX8M平台的save_boot_params
函数为例继续分析。
save_boot_params
函数开始,使用 adr
指令将 rom_pointer
的地址存储到寄存器 x0
中。rom_pointer
是由optee固件提供的 ATAG/FDT 地址。本质是将armv8中的x1-x30寄存器保存到这个地址上,保存栈地址。stp
指令将 x1
到 x30
的值存储到 x0
指向的内存地址,并且每次存储后 x0
自增 16 个字节。mov
指令将栈指针 sp
的值存储到寄存器 x30
中。str
指令将 x30
的值存储到 x0
指向的内存地址,并且 x0
自增 8 个字节。save_boot_params_ret
标签处。.global save_boot_params
save_boot_params:
/* The firmware provided ATAG/FDT address can be found in r2/x0 */
adr x0, rom_pointer
stp x1, x2, [x0], #16 //将x1和x2的值
stp x3, x4, [x0], #16
stp x5, x6, [x0], #16
stp x7, x8, [x0], #16
stp x9, x10, [x0], #16
stp x11, x12, [x0], #16
stp x13, x14, [x0], #16
stp x15, x16, [x0], #16
stp x17, x18, [x0], #16
stp x19, x20, [x0], #16
stp x21, x22, [x0], #16
stp x23, x24, [x0], #16
stp x25, x26, [x0], #16
stp x27, x28, [x0], #16
stp x29, x30, [x0], #16
mov x30, sp
str x30, [x0], #8
/* Returns */
b save_boot_params_ret
save_boot_params_ret
函数是通用函数。
1.adr
指令将地址 2049a800
存储在寄存器 x0
中。这个地址定义的是异常向量vectors位置。
2.mrs
指令将当前异常级别(Exception Level)的值存储在寄存器 x1
中。
3.cmp
指令将寄存器 x1
和常数 0xc
,0x8
,0x4
进行比较,跳转到对应标签处。这三个常数分别代码异常等级EL3,EL2和EL1。标签实现的功能是设置各异常等级的vbar,src,cptr和cpacr寄存器。例如对于EL3,msr
指令将寄存器 x0
的值存储到 vbar_el3
寄存器中,这就设置了EL3的异常向量。mov
指令将常数 0x33ff
移动到寄存器 x0
中。msr
指令将寄存器 x0
的值存储到 cptr_el2
寄存器中,打开FP/SIMD功能。b
指令无条件跳转到地址 isb指令。下面就是执行apply_core_errata
和lowlevel_init
函数了。apply_core_errata
不作分析。
ELn | 应用范围 |
---|---|
EL0 | 应用层 |
EL1 | 操作系统内核或者一些特权函数 |
EL2 | Hypervisor虚拟化 |
EL3 | Secure Monitor |
mrs
指令将当前异常级别(Exception Level)的值存储在寄存器 x0
中。
cmp
指令将寄存器 x0
和常数 0xc
进行比较,确定是否处于EL3。
b.eq
指令检查比较结果,如果相等,则跳转到地址 2049b594
处。如果不相等,使用 ret
指令返回到调用 lowlevel_init
函数的位置。
2049b594
处的代码如下:
2049b594: d50344ff msr daifclr, #0x4
2049b598: d5033fdf isb
2049b59c: d65f03c0 ret
msr
指令将 0x4
存储到 daifclr
寄存器中,用于清除 DAIF 寄存器的指定位。isb
指令执行指令同步屏障操作。然后返回,使用 ret
指令返回到调用 lowlevel_init
函数的位置。下一步启动主核/多核,执行_main
函数。
1.ldr
将0x96dff0
(CONFIG_SPL_STACK)加载到x0
中。
2.and
指令使用位掩码将寄存器 x0
和常数 0xfffffffffffffff0
进行按位与操作,并将结果存储在栈指针寄存器 sp
中,对栈指针进行对齐。
3.mov
指令将栈指针寄存器 sp
中的值复制到寄存器 x0
中,设置对齐后的栈地址。(sp->x0)
4.bl
指令调用函数 board_init_f_alloc_reserve
,将栈指针作为参数传递,并将返回值存储在寄存器 x0
中。board_init_f_alloc_reserve
函数的目的是在顶部地址中预留一些内存。首先,根据配置项 CONFIG_VAL(SYS_MALLOC_F_LEN)
的值,可能会预留一部分内存作为早期 malloc
的内存池,从 传入的对齐后地址中减去该长度。接下来,通过将这个地址向下舍入到最接近的 16 字节的倍数,来保留一块内存用于存储全局数据结构 struct global_data
,并确保其对齐。最后,将更新后的 x0
值返回。
5.mov
指令将寄存器 x0
中的值复制到栈指针寄存器 sp
中(x0->sp)。mov
指令将寄存器 x0
中的值复制到寄存器 x18
中,备份栈指针。
6.bl
指令调用函数 board_init_f
(board/freescale/imx93_evk/spl.c)初始化必要的i.MX外设如串口、定时器、pmic和ddr,初始化内存分配系统,最后调用board_init_r
(common/spl/spl.c)执行跳转。对于board_init_r
来说,支持5种跳转方式,它们分别是①通过ATF跳转uboot;②通过optee跳转uboot;③通过RISCV OpenSBI跳转uboot;④直接跳转到Linux;⑤直接跳转uboot。**如果上面都不支持,就不进行跳转,转而使用ROM API将uboot image下载到ddr的指定位置。**实际的跳转流程是在ROM API下载image后,直接进入ATF,然后由ATF控制跳转进入Uboot。对于I.MX系列平台,在执行board_init_f
函数的最后就直接跳转进入了ATF,board_init_f
之后的代码(如spl_relocate_stack_gd
和clear_loop
)不再被执行。这一点与大部分资料所述的执行步骤不同。
u-boot-spl: file format elf64-littleaarch64
Disassembly of section .text:
000000002049a000 <_start>:
2049a000: 1400000a b 2049a028
2049a004: d503201f nop
000000002049a008 <_TEXT_BASE>:
2049a008: 80200000 .inst 0x80200000 ; NYI
2049a00c: 00000000 udf #0
000000002049a010 <_end_ofs>:
2049a010: 0001a1e0 .inst 0x0001a1e0 ; undefined
2049a014: 00000000 udf #0
000000002049a018 <_bss_start_ofs>:
2049a018: 00080000 .inst 0x00080000 ; undefined
2049a01c: 00000000 udf #0
000000002049a020 <_bss_end_ofs>:
2049a020: 000800b0 .inst 0x000800b0 ; undefined
2049a024: 00000000 udf #0
000000002049a028 :
2049a028: 140005b6 b 2049b700
000000002049a02c :
2049a02c: 10003ea0 adr x0, 2049a800
2049a030: d5384241 mrs x1, currentel
2049a034: f100303f cmp x1, #0xc
2049a038: 540000a0 b.eq 2049a04c // b.none
2049a03c: f100203f cmp x1, #0x8
2049a040: 54000120 b.eq 2049a064 // b.none
2049a044: f100103f cmp x1, #0x4
2049a048: 540001a0 b.eq 2049a07c // b.none
2049a04c: d51ec000 msr vbar_el3, x0
2049a050: d53e1100 mrs x0, scr_el3
2049a054: b2400c00 orr x0, x0, #0xf
2049a058: d51e1100 msr scr_el3, x0
2049a05c: d51e115f msr cptr_el3, xzr
2049a060: 1400000a b 2049a088
2049a064: d53c1101 mrs x1, hcr_el2
2049a068: b71000a1 tbnz x1, #34, 2049a07c
2049a06c: d51cc000 msr vbar_el2, x0
2049a070: d2867fe0 mov x0, #0x33ff // #13311
2049a074: d51c1140 msr cptr_el2, x0
2049a078: 14000004 b 2049a088
2049a07c: d518c000 msr vbar_el1, x0
2049a080: d2a00600 mov x0, #0x300000 // #3145728
2049a084: d5181040 msr cpacr_el1, x0
2049a088: d5033fdf isb
2049a08c: 94000003 bl 2049a098
2049a090: 9400053d bl 2049b584
000000002049a094 :
2049a094: 940002bf bl 2049ab90 <_main>
000000002049ab90 <_main>:
2049ab90: 58000300 ldr x0, 2049abf0
2049ab94: 927cec1f and sp, x0, #0xfffffffffffffff0
2049ab98: 910003e0 mov x0, sp
2049ab9c: 94000b86 bl 2049d9b4
2049aba0: 9100001f mov sp, x0
2049aba4: aa0003f2 mov x18, x0
2049aba8: 94000b88 bl 2049d9c8
2049abac: d2800000 mov x0, #0x0 // #0
2049abb0: 94000a8f bl 2049d5ec
2049abb4: 94000b7b bl 2049d9a0
2049abb8: f100001f cmp x0, #0x0
2049abbc: 9a921012 csel x18, x0, x18, ne // ne = any
2049abc0: 910003e1 mov x1, sp
2049abc4: f100001f cmp x0, #0x0
2049abc8: 9a811000 csel x0, x0, x1, ne // ne = any
2049abcc: 9100001f mov sp, x0
2049abd0: 58000140 ldr x0, 2049abf8
2049abd4: 58000161 ldr x1, 2049ac00
000000002049abd8 :
2049abd8: f800841f str xzr, [x0], #8
2049abdc: eb01001f cmp x0, x1
2049abe0: 54ffffc3 b.cc 2049abd8 // b.lo, b.ul, b.last
2049abe4: aa1203e0 mov x0, x18
2049abe8: f9403e41 ldr x1, [x18, #120]
2049abec: 14000afa b 2049d7d4
2049abf0: 20519dd0 .inst 0x20519dd0 ; undefined
2049abf4: 00000000 udf #0
2049abf8: 2051a000 .inst 0x2051a000 ; undefined
2049abfc: 00000000 udf #0
2049ac00: 2051a0b0 .inst 0x2051a0b0 ; undefined
2049ac04: 00000000 udf #0