前面分析了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寄存器的定义如下。

Uboot启动分析--start.S启动分析(1)_数据

.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:处。

Uboot启动分析--start.S启动分析(1)_嵌入式_02

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的开关。

Uboot启动分析--start.S启动分析(1)_嵌入式_03

SA bit位则用与控制SP对齐检查,当置位1时,在EL3下使用load 或者store指令时,当用SP作为基址并且不是16字节对齐的话,则会产生一个SP非对齐异常。

Uboot启动分析--start.S启动分析(1)_u-boot_04

C BIT:d-cache,数据cache使能位。

A BIT:对齐检查使能,

M BIT :是否使能MMU

Uboot启动分析--start.S启动分析(1)_u-boot_05

我们继续往下走:

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的任务就完成了,由于涉及汇编和指令集比较难理解,所以我们下一节接着分析。