uboot版本2016.03
uboot启动流程详解
从上一节了解到,uboot启动入口点是符号_start所在的位置,_start定义在/arch/arm/lib/vector.S中,如下所示:
_start函数
#include <config.h>
.globl _start
.section ".vectors", "ax"
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
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
ldr pc, _fiq
程序进入_start,首先遇到的就是中断向量表(代码13~20行),随后执行指令b reset进行跳转。reset函数定义在/arch/arm/cpu/armv7/start.S中,如下所示:
reset函数
.globl reset
.globl save_boot_params_ret
reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
第4行就是reset函数,随后代码执行到第6行,执行指令b save_boot_params进行跳转,save_boot_params在该文件的定义如下所示:
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
ENDPROC(save_boot_params)
.weak save_boot_params
从上图可以得知,实际是程序会继续跳转到 save_boot_params_ret函数,save_boot_params_ret函数代码如下:
save_boot_params_ret函数
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
在第6行中,读取cpsr寄存器的值到r0寄存器。
第7行中,将r0寄存器的值和0x1f做与运算,并将结果保存到r1寄存器
第8行中,判断与运算结果是否等于0x1a,如果相等就将cpsr条件标志位置位。本行指令的目的就是判断cpu是否处于hyp模式。
第9行中,如果不处于hyp模式,就将r0寄存器的低5位清零。
第10行中,将r0寄存器的低5位设置为10011b。
第11行中,将r0寄存器的值与0xc0进行或运算,最后r0寄存器的值变为0xd0。
第12行中,将r0寄存器的值写入cpsr寄存器中。
总结:以上代码的目的就是,设置cpu为svc特权模式,并关闭fiq快速中断和irq中断。
继续执行以下代码:
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
此时条件编译成立。
第3行,从cp15协处理器中读取SCTLR寄存器的值到r0寄存器。
SCTLR寄存器示意图
第4行,从CR_V的定义可知:
#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */
指令的作用是将r0寄存器的bit13位进行复位。bit13为是中断向量表控制为,如果为0表示向量表的基地址为0x00000000并且可以通过设置VBAR寄存器的值确定向量表偏移地址。如果为1表示向量表基地址为0xFFFF0000,并且不能软件修改偏移地址。
第5行,将r0寄存器的值写回SCTLR寄存器。
第8行,将_start的值0x87800000读入到r0寄存器。
第9行,将r0的寄存器写入到VBAR寄存器。
总结:以上代码的功能是设置中断向量表示重定位。
继续执行以下代码:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main
此处没用定义CONFIG_SKIP_LOWLEVEL_INIT,因此会依次执行cpu_init_cp15和cpu_init_crit函数。
cpu_init_cp15函数
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array
mcr p15, 0, r0, c7, c10, 4 @ DSB
mcr p15, 0, r0, c7, c5, 4 @ ISB
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endif
mcr p15, 0, r0, c1, c0, 0
/***********省略************/
mov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)
cpu_init_cp15代码的作用主要是关闭cache和mmu。因为cache的作用是加快cpu从内存取指令的速度,但是在上电之初,在cpu初始化完成以后,内存还没有初始化完成,如果此时堆cache进行指令读取可能会发生取指令异常。关闭mmu是因为在上电之初访问的都是实际地址,和mmu没有关系,为了防止出错所以关闭。
cpu_init_crit函数:
ENTRY(cpu_init_crit)
/*
* 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.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
从代码中可以发现,cpu_init_crit函数直接调用了lowlevel_init函数,lowlevel_init函数定义在/arch/arm/cpu/armv7/lowlevel_init.S中,代码如下:
lowlevel_init函数
/*
* A lowlevel_init function that sets up the stack to call a C function to
* perform further init.
*
* (C) Copyright 2010
* Texas Instruments, <www.ti.com>
*
* Author :
* Aneesh V <[email protected]>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <asm-offsets.h>
#include <config.h>
#include <linux/linkage.h>
ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr}
/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop {ip, pc}
ENDPROC(lowlevel_init)
lowlevel_init函数,第22行将sp指针指向CONFIG_SYS_INIT_SP_ADDR,其中,依次解析宏定义:
CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET
= IRAM_BASE_ADDR + CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE
= IRAM_BASE_ADDR + IRAM_SIZE - 256
= 0x00900000 + 0x00020000 - 256
= 0X0091FF00
因此sp指针将指向0x0091FF00处。(0X00900000~0X0091FFFF是内部ram地址)
第23行,对sp指针8字节对齐。
第34行,将sp指针减去GD_SIZE大小。
第35行,对sp指针8字节对齐。
sp指针地址图
第36行,将 sp 地址保存在 r9 寄存器中。
第42行,将 ip 和 lr 压栈
第57行,调用函数 s_init,得,又来了一个函数。
第58行,将第 36 行入栈的 ip 和 lr 进行出栈,并将 lr 赋给 pc。
总结:lowlevel_init函数完成了对sp指针的设置,r9寄存器设置,以及ip和lr寄存器入栈,调用了s_init函数。
继续执行s_init函数,定义在/arch/arm/cpu/armv7/mx6/soc.c,代码如下:
s_init函数
void s_init(void)
{
struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
u32 mask480;
u32 mask528;
u32 reg, periph1, periph2;
if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
return;
/***部分省略****/
}
从代码中可以看出,它会判断当前的cpu是否为以上4种型号,如果是则直接返回,因此s_init函数相当于是一个空函数,会直接返回。
s_init退出以后会返回到lowlevel_init,再返回到cpu_init_crit,最终返回到save_boot_params_ret,因此函数调用路径如下所示:
--->_start函数
--->reset函数
--->save_boot_params_ret函数
--->cpu_init_cp15函数
--->cpu_init_crit函数
--->lowlevel_init函数
--->s_init函数
s_init函数执行完毕进行返回,返回顺序s_init->cpu_init_crit->save_boot_params_ret。至此代码将执行下一阶段_main函数