当前位置: 首页 > news >正文

ARM64 启动全流程拆解:从 BootROM 到 U-Boot 到底发生了什么?

ARM64 启动全流程拆解:从 BootROM 到 U-Boot 到底发生了什么?

记得第一次做嵌入式 Linux 移植的时候,对着开发板按了半天电源键,串口啥都没打印。我当时心态是:板子是不是坏了?

后来才发现,ARM64 的启动流程远比我以为的复杂。BootROM → SPL → ATF → U-Boot → Kernel,每一级都有自己的一套规矩。

今天我来把这套东西拆开揉碎,写清楚。

一、按下电源键之后,CPU 在干嘛?

先看整体流程,有个宏观印象:

初始化 DDR
加载下一级

EL3 → EL2
ATF BL31/BL32

EL2 → EL1
设备树 + Kernel

上电复位

BootROM
芯片内部固件

SPL/TF-A
初级阶段

U-Boot
完整引导加载

Linux Kernel

init 进程

ARM 处理器上电后,CPU 的Program Counter会被硬件强制设置到一个固定地址——这个地址指向芯片内部固化的一段 ROM 代码,这就是BootROM

BootROM 是芯片出厂就烧好的,你改不了它。它的任务极其简单:

  1. 初始化最基本的外设(通常是 SPI / SD / NAND 控制器)
  2. 从预定义的启动介质读取一小段代码到 SRAM(内部 RAM,不需要 DDR 初始化就能用)
  3. 跳转到这段代码执行

BootROM 支持的启动介质各有不同,典型的 BootROM 介质选择流程:

0001

0010

0100

1000

失败

成功

上电复位

跳转到 Reset Vector
0x0000_0000 / 0xFFFF_0000

执行 BootROM

检查启动引脚
BOOT_MODE[3:0]

从 SD/eMMC 启动

从 SPI NOR Flash 启动

从 NAND Flash 启动

USB DFU 下载模式

读取前 128KB
到 SRAM

等待 USB 下载

校验签名
/CRC

死循环等待

跳转到 SPL
SRAM 中执行

关键点:这时 DDR 还没初始化,代码只能在芯片内部的 SRAM 里跑。SRAM 通常只有 64KB~512KB,这也是为什么 BootROM 加载的代码必须很小。

二、SPL:比 BootROM 多一点,比 U-Boot 少一点

SPL(Secondary Program Loader),也有的芯片方案叫它 MLO 或 IBoot。

BootROM 把 SPL 加载到 SRAM 后,SPL 要干的事情:

最重要的一件事:初始化 DDR。DDR 的初始化参数(时序、频率、ODT、ZQ 校准)在硬件设计阶段就确定了,SPL 从 BootROM 那里得到这些参数,然后:

/* 伪代码:DDR 初始化序列 */voidddr_init(void){/* 1. 设置 DDR 控制器 */writel(DDR_CONF_2T,&ddrc->cfg);writel(ROW_BITS_16,&ddrc->row_config);/* 2. 设置 PHY 时序 */writel(PHY_DQS_SLEW_RATE,&ddr_phy->dqs_sr);writel(PHY_CLK_DELAY_VAL,&ddr_phy->clk_delay);/* 3. ZQ 校准 */set_zq_calibration(ZQ_CAL_LONG);/* 4. 等待 DDR 就绪 */while(!(readl(&ddrc->stat)&DDR_READY));/* 5. 简单的 DDR 读写测试 */volatileuint32_t*test=(uint32_t*)DDR_BASE;*test=0xDEADBEEF;if(*test!=0xDEADBEEF)panic("DDR init failed!");}

DDR 初始化成功后,SPL 就有了大块内存可用。接着它把完整的 U-Boot 镜像从 Flash/SD/NAND 加载到 DDR 的某个地址,然后跳过去。

SPL 和 U-Boot 是同一套代码树,编译时通过 CONFIG_SPL_BUILD 区分。这也是为什么很多开发板上用同一个u-boot.bin但前面有个偏移——前 128KB 是 SPL,后面是完整的 U-Boot。

三、ATF:EL3 的看门人

