uboot学习之uboot-spl的程序流程分析

时间:2021-08-16 19:19:12

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之前的汇编的作用如下:

  1. 初始化异常向量表
  2. 设置cpu svc模式,关中断
  3. 设置异常向量入口

如果没有定义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工作:

  1. 前面总结过的部分,初始化异常向量表,设置svc模式,关中断
  2. 初始化mmu cache tlb
  3. 板级初始化,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

上一篇:http://www.cnblogs.com/yeqluofwupheng/p/7341989.html

下一篇:http://www.cnblogs.com/yeqluofwupheng/p/7355248.html