3. BootROM工作原理

芯片上电那一刻,发生了什么?

很多人以为第一个跑起来的是操作系统。其实不是。第一个执行的是固化在芯片内部的一段只读代码——BootROM。它被焊死在硅片上,你改不了,也删不掉。

我个人习惯把 BootROM 比作「芯片的出厂设置」。它不需要外部存储,不需要初始化内存,甚至不需要时钟稳定——它就在那里,上电即跑。

3.1 BootROM 到底干了什么?

说白了,BootROM 就三件事:

  1. 初始化最基础的硬件——比如 CPU 核心、一级缓存、中断向量表。
  2. 确定启动介质——从哪加载下一段代码?NAND Flash?eMMC?SD 卡?还是 SPI NOR?
  3. 加载并跳转——把下一级 bootloader(比如 SPL、U-Boot 的第一阶段)从介质里读出来,然后跳过去执行。

嗯,这里要注意:BootROM 不会初始化所有外设。它只碰那些「必须碰」的东西。比如 DDR 内存,它通常不碰——那是下一级 bootloader 的事。

核心原则:BootROM 追求最小化。代码体积越小,芯片成本越低。你想想看,BootROM 占用的 die area 可是真金白银。

3.2 启动介质的选择策略

BootROM 怎么知道从哪启动?

常见做法有几种:

策略 原理 典型场景
固定引脚采样 上电时读取几个 GPIO 电平,组合成启动索引 NXP i.MX、TI AM335x
烧写 fuse 芯片出厂时一次性熔断 eFuse,固化启动顺序 安全要求高的设备
轮询尝试 按固定顺序逐个介质尝试,直到读到有效头部 Allwinner、Rockchip
回退机制 主介质失败后自动切到备份介质 工业级、车载设备

我在项目中遇到过一种情况:某款芯片的 BootROM 默认从 SPI NOR 启动,但板子上焊的是 NAND Flash。结果上电后 BootROM 读不到有效数据,直接卡死。后来查手册才发现,需要拉高一个特定引脚才能切换介质。嗯,这种坑,踩过一次就记住了。

避坑指南:我曾经因为没仔细看 BootROM 的介质检测时序,导致板子启动成功率只有 70%。后来用示波器抓了 SPI 总线才发现,BootROM 在检测介质时会发送一个特定的「读 ID」命令,如果介质响应超时,它就跳过了。解决办法是在 Flash 的 WP# 引脚上加一个上拉电阻,确保 BootROM 能正确识别。

3.3 BootROM 的典型执行流程

下面这张图展示了 BootROM 从上电到跳转的完整路径:

BootROM 典型执行流程 芯片上电 / 复位 初始化 CPU 核心 设置栈指针 / 异常向量 采样启动引脚 / 读 fuse 初始化目标介质控制器 读取介质头部 / 校验 跳转到下一级 bootloader 这一步决定了 从哪个介质加载代码 校验失败则回退 到下一个介质

3.4 介质头部格式:BootROM 怎么认代码?

BootROM 读到介质上的原始二进制数据后,怎么知道这是不是合法的 bootloader?

答案是:介质头部。BootROM 会检查一个固定偏移处的魔数(magic number),以及一些关键字段。

以我调试过的某款 ARM Cortex-A7 芯片为例,它的 BootROM 期望的介质头部格式如下:

偏移 0x000:魔数 0xEA0000FE(ARM 跳转指令)
偏移 0x004:镜像总长度(字节)
偏移 0x008:加载地址(DDR 目标地址)
偏移 0x00C:入口地址(跳转目标)
偏移 0x010:校验和(累加和或 CRC32)
偏移 0x014:版本号 / 配置标志
偏移 0x018 - 0x1FF:保留 / 填充

BootROM 读到这些字段后,会做几件事:

  • 校验魔数是否正确
  • 计算校验和是否匹配
  • 检查镜像长度是否在合理范围内

全部通过,才把代码从介质拷贝到 SRAM 或指定的加载地址,然后跳转。

注意:BootROM 通常只有很小的内部 SRAM 可用(几 KB 到几十 KB)。所以它加载的下一级 bootloader 必须足够小,能塞进这片 SRAM。这就是为什么很多芯片有 SPL(Secondary Program Loader)——它负责初始化 DDR,然后加载完整的 U-Boot。

3.5 一个实际案例:NAND Flash 启动的坑

我记得有一次调试一块板子,BootROM 能从 SPI NOR 正常启动,但切到 NAND Flash 就死活不行。

用逻辑分析仪抓了 NAND 总线,发现 BootROM 发送了读 ID 命令(0x90),NAND 也正确返回了厂商 ID 和设备 ID。但 BootROM 接下来发送读页命令时,NAND 返回的数据全是 0xFF。

查了半天,问题出在 NAND 的 坏块管理 上。BootROM 默认从第 0 块开始读,但第 0 块恰好是坏块。BootROM 没有坏块跳过能力——它太简单了,不会去读 OOB 区域检查坏块标记。

解决办法:在烧录时强制把 bootloader 写到第 1 块,并在第 0 块填充一个无效的魔数,让 BootROM 跳过它。

经验总结:如果你用 NAND Flash 做启动介质,一定要确认 BootROM 是否支持坏块跳过。大部分 BootROM 不支持。所以要么保证第 0 块是好的,要么在烧录工具里做重映射。

3.6 BootROM 的局限性

BootROM 虽然重要,但它不是万能的。我列几个常见的限制:

  • 不支持文件系统——BootROM 只读裸数据,不认识 FAT32、ext4 这些。它按固定偏移读固定长度的数据。
  • 不支持复杂校验——大部分 BootROM 只做简单的累加和或 CRC32,没有 RSA 签名验证。安全启动需要额外硬件模块。
  • 不支持动态配置——BootROM 的行为在芯片出厂时就固定了。你不能通过软件更新它。
  • 调试困难——BootROM 执行期间,串口通常还没初始化。你没法用 print 调试。我一般用 GPIO 翻转来测量执行时间。

嗯,说到这里,你应该明白了:BootROM 是芯片启动的起点,但它只做最必要的事。真正的重头戏,在下一级 bootloader 里。