4. U-Boot启动流程分析(上):第一阶段start.S分析、CPU初始化、异常向量表设置
好,咱们正式开始啃U-Boot启动流程这块硬骨头。这一章我先讲第一阶段,也就是汇编部分。很多初学者一看到start.S就头大,满屏的汇编宏和条件编译,其实没那么可怕。你把它拆成几个功能块来看,就清晰了。
我个人习惯把第一阶段分成三个核心任务:异常向量表设置、CPU初始化、以及为C运行环境铺路。说白了,就是让CPU先认识自己,再认识内存,最后能跑C代码。
4.1 入口点与异常向量表
U-Boot的入口点通常是 _start,定义在 arch/arm/lib/vectors.S 或 arch/arm/cpu/armv7/start.S 里。不同架构路径不同,但思路一致。
为什么一上来就要搞异常向量表?因为CPU上电后,第一条指令就是从复位向量地址取的。ARM架构规定,0x00000000或0xFFFF0000处放一张向量表,每个表项占4字节,存一条跳转指令。
核心要点:异常向量表是CPU的“紧急联络簿”。复位、中断、数据中止等异常发生时,CPU自动跳转到对应表项执行。
来看一段典型的向量表代码:
_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中断
_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
注意看,复位向量用的是 b reset,其他用的是 ldr pc, xxx。为什么?因为复位后CPU处于SVC模式,且MMU还没开,地址空间是平坦的。用 b 跳转范围有限(±32MB),但复位后通常够用。其他异常用 ldr pc 是因为它们可能发生在任意阶段,需要能跳到任意地址。
我的经验:有一次移植到一颗Cortex-A7芯片上,板子死活不进中断。查了两天,发现是向量表基地址没重定位。ARM默认从0x0取向量表,但我把代码链接到了0x87800000。后来在start.S里加了 mcr p15, 0, r0, c12, c0, 0 设置VBAR,问题解决。嗯,这个坑我印象很深。
4.2 CPU初始化:从复位到SVC模式
CPU复位后处于什么模式?ARM架构下通常是SVC模式(超级管理员模式),但有些芯片会进入其他模式。所以第一步,我们要确保CPU进入SVC模式,并且关掉所有中断。
这段代码通常在 reset 标号后面:
reset:
/* 设置CPU为SVC模式,关中断 */
mrs r0, cpsr
bic r0, r0, #0x1f @ 清除模式位
orr r0, r0, #0xd3 @ SVC模式 + 关IRQ/FIQ
msr cpsr, r0
这里 0xd3 的含义:bit[4:0] = 10011(SVC模式),bit[7] = 1(关IRQ),bit[6] = 1(关FIQ)。你想想看,在初始化阶段,我们绝对不希望被任何中断打断,否则CPU状态就乱套了。
| CPSR位域 | 值 | 含义 |
|---|---|---|
| M[4:0] | 10011 | SVC模式 |
| I (bit7) | 1 | 禁止IRQ |
| F (bit6) | 1 | 禁止FIQ |
4.3 CPU关键寄存器初始化
接下来要初始化一些CPU控制寄存器。不同架构差异很大,我以ARMv7为例讲几个关键的。
4.3.1 SCTLR:系统控制寄存器
SCTLR控制着MMU、Cache、对齐检查等开关。刚复位时,这些功能都是关闭的。我们要确保MMU和Cache是关的,因为还没初始化页表。
mrc p15, 0, r0, c1, c0, 0 @ 读取SCTLR
bic r0, r0, #0x0005 @ 关MMU (bit0) 和 关对齐检查 (bit2)
bic r0, r0, #0x1000 @ 关I-Cache (bit12)
bic r0, r0, #0x0004 @ 关D-Cache (bit2) — 注意不同架构位不同
mcr p15, 0, r0, c1, c0, 0 @ 写回SCTLR
注意:不同ARM版本SCTLR的位定义有差异。比如Cortex-A8和Cortex-A7的Cache控制位就不一样。移植时一定要查对应TRM(技术参考手册)。我曾经因为照搬了A8的代码到A7上,Cache死活刷不干净,折腾了一下午。
4.3.2 设置中断向量基地址(VBAR)
前面提到过,ARM允许把异常向量表放在任意地址。通过CP15的c12寄存器设置:
ldr r0, =_start @ 向量表基地址
mcr p15, 0, r0, c12, c0, 0 @ 写入VBAR
这一步很关键。如果你的U-Boot链接地址不是0x0,就必须设置VBAR,否则异常来了CPU会去0x0取指令,而那里可能什么都没有。
4.4 整体启动流程图
我把第一阶段的核心流程画了张图,方便你对照理解:
4.5 一些容易被忽略的细节
讲几个我实际移植中遇到的坑:
- 栈指针设置时机:有些移植代码在设置SVC模式后立刻设SP,但此时内存控制器可能还没初始化。我建议等DDR初始化完成后再设SP,否则写栈会写飞。
- WDT(看门狗)处理:很多SoC默认看门狗是开的。如果不在第一阶段关掉,板子会在初始化过程中反复复位。我习惯在
reset开头就加一段关WDT的代码。 - CPU版本检测:有些芯片有多个CPU核,或者有大小核架构。start.S里通常要读MIDR寄存器判断当前是哪个核,主核继续初始化,从核进WFI等待。
我的调试技巧:如果板子卡在start.S里出不来,我会在关键步骤后加GPIO翻转。比如设置完SVC模式后拉高一个GPIO,初始化完SCTLR后再拉低。用示波器看GPIO波形,就知道卡在哪一步了。这比瞎猜高效得多。
4.6 第一阶段完成后CPU的状态
当start.S执行完,即将跳转到C代码入口(通常是 _main)时,CPU应该处于以下状态:
| 资源 | 状态 | 说明 |
|---|---|---|
| CPU模式 | SVC | 超级管理员模式,权限最高 |
| IRQ/FIQ | 关闭 | 防止中断干扰初始化 |
| MMU | 关闭 | 使用物理地址 |
| I-Cache | 关闭 | 避免取指不一致 |
| D-Cache | 关闭 | 避免数据不一致 |
| 异常向量表 | 已设置 | 指向U-Boot的向量表 |
| 栈指针 | 未设置 | 等C代码入口设置 |
嗯,第一阶段的核心内容就这些。说白了就是让CPU从“混沌”状态进入一个“可控”状态,为后续的C代码执行铺平道路。下一阶段我们会进入C代码,开始板级初始化和设备驱动配置。但那是后话了,先把这一阶段的代码吃透再说。