4、BootROM与一级Bootloader:芯片上电流程、BootROM角色、一级Bootloader加载与校验

芯片上电后,第一行代码从哪里来?

这个问题,我当年刚入行时也琢磨了很久。你想想看,这时候内存还没初始化,Flash也读不了,CPU怎么知道自己该干什么?

答案就在芯片内部的一块只读存储器里——BootROM。

4.1 芯片上电后的“第一口气”

芯片上电复位后,CPU会从一个固定的物理地址取指执行。这个地址,通常就是BootROM的映射基址。

我习惯把上电流程拆成三个阶段:

  1. 硬件复位阶段:电源稳定、时钟起振、复位信号释放。CPU内部寄存器被设成默认值,PC指针指向BootROM入口。
  2. BootROM执行阶段:运行芯片出厂固化的只读代码。完成最基础的硬件初始化,然后寻找并加载一级Bootloader。
  3. 一级Bootloader阶段:从BootROM手中接过控制权,继续初始化DDR、加载完整固件。

这里有个关键点:BootROM是只读的,出厂后就不能改了。所以它的代码必须极其精简、极其稳定。我在项目中遇到过一颗芯片,BootROM里有个小bug导致某些批次的NAND Flash读时序不对,那真是叫天天不应——因为BootROM没法打补丁。

核心认知:BootROM是芯片上电后第一个执行的软件实体,它定义了整个信任链的起点。如果BootROM被攻破,后面的一切安全措施都是白搭。

4.2 BootROM到底干了什么?

说白了,BootROM就是一个微型启动管理器。它做的事情可以归纳为以下几点:

  • 基础硬件初始化:关闭看门狗、设置系统时钟、初始化堆栈指针。有些芯片还会在这里配置管脚复用。
  • 启动介质检测:按照预设的优先级顺序,轮询各个启动设备——比如先从SPI NOR Flash读,不行再试NAND,再不行就试SD卡。
  • 一级Bootloader加载:从检测到的启动介质中,把一级Bootloader(通常叫SPL或BL1)读到芯片内部的SRAM里。
  • 完整性校验:对加载进来的一级Bootloader做签名验证或哈希校验。这一步是可信启动的基石。
  • 跳转执行:校验通过后,清理自身占用的资源,跳转到一级Bootloader的入口地址。

嗯,这里要注意:BootROM能用的内存非常有限。大多数芯片内部SRAM只有几十KB到几百KB。所以一级Bootloader必须做得足够小,才能塞进这片“弹丸之地”。

个人经验:我建议你在选型阶段就确认好芯片内部SRAM的大小。曾经有个项目,一级Bootloader编译出来128KB,芯片SRAM只有96KB——最后只能砍功能、改优化选项,折腾了两周才塞进去。

4.3 一级Bootloader的加载流程

BootROM加载一级Bootloader的过程,其实很像一个微型bootloader在做自己的本职工作。我画了一张图,帮你理清这个流程:

BootROM加载一级Bootloader流程图 BootROM 加载一级 Bootloader 流程 芯片上电复位 BootROM 入口执行 关看门狗 · 设时钟 · 初始化栈 配置管脚复用 检测启动介质 SPI NOR Flash NAND Flash SD/eMMC 加载一级Bootloader到SRAM

流程看起来不复杂,但每个环节都有坑。我挑几个重点说说。

4.4 启动介质检测顺序

BootROM怎么知道该从哪个设备启动?

通常有两种方式:

方式 原理 典型场景
管脚采样 芯片上电时采样特定GPIO的电平组合,决定启动介质 大多数SoC,如i.MX、STM32MP1
固定优先级轮询 BootROM按固定顺序尝试每个设备,直到读到有效数据 部分MCU,如某些TI芯片

我个人更倾向于管脚采样的方式。为什么?因为固定优先级轮询有个隐患:如果某个设备损坏了,BootROM可能会卡在它上面,导致启动超时。我在调试一块开发板时就遇到过——SD卡槽里插着一张坏卡,BootROM反复尝试读取,等了十几秒才fallback到SPI Flash。

避坑指南:我曾经因为启动管脚的上拉电阻没焊,导致芯片死活不从NAND启动。查了两天才发现是管脚电平不对。所以硬件设计时,启动配置管脚一定要留测试点,方便调试。

4.5 一级Bootloader的签名校验

可信启动的核心就在这里。

BootROM从Flash里读出一级Bootloader的二进制数据后,不会直接跳转。它会先做一件事——校验。

常见的校验方式有两种:

  • 哈希校验:BootROM里硬编码了一个哈希值,把加载进来的数据算一遍哈希,比对是否一致。这种方式简单,但哈希值不能更新。
  • 签名验证:BootROM里存了公钥,用公钥验证一级Bootloader附带的数字签名。这种方式灵活,公钥可以更换(如果芯片支持OTP写入)。

我参与过的一个车规级项目,用的就是签名验证。BootROM里烧录了根公钥的哈希,一级Bootloader的头部包含签名和证书链。BootROM先验证证书链有效性,再用根公钥验签。通过后才跳转。

关键点:签名校验必须在BootROM内部完成,不能依赖外部代码。因为这时候还没有任何可信代码在运行,BootROM就是唯一的信任锚点。

4.6 一级Bootloader的布局结构

一级Bootloader在Flash里不是随便放的。它有固定的格式要求,BootROM才能正确解析。

我常用的布局是这样的:

+----------------------------+
|  Header (64字节)           |
|  - 魔数 (4字节)            |
|  - 版本号 (4字节)          |
|  - 镜像长度 (4字节)        |
|  - 加载地址 (4字节)        |
|  - 入口地址 (4字节)        |
|  - 签名算法ID (4字节)      |
|  - 保留 (40字节)           |
+----------------------------+
|  签名数据 (256/512字节)    |
+----------------------------+
|  一级Bootloader镜像体      |
|  (压缩或未压缩)            |
+----------------------------+

魔数用来让BootROM确认这是一级Bootloader的有效镜像。我见过有人把魔数写错,结果BootROM怎么都认不出来,板子一直起不来。

小技巧:魔数建议用ASCII可读字符,比如“BL01”。调试时用十六进制查看器一眼就能认出来,省心。

4.7 从BootROM到一级Bootloader的交接

校验通过后,BootROM会做最后的清理工作:

  1. 关闭自身用过的外设时钟
  2. 设置栈指针到一级Bootloader指定的位置
  3. 跳转到入口地址

这里有个细节:BootROM跳转前,通常会通过寄存器传递一些信息给一级Bootloader。比如启动介质类型、时钟频率、DDR初始化参数等。我习惯在芯片手册里查清楚这些寄存器的定义,否则一级Bootloader拿不到正确的参数,后面DDR初始化就会出问题。

嗯,交接完成后,BootROM就功成身退了。从此,控制权完全交给了一级Bootloader。而一级Bootloader要做的第一件事,就是初始化DDR,为加载更大的二级Bootloader或操作系统内核做准备。

这部分内容,我们会在后面的章节里详细展开。