2、BootROM与一级引导:芯片内部固化代码如何加载SPL
好,咱们接着聊。上一章我们说了嵌入式Linux启动的宏观流程,这一章我们深入到底层——芯片上电后,第一行代码到底是谁在执行?
答案是:BootROM。
BootROM,全称是Boot Read-Only Memory。它是芯片出厂时,由芯片厂商固化在芯片内部的一段只读代码。这段代码你改不了,也删不掉。它就像芯片的“出厂设置”,负责在芯片上电后,完成最基本的硬件初始化,然后去加载下一级引导程序。
我个人习惯把BootROM叫做“芯片的BIOS”,虽然不完全准确,但功能上很像。你想想看,PC有BIOS,嵌入式芯片有BootROM,本质上都是“第一棒”。
2.1 BootROM到底干了什么?
芯片上电后,CPU复位,程序计数器(PC)会指向一个固定的地址。这个地址,就是BootROM的入口。不同的芯片架构,这个地址不一样。比如ARM Cortex-A系列,复位后通常从0x00000000或0xFFFF0000开始执行。
BootROM的主要工作,我总结为三步:
- 最基本的硬件初始化:设置CPU模式、关闭看门狗、初始化栈指针、配置时钟(至少让CPU能跑起来)。
- 检测启动介质:去检查外部存储设备上有没有合法的启动代码。比如SD卡、eMMC、NAND Flash、SPI Flash等。
- 加载一级引导程序:从检测到的启动介质中,把一小段代码(通常是SPL)拷贝到芯片内部的SRAM中,然后跳转过去执行。
嗯,这里要注意:BootROM本身非常小,通常只有几KB到几十KB。所以它不可能做太复杂的事情。它只负责“把下一棒交出去”。
核心要点:BootROM是只读的,不可修改。它决定了芯片支持哪些启动方式。比如有些芯片只支持从SD卡启动,有些支持从NAND启动,这都是BootROM写死的。
2.2 什么是SPL?为什么需要它?
SPL,全称是Secondary Program Loader,也叫二级程序加载器。它是U-Boot的一部分,是U-Boot的“迷你版”。
你可能会问:为什么BootROM不直接加载完整的U-Boot?
原因很简单:芯片内部的SRAM太小了。一般只有几十KB到几百KB。而完整的U-Boot通常有几百KB甚至几MB。SRAM根本装不下。
所以,BootROM只能加载一个“小个子”的SPL。SPL的任务是:
- 初始化DDR(内存控制器)
- 初始化更复杂的时钟和PLL
- 从存储介质中加载完整的U-Boot到DDR中
- 跳转到U-Boot执行
说白了,SPL就是BootROM和U-Boot之间的“桥梁”。BootROM负责把SPL请进来,SPL负责把U-Boot请进来。
个人经验:我在项目中遇到过SPL编译出来太大,超过了芯片SRAM大小的情况。当时排查了很久,最后发现是开启了不必要的驱动。SPL一定要精简,能省则省。我曾经为了省几百字节,把SPL里的打印信息都删掉了。
2.3 BootROM如何找到SPL?
这是本章最核心的问题。BootROM怎么知道SPL放在哪里?
答案是:固定的偏移地址 + 特定的文件头格式。
芯片厂商在BootROM中写死了启动介质的读取逻辑。比如,对于SD卡,BootROM会去读取SD卡的特定扇区(比如第1个扇区,偏移0x200处)。这个扇区里存放的,就是SPL的镜像文件。
但是,BootROM怎么知道这个扇区里的数据是合法的SPL,而不是垃圾数据?
这里就需要一个“文件头”了。SPL镜像的前面,会有一个特定的头部结构,通常包含:
- 魔数(Magic Number):一个固定的数值,比如0x12345678,用来标识这是合法的SPL。
- 镜像大小:SPL的实际大小。
- 加载地址:SPL应该被加载到SRAM的哪个地址。
- 校验和(Checksum):用来验证数据完整性。
BootROM读取到数据后,会先检查魔数是否正确。如果正确,再校验数据完整性。全部通过后,才把SPL拷贝到SRAM中,然后跳转执行。
避坑指南:我曾经遇到过SPL烧录到SD卡后,芯片死活不启动。用示波器量了时钟、电源,都没问题。最后发现是SPL的魔数写错了。因为烧录工具版本不对,导致文件头被破坏了。从那以后,我每次烧录完都会用hexdump检查一下前几个字节。
2.4 不同芯片的BootROM差异
不同厂商的BootROM实现差异很大。我列个表格,方便你对比:
| 芯片厂商 | 典型芯片 | BootROM大小 | SRAM大小 | SPL加载位置 |
|---|---|---|---|---|
| TI(德州仪器) | AM335x | 约64KB | 64KB~128KB | SD卡第1扇区 |
| NXP(恩智浦) | i.MX6 | 约96KB | 256KB | SD卡偏移1KB处 |
| Allwinner(全志) | H3 | 约32KB | 32KB | SD卡偏移8KB处 |
| Rockchip(瑞芯微) | RK3288 | 约48KB | 128KB | SD卡偏移32KB处 |
你看,差异很大。所以移植U-Boot时,第一件事就是查芯片手册,搞清楚BootROM的启动流程和SPL的加载规则。
2.5 核心流程图:BootROM加载SPL
下面我用SVG画一张流程图,把整个过程串起来。这张图我建议你保存下来,以后调试启动问题时,对着图看,思路会清晰很多。
2.6 实际项目中的调试技巧
最后,分享几个我在实际项目中用到的调试技巧:
- 用串口打印BootROM信息:很多芯片的BootROM支持通过串口输出调试信息。比如TI的AM335x,上电后按住某个按键,BootROM会通过串口打印启动介质检测结果。这个功能非常有用。
- 用JTAG单步调试:如果SPL死活跑不起来,别犹豫,上JTAG。直接看PC指针停在哪里,是卡在BootROM里,还是卡在SPL里。一清二楚。
- 检查SPL的链接地址:SPL的链接地址必须和BootROM期望的加载地址一致。否则跳转后直接跑飞。我见过很多新手在这里栽跟头。
- 用示波器看启动时序:有时候不是代码问题,是硬件问题。比如SD卡的电平不对,或者时钟没起来。示波器一量,什么都明白了。
个人习惯:我每次拿到一块新板子,第一件事就是看BootROM的文档。搞清楚它支持哪些启动方式,SPL的加载地址是多少,魔数是什么。这些信息都记在笔记本上。磨刀不误砍柴工,真的。
好了,这一章就到这里。BootROM和SPL的配合,是嵌入式Linux启动的第一步,也是最关键的一步。这一步走稳了,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321