1、 _start 入口
根据u-boot.lds可以了解到,所有程序的入口点都是 _start 符号, _start 位于u-boot-2020.01/arch/arm/lib/vectors.S函数里:
具体代码如下:
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */
.globl _reset
.globl _undefined_instruction
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _fiq
.macro ARM_VECTORS
#ifdef CONFIG_ARCH_K3
ldr pc, _reset
#else
b reset
#endif
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
.endm
因此,可以看出当发生复位时,硬件会自动跳转到_start地址,然后根据向量表执行b reset,进入reset处理函数(在start.S中定义)
2、reset
reset函数是U-Boot启动过程中最先执行的代码,负责将系统从硬件复位状态初始化到可以运行C代码的环境。下面我将详细分析reset函数的功能和执行流程。
1、reset函数整体流程
2、详细功能分解
(1)save_boot_params
reset:
b save_boot_params /* 跳转到引导参数保存 */
其中:
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
ENDPROC(save_boot_params)
.weak save_boot_params
提供板级定制入口,允许保存从BootROM传递的启动参数(如内存配置、设备状态等)。默认弱符号实现直接返回,厂商可重写此函数。
save_boot_params_ret:
1、虚拟化支持检查(ARMv7 LPAE)
(1)检查CPU是否支持虚拟化扩展
#ifdef CONFIG_ARMV7_LPAE
mrc p15, 0, r0, c0, c1, 1 /* 读取ID_PFR1寄存器 */
and r0, r0, #CPUID_ARM_VIRT_MASK /* 提取虚拟化支持位 */
cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
beq switch_to_hypervisor /* 如果支持则切换到hypervisor模式 */
switch_to_hypervisor_ret:
#endif
2、处理器模式设置
(1)确保系统运行在特权级的SVC模式
(2)启动阶段禁用所有中断
(3)保留HYP模式的特殊情况处理
mrs r0, cpsr /* 读取当前程序状态寄存器 */
and r1, r0, #0x1f /* 提取模式位[4:0] */
teq r1, #0x1a /* 比较是否为HYP模式(0x1a) */
bicne r0, r0, #0x1f /* 如果不是HYP模式,清除模式位 */
orrne r0, r0, #0x13 /* 设置为SVC模式(0x13) */
orr r0, r0, #0xc0 /* 设置I/F位,禁用FIQ和IRQ */
msr cpsr,r0 /* 写回CPSR */
3、异常向量表配置
(1)允许向量表位于任意内存地址
(2)确保向量表地址正确对齐
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
mrc p15, 0, r0, c1, c0, 0 /* 读取系统控制寄存器(SCTLR) */
bic r0, #CR_V /* 清除V位(bit13),使用低地址异常向量 */
mcr p15, 0, r0, c1, c0, 0 /* 写回SCTLR */
#ifdef CONFIG_HAS_VBAR
ldr r0, =_start /* 获取_start地址 */
mcr p15, 0, r0, c12, c0, 0 /* 设置VBAR(向量基址寄存器) */
#endif
#endif
4、进行CPU初始化
(1)调用函数cpu_init_cp15和cpu_init_crit分别初始化CP15和CRIT;
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
bl cpu_init_crit
#endif
#endif
5、跳转到C环境
bl _main
(2)cpu_init_cp15
cpu_init_cp15
函数是U-Boot启动过程中对ARM处理器最底层的初始化代码,负责配置CP15系统控制协处理器,为后续启动流程建立稳定的硬件环境。
ENTRY(cpu_init_cp15)
/*
* 第一步:无效化所有缓存和TLB
* 确保启动时没有残留的旧缓存数据影响系统
*/
mov r0, #0 @ 准备MCR操作的零值
mcr p15, 0, r0, c8, c7, 0 @ 无效化所有TLB(地址转换缓存)
mcr p15, 0, r0, c7, c5, 0 @ 无效化指令缓存(I-Cache)
mcr p15, 0, r0, c7, c5, 6 @ 无效化分支预测数组
mcr p15, 0, r0, c7, c10, 4 @ 数据同步屏障(DSB),确保内存操作完成
mcr p15, 0, r0, c7, c5, 4 @ 指令同步屏障(ISB),清空流水线
/*
* 第二步:配置MMU和缓存控制
* 设置系统控制寄存器(SCTLR)的关键位
*/
mrc p15, 0, r0, c1, c0, 0 @ 读取系统控制寄存器
bic r0, r0, #0x00002000 @ 清除V位(bit13),使用低地址异常向量
bic r0, r0, #0x00000007 @ 清除CAM位(bit2-0),关闭MMU和数据/指令缓存
orr r0, r0, #0x00000002 @ 设置A位(bit1),启用对齐检查
orr r0, r0, #0x00000800 @ 设置Z位(bit11),启用分支预测
#if CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
bic r0, r0, #0x00001000 @ 清除I位(bit12),禁用指令缓存
#else
orr r0, r0, #0x00001000 @ 设置I位(bit12),启用指令缓存
#endif
mcr p15, 0, r0, c1, c0, 0 @ 写回系统控制寄存器
...
/*
* 第五步:设置临时栈
* 为可能需要调用C函数的勘误处理准备运行环境
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr r0, =(CONFIG_SPL_STACK) @ SPL阶段使用专用栈
#else
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR) @ 正常U-Boot使用初始化栈
#endif
bic r0, r0, #7 @ 8字节对齐
mov sp, r0 @ 设置栈指针
/*
* 第七步:返回调用者
*/
mov pc, r5 @ 通过保存的lr返回
ENDPROC(cpu_init_cp15)
cpu_init_cp15函数主要的操作如下:
- 缓存和TLB初始化:通过CP15协处理器指令无效化I-Cache/D-Cache/TLB
- MMU/缓存控制:配置系统控制寄存器,按需启用指令缓存和分支预测
- 运行环境准备:设置临时栈空间,为可能的C函数调用做准备
(3)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)
#endif
其中,lowlevel_init:
以下是直接在代码上添加的关键注释和整体功能总结:
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* 底层初始化函数,设置栈以便调用C函数进行后续初始化
* 注意:此时全局数据(GD)尚未可用
*/
#include <asm-offsets.h>
#include <config.h>
#include <linux/linkage.h>
/*-------------------------------------------
* 定义弱符号s_init(板级早期初始化函数)
* 默认实现为空,板级代码可覆盖实现
*-----------------------------------------*/
.pushsection .text.s_init, "ax"
WEAK(s_init) // 弱符号定义,可被板级代码覆盖
bx lr // 默认直接返回
ENDPROC(s_init)
.popsection
/*-------------------------------------------
* 主函数:lowlevel_init(底层初始化)
* 核心功能:设置临时运行环境
*-----------------------------------------*/
.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)
/* [关键1] 设置临时栈指针 */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =CONFIG_SPL_STACK // SPL阶段使用专用栈
#else
ldr sp, =CONFIG_SYS_INIT_SP_ADDR // 正常U-Boot使用初始化栈
#endif
bic sp, sp, #7 // 栈指针8字节对齐(符合AAPCS规范)
/* [关键2] 全局数据区处理 */
#ifdef CONFIG_SPL_DM
mov r9, #0 // 使用设备树时不需GD(r9传统存放GD指针)
#else
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata // SPL阶段使用预定义的GD区域
#else
sub sp, sp, #GD_SIZE // 正常U-Boot:在栈上分配GD空间
bic sp, sp, #7 // 重新对齐栈指针
mov r9, sp // 设置GD指针
#endif
#endif
/* [关键3] 保存返回地址 */
push {ip, lr} // ip保存原始lr,lr是当前返回地址
/* [关键4] 调用板级早期初始化 */
bl s_init // 调用板级特定初始化(可能是空实现)
/* [关键5] 恢复返回地址并返回 */
pop {ip, pc} // 恢复ip为原始lr,pc跳转返回
ENDPROC(lowlevel_init)
.popsection
整体功能总结
- 为U-Boot启动早期阶段建立最基本的运行环境
- 在全局数据(GD)和DRAM初始化之前提供临时栈空间
- 为板级特定初始化提供执行入口
一些细节总结:
(1)在imx6ull中,31行CONFIG_SYS_INIT_SP_ADDR有如下定义:
#define PHYS_SDRAM MMDC0_ARB_BASE_ADDR
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
其中,IRAM_BASE_ADDR 与 IRAM_SIZE 的定义如下:
#define IRAM_BASE_ADDR 0x00900000
...
#if !(defined(CONFIG_MX6SX) || \
defined(CONFIG_MX6UL) || defined(CONFIG_MX6ULL) || \
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif
因此CONFIG_MX6ULL被定义后,IRAM_SIZE为 0x00020000即128K
(2)函数中调用的s_init 判断Soc为is_mx6ull时,会直接返回
void s_init(void)
{
...
if (is_mx6sx() || is_mx6ul() || is_mx6ull() || is_mx6sll())
return;
...
}
此时函数一路返回路径为:
--> s_init
--> lowlevel_init
--> cpu_init_crit
--> save_boot_params_ret --> bl _main
总结:
从 _start 到 _main 的汇编阶段,主要做了以下几件事:
1、设置CPU运行模式为SVC模式,关闭FIQ和IRQ中断
原因:
(1)SVC 模式可以访问所有系统寄存器(如 MMU、缓存控制),U-Boot 需要初始化关键硬件(如内存控制器、时钟),必须运行在特权模式下
(2)在 U-Boot 初始化阶段关闭中断(通过设置 CPSR 的 I 和 F 位)是为了:
-
U-Boot 的硬件初始化(如 DDR 配置、时钟设置)必须是不可中断的,**
-
早期阶段,中断控制器(如 GIC、NVIC)可能尚未配置,此时响应中断会导致未定义行为(如死机)
2、****设置CP15协处理器的若干个寄存器,包括了设置异常向量表重定位并写入异常向量表的地址,失效Cache,关闭MMU,清除Cache
(1)重定位异常向量表是因为,我们知道异常向量表的地址是0x00000000,而Uboot是在DDR中运行的,DDR的起始地址肯定不是0x00000000,那么异常向量表必须重定位,这里只是配置好了重定位所需要的寄存器内容,实际的向量表重定位由后续的relocate_vector函数完成。
(2)关闭MMU是因为,MMU负责虚拟地址到实际物理地址的转换,此时还没有加载操作系统,操作的都是实际物理地址,不需要MMU来转换。
(3)清除Cache是因为,Cache的内容是从DDR中缓存过来的,起始阶段DDR中还没有我们加载的内容,此时从DDR中缓存内容到Cache中,如果CPU从Cache中取数据可能导致错误。
3、设置芯片内部的IRAM
划分出一部分用来作为堆栈(方便后续调用C函数),划分一部分用来存储uboot中的重要变量:struct global_data,这个结构体中包含了cpu的时钟频率信息,总线的时钟频率,uboot重定位的地址,外部DDR的大小,Uboot本身的大小的起始地址与终止地址,malloc内存池的大小和位置等等众多重要数据,这些结构体内部的变量是在后续的board_init_f()函数中被初始化的,这些变量被初始化完成以后,Uboot会根据其值进行重定位和一系列对外设的操作。
引用: