4、BootROM的启动设备驱动:NAND Flash驱动、eMMC驱动、SD卡驱动、SPI NOR Flash驱动、USB驱动
好,咱们接着聊BootROM。上一节我们把启动设备的枚举和选择逻辑讲清楚了。这一节,咱们深入到底层——BootROM到底是怎么跟这些存储设备打交道的?
说白了,BootROM里内置的驱动,跟咱们在U-Boot或者Linux内核里写的驱动,完全是两码事。我当年第一次看BootROM的驱动源码时,第一反应是:这也叫驱动?太简陋了吧!
但后来我明白了。BootROM的驱动,追求的不是功能完整,而是极致的精简和可靠。它只需要做一件事:把启动代码的第一阶段(通常是SPL或U-Boot的前几KB)从存储介质里读出来,放到SRAM里执行。仅此而已。
核心原则:BootROM驱动 = 最小化初始化 + 最简读写接口 + 硬件ECC(如果有) + 超时保护。
下面我们逐个来看。
4.1 NAND Flash驱动
NAND Flash,这玩意儿在嵌入式里太常见了。但BootROM读NAND,其实挺头疼的。为什么?因为NAND有坏块,有位翻转,需要ECC校验。
BootROM的NAND驱动,通常只支持Raw NAND,也就是直接操作NAND的接口。它不会去管FTL(Flash Translation Layer)那套东西。
我见过一个典型的BootROM NAND驱动流程:
- 控制器初始化:配置NAND控制器的时序参数。这步很关键,时序配错了,读出来的数据全是乱的。我记得有一次,板子上的NAND时钟频率跟BootROM里配的不一致,结果死活读不出正确的数据。查了两天才发现是硬件改版后时序变了,但BootROM没更新。
- 发送读命令:发送0x00(读页)和0x30(读完成)命令序列。
- 等待RB(Ready/Busy)信号:轮询或者等待中断。BootROM里几乎都是轮询,因为简单。
- 读取数据:从NAND的页缓冲区里把数据读出来。通常是256字节、512字节或2048字节一页。
- ECC校验:如果硬件支持,BootROM会启用硬件ECC。如果不支持,那就只能靠软件做简单的奇偶校验,或者干脆不校验——赌一把。
注意:BootROM的NAND驱动,通常不处理坏块。它假设启动区域(前几MB)是好的。如果坏块恰好出现在启动区域,那板子就变砖了。所以很多厂商会在生产时对NAND进行坏块扫描,确保启动区域无坏块。
代码示例?嗯,我写个伪代码意思一下:
// BootROM NAND读页伪代码
int bootrom_nand_read_page(uint32_t page_addr, uint8_t *buffer) {
// 1. 发送列地址和行地址
nand_send_cmd(0x00);
nand_send_addr(page_addr & 0xFF); // 列地址低8位
nand_send_addr((page_addr >> 8) & 0xFF); // 列地址高8位
nand_send_addr((page_addr >> 16) & 0xFF);// 行地址低8位
nand_send_addr((page_addr >> 24) & 0xFF);// 行地址高8位
// 2. 发送读确认命令
nand_send_cmd(0x30);
// 3. 等待RB信号,超时则返回错误
if (wait_for_rb(1000) != OK) {
return -ETIMEOUT;
}
// 4. 读取数据(假设页大小为2048字节)
for (int i = 0; i < 2048; i++) {
buffer[i] = nand_read_byte();
}
// 5. 读取OOB区域(可选,用于ECC校验)
// ...
return 0;
}
你看,就这么简单。没有坏块管理,没有磨损均衡,没有多平面操作。就是最原始的读页。
4.2 eMMC驱动
eMMC就比NAND友好多了。它内部集成了控制器,对外暴露的是MMC协议接口。BootROM操作eMMC,本质上就是操作一个MMC设备。
eMMC驱动的初始化流程,我总结一下:
- 时钟初始化:先给一个慢速时钟(比如400KHz),用于初始化通信。
- 发送CMD0(GO_IDLE_STATE):复位设备。
- 发送CMD1(SEND_OP_COND):获取设备电压范围,并启动初始化。这一步会循环直到设备就绪。
- 发送CMD2(ALL_SEND_CID):获取设备CID(Card Identification)号。
- 发送CMD3(SET_RELATIVE_ADDR):设置设备相对地址(RCA)。
- 发送CMD9(SEND_CSD):获取CSD(Card Specific Data)寄存器,里面包含了设备容量、块大小等信息。
- 发送CMD7(SELECT/DESELECT_CARD):选中设备。
- 切换到高速模式:如果设备支持,发送CMD6(SWITCH_FUNC)切换到高速时钟。
嗯,这流程跟U-Boot里的mmc_init()几乎一模一样。只不过BootROM里没有那么多错误处理和重试机制。
我个人习惯,在调试eMMC启动问题时,会先检查CMD1的响应。如果设备一直返回busy,那多半是电压不匹配或者硬件连接有问题。
小技巧:很多BootROM的eMMC驱动,默认只支持4位数据总线。如果你的板子硬件支持8位,但BootROM没配,那启动速度会慢一半。我曾经在项目里为了加快启动速度,硬是把BootROM的eMMC驱动改成了8位模式——虽然只是改了一个寄存器值,但效果立竿见影。
4.3 SD卡驱动
SD卡驱动跟eMMC驱动非常相似,因为它们都基于MMC协议。区别在于初始化流程略有不同:
- SD卡使用CMD0 + CMD8 + ACMD41的组合来初始化,而不是eMMC的CMD1。
- SD卡有写保护开关检测(虽然BootROM用不上)。
- SD卡支持SPI模式,但BootROM通常只用SD模式。
BootROM的SD卡驱动,我见过最精简的实现,只有不到200行C代码。它只实现了:
- 初始化
- 读单块(CMD17)
- 读多块(CMD18)
写操作?不存在的。BootROM不需要写SD卡。
这里有个坑:SD卡规范里规定,初始化时如果卡返回Illegal Command,需要重试或者换一种方式。有些BootROM实现偷懒,不处理这个情况,导致某些兼容性差的SD卡无法启动。我遇到过一块金士顿的SD卡,在A板子上能启动,在B板子上就不行。最后发现是B板子的BootROM没处理CMD8的响应超时。
4.4 SPI NOR Flash驱动
SPI NOR Flash,这是我最喜欢的启动介质。为什么?因为它简单、可靠、接口标准。几乎所有MCU和MPU都支持从SPI NOR Flash启动。
BootROM的SPI NOR驱动,核心就是操作SPI控制器,发送标准的SPI Flash命令:
| 命令 | 代码 | 说明 |
|---|---|---|
| READ ID | 0x9F | 读取Flash的厂商ID和设备ID,用于识别型号 |
| READ | 0x03 | 标准读,速度较慢(通常20MHz以下) |
| FAST READ | 0x0B | 快速读,支持更高时钟频率 |
| Dual Output Read | 0x3B | 双线输出读,提高吞吐量 |
| Quad Output Read | 0x6B | 四线输出读,速度最快 |
BootROM通常会先发送READ ID命令,识别出Flash型号,然后根据型号选择最优的读命令。如果识别失败,就回退到标准的0x03读命令。
我建议你在设计板子时,尽量选择BootROM已知的SPI Flash型号。否则BootROM可能无法识别,只能用最慢的模式读,启动时间会变长。
关键点:SPI NOR Flash的启动速度,很大程度上取决于SPI时钟频率和使用的读命令模式。Quad Output Read比Standard Read快4倍以上。如果你的BootROM支持,一定要开启。
4.5 USB驱动
USB驱动在BootROM里比较特殊。它通常不是用来从USB设备启动的(虽然有些SoC支持USB下载模式),而是用来实现USB下载功能——也就是通过USB把固件烧录到板子上。
BootROM的USB驱动,一般实现为USB Device模式(从设备)。它枚举成一个特定的USB设备(通常是DFU设备或者自定义的Vendor Class设备),然后等待主机发送数据。
这个驱动的实现,比前面几个都复杂。因为它需要处理:
- USB协议栈:包括SOF包、SETUP包、IN/OUT事务。
- 端点管理:至少需要一个控制端点(端点0)和一个批量端点。
- 描述符:设备描述符、配置描述符、接口描述符、端点描述符。
我记得有一次,客户反馈说板子无法通过USB下载固件。我排查了半天,发现是BootROM里的USB描述符里,厂商ID(VID)写错了。主机端驱动匹配不上,自然无法通信。这种问题,说难不难,但查起来真要命。
USB驱动的初始化流程大致如下:
- 初始化USB控制器:配置时钟、引脚、中断(如果有)。
- 设置设备地址为0:USB设备刚上电时,地址为0。
- 等待主机发送SETUP包:主机通过控制端点0发送请求。
- 解析并响应请求:比如GET_DESCRIPTOR请求,返回设备描述符。
- 主机分配地址:通过SET_ADDRESS请求分配唯一地址。
- 主机读取配置描述符:获取设备能力信息。
- 主机选择配置:通过SET_CONFIGURATION请求激活配置。
- 进入数据传输阶段:主机通过批量端点发送固件数据。
整个过程,BootROM必须严格遵循USB 2.0规范。任何时序偏差都可能导致枚举失败。
注意:USB驱动的代码量通常是所有BootROM驱动里最大的。因为它不仅要处理协议,还要处理各种错误情况——比如主机断开连接、超时、数据包丢失等。而且BootROM的USB驱动通常没有操作系统支持,所有事情都得在中断或轮询里完成。
知识体系总览
说了这么多,我画张图帮你理一理思路:
这张图把五个驱动的关键特性都列出来了。你可以看到,虽然它们接口不同、协议不同,但设计思想是一致的:简单、可靠、快速。
好了,这一节的内容就到这里。BootROM的驱动,说白了就是「够用就行」。它不需要考虑热插拔、电源管理、多线程并发这些复杂场景。它只需要在芯片上电后的那几百毫秒里,把下一阶段的代码读进来。
理解了这个思路,你再看U-Boot的驱动,就会觉得U-Boot的驱动简直是「豪华版」了。
可可精品资料库