uboot-spl的程序流程主要包含下面的几个函数:
_start->reset->save_boot_params->cpu_init_crit->lowlevel_init->_main->board_init_f
在armv7架构的uboot-spl,主要需要做如下事情
- 关闭中断,svc模式
- 禁用MMU、TLB
- 芯片级、板级的一些初始化操作
- IO初始化
- 时钟
- 内存
- 选项,串口初始化
- 选项,nand flash初始化
- 其他额外的操作
- 加载BL2,跳转到BL2
下面详细分析一下它的过程。(注意,本人是以uboot2011的源码版本来分析的。)
一、首先,通过uboot-spl编译脚本/u-boot/arch/arm/cpu/armv7/u-boot-spl.lds,程序如下:
...//前面省略
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.text :
{
__start = .;
arch/arm/cpu/armv7/start.o (.text)
*(.text*)
} >.sram . = ALIGN();
.rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram . = ALIGN();
.data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
. = ALIGN();
__image_copy_end = .;
_end = .; .bss :
{
. = ALIGN();
__bss_start = .;
*(.bss*)
. = ALIGN();
__bss_end__ = .;
} >.sdram
}
对于任何程序,入口函数是在链接时决定的,uboot的入口是由链接脚本决定的。uboot下armv7链接脚本默认目录为arch/arm/cpu/u-boot.lds。这个可以在配置文件中与CONFIG_SYS_LDSCRIPT来指定。
入口地址也是由连接器决定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定。这个会在编译时加在ld连接器的选项-Ttext中。
链接脚本中这些宏的定义在linkage.h中,看字面意思也明白,程序的入口是在_start.,后面是text段,data段等。
所以uboot-spl的代码入口函数是_start。
二、_start的定义在/u-boot/arch/arm/cpu/armv7/start.S中:
.globl _start
_start: b reset
ldr pc, _undefined_instruction /* 未定义指令向量 */
ldr pc, _software_interrupt /* 软件中断向量 */
ldr pc, _prefetch_abort /* 预取指令异常向量 */
ldr pc, _data_abort /* 数据操作异常向量 */
ldr pc, _not_used /* 未使用 */
ldr pc, _irq /* irq中断向量 */
ldr pc, _fiq /* fiq中断向量 *
#ifdef CONFIG_SPL_BUILD /* 中断向量表入口地址 */
_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
_pad: .word 0x12345678 /* now 16*4=64 */
#else
_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
_pad: .word 0x12345678 /* now 16*4=64 */
#endif /* CONFIG_SPL_BUILD */ .global _end_vect
_end_vect: .balignl ,0xdeadbeef
.global声明_start为全局符号,_start就会被连接器链接到,也就是链接脚本中的入口地址了。
以上代码是设置arm的异常向量表,arm异常向量表如下:
地址 |
异常 |
进入模式 |
描述 |
0x00000000 |
复位 |
管理模式 |
复位电平有效时,产生复位异常,程序跳转到复位处理程序处执行 |
0x00000004 |
未定义指令 |
未定义模式 |
遇到不能处理的指令时,产生未定义指令异常 |
0x00000008 |
软件中断 |
管理模式 |
执行SWI指令产生,用于用户模式下的程序调用特权操作指令 |
0x0000000c |
预存指令 |
中止模式 |
处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取中止异常 |
0x00000010 |
数据操作 |
中止模式 |
处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常 |
0x00000014 |
未使用 |
未使用 |
未使用 |
0x00000018 |
IRQ |
IRQ |
外部中断请求有效,且CPSR中的I位为0时,产生IRQ异常 |
0x0000001c |
FIQ |
FIQ |
快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常 |
8种异常分别占用4个字节,因此每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,reset异常是直接跳转到reset函数,其他7种异常是用ldr将处理函数入口地址加载到pc中。
后面汇编是定义了7种异常的入口函数,这里没有定义CONFIG_SPL_BUILD,所以走后面一个。
接下来定义的_end_vect中用.balignl来指定接下来的代码要16字节对齐,空缺的用0xdeadbeef,方便更加高效的访问内存。
后面,开始声明中断起始地址:
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de /* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif /* IRQ stack memory (calculated at run-time) + 8 bytes */
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de
这里声明中断处理函数栈起始地址,给出的值是0x0badc0de,是一个非法值,注释也说明了,这个值会在运行时重新计算,代码是在interrupt_init中。
/*
* the actual reset code
*/ reset:
bl save_boot_params
/*
* set the cpu to SVC32 mode
*/
@ mov r0, #
@ cmp r0, #
@ beq reset
here:
mrs r0, cpsr
bic r0, r0, #0x1f /*工作模式位清零 */
orr r0, r0, #0xd3 /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */
msr cpsr,r0
/*以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置一,从而屏蔽了IRQ和FIQ中断。*/ #if defined(CONFIG_ARM_A7)
@set SMP bit
mrc p15, , r0, c1, c0,
orr r0, r0, #(<<)
mcr p15, , r0, c1, c0,
#endif
@#if defined(CONFIG_ARCH_SUN9IW1P1)
@ ldr r0, =0x008000e0
@ ldr r1, =0x16aa0001
@ str r1, [r0]
@#endif
在上电或者重启后,处理器取得第一条指令就是b reset,所以会直接跳转到reset函数处,而reset首先是跳转到save_boot_params中。
三、save_boot_params的定义如下:
void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)__attribute__((weak, alias("save_boot_params_default")));
save_boot_params函数在/u-boot/arch/arm/cpu/armv7/cpu.c中定义,该函数什么都没做。
这句话的意思:__attribute__((weak))将本模块的save_boot_params转成弱符号类型,如果该函数在其他地方没有定义,则为空函数。
由于它没有定义,所以这是个空函数。
接着,后面运行这段程序:
#if defined(CONFIG_OMAP34XX)
/* Copy vectors to mask ROM indirect addr */
adr r0, _start @ r0 <- current position of code
add r0, r0, # @ skip reset vector
mov r2, # @ r2 <- size to copy
add r2, r0, r2 @ r2 <- source end address
mov r1, #SRAM_OFFSET0 @ build vect addr
mov r3, #SRAM_OFFSET1
add r1, r1, r3
mov r3, #SRAM_OFFSET2
add r1, r1, r3
next:
ldmia r0!, {r3 - r10} @ copy from source address [r0]
stmia r1!, {r3 - r10} @ copy to target address [r1]
cmp r0, r2 @ until source end address [r2]
bne next @ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
/* No need to copy/exec the clock code - DPLL adjust already done
* in NAND/oneNAND Boot.
*/
bl cpy_clk_code @ put dpll adjust code behind vectors
#endif /* NAND Boot */
#endif /* CONFIG_OMAP34XX */
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
这里需要注意,ARM默认的异常向量表入口在0x0地址,uboot的运行介质(norflash nandflash sram等)映射地址可能不在0x0起始的地址,所以需要修改异常向量表入口。
与网上的不同的是,这里没有对协处理器cp15的操作。
在cpy_clk_code之前的汇编的作用如下:
- 初始化异常向量表
- 设置cpu svc模式,关中断
- 设置异常向量入口
如果没有定义CONFIG_SYS_NAND_BOOT和CONFIG_SYS_ONENAND_BOOT,则进入cpy_clk_code。
四、下面直接看cpu_init_crit。
cpu_init_crit:
/*
* Invalidate L1 I/D
*/
...//省略详细的内容 /*
* disable MMU stuff and caches
*/
...//省略详细的内容 /*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
mov ip, lr @ persevere link reg across call
bl lowlevel_init @ go setup pll,mux,memory
mov lr, ip @ restore link
mov pc, lr @ back to my caller
#endif
在禁止了L1 I/D、MMU之后,调用了lowlevel_init。他在/u-boot/arch/arm/cpu/armv7/lowlevel_init.S中有定义
五、lowlevel_init函数
它是与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从mem启动,则会做memory初始化,方便后续拷贝到mem中运行。
lowlevel_init函数则是需要移植来实现,做clk初始化以及ddr初始化
从cpu_init_crit返回后,_start的工作就完成了,接下来就要调用_main,总结一下_start工作:
- 前面总结过的部分,初始化异常向量表,设置svc模式,关中断
- 初始化mmu cache tlb
- 板级初始化,pll memory初始化
六、初始化栈指针sp为调用board_init_f做准备,这个过程一般是在_main中实现的。
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, # /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000 @bl boot_standby_relocat bl board_init_f
首先进行堆栈的设置,然后就跳转到board_init_f函数,其中传递给该函数的参数为0。
然后调用/u-boot/arch/arm/lib/board.c
此时已经进入C语言的代码,spl就结束了。
参考:
http://blog.csdn.net/xiaohai1232/article/details/60775435#t0
http://blog.csdn.net/skyflying2012/article/details/25804209
http://blog.csdn.net/ooonebook/article/details/52957395
https://wenku.baidu.com/view/254d6bc3d15abe23482f4dec.html
https://wenku.baidu.com/view/eb73e1edb8f67c1cfad6b89b.html