UBOOT学习笔记(五): UBOOT汇编启动阶段

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会根据其值进行重定位和一系列对外设的操作。

引用:

imx6ull uboot启动流程 - 流水灯 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值