前面分析了spl-uboot lds的链接脚本,提到了_start符号是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址。并且我们也知道start.S的代码段也是位于整个spl-uboot代码段最开始的位置,而_start符号对于Armv8架构来说位于则位于 arch\arm\cpu\armv8\start.S文
start.s
.globl _start
_start:
b reset
_start函数的第一步指令就是跳转到reset函数,这个待会儿再分析。先看看下面段定义。
对于i.MX8MP来说,CONFIG_SYS_TEXT_BASE是0x40200000。.quad在存储器中分配8个字节的数40200000,
紧接着定义了几个符号_end_ofs _bss_start_ofs 和_bss_end_ofs,他们的地址也都是位于.text段中,值则是根据之前lds脚本中提到的长度为0的字符数组的值减去_start符号所在的地址,也就是离_start符号的地址值的偏移。还记得上篇分析链接脚本里面提到的对于8mp,将bss段放在了外部存储sdram的0x96e000开始的8K空间内吗?所以,__bss_start的值即为0x96e000,而_start则是放在了.text段的最前面也就是内部sram空间0x920000处,所以对于_bss_start_ofs来说,其值便为0x04e000。
.align 3
.globl _TEXT_BASE
_TEXT_BASE:
.quad CONFIG_SYS_TEXT_BASE //0x40200000
//链接脚本定义了以下的段信息
.globl _end_ofs
_end_ofs:
.quad _end - _start
.globl _bss_start_ofs
_bss_start_ofs:
.quad __bss_start - _start
.globl _bss_end_ofs
_bss_end_ofs:
.quad __bss_end - _start
对于_end_ofs,则根据_end-_start得到,而_end定义如下,也就是说,_end和__bss_start一样是个不占空间的字符数组变量,其存放在.__end段中
char _end[0] __attribute__((section(".__end")));
段定义,可以看到_end位于.__end段,而.__end段在.end中,由于和.image_copy_end一样,这两个段中的两个变量所占空间都为0,所以实际上这里_end的值和__image_copy_end的值是相等的。
.text : {
. = ALIGN(8);
*(.__image_copy_start)
CPUDIR/start.o (.text*)
*(.text*)
} >.sram
.rodata : {
. = ALIGN(8);
*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
} >.sram
.data : {
. = ALIGN(8);
*(.data*)
} >.sram
.u_boot_list : {
. = ALIGN(8);
KEEP(*(SORT(.u_boot_list*)));
} >.sram
.image_copy_end : {
. = ALIGN(8);
*(.__image_copy_end)
} >.sram
.end : {
. = ALIGN(8);
*(.__end)
} >.sram
reset函数
save_boot_params
stp指令存储一对 SIMD&FP 寄存器。该指令将一对 SIMD&FP 寄存器存储到内存中。用于存储的地址是根据基址寄存器值和立即偏移量计算得出的。(Depending on the settings in the CPACR_EL1, CPTR_EL2, and CPTR_EL3 registers, and the current Security state and Exception level, an attempt to execute the instruction might be trapped.)
STP Xt1, Xt2, [Xn|SP], #imm ;
将Xt1和Xt2存入Xn|SP对应的地址内存中,然后,将Xn|SP的地址变更为Xn|SP + imm偏移量的新地址
首先开辟一段256byte的空白sram空间,将x1、x2寄存器的值保存到x0寄存器对应的地址内存(rom_pointer),然后将x0的地址变更为x0+16(这就是移动指针方便下面继续存储其他寄存器的值)。以此类推,保存x0到x30总计31个寄存器。由于是小端模式,保存完将sp指针指向x30寄存器即栈顶,保存sp指针的位置,指向x0+8位置。
.global rom_pointer
//分配一个256byte的00000***000空间
rom_pointer:
.space 256
adr x0, rom_pointer
stp x1, x2, [x0], #16
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
b save_boot_params_ret
保存结束,save_boot_params_ret跳转到_start重启系统。
save_boot_params_ret:
#if CONFIG_POSITION_INDEPENDENT
/* Verify that we're 4K aligned. */
/*保存数据以后跳转到reset重新启动*/
adr x0, _start
ands x0, x0, #0xfff
b.eq 1f
0:
/*
* //失败,必须4K对齐
* U-Boot needs to be loaded at a 4K aligned address.
*
* We use ADRP and ADD to load some symbol addresses during startup.
* The ADD uses an absolute (non pc-relative) lo12 relocation
* thus requiring 4K alignment.
*/
wfi
b 0b
1:
//修复重定位的问题
pie_fixup:
adr x0, _start /* x0 <- Runtime value of _start */
ldr x1, _TEXT_BASE /* x1 <- Linked value of _start */
subs x9, x0, x1 /* x9 <- Run-vs-link offset */
beq pie_fixup_done
adrp x2, __rel_dyn_start /* x2 <- Runtime &__rel_dyn_start */
add x2, x2, #:lo12:__rel_dyn_start
adrp x3, __rel_dyn_end /* x3 <- Runtime &__rel_dyn_end */
add x3, x3, #:lo12:__rel_dyn_end
pie_fix_loop:
ldp x0, x1, [x2], #16 /* (x0, x1) <- (Link location, fixup) */
ldr x4, [x2], #8 /* x4 <- addend */
cmp w1, #1027 /* relative fixup? */
bne pie_skip_reloc
/* relative fix: store addend plus offset at dest location */
add x0, x0, x9
add x4, x4, x9
str x4, [x0]
pie_skip_reloc:
cmp x2, x3
b.lo pie_fix_loop
pie_fixup_done:
reset:
#ifdef CONFIG_SYS_RESET_SCTRL
bl reset_sctrl
#endif
首先根据是否配置CONFIG_SYS_RESET_SCTRL来决定是否需要跳转到reset_sctrl执行。一般的系统是不会使用这个定义的,但是我们来看看reset_sctrl里面做了什么。
reset_sctrl
首先调用switch_el宏,获取当前的异常等级,并跳转到相应的标签处。
#ifdef CONFIG_SYS_RESET_SCTRL
reset_sctrl:
switch_el x1, 3f, 2f, 1f
3:
mrs x0, sctlr_el3
b 0f
2:
mrs x0, sctlr_el2
b 0f
1:
mrs x0, sctlr_el1
0:
ldr x1, =0xfdfffffa
and x0, x0, x1
switch_el x1, 6f, 5f, 4f
6:
msr sctlr_el3, x0
b 7f
5:
msr sctlr_el2, x0
b 7f
4:
msr sctlr_el1, x0
7:
dsb sy
isb
b __asm_invalidate_tlb_all
ret
#endif
加载CurrentEL寄存器的值到x1寄存器中,然后分别与0xc,0x8,0x4进行比较。x1=0xc则跳转到label3,x1=0x8则跳转到label2,x1=0x4则跳转到label1。CurrentEL寄存器的定义如下。
.macro switch_el, xreg, el3_label, el2_label, el1_label
mrs \xreg, CurrentEL
cmp \xreg, 0xc
b.eq \el3_label
cmp \xreg, 0x8
b.eq \el2_label
cmp \xreg, 0x4
b.eq \el1_label
.endm
label定义如下
3:
mrs x0, sctlr_el3
b 0f
2:
mrs x0, sctlr_el2
b 0f
1:
mrs x0, sctlr_el1
以异常等级EL3为例,此时跳转到label3。首先读取sctlr_el3寄存器数据到x0中,然后跳转到标号0:处。
sctlr_el3寄存器的EE位有两种值0和1,决定了EL3级别数据访问的大小端模式,也决定了EL3 在TLB进行虚实转换的stage1时的大小端格式。
WXN 位主要是用于控制可写内存区域是否是XN。当该bit置1时,在EL3 TLB转换表里所有的 writable 的 memory region 都会被视为 XNExecute-never ,也就意味着相应的 memory region 将无法执行 instructions,应该就是禁用了TLB。
I bit位则是用于控制i-cache的开关。
SA bit位则用与控制SP对齐检查,当置位1时,在EL3下使用load 或者store指令时,当用SP作为基址并且不是16字节对齐的话,则会产生一个SP非对齐异常。
C BIT:d-cache,数据cache使能位。
A BIT:对齐检查使能,
M BIT :是否使能MMU
我们继续往下走:
0:
ldr x1, =0xfdfffffa
and x0, x0, x1
在标号0中,将x0寄存器中保存的sctlr_el3里的值和x1寄存器中的值0xfdfffffa进行***位与***再写回到x0中,其实就是将bit2 和bit0还有bit 25 清0。再将x0寄存器的数据写回到sctlr_el3。此时的状态为:小端,MMU 关闭 i/d cache 都关闭(i cache bit控制位默认复位起来就是0,关闭icache的状态)。sctrl_el2和sctlr_el1的流程类似,此处无需赘述。
reset_sctrl的任务就完成了,由于涉及汇编和指令集比较难理解,所以我们下一节接着分析。