ARMv8 引入了Exception Level(异常等级)的概念,这是 ARM64 最核心的变化之一:

EL0
用户态

EL1
操作系统内核

EL2
虚拟机监视器

EL3 (最高特权级)
Secure Monitor

SMC 调用

启动

系统调用

ATF BL31
运行时代理

ATF BL32
OPTEE (可选)

U-Boot / UEFI
或 Hypervisor

Linux Kernel

App / libc

来看每个 EL 的角色:

异常等级典型软件权限
EL3ATF BL31 / Secure Monitor最高,控制 PSCI/电源管理/SMC
EL2U-Boot / Hypervisor虚拟化扩展,MMU 第二阶段翻译
EL1Linux Kernel操作系统内核,MMU 第一阶段翻译
EL0用户程序最低,无特权指令

ARM Trusted Firmware(ATF)承担了 EL3 的职责,提供PSCI(Power State Coordination Interface)服务。U-Boot 要关中断、重启系统、进入低功耗模式,全部要通过 SMC 指令陷入 EL3,由 ATF BL31 处理。

典型的 SMC 调用流程:

U-Boot (EL2) ATF BL31 (EL3) │ │ │ SMC #0 (PSCI_CPU_ON) │ │───────────────────────────>│ │ ├── 检查调用者权限 │ ├── 切换异常等级状态 │ ├── 配置目标 CPU 的上下文 │ │ │ SMC 返回 │ │<───────────────────────────│ │ │ │ 目标 CPU 已在指定地址启动 │

没有 ATF,ARM64 就启动不了。这是 ARM 生态在 v8 之后的强制要求。

四、U-Boot:真正的引导程序

在 ATF 准备好 EL3 环境之后,U-Boot 在 EL2 开始运行。

U-Boot 完整的加载和跳转流程:

Linux KernelU-Boot (DDR)ATF BL31 (DDR)SPL (SRAM)BootROMLinux KernelU-Boot (DDR)ATF BL31 (DDR)SPL (SRAM)BootROM初始化 DDR解析设备树加载内核镜像设置启动参数EL2 → EL1启动 init 进程加载到 SRAM加载 BL31 + BL33 到 DDR跳转到 BL31 Entry初始化 EL3 环境SMC 切换到 EL2跳转到 U-Boot跳转到 Kernel

U-Boot 启动后主要做这几件事:

4.1 加载设备树(DTB)

设备树是 ARM 嵌入式开发的基石。U-Boot 在board_init()阶段就把 DTB 从存储介质加载到内存了:

# U-Boot 命令行常见操作=>load mmc0:1${fdt_addr_r}/boot/kernel.dtb=>load mmc0:1${kernel_addr_r}/boot/Image.gz=>booti${kernel_addr_r}-${fdt_addr_r}

4.2 解析启动参数

设备树里记录了板级信息:DDR 大小、serial 地址、中断控制器 GIC 版本……

