本文将使用sama5d3xek SPL实现做为例子,具体代码可查看:https://github.com/voiceshen/u-boot/tree/sama5d3xek_spl_spi_nand
u-boot SPL (second program loader), 对许多人来说也说很陌生。下面对此进行一个简单介绍。
1. ARM SoC的启动过程:
RomBoot --> SPL --> u-boot --> Linux kernel --> file system --> start application
(RomBoot是固化在SoC内部的。)
u-boot实现了一个新功能,能在编译u-boot的同时生成SPL二进制文件。
2. SPL运行代码go through
从u-boot-spl.lds链接文件可知,启动代码也是start.S。
(reset) <arch/arm/cpu/armv7/start.S> (b lowlevel_init: arch/arm/cpu/armv7/lowlevel_init.S) (b _main) --> <arch/arm/lib/crt0.S> (bl board_init_f) --> <arch/arm/lib/spl.c> (board_init_r) --> <common/spl/spl.c> (jump_to_image_no_args去启动u-boot) 到此SPL的生命周期结束。
简单来讲:SPL所做工作,一些硬件的初始化,然后读取u-boot,最后调转至u-boot。
3. 下面具体分析SPL的相关代码。
<arch/arm/cpu/armv7/start.S>
- 110 reset:
- 111 bl save_boot_params
- 112 /*
- 113 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
- 114 * except if in HYP mode already
- 115 */
- 116 mrs r0, cpsr
- 117 and r1, r0, #0x1f @ mask mode bits
- 118 teq r1, #0x1a @ test for HYP mode
- 119 bicne r0, r0, #0x1f @ clear all mode bits
- 120 orrne r0, r0, #0x13 @ set SVC mode
- 121 orr r0, r0, #0xc0 @ disable FIQ and IRQ
- 122 msr cpsr,r0
- 123
- 124 /*
- 125 * Setup vector:
- 126 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
- 127 * Continue to use ROM code vector only in OMAP4 spl)
- 128 */
- 129 #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
- 130 /* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
- 131 mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
- 132 bic r0, #CR_V @ V = 0
- 133 mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register
- 134
- 135 /* Set vector address in CP15 VBAR register */
- 136 ldr r0, =_start
- 137 mcr p15, 0, r0, c12, c0, 0 @Set VBAR
- 138 #endif
- 139
- 140 /* the mask ROM code should have PLL and others stable */
- 141 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- 142 bl cpu_init_cp15
- 143 bl cpu_init_crit
- 144 #endif
- 145
- 146 bl _main
116~138: 看注释即可明白。
141: 因为SPL主要是对SoC进行初始化,所以不会定义CONFIG_SKIP_LOWLEVE_INIT, 即142,143行得以执行。
142: cpu_init_cpu15, 主要作用invalidate L1 I/D cache, disable MMU. 检查是否需要workaround。
143: cpu_init_crit直接跳转到lowlevel_init
下面看看lowlevel_init的实现:
<arch/arm/cpu/armv7/lowlevel_init.S>
- 18 ENTRY(lowlevel_init)
- 19 /*
- 20 * Setup a temporary stack
- 21 */
- 22 ldr sp, =CONFIG_SYS_INIT_SP_ADDR
- 23 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- 24 #ifdef CONFIG_SPL_BUILD
- 25 ldr r9, =gdata
- 26 #else
- 27 sub sp, #GD_SIZE
- 28 bic sp, sp, #7
- 29 mov r9, sp
- 30 #endif
- 31 /*
- 32 * Save the old lr(passed in ip) and the current lr to stack
- 33 */
- 34 push {ip, lr}
- 35
- 36 /*
- 37 * go setup pll, mux, memory
- 38 */
- 39 bl s_init
- 40 pop {ip, pc}
- 41 ENDPROC(lowlevel_init)
23: 确保sp是8字节对齐。
25:将gdata的地址存入到r9寄存器中。
39:跳转到s_init。对Atmel sama5d3xek board, s_init定义在:<arch/arm/cpu/at91-common/spl.c> 此处暂时不分析。
然后返回到start.S处,接下来调用:bl _main到<arch/arm/lib/crt0.S>
- 58 ENTRY(_main)
- 59
- 60 /*
- 61 * Set up initial C runtime environment and call board_init_f(0).
- 62 */
- 63
- 64 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
- 65 ldr sp, =(CONFIG_SPL_STACK)
- 66 #else
- 67 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
- 68 #endif
- 69 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- 70 sub sp, #GD_SIZE /* allocate one GD above SP */
- 71 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- 72 mov r9, sp /* GD is above SP */
- 73 mov r0, #0
- 74 bl board_init_f
69: 确认sp是8字对齐
70:相当于保留一个global_data的大小。
71: 确认更新后的sp是8字对齐
72:r9指向global_data
73:r0赋值0
74:跳转到board_init_f中运行。
board_init_f在<arch/arm/lib/spl.c>定义:
- 20 /*
- 21 * In the context of SPL, board_init_f must ensure that any clocks/etc for
- 22 * DDR are enabled, ensure that the stack pointer is valid, clear the BSS
- 23 * and call board_init_f. We provide this version by default but mark it
- 24 * as __weak to allow for platforms to do this in their own way if needed.
- 25 */
- 26 void __weak board_init_f(ulong dummy)
- 27 {
- 28 /* Clear the BSS. */
- 29 memset(__bss_start, 0, __bss_end - __bss_start);
- 30
- 31 /* Set global data pointer. */
- 32 gd = &gdata;
- 33
- 34 board_init_r(NULL, 0);
- 35 }
29:对BSS段进行清零操作。
34: 跳转到board_init_r
board_init_r在<common/spl/spl.c>中定义:
- 132 void board_init_r(gd_t *dummy1, ulong dummy2)
- 133 {
- 134 u32 boot_device;
- 135 debug(">>spl:board_init_r()\n");
- 136
- 137 #ifdef CONFIG_SYS_SPL_MALLOC_START
- 138 mem_malloc_init(CONFIG_SYS_SPL_MALLOC_START,
- 139 CONFIG_SYS_SPL_MALLOC_SIZE);
- 140 #endif
- 141
- 142 #ifndef CONFIG_PPC
- 143 /*
- 144 * timer_init() does not exist on PPC systems. The timer is initialized
- 145 * and enabled (decrementer) in interrupt_init() here.
- 146 */
- 147 timer_init();
- 148 #endif
- 149
- 150 #ifdef CONFIG_SPL_BOARD_INIT
- 151 spl_board_init();
- 152 #endif
135: 输出debug信息:>>spl:board_init_r();
137~140: 如果定义了:CONFIG_SYS_SPL_MALLOC_START, 则进行memory的malloc池初始化。以后调用malloc就在这个池子里面分配内存。
142~148: 如果没有定义:CONFIG_PPC, 则进行timer的初始化:timer_init() <arm/arm/cpu/armv7/at91/time.c>
150~150: CONFIG_SPL_BOARD_INIT, 则调用spl_board_init(). 这是board相关的定义,<board/atmel/sama5d3xek/sama5d3xek.c>
一切就绪后,就要检查从什么设备来启动了。这里就贴出RAM,MMC, NAND相关代码
- 154 boot_device = spl_boot_device();
- 155 debug("boot device - %d\n", boot_device);
- 156 switch (boot_device) {
- 157 #ifdef CONFIG_SPL_RAM_DEVICE
- 158 case BOOT_DEVICE_RAM:
- 159 spl_ram_load_image();
- 160 break;
- 161 #endif
- 162 #ifdef CONFIG_SPL_MMC_SUPPORT
- 163 case BOOT_DEVICE_MMC1:
- 164 case BOOT_DEVICE_MMC2:
- 165 case BOOT_DEVICE_MMC2_2:
- 166 spl_mmc_load_image();
- 167 break;
- 168 #endif
- 169 #ifdef CONFIG_SPL_NAND_SUPPORT
- 170 case BOOT_DEVICE_NAND:
- 171 spl_nand_load_image();
- 172 break;
- 173 #endif
157~161:如果定义了CONFIG_SPL_RAM_DEVICE, 则执行spl_ram_load_image(),其就是将image下载到ram中。
162~168:如果定义了CONFIG_SPL_MMC_SUPPORT, 则执行spl_mmc_load_image(),其就是将image从mmc/sd里面读取到ram中。
169~173:如果定义了CONFIG_SPL_NAND_SUPPORT, 则执行spl_nand_load_image(), 其就是将image从nand flash中读取到ram中。
当要启动的image位于RAM中后,我们就可以启动之。
- 213 switch (spl_image.os) {
- 214 case IH_OS_U_BOOT:
- 215 debug("Jumping to U-Boot\n");
- 216 break;
- 217 #ifdef CONFIG_SPL_OS_BOOT
- 218 case IH_OS_LINUX:
- 219 debug("Jumping to Linux\n");
- 220 spl_board_prepare_for_linux();
- 221 jump_to_image_linux((void *)CONFIG_SYS_SPL_ARGS_ADDR);
- 222 #endif
- 223 default:
- 224 debug("Unsupported OS image.. Jumping nevertheless..\n");
- 225 }
- 226 jump_to_image_no_args(&spl_image);
- 227 }
214:如果是u-boot,则直接到227去运行u-boot。
218:如果是Linux,则到221去启动Linux.
至此,SPL结束它的生命,控制权交于u-boot或Linux。