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驱动流程:

  1. 控制器初始化:配置NAND控制器的时序参数。这步很关键,时序配错了,读出来的数据全是乱的。我记得有一次,板子上的NAND时钟频率跟BootROM里配的不一致,结果死活读不出正确的数据。查了两天才发现是硬件改版后时序变了,但BootROM没更新。
  2. 发送读命令:发送0x00(读页)和0x30(读完成)命令序列。
  3. 等待RB(Ready/Busy)信号:轮询或者等待中断。BootROM里几乎都是轮询,因为简单。
  4. 读取数据:从NAND的页缓冲区里把数据读出来。通常是256字节、512字节或2048字节一页。
  5. 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驱动的初始化流程,我总结一下:

  1. 时钟初始化:先给一个慢速时钟(比如400KHz),用于初始化通信。
  2. 发送CMD0(GO_IDLE_STATE):复位设备。
  3. 发送CMD1(SEND_OP_COND):获取设备电压范围,并启动初始化。这一步会循环直到设备就绪。
  4. 发送CMD2(ALL_SEND_CID):获取设备CID(Card Identification)号。
  5. 发送CMD3(SET_RELATIVE_ADDR):设置设备相对地址(RCA)。
  6. 发送CMD9(SEND_CSD):获取CSD(Card Specific Data)寄存器,里面包含了设备容量、块大小等信息。
  7. 发送CMD7(SELECT/DESELECT_CARD):选中设备。
  8. 切换到高速模式:如果设备支持,发送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驱动的初始化流程大致如下:

  1. 初始化USB控制器:配置时钟、引脚、中断(如果有)。
  2. 设置设备地址为0:USB设备刚上电时,地址为0。
  3. 等待主机发送SETUP包:主机通过控制端点0发送请求。
  4. 解析并响应请求:比如GET_DESCRIPTOR请求,返回设备描述符。
  5. 主机分配地址:通过SET_ADDRESS请求分配唯一地址。
  6. 主机读取配置描述符:获取设备能力信息。
  7. 主机选择配置:通过SET_CONFIGURATION请求激活配置。
  8. 进入数据传输阶段:主机通过批量端点发送固件数据。

整个过程,BootROM必须严格遵循USB 2.0规范。任何时序偏差都可能导致枚举失败。

注意:USB驱动的代码量通常是所有BootROM驱动里最大的。因为它不仅要处理协议,还要处理各种错误情况——比如主机断开连接、超时、数据包丢失等。而且BootROM的USB驱动通常没有操作系统支持,所有事情都得在中断或轮询里完成。

知识体系总览

说了这么多,我画张图帮你理一理思路:

BootROM 启动设备驱动知识体系 BootROM 驱动 NAND Flash eMMC SD卡 SPI NOR Flash USB 关键特性 • Raw NAND 接口 • 硬件 ECC(可选) • 不处理坏块 • 轮询 RB 信号 关键特性 • MMC 协议 • 内部 FTL • 4/8 位数据总线 • 高速模式切换 关键特性 • SD 协议 • CMD8/ACMD41 初始化 • 只读单块/多块 • 兼容性问题多 关键特性 • SPI 接口 • 标准/快速/四线读 • READ ID 识别 • 简单可靠 关键特性 • USB Device 模式 • DFU 协议 • 枚举过程复杂 • 用于固件下载 核心设计原则 最小化初始化 + 最简读写接口 + 硬件加速(如有) + 超时保护 目标:在最短时间内,把启动代码可靠地加载到 SRAM

这张图把五个驱动的关键特性都列出来了。你可以看到,虽然它们接口不同、协议不同,但设计思想是一致的:简单、可靠、快速。

好了,这一节的内容就到这里。BootROM的驱动,说白了就是「够用就行」。它不需要考虑热插拔、电源管理、多线程并发这些复杂场景。它只需要在芯片上电后的那几百毫秒里,把下一阶段的代码读进来。

理解了这个思路,你再看U-Boot的驱动,就会觉得U-Boot的驱动简直是「豪华版」了。

可可精品资料库