ARM64 启动全流程拆解:从 BootROM 到 U-Boot 到底发生了什么?
ARM64 启动全流程拆解从 BootROM 到 U-Boot 到底发生了什么记得第一次做嵌入式 Linux 移植的时候对着开发板按了半天电源键串口啥都没打印。我当时心态是板子是不是坏了后来才发现ARM64 的启动流程远比我以为的复杂。BootROM → SPL → ATF → U-Boot → Kernel每一级都有自己的一套规矩。今天我来把这套东西拆开揉碎写清楚。一、按下电源键之后CPU 在干嘛先看整体流程有个宏观印象初始化 DDR加载下一级EL3 → EL2ATF BL31/BL32EL2 → EL1设备树 Kernel上电复位BootROM芯片内部固件SPL/TF-A初级阶段U-Boot完整引导加载Linux Kernelinit 进程ARM 处理器上电后CPU 的Program Counter会被硬件强制设置到一个固定地址——这个地址指向芯片内部固化的一段 ROM 代码这就是BootROM。BootROM 是芯片出厂就烧好的你改不了它。它的任务极其简单初始化最基本的外设通常是 SPI / SD / NAND 控制器从预定义的启动介质读取一小段代码到 SRAM内部 RAM不需要 DDR 初始化就能用跳转到这段代码执行BootROM 支持的启动介质各有不同典型的 BootROM 介质选择流程0001001001001000失败成功上电复位跳转到 Reset Vector0x0000_0000 / 0xFFFF_0000执行 BootROM检查启动引脚BOOT_MODE[3:0]从 SD/eMMC 启动从 SPI NOR Flash 启动从 NAND Flash 启动USB DFU 下载模式读取前 128KB到 SRAM等待 USB 下载校验签名/CRC死循环等待跳转到 SPLSRAM 中执行关键点这时 DDR 还没初始化代码只能在芯片内部的 SRAM 里跑。SRAM 通常只有 64KB~512KB这也是为什么 BootROM 加载的代码必须很小。二、SPL比 BootROM 多一点比 U-Boot 少一点SPLSecondary 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;*test0xDEADBEEF;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。三、ATFEL3 的看门人ARMv8 引入了Exception Level异常等级的概念这是 ARM64 最核心的变化之一EL0用户态EL1操作系统内核EL2虚拟机监视器EL3 (最高特权级)Secure MonitorSMC 调用启动系统调用ATF BL31运行时代理ATF BL32OPTEE (可选)U-Boot / UEFI或 HypervisorLinux KernelApp / libc来看每个 EL 的角色异常等级典型软件权限EL3ATF BL31 / Secure Monitor最高控制 PSCI/电源管理/SMCEL2U-Boot / Hypervisor虚拟化扩展MMU 第二阶段翻译EL1Linux Kernel操作系统内核MMU 第一阶段翻译EL0用户程序最低无特权指令ARM Trusted FirmwareATF承担了 EL3 的职责提供PSCIPower State Coordination Interface服务。U-Boot 要关中断、重启系统、进入低功耗模式全部要通过 SMC 指令陷入 EL3由 ATF BL31 处理。典型的 SMC 调用流程U-Boot (EL2) ATF BL31 (EL3) │ │ │ SMC #0 (PSCI_CPU_ON) │ │───────────────────────────│ │ ├── 检查调用者权限 │ ├── 切换异常等级状态 │ ├── 配置目标 CPU 的上下文 │ │ │ SMC 返回 │ │───────────────────────────│ │ │ │ 目标 CPU 已在指定地址启动 │没有 ATFARM64 就启动不了。这是 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跳转到 KernelU-Boot 启动后主要做这几件事4.1 加载设备树DTB设备树是 ARM 嵌入式开发的基石。U-Boot 在board_init()阶段就把 DTB 从存储介质加载到内存了# U-Boot 命令行常见操作load mmc0:1${fdt_addr_r}/boot/kernel.dtbload mmc0:1${kernel_addr_r}/boot/Image.gzbooti${kernel_addr_r}-${fdt_addr_r}4.2 解析启动参数设备树里记录了板级信息DDR 大小、serial 地址、中断控制器 GIC 版本……/* 设备树中的启动参数 */ / { chosen { bootargs consolettyS0,115200 root/dev/mmcblk0p2 rw; stdout-path uart0; }; memory40000000 { device_type memory; reg 0x0 0x40000000 0x0 0x40000000; /* 1GB */ }; };4.3 跳转到内核U-Boot 最后调用bootiARM64 专用bootm是 ARM32 遗留跳转到内核入口。跳转时CPU 的状态很讲究MMU: 关Cache: 关x0 寄存器指向 DTB 地址异常等级EL2Kernel 在入口处读 x0 拿到 DTB 地址然后开启 MMU、建立页表、转换到 EL1……Linux 启动了。五、实际调试踩过的坑说几个我实际遇到过的坑坑 1SPL 太大超过 SRAM有一次在某个主控上移植芯片 SRAM 只有 128KB。SPL 编译出来 135KBBootROM 加载到 SRAM 就直接盖到未初始化区域了板子死得一塌糊涂。解决方案裁剪 SPL。去掉不用的驱动支持用CONFIG_SPL_SIZE_LIMIT做编译检查。坑 2DDR 参数没校准同一批板子有的能启动有的不能。最后发现是 DDR 的 ODT片内端接值在量产时焊接偏差导致信号完整性不够。解决增加 DDR 训练流程跑一遍写读训练Write Leveling DQS Gate Training。坑 3ATF 版本不匹配U-Boot 2024.01 配 ATF v2.8SMC 调用号对不上一 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中断分发到底走哪条路径有兴趣的可以关注。