26、存储驱动:EMMC与UFS驱动、分区读写操作、坏块管理
说到Bootloader里的存储驱动,这其实是个挺实在的话题。你想想看,Bootloader要加载内核、要读写分区,总得跟存储芯片打交道吧?我这些年调试过不少板子,从老旧的EMMC到新款的UFS,踩过的坑还真不少。今天咱们就聊聊这部分内容。
EMMC与UFS:两种主流的存储方案
先说说EMMC。这玩意儿在手机和平板上用了很多年,说白了就是把NAND Flash和一个控制器封装在一起。Bootloader访问EMMC,其实是通过MMC控制器发命令。我记得早期做项目时,EMMC的初始化时序特别容易出问题,时钟没配好,卡就认不到。
UFS呢,是近几年的新宠。它走的是SCSI协议那一套,支持全双工,速度比EMMC快了好几倍。我个人的习惯是,如果项目对性能要求高,优先考虑UFS。但UFS的驱动复杂度也上去了,调试起来更费劲。
核心区别:EMMC基于MMC协议,命令简单;UFS基于SCSI协议,命令复杂但性能强。
分区读写操作:Bootloader的核心任务
Bootloader里读写分区,说白了就是找到分区在存储设备上的起始地址和大小,然后发读写命令。我见过不少新手,上来就写死地址,结果分区表一变,代码就崩了。
正确的做法是解析分区表。常见的分区表格式有GPT和MBR。我个人更推荐GPT,因为它支持更大的容量和更多的分区。下面是一个简单的GPT分区表解析示例:
// 读取GPT头
gpt_header_t *gpt = (gpt_header_t *)malloc(512);
mmc_read(1, 1, gpt); // 从LBA1读取
// 校验签名
if (gpt->signature != GPT_SIGNATURE) {
printf("GPT签名错误\n");
return -1;
}
// 遍历分区表
gpt_entry_t *entries = (gpt_entry_t *)malloc(gpt->entry_count * sizeof(gpt_entry_t));
mmc_read(gpt->entry_lba, gpt->entry_count, entries);
for (int i = 0; i < gpt->entry_count; i++) {
printf("分区%d: 起始LBA=%llu, 大小=%llu\n",
i, entries[i].start_lba, entries[i].size);
}
嗯,这里要注意:分区读写一定要对齐。EMMC的块大小通常是512字节,UFS的块大小可能是4KB。不对齐的话,性能会大打折扣,甚至直接报错。
坏块管理:存储驱动的必修课
坏块管理,说白了就是处理存储芯片上那些坏掉的区域。NAND Flash天生就有坏块,这是物理特性决定的。我曾经在一个项目里,因为没处理好坏块,导致系统启动到一半就挂了,查了三天才发现是坏块问题。
EMMC和UFS的坏块管理方式不太一样:
| 特性 | EMMC | UFS |
|---|---|---|
| 坏块处理 | 内部自动管理,但需要发送擦除命令 | 内部自动管理,支持SCSI命令查询 |
| 重映射 | 控制器内部完成,对主机透明 | 控制器内部完成,支持显式重映射 |
| 健康状态 | 通过EXT_CSD寄存器读取 | 通过SCSI日志页读取 |
在Bootloader里,我们通常不需要自己实现坏块管理,因为EMMC和UFS的控制器已经做了。但有个坑:如果你直接操作裸NAND(比如某些低端设备),那就得自己维护坏块表了。
避坑指南:我曾经在调试EMMC时,发现写数据总是失败。后来一查,是没发擦除命令。EMMC在写之前必须先擦除,否则会报错。这个顺序千万别搞反了。
实战:EMMC驱动初始化流程
我给大家梳理一下EMMC驱动的初始化流程,这在我做过的项目里基本通用:
- 时钟配置:先给MMC控制器配时钟,初始频率要低(400KHz左右),等卡识别后再切到高速。
- 卡识别:发送CMD0(复位)、CMD1(查询电压)、CMD2(获取CID)等命令。
- 设置工作模式:发送CMD3(设置RCA)、CMD9(获取CSD)、CMD7(选中卡)。
- 切换高速模式:发送CMD6(切换功能),把时钟频率提上去。
- 读写测试:发CMD18(多块读)或CMD25(多块写),验证通信是否正常。
UFS的初始化流程类似,但用的是SCSI命令,比如INQUIRY、READ CAPACITY等。我个人觉得UFS的调试更麻烦,因为命令层级多,出问题不好定位。
警告:千万别在Bootloader里做长时间的存储读写测试。我见过有人把Bootloader搞成存储测试工具,结果启动时间从2秒拖到10秒,产品经理直接炸毛。
SVG:存储驱动核心逻辑图
下面这张图展示了Bootloader里存储驱动的核心逻辑,从初始化到分区读写,再到坏块处理:
这张图把整个流程串起来了。你从初始化开始,一步步走到坏块管理,基本就是Bootloader里存储驱动的全貌。我个人建议,在实际项目中,先把初始化调通,再搞分区读写,最后处理坏块。别想着一步到位,容易翻车。
总结一下:EMMC和UFS的驱动核心就是初始化、分区读写、坏块管理。EMMC简单但慢,UFS快但复杂。Bootloader里别搞太花哨的存储操作,够用就行。