【1】SPL简介
SPL(Secondary programloader)是uboot第一阶段执行的代码。主要负责搬移uboot第二阶段的代码到系统内存(System Ram,也叫片外内存)中运行。SPL是由固化在芯片内部的ROM引导的。我们知道很多芯片厂商固化的ROM支持从nandflash、SDCARD等外部介质启动。所谓启动,就是从这些外部介质中搬移一段固定大小(4K/8K/16K等)的代码到内部RAM中运行。这里搬移的就是SPL。在最新版本的uboot中,可以看到SPL也支持nandflash,SDCARD等多种启动方式。当SPL本身被搬移到内部RAM中运行时,它会从nandflash、SDCARD等外部介质中搬移uboot第二阶段的代码到系统内存中。
【2】SPL功能
1.Basicarm Initialization
2.UART console initialization
3.Clocks and DPLL Locking(minimal)
4.SDRAM initialization
5.Mux(minimal)
6.Boot Device Initialization, based on where we are booting from MMC1, or MMC2,or Nand, or Onenand
7.Bootloading real u-boot from the Boot Device and passing control to it.
【3】SPL配置选项CONFIG_SPL_BUILD
上文中说道“SPL复用的是uboot里面的代码”,那要生成我们所需要的SPL目标文件,我们又该如何下手呢?很容易想到,通过编译选项便可以将SPL和uboot代码分离、复用。这里所说的编译选项便是CONFIG_SPL_BUILD,在make Kconfig的时候使能。最终编译生成的SPL二进制文件有u-boot-spl,u-boot-spl.bin以及u-boot-spl.map。
【4】SPL链接文件U-boot-spl.lds
链接文件决定一个可执行程序的各个段的存储(加载)地址,以及运行(链接)地址。下面来看看SPL的链接文件U-boot-spl.lds:
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
__image_copy_start = .;
*(.vectors)
cpuDIR/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
__image_copy_end = .;
.rel.dyn : {
__rel_dyn_start = .;
*(.rel*)
__rel_dyn_end = .;
}
.end :
{
*(.__end)
}
_image_binary_end = .;
.bss __rel_dyn_start (OVERLAY) : {
__bss_start = .;
*(.bss*)
. = ALIGN(4);
__bss_end = .;
}
__bss_size = __bss_end - __bss_start;
.dynsym _image_binary_end : { *(.dynsym) }
.dynbss : { *(.dynbss) }
.dynstr : { *(.dynstr*) }
.dynamic : { *(.dynamic*) }
.hash : { *(.hash*) }
.plt : { *(.plt*) }
.interp : { *(.interp*) }
.gnu : { *(.gnu*) }
.ARM.exidx : { *(.ARM.exidx*) }
}
#if defined(CONFIG_SPL_MAX_SIZE)
ASSERT(__image_copy_end - __image_copy_start < (CONFIG_SPL_MAX_SIZE),
"SPL image too big");
#endif
#if defined(CONFIG_SPL_BSS_MAX_SIZE)
ASSERT(__bss_end - __bss_start < (CONFIG_SPL_BSS_MAX_SIZE),
"SPL image BSS too big");
#endif
#if defined(CONFIG_SPL_MAX_FOOTPRINT)
ASSERT(__bss_end - _start < (CONFIG_SPL_MAX_FOOTPRINT),
"SPL image plus BSS too big");
#endif
【5】.vector段
可以看出,正真的.text段从.vector段开始,从字面上就能看出.vector段是中断向量表的存放处。下面U-boot源码所示是根据CPU的硬件特性所定义的中断向量表,_undefined_instruction、_software_interrupt、_prefetch_abort、_data_abort、_not_used、_irq、_fiq中分别存放着相应中断类型的中断处理程序的入口地址undefined_instruction、software_interrupt、prefetch_abort、data_abort、not_used、irq、fiq。U-boot/arch/arm/lib/vectors.S:
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset /* system will auto jump here where power on */
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
/*
*************************************************************************
*
* Indirect vectors table
*
* Symbols referenced here must be defined somewhere else
*
*************************************************************************
*/
.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
.balignl 16,0xdeadbeef /* word align the following code to alignment byte boundary */
但是,SPL的中断处理过程是这样的:#ifdef CONFIG_SPL_BUILD
.align
5undefined_instruction:software_interrupt:prefetch_abort:data_abort:not_used:irq:fiq:
1:
bl
1b
/*hang and never return */也就是说,如果SPL出现中断,那就只能重启机器了,当然,reset中断得除外,因为那是机器上电后的入口地址。
【6】start.o
回到最初的.lds中的.text段,存放完vectors后,从.text段的0x20偏移处开始就是start.o了,与star.o相对应的start.S源码位于/u-boot/Arch/Arm/Cpu/Arm926ejs/目录下,其执行流程见下图。在流程图中,关于CONFIG_SKIP_LOWLEVEL_INIT以及后面可能还会接触到的CONFIG_SKIP_RELOCATE_UBOOT编译选项需要做下解释:在他们被定义了的情况下,相应的底层(low level)初始化就会被忽略,同时U-boot不会将自身(说的应该是第二阶段U-boot代码)重新加载到RAM中。通常情况下,这些变量不应被定义,除非U-boot已经被其他bootloader或debugger加载到RAM中了。
在调用lowlevel_init之前,LPC3250工作在直接运行模式(direct run mode),所有时钟运行在13MHz,而lowlevel_init调用返回时,ARM clock=208MHz,HCLK=104MHz,PCLK=13MHz。更改时钟的操作必须在SRAM中运行,否则势必会引起执行异常。
【7】_main过程
从start.S主流程可知,程序最终会进入到_main函数中运行。下面开始分析_main过程。_main过程定义在u_boot/arch/arm/lib/crt0.S中。对于SPL,_main过程执行流程:1.
为调用board_init_f()函数构建初始化环境,该初始化环境仅提供了一个堆栈和一个存储GD(global data)结构的存储空间,所述堆栈和GD的存储空间均分配在已经可用的RAM中(SRAM等)。此时,全局变量,BSS均不可用,仅常量可用。另外,在调用board_init_f()之前,GD需要被全部清零。
调用board_init_f()。board_init_f()为从系统RAM(DRAM,DDR等)中执行程序准备硬件环境。由于系统RAM当前可能还不能使用,board_init_f()必须采用GD将那些需要传递到下一级处理阶段的数据保存起来,这些数据包括重载地址(relocation destination,在新版u-boot中用于将u-boot重新加载到系统RAM的另一个地方,在“u-boot过程”中在做分析),将来在系统RAM中重新分配的堆栈、GD的地址。3.
构建中间环境(intermediate environment),此时的stack与GD均为board_init_f()在系统RAM中所分配,但是这个时候的BSS段和已经初始化的非常量还是不能使用。4.
为调用board_init_r()函数构建环境,This environment has BSS (initialized to 0), initialized non-constdata (initialized to their intended value), and stack in system RAM (for SPLmoving the stack and GD into RAM is optional – see CONFIG_SPL_STACK_R)。GD保持在board_init_f()中的设置值不变。5.
跳转到board_init_r()运行,复制u-boot第二阶段代码到系统RAM中,并跳到第二阶段代码在系统RAM的起始地址处开始运行。从crt0.S中整理出来的SPL main源码:ENTRY(_main)
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here,outside any c code */
mov r9, r0
bl board_init_f_init_reserve
mov r0, #0
bl board_init_f
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
#ifdef CONFIG_SPL_BUILD
/*use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
#endif
ldr r0, =__bss_start /*this is auto-relocated */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /*this is auto-relocated */
mov r1, #0x00000000 /*prepare zero to clear BSS */
subs r2, r3, r0 /*r2=memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated */
mov r2, #0x00000000 /*prepare zero to clear BSS */
clbss_1: cmp r0, r1 /* while not at end of BSS */
strlo r2, [r0] /*clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_1
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
原作者:qiwzh
|