BootROM与一级启动链路_技术探讨
从复位取指讲到 BootROM 与一级加载器(SPL / 一级 Boot)交权 —— 《嵌入式启动介质与启动架构》的上一级 | 编制日期 2026-10-14 | 版本号 Rev 1.0
结论:复位之后,CPU 从硬件决定的固定地址取第一条指令,这段指令属于出厂固化的 BootROM;它判定引导模式、探测介质、解析镜像头,把合法镜像搬进片内 SRAM 后跳转交权 —— 交权点就是 BootROM 与用户固件的分界线。这一段完全由芯片决定,固件工程师不能改写它,只能理解它,并顺着它的规矩设计一级加载器。
报告总结
很多「板子不启动」的问题,根源不在应用代码,而在启动链最初两级:BootROM 到底有没有找到合法镜像、有没有把控制权交出来。这一段发生在任何日志、任何调试手段介入之前,是最难观测、也最值得提前想清楚的一段。本篇把这段黑盒拆开,回答四个问题:
- 第一条指令从哪里来:复位取指地址由什么硬件决定,为什么它不可能被软件修改(第 1 章)。
- BootROM 凭什么当起点:它在地址映射中的位置、内置它的收益与代价(第 1 章)。
- BootROM 内部做什么:模式判定、介质探测、镜像头解析、下载烧录四件事(第 2 章)。
- 它做不了什么:SRAM 容量与时钟初值两项先天限制,如何塑造一级 loader 的形态(第 2 章,展开在第 3 章)。
读者路径
- 只关心「板子为什么不启动」:先读第 2 章的判定流程与 2.3 节镜像头解析,再跳到第 6 章失败模式与回退设计。
- 正在为新 SoC 编写 SPL:先读第 1 章职责分界与 1.2 节地址映射,再精读第 3 章与 2.5 节先天限制。
- 做安全启动方案:先读 2.3 节的检查项,再顺着第 5 章的签名校验展开。
- 做介质选型:本篇 2.2 节给出台账,介质特性与选型的展开在上一级文档《嵌入式启动介质与启动架构》。
1. 复位之后的第一条指令从哪来
复位释放之后的第一件事不是你的代码,而是芯片里出厂固化的一段引导代码。本章回答启动链上最基础的问题:第一条指令从哪里来、它由什么决定、以及这段固化代码与你的固件之间如何分界。
1.1 取指地址与复位向量:硬件决定的第一跳
CPU 复位释放后取第一条指令的地址,不是软件配置出来的,而是由芯片内部的总线译码逻辑硬连线决定的。复位控制器保证在释放之前,CPU、总线与存储接口都处于已知的静止状态;释放的那一刻,取指单元按照架构约定的复位向量地址发起第一次取指。不同架构对这一机制的叫法不同,但本质完全一致:第一跳由硬件决定,软件只能接受。
这条铁律带来两个直接推论。其一,启动链的第一环不依赖任何外部器件的状态,无论板上是空片还是满载固件,CPU 都会执行同一个地址上的代码;其二,想更换「第一条指令」的内容,唯一的途径是让那个地址上存放不同的代码 —— 这正是 BootROM 存在的意义。
第二条指令开始就不再是「硬连线」的领地了:BootROM 里的代码会读取引导配置、访问介质控制器,这些都是普通的软件行为。换句话说,硬件只负责把 CPU 送到 BootROM 门口,进门之后的路怎么走,全部写在那段固化代码里。理解启动链的关键分水岭正在这里:第一跳不可改变,第一跳之后的一切都由 BootROM 的设计决定,而 BootROM 的设计你又改不了 —— 所以只能读手册、顺着它来。
| 要素 | 在第一跳中的角色 | 说明 |
|---|---|---|
| 复位控制器 | 决定复位释放的时刻与顺序 | 释放前 CPU 引脚与总线处于已知的静止状态 |
| 总线译码逻辑 | 把固定取指地址路由到 BootROM 存储体 | 硬连线实现,与软件无关 |
| BootROM 存储体 | 提供第一条指令及其后的引导代码 | 掩膜 ROM,出厂固化,不可改写 |
| 复位向量约定 | 约定从哪个地址开始取指 | 机制名称因架构而异,本质相同 |
图 1 给出了常见的地址映射布局:BootROM 位于映射的顶部区域,复位向量恰好落在这里。也有架构把启动 ROM 映射在最低地址,取向不同、结论相同。具体地址与布局以对应 SoC 手册为准。
1.2 BootROM 在地址映射里的位置
复位向量落在哪里,BootROM 就必须出现在哪里 —— 这是「位置」问题的全部逻辑。多数架构把异常向量表安排在地址映射的顶部,于是把 BootROM 映射到最高地址区域;也有架构把最低地址留给启动 ROM 或可切换的启动存储。无论哪种取向,都可以用一句话判断:复位向量指向谁,谁就是启动链的第一环。
| 映射区域 | 常见相对位置 | 启动期的角色 |
|---|---|---|
| BootROM | 常见于映射顶部,也有架构放在最低地址 | 复位取指的来源,启动链第一环 |
| 片内 SRAM | 紧邻 BootROM 之下或独立区段 | 一级 loader 的搬运目标与运行区 |
| 外设寄存器区 | 映射中段 | 启动早期只访问引导所需的外设 |
| 代码空间与备份域 | 视架构而定 | 与启动关系较弱,按需了解 |
对固件工程师而言,关心位置问题主要为了两件事:一是阅读厂商资料时能对上号 —— 理解 ROM 镜像从哪个地址开始执行;二是理解为什么调试器接入之后看到的程序计数器落在某个固定区域。这些细节每家芯片不同,请以 SoC 手册的存储映射章节为准。
还有一点值得注意:BootROM 占据的映射窗口通常远大于掩膜 ROM 的实际容量,多余的地址空间往往被镜像或保留。不要据此猜测 ROM 的真实大小,同样以手册为准。
1.3 为什么现代 MCU 与 SoC 都内置 BootROM
在没有内置 BootROM 的年代,芯片要启动只有两条路:片内集成可编程存储并预烧固件,或者靠外部器件与板上跳线搭出最小引导环境。前者把固件烧录提前到芯片出厂环节,后者把复杂度转嫁给板级设计,两种方案的产线成本与售后成本都不低。
内置 BootROM 用一小块掩膜 ROM 换来了三件事:空片可启动(进下载模式完成首次烧录)、引导入口统一(多介质适配收敛到片内代码)、信任根天然在片内(安全启动链条有了起点)。这就是现代 MCU 与应用处理器几乎无一例外内置它的原因。
| 对比维度 | 无内置 BootROM | 有内置 BootROM |
|---|---|---|
| 空片出厂表现 | 无法启动,必须预烧固件或带座烧录 | 可直接进入下载模式完成首次烧录 |
| 引导入口 | 由板上硬件搭出的最小环境承担 | 统一由片内代码承担,一致性好 |
| 安全启动起点 | 信任根难落在片内,需外部器件配合 | 信任根天然在片内 ROM 中 |
| 介质灵活性 | 换介质需改硬件或预置代码 | 靠引导模式与探测顺序适配多介质 |
| 代价 | 省一片掩膜,但产线与售后成本高 | 占用掩膜成本,出厂后逻辑不可改 |
BootROM 出厂后逻辑不可修改,一旦有缺陷只能靠后续镜像绕过或芯片改版修复。部分芯片提供「ROM 补丁」机制:把补丁代码放在片内 RAM,由 ROM 在固定时机检查并挂钩。是否提供、如何启用,以 SoC 手册为准。
1.4 BootROM 与用户固件的职责分界
把职责分界画清楚,是设计一级 loader 的前提。BootROM 只做四件事:判定引导模式、按固化顺序探测介质、解析镜像头并做合法性检查、把镜像搬入片内 SRAM 后跳转交权。它不初始化 DRAM,不配置业务外设,也不关心你的应用是什么。
用户固件(从 SPL 或一级 Boot 开始)接手后做的事情正好是 BootROM 不做的:时钟与电源的完整初始化、DRAM 初始化、把二级固件搬运到位、执行二级安全校验,最后把控制权交给应用。
分界线就是「交权点」。BootROM 校验通过、跳转到镜像入口之后,通常不再参与后续过程;只有当系统复位重新回到启动流程,或者用户固件主动触发复位进入下载模式时,BootROM 才会再次被用到。各家芯片交权时的现场约定(寄存器状态、参数传递方式)不同,以 SoC 手册为准。
1.5 本文档在启动系列中的位置
本篇是《嵌入式启动介质与启动架构》的上一级。介质篇回答「从哪里读」:NOR、NAND、eMMC 等介质的特性、接口与选型;本篇回答「谁去读、读完交给谁」:复位取指如何落到 BootROM,BootROM 如何完成判定、解析与交权。两篇合起来,才是完整的启动图景。
按顺序阅读的建议:先读介质篇建立「介质侧」的图景,再读本篇理解「控制流侧」的接力;之后可以顺着第 3 章进入一级加载器的细节,顺着第 5 章进入镜像头与安全启动,顺着第 6 章研究失败回退。全系列只做文字指引,不设跳转链接,按章节号在对应文档中查找即可。
2. BootROM 里面有什么
BootROM 的代码对用户不可见,但它的行为可以从手册、工具与现象中推断。本章把它的内部结构归纳为五件事:判定、探测、解析、下载、限制。
2.1 引导模式判定:BOOT 引脚、熔丝与 eFuse
复位释放后,BootROM 要回答的第一个问题是「这次启动想干什么」。主流判定手段有三类:采样 BOOT 引脚电平组合、读取熔丝(eFuse)配置、检查备份域标志。三类手段的优先级与组合方式由芯片设计决定,常见取向是「熔丝优先于引脚」,具体以 SoC 手册为准。
BOOT 引脚是开发期最常用的手段:上电瞬间采样电平组合,映射到不同的引导介质或直接进入下载模式。熔丝则面向产线与量产:烧写之后默认引导行为永久改变,用来固化「从哪颗介质启动」这类决策。备份域标志(如果芯片支持)允许软件在复位之间传递意图,例如「下一次复位请进下载模式」。
| 判定手段 | 可否运行期改变 | 典型用途 | 工程注意点 |
|---|---|---|---|
| BOOT 引脚采样 | 可改,每次上电重新采样 | 开发期切换介质与救援 | 引脚要上下拉固定,防悬浮误判 |
| 熔丝 / eFuse | 不可逆,烧写后永久生效 | 产线固化默认引导介质 | 烧错无法挽回,需产线双重确认 |
| 备份域标志位 | 可改,掉电由备份电源维持 | 记录「请求进入下载模式」等意图 | 是否支持、如何传递以 SoC 手册为准 |
BOOT 引脚悬浮时读到的电平不可预期;电源未稳时采样则可能锁存错误组合。工程对策是给 BOOT 引脚加上下拉电阻、必要时用 RC 延时让电源稳定后再采样。判定的采样时刻与毛刺容忍度以 SoC 手册为准。
2.2 支持的引导介质与探测顺序
判定为正常引导后,BootROM 按固化的探测顺序逐个尝试可用介质。顺序由芯片设计决定,典型取向是把「最常用、最快」的介质排在前面;能否通过熔丝或配置调整顺序,各家差异很大,以 SoC 手册为准。
探测的基本动作是:把对应的存储控制器初始化到保守状态,到约定偏移处读取镜像头;若格式标识不合法,则视为「该介质上没有可引导镜像」,转入下一介质。全部失败后,多数芯片会落回下载模式,等待主机连接。各介质镜像头偏移约定的细节在第 4 章展开。
| 引导介质 | 访问方式 | 对一级 loader 的含义 | 典型场景 |
|---|---|---|---|
| 片内 Flash / 片外 NOR | XIP,直接原地执行 | 可免搬运,链路最短 | 小型 MCU、对启动时间敏感的设备 |
| SPI NAND | 拷贝执行 | 必须先搬入 SRAM 再运行 | 大容量、成本敏感的设备 |
| eMMC / SD | 拷贝执行 | 搬入 SRAM 后运行,吞吐较高 | 应用处理器、带大镜像的设备 |
| 串口 / USB 下载 | 不读介质 | 完全绕过介质,进入烧录流程 | 空片产线烧录与现场救援 |
探测顺序本质上一条「优先级链」:排在最前的介质先被尝试,失败后自动落到下一个候选,这条链的末端通常挂着下载模式。把这条链想清楚,很多「板子明明有固件却从别的介质启动了」的现象就有了解释 —— 大概率是排在前面的介质上残留了一个格式合法但内容过时的镜像头,探测根本没轮到你想启动的那颗介质。清理旧介质、或用熔丝固化探测顺序,是两条常见的治理思路。
介质的访问方式决定了启动链的形态:XIP 介质可以让后续代码原地执行,链路最短;拷贝执行介质则强制引入「搬入 SRAM」的动作,一级 loader 的体积约束(见 2.5 节)由此而来。介质本身的特性与选型,见《嵌入式启动介质与启动架构》。
2.3 镜像头解析与合法性检查
找到介质只是第一步,BootROM 还要确认「这块介质上的镜像可信且完整」。它读取约定偏移处的镜像头,逐项检查:格式标识是否为本芯片约定格式、长度是否落在允许区间、内容哈希是否与镜像头记录一致;启用安全启动的芯片还会验证签名链,这部分在第 5 章展开。
镜像头的字段布局(魔数、版本、长度、入口、哈希与签名的位置)完全是芯片私有的约定,逐字段定义以 SoC 手册为准。固件打包工具生成镜像头的方式必须与之严格对应 —— 这是「改了打包脚本就起不来」的常见根源。
| 检查项 | 检查内容 | 失败后的典型去向 |
|---|---|---|
| 格式标识 | 镜像头魔数与版本是否为本芯片约定格式 | 判为非法镜像,按探测顺序回退 |
| 长度合理性 | 镜像长度是否落在允许区间内 | 拒绝加载,避免越界搬运 |
| 哈希校验 | 镜像内容哈希与镜像头记录是否一致 | 回退下一介质或请求重试 |
| 签名校验 | 启用安全启动时验证签名链(详见第 5 章) | 判为不可信,进入回退或下载模式 |
图 3 把本章前三节串成一条完整流程:模式判定给出「正常引导还是下载模式」的分支,正常引导再经过介质选择与镜像头解析,最终跳转或回退。校验失败并不意味着立即失败,典型行为是按探测顺序回退到下一介质重试;全部回退失败后进入下载模式。失败行为的细节(重试次数、是否留痕)以 SoC 手册为准,回退设计在第 6 章展开。
2.4 内置的下载与烧录协议
所有 BootROM 都内置了一条与介质无关的退路:下载模式。此时 BootROM 不再尝试读取介质,而是作为从机等待上位机连接,通过约定的握手指令进入命令交互,接收数据块并写入目标介质或 SRAM,实现空片烧录与现场救援。
机制层面,协议都是「握手、命令、数据块、应答」的循环:主机发起握手,BootROM 应答表示就绪;主机逐条发送命令(读、写、跳转、复位),BootROM 逐条执行并回送状态。物理通道常见 UART 与 USB:前者实现简单、几乎每颗芯片都有,后者吞吐高、适合大镜像。部分芯片还提供 CAN、以太网等入口,是否提供以 SoC 手册为准。
| 物理通道 | 机制特点 | 典型用途 |
|---|---|---|
| UART 串口 | 字节流握手,实现简单、对电气环境要求低 | 产线烧录、小镜像救援的经典通道 |
| USB | 枚举后批量传输,吞吐远高于串口 | 大镜像烧录、开发期高频使用 |
| 其它通道 | 部分芯片提供 CAN、以太网等入口 | 是否提供、如何触发以 SoC 手册为准 |
具体的报文格式、命令集与上位机工具(各家命名不同)属于厂商私有协议,本文只在机制层面讨论,使用时以厂商的烧录工具文档与 SoC 手册为准。调试视角的使用技巧见第 11 章。
2.5 BootROM 的先天限制:SRAM 容量与时钟初值
BootROM 运行在一个被严格限定的环境里,两项先天限制对一级 loader 的影响最为深远。其一是片内 SRAM 容量:对非 XIP 介质,BootROM 把镜像搬进片内 SRAM 再执行,一级 loader 的体积必须小于这个上限,超出就无法被搬运执行。
其二是时钟初值:复位之后系统时钟处于保守的默认状态,远低于正常运行水平。BootROM 不会替你提速 —— 它不知道板上的时钟树约束 —— 因此介质访问、镜像搬运都发生在低速区间,大镜像的拷贝时间会明显变长。
| 先天限制 | 直接后果 | 一级 loader 的设计对策 |
|---|---|---|
| 片内 SRAM 容量上限 | 非 XIP 介质时,一级 loader 必须小于该上限 | 精简功能,只做时钟、DRAM 与搬运 |
| 复位默认时钟保守 | 介质访问速度远低于正常运行水平 | 尽早完成时钟配置,再提速搬运 |
| ROM 逻辑不可修改 | 出厂后无法打补丁修复缺陷 | 留好回退与救援路径(详见第 6 章) |
这两项限制都写在芯片里,运行期无法修改。一级 loader 的全部设计取向 —— 小体积、快时钟、只做必要的事 —— 都是对这两条限制的直接回应,第 3 章展开。
由此也能看出为什么需要两级加载:BootROM 只能完成「把一个小 loader 送进 SRAM」这一步,真正把系统带起来需要 loader 自己完成时钟、DRAM 与大镜像搬运。下一章从 SPL 讲起。
3. 一级加载器的角色:SPL 与一级 Boot
BootROM 把第一阶段代码送进 SRAM 之后,启动的故事才讲了一小半。本章的主角是 SPL(或一级 Boot):它站在 ROM 的规矩与系统的野心之间,把一个只能勉强喘息的最小环境,升级成二级固件可以大展拳脚的完整地基。
3.1 为什么需要两级:BootROM 只认它自己的规矩
BootROM 是掩膜在芯片内部的一段只读代码,流片之后一个字节都改不了。这决定了它必须把「规矩」定得又窄又硬:只认实现时写死的几种介质与访问时序、只解析自家定义的镜像头、只把第一阶段镜像装进容量有限的片内 SRAM。这些规矩在芯片出厂那一刻就冻结了,而介质生态仍在不断变化——新颗粒、新封装、越来越大的镜像,随时可能超出 ROM 的认知范围。
于是两级加载成为标准解法。第一级(SPL 或一级 Boot)是 ROM 唯一认可的「用户代码」:体积极小、格式严格,正好卡进 ROM 的全部约束;第二级才是真正的系统固件,可以放手使用 DRAM、文件系统与完整协议栈。两级的职责边界一句话就能说清:BootROM 负责「把 SPL 弄进来」,SPL 负责「把系统弄起来」。前者受限于 ROM 的规矩,后者受限于 SRAM 的容量,谁也不越界。
这个结构同时化解了三个矛盾:SRAM 装不下完整系统,就用小体积的 SPL 中转;ROM 不认识的新介质,由 SPL 自己实现驱动去补齐;镜像格式僵化,就在 SPL 之后改用更灵活的装载方案。图 5 把两级接力的全景画在一张图里:三个阶段各有各的地盘,接力棒经过两次交接,主导权最终落在二级固件手中。
不妨做个反事实推演:如果没有 SPL,会怎样?ROM 就必须直接面对完整系统——要么把 SRAM 做得跟得上镜像的体积(芯片成本直线上涨),要么让 ROM 认识市面上每一种介质、每一种颗粒的时序(流片前永远写不完的需求)。两个方向都不成立,所以两级(甚至更多级)加载不是设计偏好,而是「固定代码」与「变化需求」之间唯一便宜的缝合方式。
顺带说明一个边界情形:并非所有 SoC 都严格走两级。少数高端应用处理器的 ROM 自己就能完成 DRAM 初始化(片内带了训练固件),一级加载器便可以瘦身甚至省略,ROM 直接把系统加载器读进 DRAM 运行;反过来,一些极简 MCU 的片内 Flash 足够装下全部代码,根本不需要第二级。分级数量不是教条,而是「ROM 能力、介质形态、代码体积」三个变量共同算出来的结果——SPL 只是这个算式里最常见的中间项。
还要说明一点叫法:不同生态对第一级加载器的称呼五花八门——SPL、一级 bootloader、xloader、引导桩,有的方案甚至把它拆成更细的几小级。叫法虽乱,职责却高度一致:都被 ROM 加载、都在小内存里运行、都干着「把 DRAM 带起来、把二级固件弄进来」这几个基本动作。本文统一用 SPL 指代,读到别家资料时对号入座即可。
表 9 把 BootROM 的每一条规矩与 SPL 的接力动作逐项对齐,两级之间的分工在这张表里看得最清楚。
| 约束点 | BootROM 的规矩 | SPL 的接力动作 |
|---|---|---|
| SRAM 容量 | 只能把第一阶段代码装进有限的片内 SRAM,装不下完整系统 | 自己保持精简,再把大块头的二级固件安置到 DRAM |
| 介质类型 | 只认实现时写死的介质清单与访问时序 | 补齐 ROM 不认识的介质与新颗粒的访问细节 |
| 镜像格式 | 只解析自家镜像头,格式不合就拒绝加载 | 为二级固件准备更宽松或私有的装载与描述格式 |
| 时钟初值 | 只以保守的初值保证介质能读 | 把时钟提到系统可用的水平,再做重活 |
| 校验深度 | 只做镜像头级别的合法性检查 | 对二级固件再做完整校验,并据此决定重试或回退 |
BootROM 管「进来」,SPL 管「起来」。前者的能力边界写死在硅片里,后者的任务清单由板卡设计者决定——两级加载的灵活性,正是把「改不了的部分」和「改得了的部分」切开的结果。
3.2 SPL 的加载位置与运行位置
SPL 从哪里运行,取决于 ROM 把它加载到哪里。最常见的目的地是片内 SRAM:容量小但访问快、地址固定,ROM 的加载逻辑也因此最简单。部分 SoC 把一级代码放进 ITCM 这类紧耦合内存,换取完全确定的取指延迟,代价是可用空间比 SRAM 更紧张。
这里有一个看似悖论、实则必然的事实:SPL 自己几乎不可能在 DRAM 里运行——因为 DRAM 正是它要初始化的对象。鸡生蛋的顺序只能是:SPL 先在 SRAM 这类「开机就能用的内存」里站稳脚跟,把 DRAM 带起来,然后才有资格讨论「往 DRAM 里搬东西」。理解了这个顺序,就理解了为什么所有 SoC 的启动代码都要分好几级。
顺带一提,SPL 在 SRAM 里运行时,它的栈、全局变量、搬运缓冲区也全部挤在同一片 SRAM 里。这段内存是整个系统开机后最早被规划的「寸土寸金之地」:ROM 刚用过的区域可以复用,但要小心 ROM 是否还留有交接数据;给 DRAM 训练留的缓冲不能与代码段打架;二级固件的加载目标虽然在 DRAM,读出过程中的暂存也常落在 SRAM。SPL 的链接脚本之所以总是精雕细琢,就是因为在几百 KB 的地盘上,每一段都要精确定位。
从调试视角看,运行位置还决定了「死在哪能看见什么」:SPL 在 SRAM 里时,仿真器一挂上就能看到它的一举一动;而一旦交权进入 DRAM,早期阶段连调试口都未必就绪。所以排查启动问题时,3.2 节的这个位置概念会反复用到——先分清「这段代码运行在哪里」,再谈「它为什么没跑下去」。
XIP 介质提供了一个例外路径:如果启动介质支持内存映射取指,一级代码可以不经过搬运直接执行;片内 Flash 方案甚至完全跳过「加载」这个动作。这条免搬运路径的适用条件在 3.4 节展开。表 10 汇总了常见的加载与运行位置组合。
| 加载与运行位置 | 谁来搬运到那里 | 特点与限制 |
|---|---|---|
| 片内 SRAM | BootROM 直接读入 | 容量最小、带宽最高、断电即失,是 SPL 最常见的家 |
| 片内 ITCM 等紧耦合内存 | BootROM 或早期代码 | 访问延迟确定,适合对取指延迟敏感的小型一级代码,空间比 SRAM 更紧 |
| 片外 XIP 介质映射区 | 无需搬运,CPU 直接取指 | 省 RAM 但执行效率受介质随机读限制,见 3.4 节 |
| 片外 DRAM | SPL 搬入(SPL 自己进不了 DRAM:它负责把 DRAM 带起来) | 空间最大,是二级固件的主阵地 |
| ROM 用过的 SRAM 区域 | 无需搬运,SPL 复用即可 | 复用前先确认 ROM 是否留有交接数据,贸然覆盖会丢启动上下文 |
表 10 里最容易选错的是最后一行:复用 ROM 的地盘省内存,但 ROM 在 SRAM 里留下的交接信息(启动原因、所选介质、镜像位置)正是调试与回退决策最想要的线索。覆盖之前先把要留的字段抄走,是几乎所有成熟 SPL 都会做的一小步。
3.3 SPL 的最小任务清单:时钟、DRAM、搬运、跳转
SPL 的代码哲学是「少做,但必须做对」。它的任务清单短而固定,顺序也不能乱:时钟先行——介质读取与 DRAM 训练都建立在稳定且够用的时钟之上;DRAM 初始化——为二级固件准备落脚点;搬运二级固件——把真正的系统代码送到 DRAM;跳转交权——校验通过后把入口交给二级固件。图 6 按时间轴展开这四个任务,并标注了每个任务完成时刻的里程碑。
清单之外其实还有一项「隐性第 0 任务」:确认引导模式与介质可用性。SPL 接手时未必清楚 ROM 走到哪一步——是首选介质一次成功,还是回退路径换了介质才进来。有些设计的 SPL 会重新确认启动介质、甚至自带简化的探测逻辑,把 ROM 的选择复核一遍再动工。这一步不是每个平台都有,但在「ROM 选择不一定可靠」的多介质系统里,它是回退设计的重要一环(呼应 4.4 节)。
清单上每一项都有「不做即死」的属性:时钟不稳,DRAM 训练得到的参数就是漂移的;DRAM 未就绪,搬运无处可写;镜像未校验就跳转,执行的可能是一段坏数据。表 11 逐项列出任务、存在理由与漏做的后果。
| 任务 | 为什么必须做 | 漏做的后果 |
|---|---|---|
| 时钟先行 | 介质读取与 DRAM 训练都依赖稳定且够用的时钟 | DRAM 训练失败或介质读取超时,链路在最前面就断掉 |
| 电源与复位状态确认 | 确认供电域就绪之后才动大动作 | 低电压下初始化引发随机性故障,极难定位 |
| DRAM 初始化 | DRAM 是二级固件的主要落脚点 | 搬运无处可写,启动当场卡死 |
| 搬运二级固件 | 把真正的系统代码从介质送到 DRAM | 系统没有可执行主体,一切后续无从谈起 |
| 完整性校验 | 防止把坏镜像交给 CPU 执行 | 坏镜像被执行,回退成本陡增 |
| 跳转交权 | 清理现场后把入口交给二级固件 | 执行环境残留,二级固件行为不可预测 |
除清单之外的一切——外设枚举、文件系统、网络栈、日志框架——都不是 SPL 的事。判断一段代码该不该放进 SPL 的标准很简单:如果它不是「让二级固件能被加载和执行」的必要条件,就放到二级去。SPL 多做一件事,就多一份占用宝贵 SRAM、多一个出错环节的风险。
四个任务之间还藏着一条依赖链,值得单独点破:时钟是所有访问的前提,所以排第一;DRAM 依赖时钟就绪,才能开始训练;搬运依赖 DRAM 就绪,才有地方落数据;跳转依赖搬运与校验完成,才有可信的代码可跳。把这条链记熟,排查启动卡死时就能按图索骥——卡在训练,先查时钟;卡在搬运,先查 DRAM 训练有没有真的通过。
裁剪 SPL 还有一个实践上的好处:它越小,从介质读出的时间就越短,且越小越容易被完整覆盖测试。成熟的 BSP 团队往往给 SPL 定下硬性的体积上限,任何想把新功能塞进 SPL 的提议都要先回答一个问题——「二级固件做不了这件事吗?」大多数时候,答案是做得了。
3.4 XIP 介质上的免搬运启动路径
XIP(eXecute In Place,就地执行)是启动链路里的一条捷径:介质出现在 CPU 的内存映射里,取指地址直接落在介质窗口上,代码不用搬运就能执行。片内 Flash 天然如此;外挂 SPI NOR 借助控制器的映射读,也能做到同样的效果。对这类介质,SPL 的「搬运」任务被大幅削薄,甚至只剩时钟与 DRAM 两项。
免搬运省掉的不只是拷贝时间,还有一段中间状态:镜像在介质里是什么样,CPU 执行的就是什么样,校验逻辑因此更直接——校验通过的就是即将执行的那一份,不存在「校验的是介质里的、执行的却是拷贝途中被改坏」的窗口。但它附带四个前提条件,表 12 逐项列出,任何一条不满足,就老实退回拷贝执行路径。
| 适用条件 | 具体含义 | 不满足时的后路 |
|---|---|---|
| 介质支持内存映射取指 | CPU 访问该地址区间即触发介质读取,无需软件搬运 | 改走拷贝执行路径(见 4.2 节) |
| 介质随机读速度可接受 | 代码就地执行不卡顿,跳转与查表不被介质拖累 | 先把代码拷进 SRAM 再执行,牺牲一份内存换速度 |
| 镜像无需重定位 | 链接地址与介质映射地址一致,代码可直接落地 | 加载时做重定位,或干脆改用搬运路径 |
| 执行与擦写不同时发生 | CPU 读的区间不会被更新逻辑同时擦写 | 执行区与更新区分离(见第 10 章) |
最后一条尤其值得警惕:XIP 介质同时被 CPU 读取、被更新逻辑写入,「边执行边更新自己脚下的地」是启动与固件更新耦合时最经典的翻车场景,具体应对在第 10 章展开。
还有一个常见误解要澄清:免搬运免的是一级代码自己的搬运,不等于整条链路都不搬运。XIP 启动时二级固件通常仍要进 DRAM——除非整个系统小到可以在 Flash 里跑到底(这类极简 MCU 方案确实存在)。换句话说,XIP 改变的是 SPL 的任务清单长度,DRAM 初始化这项多半还在,只是「搬运二级固件」从必须项变成了可选或延后项。
缓存也会改变 XIP 的性能画像:介质映射区往往挂着读缓存,首次取指慢、命中后快,代码的冷热分布直接决定平均执行速度。对启动时间敏感的设计,会把「启动早期最常执行的代码」安排得对缓存友好,这层优化与介质选型同等重要,只是更隐蔽。
最后别忘了「免搬运不等于零开销」:映射窗口的建立本身也是启动工作。片外 NOR 的窗口要控制器先完成初始化与地址映射配置;窗口大小有限时,大镜像还要靠分区切换来分段可见。这些配置步骤不做,免搬运无从谈起;做得不对,则是另一类「读得出前几 KB、读不出后面」的诡异故障来源。
3.5 二级固件的加载与交权流程
交权是启动链路里责任最重的一次交接。SPL 把二级固件搬进 DRAM、完成完整性校验之后,需要做一次「现场清理」:关闭不再需要的中断、把寄存器状态交代清楚,然后跳到二级固件的入口地址。从这一刻起,启动的主导权彻底移交,SPL 的历史使命宣告结束。
入口地址从哪里来?通常写在镜像头里,由 SPL 在解析阶段取出;没有镜像头的裸镜像则由链接脚本与加载地址约定。跳转之前,SPL 一般还会把少量交接信息放进双方约定的寄存器或内存位置——启动次数、启动原因、上一级镜像在介质上的位置之类——供二级固件接手时参考。这些约定的位置与含义同样以 SoC 手册为准,跨平台照搬是最常见的错误来源。
现场清理里最容易被轻视的是栈的交接。SPL 跳走时,它自己的栈还占着 SRAM 的一块地;二级固件要么立刻把栈切到 DRAM 或新位置,要么先在约定区域暂用、再尽快迁移。栈没交接好是「跳过去就飞」类故障的高发原因——现象是入口后头几条指令还能跑,一到函数调用就跑飞,因为栈指针还指在旧地盘的半空中。
交权本身也可能失败,而且失败得很难看:跳到未初始化的入口、入口地址解析错了、镜像在搬运途中被改坏,表现都是「跳过去就没了下文」。成熟的 SPL 会在跳转前做最后一道入口合法性检查——入口落在预期地址段内、镜像头校验字段复核通过——把能提前抓住的错误都拦在跳转之前。至于拦不住的,就交给第 11 章的黑盒排查手段了。
表 13 以职责对照的方式收束本章:每一项系统能力在交权前后由谁负责,一目了然。对照这张表排查「启动到一半死掉」的问题时,可以先按交权时刻分段:交权前死的查 SPL,交权后死的查二级固件。
| 事项 | 交权前由 SPL 负责 | 交权后由二级固件接手 |
|---|---|---|
| 时钟与电源 | 提到系统可用的水平 | 按负载与功耗目标再调优 |
| DRAM | 完成初始化与训练 | 直接当作主存使用,不再关心时序细节 |
| 启动介质访问 | 读出二级固件镜像 | 视需要挂载文件系统读取资源与数据 |
| 异常与看门狗 | 维持最基本的看护 | 注册完整的异常处理体系 |
| 启动决策 | 校验失败时决定重试或回退 | 运行期故障自行管理与上报 |
交权之后还有一件收尾小事值得交代:SPL 的代码与数据还留在 SRAM 里,二级固件迟早要把这块地收回来复用。什么时候收、怎么收(直接覆盖、还是先保留一段供崩溃转储用),各家设计不同,但「SPL 的地盘最终会被回收」这个预期要有——在 SRAM 里遗留永久数据结构的二级固件,等于悄悄侵占了启动代码的遗产,下一次重启就会撞上自己留下的东西。
4. 启动介质与访问方式
介质决定路径:能不能 XIP、要不要搬运、镜像头放在哪、探测失败往哪退——这些都不是实现细节,而是启动架构的分岔口。本章把常见介质逐个过一遍,并给出选型与启动时间之间的权衡关系。
4.1 片内 Flash 与片外 NOR 的 XIP 路径
能把 XIP 路径走通的介质有一个共同特征:支持内存映射方式的随机读取。片内 Flash 与控制器同片而生,映射窗口直达;片外 SPI NOR 借助控制器的映射读,也能让取指直达介质。图 7 的左半边就是这条路径:CPU 与介质之间只有取指动作,没有搬运环节,右半边则是下一节要讲的拷贝执行路径,两条路径一对比,差异一目了然。
XIP 的代价在于执行效率与介质随机读性能强绑定。顺序读再快的介质,随机读也未必好看,而代码执行恰恰充满跳转与查表,随机读的短板会被不断放大。因此走 XIP 路径选介质时,随机读性能的权重应当高于容量与成本——这是 XIP 方案与存储方案最大的选型差异。
容量账也要算清楚:XIP 意味着整份代码常驻介质、随时可被取指,介质容量就是代码空间的硬上限。片内 Flash 的容量在芯片选型时就锁死了,片外 NOR 虽可扩但容量越大随机读越吃亏;相比之下,拷贝执行路径只要 DRAM 够大,介质里的镜像可以做多大都不影响执行性能。代码体积增长预期强的产品,选型时就要把这笔账算进去。
此外,XIP 介质在启动链路里往往身兼数职:既放代码,也放只读数据、参数表甚至文件系统镜像。映射窗口让 CPU 像访问内存一样访问这些内容,代价是窗口地址译码、访问宽度这些约定全部压在控制器配置上——配置不对,代码读得出来、数据却对不上号,这类「半好半坏」的故障在 XIP 系统里格外难查。
还要留意窗口与地址空间的匹配问题:CPU 的取指地址必须落在介质映射的窗口之内,镜像的链接地址因此被窗口位置绑定。窗口小而镜像大的组合,要么分区切换,要么部分代码走「从窗口拷出再执行」的混合路径——很多真实系统其实是 XIP 与拷贝执行的混合体,只是比例不同。这也是「纯粹路径对比」落到具体芯片时,必须先翻手册的原因。
表 14 汇总了常见介质的访问方式、能否 XIP,以及它们在一级启动链路里的典型角色。这张表也是本章后面几节的索引:能 XIP 的看本节,不能 XIP 的看 4.2 节,镜像头约定看 4.3 节。
| 介质 | 访问方式 | 能否 XIP | 一级链路里的典型角色 |
|---|---|---|---|
| 片内 Flash | 内存映射读,控制器与介质同片 | 能 | MCU 单片方案的主场,一级代码常驻于此 |
| 片外 SPI NOR | 经控制器的内存映射窗口读 | 能 | 稳妥的二级固件载体,也常用来放 SPL |
| SPI NAND | 按页读出,需纠错与坏块管理 | 不能 | 大容量方案的拷贝执行起点 |
| eMMC | 块设备协议访问 | 不能 | 应用处理器最常见的启动介质 |
| SD 卡 | 块设备协议访问,支持插拔 | 不能 | 调试、量产烧录与恢复介质的常客 |
4.2 SPI NAND、eMMC 与 SD 的拷贝执行路径
NAND 类介质与块设备类介质(eMMC、SD)都无法 XIP:前者按页读出,还必须处理纠错与坏块管理;后者走块设备协议,CPU 根本无法对它们直接取指。它们的启动路径因此必然是「先拷进 RAM,再执行」——图 7 右半边展示的正是这条链:介质把镜像按页或按块交出来,搬运者把它写进 RAM,CPU 再从 RAM 统一取指。
拷贝执行换来的好处是执行的确定性:代码进了 RAM 之后,取指速度与介质彻底解耦,介质只在搬运阶段参与。代价是搬运时间与镜像尺寸、页读速度、纠错开销线性相关,镜像越大这段越疼。一条典型的拷贝执行路径包含四个步骤:
- 就绪判定:确认介质完成上电初始化、可以安全读出(见第 8 章);
- 定位读取:按约定的偏移与对齐读出镜像(约定见 4.3 节);
- 边读边校验:数据流过时同步核对校验字段,坏数据不留到执行阶段;
- 写入与跳转:镜像在 DRAM 就位后清理现场、跳转交权(见 3.5 节)。
这条路径还让「多份镜像」成为可能:RAM 里跑的代码与介质上的镜像彼此独立,A/B 双镜像、备份镜像都可以从容布局——第 6 章的回退设计,正是建立在拷贝执行带来的这份自由之上。XIP 方案想做双镜像,反而要小心「执行区与更新区」的边界问题。
拷贝执行路径里还有一个常被低估的角色:加载器自身的精简版。镜像特别大时,常见做法是先拷入一个「小加载器」——它只负责把大镜像的主体分批读入 DRAM——读的过程甚至可以边收边校验、边解压。这条「用小代码搬大镜像」的思路与两级加载一脉相承:每一级只搬动下一级需要的最小集合,体积压力被逐级摊薄。
NAND 类介质还有一道拷贝执行路径独有的工序:坏块管理。出厂时就可能带着坏块,使用中还会继续增加,介质里的「逻辑连续」未必是「物理连续」。ROM 阶段通常只做最朴素的跳块处理,把真正的坏块表维护交给 SPL 之后的软件;这意味着镜像必须写得足够皮实——按块对齐、块间不互相依赖,坏一块不至于让整个镜像读不出来。
eMMC 与 SD 走块设备协议,没有坏块这道工序,但多了协议层:上电后要先完成初始化握手、确定访问模式,才能开始读块。eMMC 还常划出专门的启动分区,把启动镜像与用户数据在硬件层面隔开,BOOT 引脚或寄存器配置决定 ROM 从哪个分区找镜像。这些机制各家细节不一,同样是「以 SoC 与器件手册为准」的典型区域。
SD 卡的可插拔性还带来一个拷贝执行路径特有的分支:卡不在位怎么办?对固定焊装的介质,探测失败基本等于介质坏了;而 SD 卡探测失败可能只是「没插卡」。设计里若把 SD 作为可选介质,就要明确无卡时的行为——快速失败跳到下一候选,而不是在卡检测上空等;若把 SD 作为救援介质,则要保证它排在链条末尾、永远够得着。这个分支处理不好,最常见的症状就是「插着卡能启动、拔了卡就变砖」,反之亦然。
4.3 各介质的镜像头与偏移约定
镜像头是启动链路里最「地盘化」的部分:每个 SoC 家族都有自己的魔数、字段布局与起始偏移约定,互不通用。SPL 与二级固件的镜像头往往还不一样——前者迁就 ROM 定死的格式,字段精简到不能再精简;后者由装载方案自定,可以描述重定位表、多段布局与更丰富的校验信息。
偏移约定决定了 ROM 或 SPL 到介质的哪个位置去找镜像:有的家族从介质起始位置开始放镜像,有的要先跳过一段保留区或分区表,有的要求按介质页或擦除块对齐,方便整页整块地读出。约定错了不会有任何报错——只会在错误的位置读到空数据或残片,然后表现为「介质明明是好的,就是不启动」。
还要意识到「镜像头往往不止一层」。ROM 解析的那一层格式最死板,因为它要迁就 ROM 里写死的解析代码;SPL 搬运二级固件时,处理的又可能是另一层更丰富的头。两层头各管一段:ROM 的头告诉 ROM 「把哪段数据读到哪、跳到哪」,加载器的头才描述二级固件的段布局、重定位信息与完整校验。调试启动问题时,先分清当前这级加载器认的是哪层头,再对号检查字段,能少走很多弯路。
对齐这件事再强调一遍也不为过:偏移差一个字节,按页读出的逻辑就可能把镜像截短或把相邻数据读进来。工程上稳妥的做法,是让烧录工具、镜像打包脚本与启动代码共用同一份偏移与对齐定义,而不是三处各自硬编码——三处不一致的偏移,是「实验室能启动、产线批量翻车」的经典来源。
镜像头里还有一个字段值得单独说:目标加载地址。它与入口地址是两回事——加载地址说「镜像搬到内存的哪里」,入口地址说「从镜像的哪个位置开始执行」。两者分开描述,才容得下「加载后重定位」「压缩镜像解到加载地址、入口指向解压结果」这类灵活组合。看到加载与入口指向不同位置的设计,先不要怀疑出错,多半是压缩或重定位在起作用。
不同 SoC 家族的镜像头布局、字段顺序与起始偏移差异极大,跨平台复用启动配置是最常见的翻车点。动手之前先核对目标芯片手册的启动章节,把偏移、对齐与镜像头字段逐项确认,再写配置。
表 15 把需要确认的约定项汇总成一张清单,动手前逐项打钩。
| 约定项 | 常见做法 | 注意 |
|---|---|---|
| 镜像起始偏移 | 有的家族从介质起始位置开始,有的先跳过保留区或分区表 | 以 SoC 手册为准,不可凭经验照搬 |
| 镜像头内容 | 魔数、镜像大小、入口地址、校验字段、目标加载地址 | 字段布局与字节序同样不可猜测 |
| 对齐要求 | 按介质页或擦除块对齐,便于整页整块读出 | 对齐错位会让读出截断或混入相邻数据 |
| 多份镜像布局 | 主镜像与备份镜像分开摆放,各自独立可校验 | 偏移写错会让回退机制找到一块空区 |
| 下载模式握手 | 串口或 USB 协议的握手格式由 ROM 决定 | 主机端工具必须与 ROM 的协议匹配 |
4.4 介质探测顺序与回退链条
多介质系统里,「从哪个介质启动」是一串决策:先读 BOOT 引脚与熔丝确定候选列表,再按优先级逐个尝试解析镜像头,全部失败则进入下载模式等待救援。链条的每一环都要消耗时间,而设计时必须预算的是最坏情况——首选介质损坏、逐级回退、最终兜底,这条最坏路径的耗时决定了系统「救活自己」要多久。
回退链条的长度与可靠性是一对矛盾:候选越多越抗单点故障,但每个候选的探测开销都会累加到最坏路径上。常见的折中是给首选介质配一个备份镜像(同一介质内的 A/B 布局),而不是无限堆砌候选介质——介质级回退留给真正需要的场合,镜像级回退覆盖绝大多数故障。表 16 把探测链条逐环拆开,标注每一环的决策内容与时间代价。
探测还有两个容易被忽视的时间陷阱。一是 BOOT 引脚的采样时刻:ROM 在复位释放后的固定时刻采样引脚电平,上电抖动、上下拉尚未稳定、电平转换芯片没就绪,都可能让 ROM 读到一个「正确的错值」,从此走上完全不同的探测路径——这类故障只在上电瞬间发生,复位一次就好,最迷惑人。
二是探测超时的设计:对无响应或慢响应的介质,等待多久算失败?没有超时,链条会在坏介质上无限期空等;超时太短,慢介质又被误杀。把每个候选的尝试次数与超时上限写进启动预算,最坏路径的启动时间才是可计算、可承诺的。
探测顺序本身也是可配置的设计面:不少平台允许用 BOOT 引脚的组合预排候选优先级,甚至允许软件在运行期改写优先级熔丝。安排顺序的原则很朴素——最可能成功、最值得信任的介质排最前;调试用的可插拔介质(如 SD 卡)既不能排太前(否则误插一张卡就改从卡启动),又要在救援场景下够得着。这个两难没有标准答案,只有按产品形态权衡的结果。
| 探测环节 | 决策内容 | 对启动时间的影响 |
|---|---|---|
| 引导模式判定 | 读 BOOT 引脚与熔丝,确定候选介质列表 | 每多一个候选介质,就多一段探测时间 |
| 首选介质尝试 | 按约定偏移读镜像头并完成合法性检查 | 失败即进入下一候选,空等或重试都会拉长启动 |
| 回退介质尝试 | 依次尝试预定顺序上的备用介质或备份镜像 | 链条越长,最坏情况的启动时间越长 |
| 兜底进入下载模式 | 全部候选失败后,等待主机通过下载协议连接 | 对正常启动无影响,是救援的最后入口(见 6.4 节) |
把这张表读成一张时间账单会更直观:正常路径只花第一、二行的钱,回退路径逐行累加,最坏路径是全部行之和。产品对「上电到出画面」有硬指标时,先按这张账单算最坏值——它比典型值诚实得多,而用户永远在坏的那一天盯着最坏值。
4.5 介质选型对启动时间的影响
介质特性对启动时间的影响分两条路径:XIP 路径看随机读,拷贝执行路径看顺序读带宽与纠错开销。前者决定执行流畅度,后者决定搬运耗时,所以选型之前先确定自己走哪条路径——路径选错,介质再快也快不到点子上。
镜像尺寸是常被忽略的变量:搬运时间随尺寸线性增长,把 SPL 与二级固件裁到最小、把非必需内容延后加载,往往比换更快的介质见效更大。NAND 类介质还要把纠错与坏块检查的开销算进搬运账单,控制器能力强弱在这里会拉开明显差距。
把选型时要过的账目汇总成一份清单,逐项核对:
- 随机读速度:XIP 方案的第一指标,跳转与查表多的代码尤其敏感;
- 顺序读带宽:拷贝执行方案的第一指标,直接决定搬运耗时;
- 纠错与坏块开销:NAND 类每页读出都附带,控制器强弱差距明显;
- 镜像尺寸与加载方式:能否按需加载、分段加载,比绝对带宽更值得优化;
- 是否免搬运:满足 3.4 节的四个条件时优先 XIP,省下整段拷贝时间。
第 7 章会给出完整的启动时间预算模型,把本章的介质因素放进各阶段逐一量化。这里先记住一个结论:路径选择(XIP 还是拷贝执行)对启动时间的影响,通常大于同类介质之间的品牌差异——先把路径选对,再去抠介质参数。
最后提醒一句时间维度的老化问题:启动时间是「全新介质」的成绩单,不是终身保证。NAND 颗粒用久了页读变慢、坏块增多,纠错开销随之上涨;写过的块读出干扰也会拖慢速度。对启动时间敏感、生命周期又长的产品,验收时应当用「写满擦净若干轮之后的介质」再测一遍——新介质上的漂亮数字,可能是启动链路最经不起岁月的一个指标。