3. BootROM基础

芯片上电之后到底发生了什么?

这个问题,我当年刚入行时也困惑了很久。看着示波器上那根电源轨缓缓爬升,心里想的是:CPU这会儿在干嘛?谁在管它?

后来做了几个项目才明白,从按下电源到Linux控制台出现,中间藏着一整套精密的接力赛。今天我们就来拆解这条链路,看看BootROM和U-Boot各自扮演什么角色。

3.1 上电后的第一口气:Reset与BootROM

芯片上电,复位信号释放。CPU从复位向量指向的地址开始取指——这个地址,通常是芯片内部BootROM的入口。

BootROM是芯片出厂时固化的一段只读代码。它很小,几KB到几十KB不等。但它要做的事情,一件比一件关键。

BootROM的核心职责:

  • 初始化最基本的硬件(CPU、时钟、SRAM)
  • 检测启动介质选择引脚(Boot Mode)
  • 从选定介质加载下一级代码到SRAM
  • 验证签名(如果使能安全启动)
  • 跳转到下一级代码入口

说白了,BootROM就是芯片的"接生婆"。它自己不能干太多活,但必须保证下一棒能顺利交出去。

我个人的习惯:看一个新芯片的Datasheet时,先翻BootROM章节。那里藏着芯片设计者对启动流程的全部理解。看懂它,你就掌握了这个芯片的"脾气"。

3.2 第二棒:U-Boot SPL(如果存在)

有些芯片的SRAM很小,装不下完整的U-Boot。这时候就需要一个中间人——SPL(Secondary Program Loader)。

SPL是U-Boot的一个精简版本。它被BootROM加载到SRAM里,然后由它去初始化DDR,再把完整的U-Boot从Flash搬到内存里。

对比项 BootROM SPL U-Boot
存储位置 芯片内部ROM Flash(前几个块) Flash(后续区域)
大小 4KB ~ 64KB 16KB ~ 64KB 200KB ~ 2MB
运行环境 SRAM SRAM DDR
可修改性 不可修改 可修改(需重新烧写) 可修改
主要任务 加载SPL或U-Boot 初始化DDR,加载U-Boot 加载操作系统

嗯,这里要注意:不是所有芯片都有SPL阶段。如果芯片内部SRAM够大,BootROM可以直接把U-Boot完整加载进去。但大多数ARM SoC都需要SPL这个中间层。

我曾经踩过的坑:某次调试一块新板子,U-Boot死活跑不起来。查了两天才发现,SPL里DDR初始化参数配错了。BootROM把SPL加载到SRAM后,SPL一初始化DDR就挂。后来我养成了一个习惯——SPL阶段先加串口打印,看到"Hello"再往下走。

3.3 第三棒:U-Boot完整版

U-Boot被加载到DDR后,才真正进入"完全体"状态。这时候它有了充足的内存空间,可以做更多事情。

U-Boot的主要工作包括:

  • 初始化外设:网卡、存储控制器、USB等
  • 建立设备树:把硬件信息传递给内核
  • 加载内核:从Flash、网络或USB读取内核镜像
  • 启动内核:设置启动参数,跳转到内核入口

你想想看,U-Boot其实就是一个"迷你操作系统"。它有命令行、文件系统支持、网络协议栈。只不过它的最终目的不是自己运行,而是把控制权交给真正的操作系统。

3.4 最后一棒:操作系统内核

U-Boot跳转到内核入口后,它的使命就完成了。内核接手后,会做自己的初始化:

  1. 解压自身(如果是压缩镜像)
  2. 初始化内存管理
  3. 初始化中断控制器
  4. 初始化调度器
  5. 挂载根文件系统
  6. 启动init进程

从这一刻起,BootROM和U-Boot都成了"历史"。但它们留下的遗产——正确的硬件初始化、完整的设备树、合理的启动参数——决定了内核能否顺利跑起来。

3.5 完整链路图

下面这张图,把整个启动流程串起来了。我建议你把它存下来,调试时对照着看。

芯片上电到操作系统加载完整链路 ① 芯片上电 → 复位释放 ② BootROM 执行 SRAM 够大? 是 否 ③ 加载 SPL 到 SRAM ④ SPL 初始化 DDR ⑤ 加载 U-Boot 到 DDR ③ 加载 U-Boot 到 SRAM ④ U-Boot 初始化 DDR 图例说明 复位/硬件 BootROM 加载阶段

3.6 BootROM与U-Boot的分工协作

说到底,BootROM和U-Boot的分工就一句话:BootROM负责"活下来",U-Boot负责"活得好"。

BootROM的代码是固定的,出厂后改不了。所以它只做最基础、最安全的事情。U-Boot则灵活得多,可以适配不同的硬件配置、不同的启动介质、不同的内核版本。

分工边界:

  • BootROM:管CPU最基本配置、管启动介质选择、管安全验证
  • U-Boot:管DDR初始化、管外设驱动、管内核加载、管命令行交互

我记得有一次调试一块新板子,BootROM总是加载U-Boot失败。后来发现是Flash的时序参数在BootROM里写死了,而那块Flash偏偏不兼容。最后只能换Flash型号。这件事让我意识到:BootROM虽然小,但它的"硬编码"特性决定了整个板子的硬件选型边界。

3.7 避坑指南

我曾经遇到过的几个典型问题:

  • BootROM加载U-Boot超时:Flash读取速度太慢,BootROM等不及就放弃了。解决办法是换更快的Flash,或者在BootROM配置里延长超时时间(如果能改的话)。
  • SPL阶段DDR初始化失败:DDR参数配置错误,或者PCB布线导致信号质量差。我建议在SPL里加一段DDR读写测试,跑完再往下走。
  • U-Boot跳转内核后死机:设备树里某个外设地址写错了,或者内核启动参数不对。这时候串口打印是救命稻草。

嗯,说到串口打印,我个人的习惯是在每个阶段都加一个唯一的打印信息。比如BootROM打印"B",SPL打印"S",U-Boot打印"U"。这样看串口输出就知道卡在哪一步了。简单,但极其有效。

3.8 小结

从芯片上电到操作系统加载,这条链路说长不长,说短不短。但每一步都有它的意义。BootROM是"开荒者",在什么都没有的环境里建立起最基本的运行条件。U-Boot是"建设者",把硬件资源全部激活,为内核准备好舞台。内核是"表演者",最终在舞台上唱主角。

理解了这个全景,你再看具体的BootROM代码、U-Boot代码,就不会觉得它们是一堆杂乱无章的初始化函数了。它们是一个精心设计的接力赛,每一棒都知道自己要做什么,也知道下一棒是谁。