/* 设备树中的启动参数 */ / { chosen { bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw"; stdout-path = &uart0; }; memory@40000000 { device_type = "memory"; reg = <0x0 0x40000000 0x0 0x40000000>; /* 1GB */ }; };

4.3 跳转到内核

U-Boot 最后调用booti(ARM64 专用,bootm是 ARM32 遗留)跳转到内核入口。

跳转时,CPU 的状态很讲究:

  • MMU: 关
  • Cache: 关
  • x0 寄存器:指向 DTB 地址
  • 异常等级:EL2

Kernel 在入口处读 x0 拿到 DTB 地址,然后开启 MMU、建立页表、转换到 EL1……Linux 启动了。

五、实际调试踩过的坑

说几个我实际遇到过的坑:

坑 1:SPL 太大超过 SRAM

有一次在某个主控上移植,芯片 SRAM 只有 128KB。SPL 编译出来 135KB,BootROM 加载到 SRAM 就直接盖到未初始化区域了,板子死得一塌糊涂。

解决方案:裁剪 SPL。去掉不用的驱动支持,用CONFIG_SPL_SIZE_LIMIT做编译检查。

坑 2:DDR 参数没校准

同一批板子,有的能启动有的不能。最后发现是 DDR 的 ODT(片内端接)值在量产时焊接偏差导致信号完整性不够。

解决:增加 DDR 训练流程,跑一遍写读训练(Write Leveling + DQS Gate Training)。

坑 3:ATF 版本不匹配

U-Boot 2024.01 配 ATF v2.8,SMC 调用号对不上,一 PSCI 操作就卡死。

解决:U-Boot 和 ATF 保持同源发布版本。

六、总结一下时序

00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms00 ms加载 SPL 到 SRAM初始化 DDR加载 ATF + U-Boot跳转 BL31SMC 代理初始化切换到 EL2外设初始化加载设备树 + Kernel跳转 KernelMMU 初始化init 启动BootROMSPLATFU-BootKernelARM64 启动时序

ARM64 的启动不是一个简单的"加载 → 跳转"。BootROM → SPL → ATF → U-Boot → Kernel,每一级都在补充上一级的能力限制,最终把系统带到 Linux 可以接管的状态。

下次你按电源键的时候,脑子里过一遍这整个流程,是不是感觉板子也没那么神秘了?


这篇写完了。下期打算写 ARM64 异常处理的完整路径:中断来了 CPU 都做了啥?从 EL1 IRQ 到 EL3 FIQ,中断分发到底走哪条路径?有兴趣的可以关注。

http://www.jsqmd.com/news/1133244/

相关文章:

  • 时空视觉跨镜步态推演人体肢体情绪量化系统专项技术白皮书
  • MIC1557+PIC18F46K42高精度定时系统设计与优化
  • PIC18LF25K40与MC74HC165A实现高效IO扩展方案
  • 基于A89307与STM32L4R9AI的高性能FOC电机控制方案解析
  • 终极免费指南:3分钟解锁WeMod专业版,畅享完整游戏修改功能
  • 终极WeMod增强指南:零成本解锁专业版功能的完整教程
  • 人工智能AI编程 Agent 入门系列教程之理解 Claude Code 智能体循环、上下文、工具与权限
  • bert-ancient-chinese 模型部署实战:Hugging Face 快速调用与本地推理 3 步指南
  • AD74413R与TM4C129XNCZAD的高精度工业信号处理方案
  • ADS 2022 威尔金森功分器设计:从原理图到版图仿真的 3 个关键步骤与 1 个 Delta 参数调优
  • 基于DRV8213和PIC18的智能散热系统设计与实现
  • PCF8591 ADC/DAC模块与PIC18LF4685的I2C通信实战
  • 基于A89307和PIC18F57K42的BLDC电机FOC控制方案
  • OBS多平台直播终极指南:3步实现一键多平台RTMP推流
  • ComfyUI-VideoHelperSuite:5步轻松实现AI视频处理,告别命令行复杂操作
  • 工业4-20mA电流环与INA196电流检测设计实践
  • OpenClaw定制技能开发:状态机驱动的机械手行为编程
  • ICM-42688-P与MKV42F64VLH16在工业自动化中的协同应用
  • LTC6904与PIC18LF27K40实现高精度可编程方波发生器
  • 5分钟完全指南:使用Wand-Enhancer解锁Wand游戏修改器的专业功能
  • Wand-Enhancer:基于本地化补丁技术的WeMod客户端功能增强方案
  • 持续进化的企业知识引擎:WSaiOS知识获取引擎架构与机制研究
  • 因子分析与主成分分析对比:基于23家上市公司21项财务指标的R语言建模
  • 断言的知识
  • Keycloak密码加密实战:算法选型、参数调优与安全迁移方案
  • 基于STM32与PWM控制器的数字降压电源设计
  • 终极指南:如何轻松限制腾讯ACE-Guard反作弊系统资源占用,告别游戏卡顿
  • 3分钟解锁Wand游戏修改器:开源增强方案完全指南
  • ComfyUI-VideoHelperSuite终极指南:5步轻松掌握AI视频处理
  • Scikit-learn 1.5.0 实战:5步完成鸢尾花分类,准确率超95%(附完整代码)