- 分析SPL armv8 源代码(以rk3399分析为例)
1.SPL 链接脚本
arch/arm/cpu/armv8/u-boot-spl.lds:
14 MEMORY { .sram : ORIGIN = IMAGE_TEXT_BASE,
15 LENGTH = IMAGE_MAX_SIZE }
16 MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR,
17 LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
18
19 OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
20 OUTPUT_ARCH(aarch64)
21 ENTRY(_start) //spl 入口地址
22 SECTIONS
23 {
24 .text : {
25 . = ALIGN(8);
26 *(.__image_copy_start)
27 CPUDIR/start.o (.text*) //启动文件
28 *(.text*)
29 } >.sram
30
31 .rodata : {
32 . = ALIGN(8);
33 *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*)))
34 } >.sram
35
36 .data : {
37 . = ALIGN(8);
38 *(.data*)
39 } >.sram
40
41 .u_boot_list : {
42 . = ALIGN(8);
43 KEEP(*(SORT(.u_boot_list*)));
44 } >.sram
45
46 .image_copy_end : {
47 . = ALIGN(8);
48 *(.__image_copy_end)
49 } >.sram
50
51 .end : {
52 . = ALIGN(8);
53 *(.__end)
54 } >.sram
55
56 _image_binary_end = .;
57
58 .bss_start (NOLOAD) : {
59 . = ALIGN(8);
60 KEEP(*(.__bss_start));
61 } >.sdram
62
63 .bss (NOLOAD) : {
64 *(.bss*)
65 . = ALIGN(8);
66 } >.sdram
67
68 .bss_end (NOLOAD) : {
69 KEEP(*(.__bss_end));
70 } >.sdram
71
72 /DISCARD/ : { *(.dynsym) }
73 /DISCARD/ : { *(.dynstr*) }
74 /DISCARD/ : { *(.dynamic*) }
75 /DISCARD/ : { *(.plt*) }
76 /DISCARD/ : { *(.interp*) }
77 /DISCARD/ : { *(.gnu*) }
78 }
重点分析:
首先定义两块物理内存区域,分别为
.sram 存放spl 的.text和.data内容,即.text和.data链接地址从CONFIG_SPL_TEXT_SIZE开始。
.sdram存放spl的.bss 内容,即.bss链接地址从CONFIG_SPL_BSS_START_ADDR开始。
1>.SPL Text section 起始地址和大小:
- IMAGE_TEXT_BASE = CONFIG_SPL_TEXT_SIZE
- IMAGE_MAX_SIZE = CONFIG_SPL_MAX_SIZE
执行链接时,指定链接地址为CONFIG_SPL_TEXT_SIZE,空间大小为CONFIG_SPL_MAX_SIZE,宏定义在include/configs/xxx.h:
scripts/Makefile.spl:
152 ifneq ($(CONFIG_$(SPL_TPL_)MAX_SIZE),)
153 LDPPFLAGS += -DIMAGE_MAX_SIZE=$(CONFIG_$(SPL_TPL_)MAX_SIZE)
154 endif
155 ifneq ($(CONFIG_$(SPL_TPL_)TEXT_BASE),)
156 LDPPFLAGS += -DIMAGE_TEXT_BASE=$(CONFIG_$(SPL_TPL_)TEXT_BASE)
157 endif
include/configs/rk3399_common.h :
#define CONFIG_SPL_MAX_SIZE 0x100000
configs/rock960-rk3399_defconfig :
CONFIG_SPL_TEXT_BASE=0xff8c2000
2>.SPL Bss section
- CONFIG_SPL_BSS_START_ADDR
- CONFIG_SPL_BSS_MAX_SIZE
include/configs/rk3399_common.h:
28 #define CONFIG_SPL_STACK 0xff8effff
29 #define CONFIG_SPL_MAX_SIZE 0x30000 - 0x2000
31 #define CONFIG_SPL_BSS_START_ADDR 0xff8e0000
32 #define CONFIG_SPL_BSS_MAX_SIZE 0x10000
2.SPL 启动分析
上面可知,spl的入口地址为_start,汇编文件为start.S。
2.1.start.S( arch/arm/cpu/armv8/start.S )
18
19 .globl _start
20 _start:
21 #if defined(LINUX_KERNEL_IMAGE_HEADER)
22 #include <asm/boot0-linux-kernel-header.h>
23 #elif defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
24 /*
25 * Various SoCs need something special and SoC-specific up front in
26 * order to boot, allow them to set that in their boot0.h file and then
27 * use it here.
28 */
29 #include <asm/arch/boot0.h>
30 #else
31 b reset
32 #endif
- LINUX_KERNEL_IMAGE_HEADER用于控制是否在镜像头部增加linux kernel镜像头。
- CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK用于控制在SPL头部是否添加soc厂商自定义的一些header(refer to arch/arm/Kconfig);rk3399在boot0.h中预留了16k空间给ATF(arm trusted firmware).
然后进入reset,执行save_boot_params ,该定义用weak修饰,表示厂商可以重新实现,如果不定义,则执行默认操作。rk3399重新定义了这个函数,实现读取特定寄存器(pmugrf模块)来判断是否需要重新进入bootrom,如果不需要,则返回继续执行 save_boot_params_ret。
第61-84行:对spl代码进行符号修正,实现位置无关,rk3399没有定义CONFIG_POSITION_INDEPENDENT,因为bootrom会将spl直接load到其链接地址上,即没有必要进行修正,从而加快了启动速度。
55 reset:
56 /* Allow the board to save important registers */
57 b save_boot_params
58 .globl save_boot_params_ret
59 save_boot_params_ret:
60
61 #if CONFIG_POSITION_INDEPENDENT
66 pie_fixup:
67 adr x0, _start /* x0 <- Runtime value of _start */
68 ldr x1, _TEXT_BASE /* x1 <- Linked value of _start */
69 sub x9, x0, x1 /* x9 <- Run-vs-link offset */
70 adr x2, __rel_dyn_start /* x2 <- Runtime &__rel_dyn_start */
71 adr x3, __rel_dyn_end /* x3 <- Runtime &__rel_dyn_end */
72 pie_fix_loop:
73 ldp x0, x1, [x2], #16 /* (x0, x1) <- (Link location, fixup) */
74 ldr x4, [x2], #8 /* x4 <- addend */
75 cmp w1, #1027 /* relative fixup? */
76 bne pie_skip_reloc
77 /* relative fix: store addend plus offset at dest location */
78 add x0, x0, x9
79 add x4, x4, x9
80 str x4, [x0]
81 pie_skip_reloc:
82 cmp x2, x3
83 b.lo pie_fix_loop
84 pie_fixup_done:
85 #endif
最后执行 bl _main,跳转到arch/arm/lib/crt0_64.S:
66 ENTRY(_main)
71 #if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
72 ldr x0, =(CONFIG_TPL_STACK)
73 #elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
74 ldr x0, =(CONFIG_SPL_STACK)
75 #elif defined(CONFIG_SYS_INIT_SP_BSS_OFFSET)
76 adr x0, __bss_start
77 add x0, x0, #CONFIG_SYS_INIT_SP_BSS_OFFSET
78 #else
79 ldr x0, =(CONFIG_SYS_INIT_SP_ADDR)
80 #endif
81 bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */
82 mov x0, sp
83 bl board_init_f_alloc_reserve
84 mov sp, x0 //设置栈指针栈底地址
85 /* set up gd here, outside any C code */
86 mov x18, x0 //arm64 gd对应的寄存器为x18,arm32为r9
87 bl board_init_f_init_reserve
88
89 mov x0, #0
90 bl board_init_f
91
92 #if !defined(CONFIG_SPL_BUILD)
93 /*
94 * Set up intermediate environment (new sp and gd) and call
95 * relocate_code(addr_moni). Trick here is that we'll return
96 * 'here' but relocated.
97 */
98 ldr x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */
99 bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */
100 ldr x18, [x18, #GD_NEW_GD] /* x18 <- gd->new_gd */
101
102 adr lr, relocation_return
103 #if CONFIG_POSITION_INDEPENDENT
104 /* Add in link-vs-runtime offset */
105 adr x0, _start /* x0 <- Runtime value of _start */
- 首先建立c语言运行环境,根据宏定义设置栈指针,第74行设置spl的栈指针,CONFIG_SPL_STACK(指向栈底),即 0xff8effff;第79行设置uboot栈指针,CONFIG_SYS_INIT_SP_ADDR(定义在DRAM空间),因为u-boot运行时,DDR已经被初始化了,并且uboot本身也是运行在ddr中。
- 第81-82行,将x0低4位清零,表示16字节对齐,然后赋值为栈指针sp。
- 第83行,为malloc和gd预留空间。如下所示,top存放的是x0,即当前sp的值,对于rk3399,CONFIG_SPL_SYS_MALLOC_F_LEN=0x4000,即16k;然后再预留一个global_data的大小,并对top进行16字节对齐,最后返回top,可以通过x0获得该值。
45 ulong board_init_f_alloc_reserve(ulong top)
46 {
47 /* Reserve early malloc arena */
48 #if CONFIG_VAL(SYS_MALLOC_F_LEN)
49 top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
50 #endif
51 /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
52 top = rounddown(top-sizeof(struct global_data), 16);
54 return top;
55 }
- 第84行,设置新的栈指针sp;
- 第86行,将gd的首地址赋值为x18;
此时spl在SRAM栈的布局如下所示:
-第87行,在调用 board_init_f_init_reserve时,此时x0保存的是gd的基地址;调用之后,如下图所示:
-第90行,跳转到 bl board_init_f,该函数是一个weak函数,rk3399重定义该函数,主要实现功能:
- 初始化uart;
- 初始化uboot DM,解析device tree;
- 安全timer的初始化;
- 初始化pinctrl;
- 初始化ddr.
到此,spl的所有子系统基本初始化完毕,DDR也可以使用了。执行完board_init_f返回后(Note:spl和uboot调用的board_init_f完全不同,spl调用的是rk3399-board-spl.c中的,而uboot调用的是common/board_f.c)。
接着spl 执行 board_init_r函数(common/spl/spl.c):
- 进行memory的malloc池的初始化
- timer_init() 初始化时钟
- spl_board_init() //board 相关初始化
- boot_from_devices //Try loading a booting U-Boot to ram from a list of devices,example RAM,MMC, NAND;
- jump_to_image_no_args(&spl_image) // 跳转u-boot入口地址 entry_point,entry_point是由u-boot.img头部信息提供的
- SPL结束其生命,将控制权交给u-boot/Linux.
至此,spl分析结束。