本文要解决的是「一颗 eMMC 焊到板子上之后,从硬件上电、内核枚举、模式切换、 块设备挂载、一直到产线烧录与 OTA 的整条链路,怎么做才不会踩坑」。
很多团队在 eMMC 上的问题并不是「不会写驱动」,而是分散在多个环节的细节没有对齐: 硬件上电顺序没按规范做、EXT_CSD 没读全就切了 HS400、Cache 开着但掉电策略没配套、 U-Boot 与内核分区表不一致、产线第一次烧录用的工具跟量产工具不是同一个...... 这些坑每一个单独查资料都能解决,但串起来看才能真正省时间。
- 驱动开发者:需要理解 MMC 子系统的分层、控制器驱动怎么接、模式切换怎么调、设备树怎么写。
- BSP / 内核移植工程师:需要 config 清单、DTS 样例、U-Boot 配套、与启动链路的衔接。
- 硬件 / PCB 工程师:需要知道驱动行为对硬件的约束(电源时序、门控、复位、差分走线、测试点)。
- 产线与测试工程师:需要首件烧录 SOP、掉电测试方法、寿命与健康度采集手段。
- 存储产品经理:需要容量/分区规划依据、寿命指标口径、模式与性能的取舍关系。
不在本文范围:eMMC 器件内部的 FTL 算法、ECC 纠错算法、NAND 阵列时序电路; 这些由器件 datasheet 与《eMMC 软件设计规范》负责。本文只在「驱动需要用到它的哪个字段」这个层面引用。
0.2 术语与缩略语
下表把全文会反复出现的名词一次性讲清,后文不再解释。标星号的三条是最容易误解的。
| 缩写 / 术语 | 英文全称 | 含义与工程含义 |
|---|---|---|
| eMMC | embedded MultiMediaCard | 焊在板上的不可移除存储器件,内置 FTL,对主机呈现为块设备 |
| FTL | Flash Translation Layer | 器件内部的地址翻译与磨损均衡层;主机不可见,也不可绕过 |
| RPMB | Replay Protected Memory Block | 带 HMAC 认证与防回放计数器的安全存储区,容量仅数百 KB |
| HS200 | High Speed 200 | eMMC 4.5 引入的单数据线 200 bit/s 模式,是 HS400 的过渡基础 |
| HS400 | High Speed 400 | eMMC 5.0 引入的 DDR 双数据线模式,理论 400 MB/s 级别接口速率 |
| HS400EX | HS400 Enhanced Strobe | eMMC 5.1 引入,用 DS 信号提供采样时钟,抑制 WR skew |
| DS | Data Strobe | eMMC 输出的数据选通信号,控制器据此在数据眼中心采样 |
| OCR | Operation Conditions Register | 卡上电后的能力与电压登记表,CMD1 用它做初始化握手(★) |
| CMD1 | Send Operating Conditions Reg | eMMC 专属初始化命令,循环发送直到 OCR 的 CCS 位为 1 |
| CID / CSD / CSR | Card IDentification / Card Specific Data / Card Status | 三张 128bit 寄存器;CSR 在 CMD13 查询 |
| RCA | Relative Card Address | CMD3 分配的主机侧短地址,4 字节高 16 位为 RCA |
| EXT_CSD | Extended Card Specific Data | 512 字节扩展寄存器,速率模式、Cache、分区配置、寿命全在这里(★) |
| CMD6 | SWITCH | 写 EXT_CSD 的专用命令,参数带 R1b,切模式与开关 Cache 全用它 |
| ADTC | Addressed Data Transfer Command | 带地址的数据传输类命令,如 CMD17/18/25/35 |
| DCMD | Device Control Command | 设备自终止类命令,如 CMD6,地址固定为 0 |
| rpmb 分区 | RePlay Protected Memory Block partition | eMMC 硬件分区之一,经 /dev/mmcblk0rpmb 或 TEE 访问 |
| SDHCI | SD Host Controller Interface | SD/MMC 控制器的标准寄存器与编程模型规范 |
| CMDQ | Command Queuing | 把读写命令放进硬件队列,控制器批量取指执行,见 9.5 节 |
| CQE | Command Queue Engine | 承载 CMDQ 的硬件引擎,寄存器组叫 CQEQ |
| fio | - | Linux 侧性能压测工具,用于建立基线与回归对比 |
| GPT / MBR | GUID Partition Table / Master Boot Record | 分区表格式;eMMC 量产推荐 GPT |
| PARTUUID | Partition UUID | GPT 分区唯一标识,设备树里用它而非 /dev/mmcblk0p2 |
| Boot0 / Boot1 | Hardware partition 0/1 | eMMC 出厂即有的两个启动分区,不计入标称容量 |
| BKOPS | Background Operations | 让 Cache 开关在安全时机自动生效的机制 |
| WR skew | Write Timing Skew | 写入数据相对时钟的偏移,靠 DS 增强选通抑制 |
三个最容易误解的点,先记住: ① eMMC 里的 NAND 不能像 SPI NAND 那样直接写,MTD / UBI 那套在 eMMC 上 完全用不上 ,内核只把它当块设备; ② eMMC 的硬件分区(Boot0/Boot1/RPMB)不算在标称容量里 , 128 GB 的卡实际可用只有 128 GB 减去约 16 MB; ③ 切到 HS400 需要重新训练采样点,不是把频率调高就行。
0.3 内核版本与接口差异
MMC 子系统这几年的改动集中在「CQE 命令队列」与「缓存策略」两块,控制器驱动接口相对稳定。 下面按版本列出对本文内容有实际影响的差异点,避免用新内核的命令去查老内核的树。
| 内核版本 | 关键变化 | 对本文的影响 |
|---|---|---|
| 4.14 及更早 | 无 CQE,quirks 少 | 本文第 7/9 章的 CQE 内容不适用;模式切换逻辑分散在各控制器驱动里 |
| 4.19 | mmc_of_parse() 统一大部分 DT 解析;引入 mmc-hs400-enhanced-strobe |
设备树写法基本定型,本文第 5 章写法适用 |
| 5.4 | 控制器驱动从 sdhci.c 拆出 sdhci-of-dwcmsh.c 等 |
看错文件找不到 DSR 处理的排查思路要更新 |
| 5.10 | 引入 mmc_cqe 的 DT 描述(mmc-cqe)与更多 SoC 支持 |
第 9.5 章 CQE 内容适用 |
| 5.15 | 缓存与回写策略从 bootloader 传到内核并完善 | 第 10 章 Cache 一致性方案需与 U-Boot 声明对齐 |
| 6.1 | DTS 顶层 mmc 节点属性清理,废弃一批老写法 |
老设备树需迁移,见 5.4 节 |
| 6.6 | mmc-regulator 增加 regulator-allow-set-load |
电源门控方案需检查 13.3 节列出的新属性 |
版本相关的三条硬规则
① 结构体字段名随版本变化,mmc_host 在 4.x 与 6.x 之间成员差别很大, 直接照抄老代码的字段初始化会编译失败或语义错位;
② 本文所有内核配置项与 DT 属性都标注了「最低版本」,低于该版本时请先确认属性是否被解析;
③ 线上设备的内核版本一旦定了,就不要在补丁里引用新版本独有的 API,需要兼容时用 #if LINUX_版本号 V1.0 >= KERNEL_VERSION(x,y,0) 包起来并写清理由。
0.4 硬件平台与参考器件
本文以「ARM64 主控 + 1 颗 eMMC + 独立 LDO 供电 + SDHCI 兼容控制器」为基准平台。 下表列出参考器件的规格区间,具体型号参数以对应料号正式 datasheet 为准。
| 维度 | 基准取值 | 说明 |
|---|---|---|
| eMMC 容量 | 32 GB / 64 GB / 128 GB | 覆盖主流量产档位;小于 4 GB 的器件已极少 |
| eMMC 版本 | 5.1(HS400EX) | 向下兼容 4.51(HS400)、4.5(HS200) |
| 封装 | FBGA-153,0.4 mm 球径 | 需 6 层 PCB;HS400 差分线需 100 欧姆阻抗控制 |
| 接口模式 | HS400(双 DDR 数据线) | 本文主线;HS200 / HS 作为降级路径 |
| 主控 | 四核 A53 + SDHCI 兼容 MMC 控制器 | 示例平台;实际项目以 SoC 手册为准 |
| 总线宽度 | 8 bit DAT + 1 bit CMD | eMMC 固定 1 bit CMD,数据线 1/4/8 bit 三档 |
| 供电 | VCC 3.3 V / VCCQ 1.8~3.3 V / VDDIO 1.8~3.3 V | VCCQ 需与 SoC IO 电平一致 |
| 存储形态 | 裸 eMMC / eMCP(eMMC+LPDDR 合封)/ uMCP(uMMC+LPDDR) | 合封形态软件栈完全一致,仅 DRAM 侧有差异 |
本文与配套文档的分工
《eMMC 电路设计指南》 → 怎么接、怎么供电、怎么走线(硬件全貌)
《eMMC 软件设计规范》 → 器件协议:命令序列、EXT_CSD 位定义、寿命口径(器件层)
本文 → 内核配置、设备树、框架、模式切换、块设备路径、测试、调试、量产(系统集成层)
冲突时的优先级:datasheet > 电路设计指南 > 软件设计规范 > 本文。
第 1 章 · eMMC 器件与协议基础
在动手写驱动之前,先把「eMMC 到底是什么、它有哪些模式、上电后怎么活过来」讲清楚。 这一章的内容决定了后面所有代码的时序约束。
1.1 eMMC 是什么:NAND + FTL + Cache
从主机视角看,eMMC 就是一个能挂到 /dev 下的块设备;但从硬件视角看,它是 「NAND 阵列 + 内部控制器(FTL、ECC、缓存、调度)+ 主机接口」三层叠在一起的东西。 理解这三层的分工,才能明白哪些行为是主机能控制的、哪些只能「请求并等待」。
主机接口层(Host Interface)CMD(1 bit) + DAT7:0 + CLK + RST_n + VDDIO + 供电域器件内控制器(FTL / Cache / Scheduler)FTL:LBA 与物理块地址翻译Cache:eMMC 内部可缓存(可关闭)ECC:片内纠错,主机不可见Scheduler:写合并与调度NAND 阵列(Raw NAND)裸存储:擦除块 / 页 / SLC / TLC;坏块由器件内部管理,主机不可见主机能控制的:命令序列、模式切换、Cache 开关、块设备挂载与调度主机控制不了的:FTL 地址翻译、坏块替换、ECC 强度、磨损均衡算法
图 1 · eMMC 内部三层结构与主机可见边界
这张图解释了三个常见现象的成因:
- 为什么 eMMC 不能像 SPI NAND 那样做「整片擦除」 ------FTL 在中间,主机看到的是 LBA,
blkdiscard只能「告知」器件某段逻辑块可回收,不能强制释放物理块。 - 为什么 eMMC 的坏块不用主机管 ------坏块管理在 FTL 里,主机只会在
EXT_CSD[230](Pre-EOL Info)看到寿命状态。 - 为什么「写完立刻断电」会丢数据------数据可能还在器件 Cache 里没落 NAND, 需要主机显式发 Cache Flush(见 10.3 节)。
1.2 卡上存储区布局:Boot / RPMB / 用户区
eMMC 内部把存储划成若干硬件分区,各分区大小在出厂时由器件内部固件固化、 不可通过 EXT_CSD 改写(只有「是否使能」可配)。理解这一点,才能解释分区表里那些「凭空多出来」的设备节点。
Boot0约 4 MBBoot1约 4 MBRPMB约 128 KB用户区 User Area标称容量减约 16 MBBoot0 / Boot1:器件出厂即存在,切分点固化在器件内部;Bootloader 厂商约定用其中一个,另一个留作 A/B 备份RPMB:Replay Protected Memory Block,HMAC 认证 + 单调递增计数器,主机可申请 Key 后访问标称 128 GB 卡的容量核算用户区约等于 128 GB 减 Boot0(4 MB) 减 Boot1(4 MB) 减 RPMB(128 KB) 减器件内部保留(数 MB)结论:分区表可用容量永远略小于标称容量,做容量规划时按用户区算,不要按标称算说明:各分区实际大小以具体料号 datasheet 的 partition table 描述符为准,不同厂商可能不同
图 2 · eMMC 硬件分区布局与容量核算
| 设备节点 | 所属分区 | 可否被分区表引用 | 典型用途 |
|---|---|---|---|
| /dev/mmcblk0 | 用户区(整块) | 不要直接写 | 整卡容器;dd 到它会清空所有分区 |
| /dev/mmcblk0boot0 | Boot0 硬件分区 | 绕过分区表 | Bootloader 存放区(主) |
| /dev/mmcblk0boot1 | Boot1 硬件分区 | 绕过分区表 | Bootloader 备份区(A/B 方案) |
| /dev/mmcblk0rpmb | RPMB 硬件分区 | 需先 Key 编程 | 安全存储:密钥、计数器、授权票据 |
| /dev/mmcblk0p1 ~ pN | 用户区内的 GPT/MBR 分区 | 正常引用 | boot / rootfs / data / userdata |
量产最危险的三条命令
dd if=/dev/zero of=/dev/mmcblk0 bs=1M # 清空用户区所有分区,设备变砖
dd if=/dev/zero of=/dev/mmcblk0boot0 # 覆盖 Bootloader,板子变砖且不进系统
sgdisk --zap-all /dev/mmcblk0 # 抹掉分区表,同样不可逆
这三条只能在产线首件 / 返修工位 由受控脚本执行,禁止开放给现场调试终端。 开发机上的 shell 别名、脚本里的通配符,以及误把 /dev/mmcblk1(SD 卡枚举) 当成 eMMC,都是高频事故源。建议产线脚本在执行前打印 mmcblk0 的 NAME / 容量 / /sys/block/*/device 链接做二次确认。
1.3 速度模式谱系:从 HS 到 HS400EX
eMMC 的「速度模式」是一组**时序参数(频率 + 数据线数 + 采样方式 + 电压)**的组合。 每种模式都对应 datasheet 里一张时序表和一组 EXT_CSD 位。搞混模式和频率的关系, 是「切了模式但没提速」的根因。
| 模式 | 引入版本 | 数据线 | 采样方式 | 典型最高频率 | 理论峰值 | 驱动侧要点 |
|---|---|---|---|---|---|---|
| MMC / High Speed | 早期 | 1 / 4 / 8 bit | CLK 上升沿 | 26 MHz | 约 25 MB/s(8 bit) | Linux 里对应 MMC_TIMING_LEGACY / HIGH_SPEED |
| HS200 | 4.5 | 1 bit(单线) | CLK 上升沿 + 训练采样点 | 200 MHz | 约 25 MB/s(时序余量大) | 需要 CMD21 训练;对应 MMC_TIMING_HS200 |
| HS400 | 5.0 | 2 bit(双 DDR) | CLK 上下沿 + 训练采样点 | 200 MHz | 约 200 MB/s | 需要 DS 或控制器采样点训练;MMC_TIMING_HS400 |
| HS400EX | 5.1 | 2 bit(双 DDR) | DS 增强选通(抑制 WR skew) | 200 MHz | 与 HS400 同,裕量更好 | 需额外 DS 引脚接线;DT 用 mmc-hs400-enhanced-strobe |
两个反直觉的事实
① HS200 的峰值带宽其实和 HS 接近 , 它的价值是把时序窗口从「固定 7.5 ns」放宽到「可训练」,为 HS400 打基础; 真正的带宽跃升发生在切到 HS400 的双数据线 DDR 那一刻。
② HS400 不是「把频率调高」得到的 ------HS 与 HS200 都是单数据线, HS400 是靠「双数据线 + 上下沿采样」把每周期搬运量翻倍再叠加频率。 因此从 HS200 切 HS400 时,采样点必须重新训练,不能沿用 HS200 的训练结果。
Legacy / MMC小于 26 MHz1/4/8 bitHigh Speed小于 52 MHz1/4/8 bitHS200200 MHz1 bit SDRHS400200 MHz2 bit DDRHS400EX200 MHz2 bit DDR + DS切换手段:CMD1(初始化 OCR)→ CMD6 写 EXT_CSD(模式)→ CMD6(Cache 与电压切换)→ CMD21(训练)每次切换都必须等 R1b 的 busy 应答结束(DAT0 拉低期间禁止发下一条命令),否则器件状态机会锁死
图 3 · eMMC 速度模式切换阶梯与每一级的切换手段
1.4 命令集与 R1 / R1b 响应
主机与卡之间的所有交互都通过「命令索引 + 参数 + CRC」构成的一条命令完成。 驱动代码里看到的 struct mmc_command 就是这个结构。 工程上必须记住的差别只有两点:响应类型 与是否带数据。
| 响应类型 | 含义 | 代表命令 | 驱动等待要点 |
|---|---|---|---|
| 无响应 R0 | 纯命令,不回任何状态 | CMD0 GO_IDLE_STATE、CMD4 SET_DCR |
发完即返回;失败只能靠超时发现 |
| R1 | 短格式状态,含 CMD 索引 | CMD1(OCR 阶段)、CMD9、CMD13 |
检查 resp[0] 最高位是否为命令索引 |
| R1b | R1 + busy 信号(DAT0 拉低) | CMD6 SWITCH、CMD1 电压切换后 |
必须轮询等待 busy 结束;切模式可长达 2 s |
| R2 | 48 bit,含 CID | CMD2 ALL_SEND_CID、CMD3 分配 RCA |
读 128 bit CID |
| R3 | 32 bit,含 R1 状态内容 | CMD6 的 R1b 变体、SD 专有命令 |
与 R1 相同的响应内容 |
| R6 / R7 | R1 + 相对地址 / 电压窗口 | CMD16 设定块长、CMD17/18 传参校验 |
R7 的 19 位电压窗口用于校验卡状态 |
| 带数据 R1b | 数据 + busy 结尾 | CMD25 写多个块 |
写完必须等 busy,此时数据才算真正落到 NAND 或 Cache |
R1b 等待不足等于卡死,这是 eMMC 调试里最高频的「卡不动」根因
场景:切 Cache 时 CMD6 发出,busy 信号持续 300 ms; 驱动里 timeout_ms 填了 10 ms,函数返回 -ETIMEDOUT。 上层继续发下一条命令,而器件还在 busy 状态机里------后续所有命令被忽略,看起来像「卡死了」。
正确做法:CMD6 的 timeout_ms 给足 切模式不小于 1000 ms、切 Cache 不小于 500 ms、切电压不小于 100 ms (按 datasheet 取值),并在超时后走「整卡 reset 恢复」路径,而不是继续用。
1.5 EXT_CSD 关键字段速览
EXT_CSD 是 512 字节的扩展寄存器区,通过 CMD6 的读参数模式读出。 Linux 里对应 struct mmc_ext_csd,由 mmc_read_ext_csd() 填充。 驱动开发者最常打交道的字段如下。
| 字节 | 字段名 | 含义 | 驱动里的用途 |
|---|---|---|---|
| 7 | REV | EXT_CSD 版本 | 判定支持 4.5 / 5.0 / 5.1 特性;mmc_ext_csd.rev |
| 183 | SEC_FEATURE_SUPPORT | 安全特性集(AFU / RPMB) | 判断 RPMB 与认证特性是否可用 |
| 192 | DEVICE_TYPE | 卡类型:HS200 / HS400 / 5.1 | card->ext_csd.hs200 与 .hs400 的判定依据 |
| 196 | HS_200_TIMING | HS200 时序支持位 | 与 DEVICE_TYPE 一起确认能否切 HS200 |
| 212 | DSTR_SUPPORT | DS 信号支持位 | 判定能否启用 HS400EX |
| 215 | HS400_TUNING_BLOCK_T | HS400 训练块类型 | 决定 CMD21 训练数据的解析方式 |
| 224 | HESW | Boot / RPMB 硬件分区使能 | EXT_CSD_PARTITION_CONFIG,控制是否上报 boot/rpmb 节点 |
| 225 | HS_200_TUNING_BLOCK | 训练块地址 | 训练块就在用户区里,需先由 U-Boot 预置 |
| 228 / 239 | CACHE_SIZE / CACHE_SIZE_E1 | Cache 大小(两处拼成完整值) | mmc_cache_size() |
| 234 | CACHE_UNIT_SIZE | Cache 写粒度 | 决定 flush / 失效的最小单位 |
| 241 | BKOPS | 后台操作支持与自陷使能 | 控制 BKOPS_EN / BKOPS_DIS 自动生效时机 |
| 246 | CACHE_CTRL | Cache 使能与失效 | CACHE_ENABLE 位就是 cache_enable() 写的 |
| 249 | PARTITION_TABLE_SET | 分区表状态 | 产线烧录后需确认都为 1(完成标记) |
| 268 / 269 | PRE_EOL_LIFE_TIME_EST | 预估寿命 A / B / C | 健康度上报的数据来源,见 18.4 节 |
| 21/22/23 | C_SIZE / C_SIZE_MULT / ERASE_GRP_DEF | 容量计算三要素 | 容量 = (C_SIZE+1) × 256 × C_SIZE_MULT × 512B |
手算校验容量:驱动和产线都用得上
C_SIZE_EXT = ((EXT_CSD[183:185] & 0x00FFFF) << 8) | EXT_CSD[185] /* [185:186] 共 22 bit */
容量 = (C_SIZE_EXT + 1) * 512B * 256 * (C_SIZE_MULT_EXT)
/* C_SIZE_MULT_EXT = (EXT_CSD[223:222] & 0x03) | (EXT_CSD[221] & 0xC0) >> 6
ERASE_GRP_DEF 决定这个 256/512 是写还是擦除单位,仅在超大容量时影响结果 */
调试时用这段算式和 lsblk 报出的容量对一下:对不上说明 EXT_CSD 读的不是 你期望的那张卡 (多卡场景)或 EXT_CSD_REV 解析路径不对。
1.6 上电时序与初始化状态机
eMMC 有自己的内部状态机。上电后卡处于 Pre-Idle 附近, 必须由主机发 CMD1 完成电压协商与初始化 ,才能进入 Stand-by 接受 CMD2 / CMD3。 驱动代码里 mmc_send_init_ocr() 就是这个循环。
Pre-IdleVCC 稳定≥300 msCLK 可提供Init OCR循环 CMD1直到 OCR.CCS = 1IdentificationCMD2 → CMD3→ CMD9 → CMD7Bus SetupCMD16 块长CMD6 总线宽度高速模式CMD6 切 HS200再切 HS400Trans(传输态)------ 一切数据命令的执行状态CMD17/18(单块读写)、CMD25/18(多块读写)在这里发出;写命令结束必等 R1b busyHS200 / HS400 切换在回到 Stand-by 之后做,切换完再回 Trans关键约束:CMD1 必须在 VCC 稳定且时钟可用后开始;busy 期间不得插入任何其它命令失败恢复:任一步骤超时则拉 RST_n 复位(低有效,保持不小于 1 个时钟周期)→ 重新走 Init OCR
图 4 · eMMC 上电初始化状态机与关键 CMD 顺序
RST_n 复位的两个常见误用
① 复位脉冲太短 :datasheet 要求 RST_n 保持低电平至少 1 个主机时钟周期。 如果 RST_n 直接接到 SoC 的 GPIO 且该 GPIO 上电默认状态不确定,首次上电可能出现 「器件没有真正复位」的偶发初始化失败。规范做法是用 regulator + reset-gpios, 并在 DT 里明确初始电平。
② 复位后立刻发命令 :RST_n 拉高后器件需要内部状态机复位时间(典型不小于 1 ms), 此时发出的 CMD 可能被吃掉。Linux 的做法是在 DT 里用 reset-delay-ms 预留延时, 或由 pwrseq 的 post-power-on-delay-ms 覆盖。
第 2 章 · 硬件设计规范(驱动视角)
本章只讲与驱动行为直接相关的硬件约束。 完整的电路方案、PCB 叠层、回流路径、焊接与生产细节见《eMCP 电路设计指南》, 本章的作用是解释「为什么硬件必须这样设计,否则驱动会怎么坏」。
2.1 引脚定义与连接框图
eMMC 的 153 个焊球里真正需要主控连接的只有 30 来个,其余是电源、地和保留球。 驱动开发者需要清楚每一根信号线的电气角色,因为信号完整性问题最终会以 -ETIMEDOUT 或 CRC 错误的形式暴露在 dmesg 里。
| 信号组 | 引脚 | 电气角色 | 驱动侧关联 | 设计红线 |
|---|---|---|---|---|
| CLK | 1 根 | 主控输出给卡的时钟,HS400 下 200 MHz | mmc_set_ios 里配置输出时钟 |
50 欧姆阻抗;长度匹配 DAT 差分对在 5 mm 以内 |
| CMD | 1 根 | 双向命令与响应,串行 | 命令索引与 CRC 校验 | 必须上拉(卡侧或主控侧二选一),禁止浮空 |
| DAT7:0 | 8 根 | HS200 单线 / HS400 双线 DDR | 总线宽度切换与采样点训练 | HS400 必须按 2 组 4 根差分对布线,不要单端走 8 根 |
| DS | 1 根 | 卡输出数据选通(HS400EX) | DT 属性 mmc-hs400-enhanced-strobe |
需从 SoC 引到卡 DS 球;走线与 CLK 平行等长 |
| RST_n | 1 根 | 低有效复位,主控输出 | reset-gpios 与 mmc_reset() |
不能悬空;必须有确定的上电默认电平 |
| CMD_DS | 1 根 | HS400 模式下的命令选通(部分器件) | 由主机驱动,不由软件控制 | 按 datasheet 确认是否需要引出 |
| VCC | 多球 | NAND 阵列与内核电源,典型 3.0~3.6 V | 对应 regulator-supply 与 13.3 节门控 |
上电时序要求最严,见 2.2 节 |
| VCCQ | 多球 | 接口电源,跟随 SoC IO 电平 | io-voltage 与 CMD11 电压切换 |
必须与 SoC 的 MMC IO 域电平一致,否则 CMD 通信直接失败 |
| VDDIO | 多球 | 部分器件的第二 IO 电源 | 同上 | 与 datasheet 的 BGA 球表逐球核对,不可想当然 |
SoC MMC 控制器CMD / CLK / DAT7:0DS / RST_neMMC 器件FTL + CacheNAND 阵列PMIC / LDOVCC / VCCQ / VDDIOGPIO 门控VCC_EN 开关控制低功耗CLK / CMD / DAT7:0 / DS(HS400 双 DDR 差分)VCC_EN(可由主控 GPIO 驱动)驱动视角的三条硬约束① VCCQ 必须与 SoC IO 域等电平,否则 CMD 通信在第一根命令就失败(不是软件能补的)② CMD 必须有确定上拉,浮空时上电瞬间的噪声会被误判为 R1 响应,导致 OCR 阶段假成功③ HS400 的 DAT 必须差分对布线;单端走线在 200 MHz 下几乎必然出现 CRC 错误图示为功能框图,实际 BGA 球位见器件 datasheet 的 Ball Assignment 表与 PCB 库文件
图 5 · eMMC 与 SoC 连接框图(驱动视角标注)
2.2 电源域与上电时序
eMMC 有三路电源,时序关系被 datasheet 明确规定。时序不满足的典型表现是 「冷启动偶尔不识别,热重启正常」------因为热重启时电源已经稳定,反而绕过了问题。
| 电源域 | 典型电压 | 作用 | 上电时序要求 | 关机时序要求 |
|---|---|---|---|---|
| VCC | 3.0 ~ 3.6 V | NAND 阵列、FTL 内核、PLL | 最先;稳定后至少等 300 ms 且 CLK 可提供才能发 CMD1 | 最后断;需等器件完成 Cache 落盘(见 10.3 节) |
| VCCQ | 1.7 ~ 2.0 / 2.7 ~ 3.6 V | 接口域(CMD / DAT / CLK) | 与 VCC 无强制先后,但必须先于或同时于第一次命令有效 | 与 VCC 同步或先断 |
| VDDIO | 1.7 ~ 2.0 / 2.7 ~ 3.6 V | 第二 IO 域(部分器件) | 同 VCCQ | 同 VCCQ |
上电时序(VCC 领先,CMD1 延后)VCCVCC 上升后稳定不小于 300 ms 且 CLK 已提供VCCQVCCQ 上升后稳定(须早于首次命令)CMD1开始循环发 CMD1(OCR 握手)关机时序(与上电相反):先停止所有 I/O,发 Cache Flush 或 BKOPS_EN,等 R1b 完成再拉 RST_n,最后断 VCC,最后断 VCCQ。顺序错了就是「偶发丢数据」。时间刻度为示意;实际延时参数以器件 datasheet 的 Power-On / Power-Off Timing 章节为准
图 6 · eMMC 上电与关机时序波形(VCC 领先、CMD1 延后)
关机顺序错了,数据会「已经 fsync 了但还是丢」
Linux 里 sync() / fsync() 只保证数据交给了 块设备驱动,而 eMMC 驱动的完成回调可能在数据还留在器件 Cache 里时就返回了。 如果系统紧接着通过 PMIC 直接切 VCC,数据就没了。
两条解决路径:① 关闭 eMMC Cache (EXT_CSD246),写命令的 R1b 完成即代表落到 NAND, 代价是随机写性能下降;② 保留 Cache,在关机流程里显式 Flush, 并在 PMIC 断电前等待完成。第 10 章与第 13 章会详细展开。
2.3 VDD 门控与低功耗设计
eMMC 待机电流(mA 级)远高于主控的微安级,对电池供电的设备是可观的负担。 主流做法是用主控 GPIO 或 PMIC 的负载开关控制 VCC:不用时彻底断电, 用到时重新上电并重新枚举。这一节讲门控电路与软件配合的完整要求。
| 门控位置 | 控制对象 | 断电后可见性 | 重新上电步骤 | 适用场景 |
|---|---|---|---|---|
| VCC 域 | NAND 阵列 + 内核 | 卡彻底消失 ,/dev/mmcblk0 消失 |
上电 → 等 300 ms → 驱动重新 probe 枚举 | 深度睡眠、电池设备,推荐 |
| VCCQ 域 | 仅接口域 | 卡仍在,但主机侧读不到 CID(CMD 通道断) | 仅需重新握手,枚举结果可缓存 | 轻量省电,保留数据完整性 |
| 整机电源域 | eMMC + 主控休眠域 | 全部掉电,等价于关机 | 完整冷启动 | 深度待机(RAM 保电) |
运行态mmcblk0 已挂载,正常读写umountCache Flush + syncremove_hostGPIO 拉低断 VCC休眠态:VCC 已断,/dev/mmcblk0 不存在,驱动已 remove_host唤醒路径:GPIO 拉高给 VCC 上电,延时不小于 300 ms,驱动重新 probe,重新枚举,再 mount注意:重新枚举会消耗 200~600 ms(大容量卡读 EXT_CSD 更慢),因此「按一次唤醒」的用户体验明显变差省电收益对照:eMMC 待机约 2~5 mA,VCC 切断后仅剩 VCCQ 域约 100~300 微安(具体值查 datasheet)
图 7 · VDD 门控与 Linux 侧卸载、移除、重枚举的配合时序
门控方案的三个必做项
① 断电前必须让块设备彻底消失 :只 umount 不 remove_host, 内核里仍可能有 in-flight 请求,切电后会触发 -EIO 甚至 panic; ② 唤醒时预留足 300 ms 硬延时 :这个延时不能用 request_thread 塞进 probe 里 (会卡住设备注册),应在电源上电后由 GPIO 置位完成、中断里再触发 probe, 或干脆用 regulator 的 regulator-boot-on 语义管理; ③ 不要在有 I/O 的路径上反复门控:每次门控等于一次完整重枚举, 频繁门控的用户体验比省下的那点功耗更糟。
2.4 时钟与复位
时钟与复位是最容易被忽略但最影响稳定性的两根线。Linux 侧的表现是 「mmc0: Timeout waiting for hardware interrupt 偶发出现」。
| 信号 | 要求 | 满足后的 Linux 表现 | 违反时的症状 | 验证方法 |
|---|---|---|---|---|
| CLK 源 | 由 SoC 提供,不能来自外部晶振分频后再分频(抖动叠加) | 命令与数据传输稳定 | 偶发 CRC 错误与超时,频率越高越明显 | 示波器看抖动与占空比 |
| CLK 最大频率 | 不超 datasheet 上限(HS400 为 200 MHz) | DT 的 max-frequency 生效 |
超频后错误率飙升但不一定报错,可能静默丢数据 | 逐步加压测,测到错误就退档 |
| CLK 门控 | 空闲时可门控,恢复后第一个命令要留额外延时 | 低功耗与性能兼得 | 唤醒后第一条命令超时,重试后正常 | 反复 suspend/resume 压测 |
| RST_n 极性 | 低有效;上电默认应为低(器件保持复位态) | 开机时序干净 | 上电瞬间器件状态不确定,冷启动偶发失败 | 上电时示波器抓 RST_n |
| RST_n 复位脉宽 | 低电平至少 1 个主机时钟周期 | 复位可靠 | 复位无效,旧状态残留 | 同上,抓脉宽 |
| 复位后延时 | RST_n 拉高后等不小于 1 ms 再发命令 | 初始化成功 | CMD1 被忽略,OCR 阶段超时 | 在 reset-gpios 后加 reset-delay-ms |
2.5 信号完整性与 HS400 差分
HS400 是双数据线 DDR、200 MHz,等效每周期传 2 bit × 200 MHz × 8 根线。 这个速率下布线规则不是「能连通就行」,而是必须满足严格的等长与阻抗要求。 驱动侧能做的补救非常有限------训练只能纠正采样点,不能修正过大的 skew。
| 项目 | HS200 要求 | HS400 / HS400EX 要求 | 违反后果 |
|---|---|---|---|
| DAT 布线 | 8 根单端,走线等长 | 2 组 4 根差分对(DAT3:0 一组、DAT7:4 一组) | HS400 下 CRC 错误率超过 1%,写数据静默损坏 |
| 差分阻抗 | --- | 100 欧姆差分(正负 10%),需与 SoC 及 PCB 厂叠层匹配计算 | 眼图闭合不良,误码高 |
| 组内 skew | 组内 5 mm 以内 | 同组 4 根 skew 在 1 mm 以内 | 训练窗口塌缩,无法找到稳定采样点 |
| CLK 与 DAT 关系 | CLK 与 DAT 长度差 5 mm 以内 | CLK 与 DAT 差分对长度差在 2 mm 以内 | 读写采样点漂移,两向性能都掉 |
| DS 走线 | 不适用 | DS 与 CLK 平行等长(差 1 mm 以内) | HS400EX 退化为 HS400,甚至训练失败 |
| 过孔与换层 | 尽量少 | 差分对内禁止中途换层;换层需对称过孔 | 阻抗不连续,插损增加 |
| 回流路径 | 有连续参考平面 | 差分对下方必须有完整地平面,不得跨分割 | 串扰,误码率随温度上升 |
HS400 降速的正确姿势:按顺序退,不要随机改 max-frequency
发现 HS400 误码时的退档顺序是:HS400 → HS200(同样 200 MHz 但单线、时序余量大得多) → HS 52 MHz(1/4/8 bit) 。注意不要从 HS400 直接跳到「HS200 加高频率」, HS200 的价值就是时序余量,混着用等于既没有 DDR 的速度也没有 SDR 的稳。
DT 侧改法:删掉 cap-hs400,保留 cap-hs200,把 max-frequency 设为 200000000;若 HS200 也不稳,把 max-frequency 降到 52000000 并 加 cap-high-speed。每一次退档都要重跑第 15 章的测试规范,不要只看能否识别。
2.6 必留测试点
eMMC 的问题几乎都需要波形才能定位到底。这些测试点不是为了调试方便, 而是量产阶段判断板子好坏、区分「芯片不良」与「板子不良」的唯一手段。
| 测试点 | 位置 | 测量方式 | 判据 | 主要能定位的问题 |
|---|---|---|---|---|
| VCC 纹波 | PMIC 输出端,近卡侧 | 示波器 AC 耦合,20 MHz 带宽限制 | 峰峰值小于 50 mV(按 datasheet) | 供电不足导致的 CRC 错误与初始化失败 |
| VCC 上电斜率 | VCC 引脚 | 示波器单次触发 | 单调上升,斜率在 datasheet 范围内 | 上电时序违规、器件内部上电失败 |
| VCCQ 电平 | SoC IO 域侧 | 万用表或示波器 DC 档 | 与 SoC IO 域完全一致 | 电平不匹配,CMD 通信全错 |
| CLK | 串联电阻后引脚 | 示波器,探头地线要短 | 频率、占空比 45~55%、抖动在允许值内 | 时钟质量问题、源分频链路问题 |
| CMD | 串联电阻后 | 逻辑分析仪加示波器 | 上升沿单调、无过冲回卷 | CMD 浮空、上拉缺失、信号完整性 |
| DAT7:0 | 每根就近 | 逻辑分析仪(8 通道) | 无过冲、上升沿单调 | 数据线断路或虚接、串扰 |
| RST_n | 卡侧引脚 | 示波器抓上电与复位过程 | 低有效、脉宽足够、复位时序正确 | 复位无效、初始电平不确定 |
| DS | 卡侧引脚 | 示波器(HS400EX 必测) | 与 CLK 平行、延迟稳定 | HS400EX 训练失败 |
强烈建议:CMD / CLK / DAT 各串 22~33 欧姆电阻
串联电阻的价值:① 调试时可用示波器在卡侧就近取样,不干扰信号; ② 与器件及主控的输出阻抗共同形成 RC 边沿,控制过冲; ③ HS400 训练失败时,把电阻从 22 欧姆调到 33 欧姆有时能直接救回来 (相当于加 RC 滤波、降低边沿速率的思路)。位置放在靠近信号源一侧(主控侧), 在靠近主控的 1~2 mm 内。
2.7 BOM 选型与分区容量约束
选型阶段的很多决定会锁死后面的软件方案。软件能补救的越来越少, 所以这一节要把「哪些选型决定不可逆」讲清楚。
| 选型维度 | 可选范围 | 对软件的不可逆影响 | 建议 |
|---|---|---|---|
| eMMC 版本 | 4.41 / 4.51 / 5.0 / 5.1 | 5.0+ 才能 HS400;5.1 才能 HS400EX | 新项目直接上 5.1,成本差极小但留足裕量 |
| 接口电压 | VCCQ 1.8 V 或 3.3 V | 与 SoC 绑死,改电压等于改硬件 | 先定 SoC 的 IO 域电平,再选器件 |
| Boot 分区策略 | Boot0 / Boot1 / 双 Boot | 决定 Bootloader 放哪、能否做 A/B | 选「双 Boot」器件,A/B OTA 才有可能 |
| 寿命等级 | 量产级 / 工业级 / 车载级 | 决定 Pre-EOL 口径与耐久测试目标 | 按实际写入量估算后再定,不要盲目选高 |
| 厂商 | 三星 / 铠侠 / 西部数据 / 镁光 / 长江存储等 | EXT_CSD 私有行为、训练块格式、时序参数有差异 | 同一产品尽量锁定单一厂商加单一料号,来料切换需重测 |
| 容量 | 16 GB ~ 256 GB | 分区规划需在产线固化 | 留 20% 余量给 OTA 双备份与日志 |
第 2 章自检
- VCCQ 电平与 SoC IO 域已逐路核对(万用表实测,不是看 datasheet)
- RST_n 有确定的上电默认电平,且 DT 里有
reset-delay-ms - CMD 上拉已确认;HS400 的 DAT 已按 2 组 4 根差分对布线,CLK 与 DAT 长度差 2 mm 以内
- VCC 纹波实测峰峰值满足判据;上电斜率单调
- 若用 VDD 门控:断电前 Flush 路径已实现,唤醒延时 300 ms 已落实
- CMD / CLK / DAT / RST_n 均有就近测试点;VCC 纹波测试点已留
- 料号已锁定,EXT_CSD 差异与训练块格式已记录到第 16 章适配表
第 3 章 · Linux MMC 子系统架构
MMC 子系统是内核里结构最规整的子系统之一:核心层(core)稳定且几乎不用改, 所有适配工作都落在 host/ 目录下的控制器驱动里。 先把这层结构看明白,才知道要改哪个文件。
3.1 整体分层总览
用户态:文件系统与应用ext4 / f2fs、mmc-utils、fio块设备层:mmcblkmmcblk.c 与 mmc_ioctl.c ------ 队列、bio、flush、discard核心层:corecore/ 目录 ------ 协议栈:命令组装、状态机解析、EXT_CSD、模式切换、电压切换控制器驱动:hosthost/ 目录 ------ 寄存器读写、时钟与中断、命令下发、卡检测硬件:SoC MMC 控制器SDHCI 或厂商控制器加 eMMC 器件core 与 host 的接口就是 struct mmc_host_ops 加 struct mmc_card 加一组 mmc_request改动原则:能在 core 层通用化就不要在 host 里打补丁;实在要打补丁,必须写清为什么不能通用
图 8 · Linux MMC 子系统分层架构(以 eMMC 为例)
| 层 | 关键文件 | 关键符号 | 读代码时关注什么 |
|---|---|---|---|
| 块设备 | drivers/mmc/block/mmcblk.c |
mmcblk_submit_bio()、mmcblk_data_cmd() |
bio 如何转成 ADTC 命令;flush 与 discard 如何映射 |
| 块设备 ioctl | drivers/mmc/block/mmc_ioctl.c |
mmc_blk_ioctl()、mmc_erase()、mmc_ffs() |
CMD38 erase 是无响应命令,靠状态查询等完成 |
| 核心 | drivers/mmc/core/core.c |
mmc_send_init_ocr()、mmc_set_ios() |
OCR 循环、IO 设置如何下发给 host |
| 核心 | drivers/mmc/core/mmc_ops.c |
mmc_erase()、mmc_set_erase_size() |
模式切换辅助函数怎么拼 CMD6 参数 |
| 核心 | drivers/mmc/core/mmc_ext_csd.c |
mmc_read_ext_csd()、mmc_select_timing() |
模式切换与卡状态判定的绝大部分逻辑在这里 |
| 核心 | drivers/mmc/core/mmc_card.c |
mmc_init_card()、mmc_manage_cache() |
整卡初始化顺序、Cache 开关时机 |
| 主机接口 | include/linux/mmc/card.h |
mmc_host、mmc_card、mmc_ext_csd |
所有可定制字段都在这里 |
3.2 源码目录与文件职责
内核 drivers/mmc/ 的实际目录结构如下。定位问题时先确定问题在哪一层, 能省掉大量无效的搜索。
drivers/mmc/
├── core/ # 协议栈:与具体控制器无关
│ ├── core.c # MMC 设备模型、总线、电源管理、init/ocr
│ ├── mmc_card.c # 整卡初始化、Cache 管理、ext_csd 解析
│ ├── mmc_ops.c # 命令构造:erase / sanitize / hp / ext_csd
│ ├── mmc_ext_csd.c # 模式切换、电压切换、卡状态位定义
│ ├── mmc_ioctl.c # ioctl 转命令
│ ├── debugfs.c # /sys/kernel/debug/mmc0 全部节点
│ ├── mmc-bus.c # 虚拟 MMC 总线(给 SDIO 用)
│ ├── sd.c / sd_io.c # SD 协议(本项目一般不用)
│ ├── block.c / block/* # bus 无关的块设备辅助
│ └── of.c # 设备树解析:mmc_of_parse()
├── block/
│ ├── mmcblk.c # /dev/mmcblk0 的实现
│ └── mmc_ioctl.c # ioctl 实现
├── host/ # 控制器驱动:所有适配工作在这里
│ ├── sdhci.c # SDHCI 通用框架
│ ├── sdhci-pci.c # PCIe 枚举的 SDHCI
│ ├── sdhci-pltfrm.c # platform 总线包装(含 DT 解析)
│ ├── sdhci-of-dwcmsh.c # Synopsys DesignWare 核心(Rockchip/ZynqMP/紫光等)
│ ├── sdhci-of-arasan.c # Arasan 系列 IP
│ ├── mmc_dw.c / mmc_dw_mmc.c # Synopsys DesignWare 核心(旧路径)
│ ├── mmc-sunxi.c # 全志
│ ├── mtk-sd.c # 联发科
│ ├── sdhci-msm.c # 高通
│ ├── sdhci-sprd.c # 展锐
│ ├── sdhci-tec.c / -nxp.c # T-Engine / NXP
│ ├── uniphier-sd.c # 松下
│ ├── cavium-mmc.c # Marvell
│ ├── meson-gx-mmc.c # Amlogic
│ ├── samsung-sdhci.c # 三星 Exynos
│ ├── au1xmmc.c # MIPS
│ └── mmc-slot-gpio.c # 卡检测与写保护 GPIO(通用)
各个 common.h(如 drivers/mmc/host/sdhci.h、 drivers/mmc/host/mmc-sdhci-core.h)里定义了控制器寄存器与 quirks, 排查「为什么这个平台的 SDHCI 行为不一样」时,第一个要看的就是它对 quirks 加了什么。
文件名里的 -of- 是什么意思
Linux 驱动命名约定:xxx-of-yyy 表示「yyy 厂商基于 OF(设备树) 实现的 xxx 控制器驱动」。所以看到 sdhci-of-dwcmsh.c 就知道它是 「用设备树描述的 Synopsys DesignWare SDHCI」,Rockchip、ZynqMP、紫光等都用这个文件。
对应地,如果某个 SoC 的支持被拆到单独文件(如 sdhci-of-arasan.c), 说明通用代码不够用,需要厂商私有时序处理。这时要去看内核 Documentation/devicetree/bindings/mmc/ 目录下的 yaml , 所有 *-of- 驱动支持的 DT 属性都在那里列出。
3.3 关键数据结构
MMC 子系统的核心是三个结构体:mmc_host(控制器)、 mmc_card(卡)、mmc_ext_csd(扩展寄存器)。 驱动代码里出现的绝大多数问题都能归结为「这三个结构体的某个字段填错了」。
struct mmc_hostops 指向 mmc_host_ops(驱动填)f_min / f_max / f_max_hwmax_freq / mode / ioscqe_on / cqe / cmdqpwrseq / mmc_pwrseqstruct mmc_cardcid / csd / rcaext_csd(内嵌结构)type / rca / statehost(反向指针)part(硬件分区链表)struct mmc_ext_csdrev / hs200 / hs400cache_size / cache_ctrlpartition_configlife_time / pre_eol_infoenhanced_strobemmc_card 结构体把 ext_csd 直接内嵌因此驱动里的访问方式是 card->ext_csd.xxx而不是 card->ext_csd_ptr->xxx三个结构体的完整定义见 include/linux/mmc/card.h;字段随内核版本增减,改代码前先看本项目内核的版本
图 9 · mmc_host / mmc_card / mmc_ext_csd 关系图
| 结构体 / 字段 | 作用 | 配错的典型症状 |
|---|---|---|
mmc_host.ops |
控制器操作函数表,必须全部提供 | probe 成功但任何命令都失败,dmesg 报 -EINVAL |
mmc_host.f_max |
软件能设的最高频率 | 设得超过硬件能力则偶发 CRC 错误 |
mmc_host.f_max_hw |
硬件实际能力,不得大于 f_max | 填错会掩盖板级问题,调优时无法判断瓶颈 |
mmc_host.max_freq |
当前生效频率(由 DT 初始化) | DT 的 max-frequency 生效值,日志里能看到 |
mmc_host.mode |
当前总线模式 | 枚举完成后应为目标模式,HS400 时看 debugfs 的 timing |
mmc_host.caps |
能力位(MMC_CAP_HS200 等) |
不填 MMC_CAP_HS400 则永远停在 HS200 |
mmc_host.caps2 |
第二组能力位(Cache / CQE / 3D 等) | Cache 相关能力没置位则 Cache 完全不生效 |
mmc_host.cqe_on |
命令队列是否使能 | 使能但 CQE 寄存器未配好则提交即挂死 |
mmc_card.type |
卡类型(MMC_TYPE_MMC 或 MMC_TYPE_SD) |
eMMC 误判为 SD 会走完全不同的初始化路径 |
mmc_card.ext_csd.rev |
EXT_CSD 版本 | 版本判断错则用 5.1 才有的字段,切 5.0 器件时失败 |
mmc_card.ext_csd.hs400 |
5.0+ 的 HS400 能力 | 未读对则切 HS400 失败,表现为超时 |
3.4 mmc_request 与命令流
struct mmc_request 是驱动与 core 之间的「工单」: core 填好命令、参数、数据缓冲,调用 host->ops->request(), host 把它翻译成寄存器操作,命令完成后回调 req->complete 通知 core。
struct mmc_request {
struct mmc_card *card; /* 关联的卡,驱动据此选 RCA 与时序 */
struct mmc_host *host;
struct mmc_command cmd; /* 命令:opcode / arg / flags / resp */
struct mmc_data *data; /* 数据:buf / blocks / blksz / dma */
struct mmc_req_cookie *cookie; /* CQE 时代的 cookie */
struct mmc_req_complete complete;/* 完成回调(core 提供) */
int (*error_check)(struct mmc_card *, struct mmc_request *);
};
struct mmc_command {
u32 opcode; /* 命令索引,如 MMC_SEND_EXT_CSD */
u32 arg; /* 32 位参数 */
unsigned int flags; /* MMC_RSP_PRESENT / MMC_RSP_R1B / ... */
unsigned int *resp; /* 响应数组(指向 host->resp) */
unsigned int resp_len; /* 1 / 2 / 4 / 6 个 word */
void *data; /* 单块数据指针 */
unsigned int datalen; /* 单块数据长度 */
};
struct mmc_data {
unsigned int blksz; /* 块大小(字节),eMMC 常为 512 */
unsigned int blocks; /* 块数 */
unsigned int flags; /* MMC_DATA_PRELATCH / MMC_DATA_WRITE */
void *buf; /* 数据缓冲 */
unsigned int timeout_ns; /* 数据超时(纳秒)*/
unsigned int xfer_len; /* 总传输长度 */
dma_addr_t dma_addr; /* DMA 地址(若用 DMA)*/
unsigned int sg_len; /* sg 表长度 */
struct scatterlist *sg; /* sg 表(多段传输)*/
};
关于 timeout_ns:它是纳秒不是毫秒
这是 eMMC 驱动里最容易被忽略的单位错误。 mmc_data.timeout_ns 与命令等待用的 timeout_ms 是两个不同字段、两种不同单位: 数据阶段用纳秒(由 core 按传输量算出默认值),命令等待用毫秒(由驱动在特定命令里手动设置)。
单位写错的后果很隐蔽:把 100 ms 写成 timeout_ns = 100(实际 100 ns), 表现是「大传输偶发超时,小传输正常」,非常难查。
3.5 probe 执行流程
从 module_init() 到 /dev/mmcblk0 出现,完整链路如下。 理解这条链路,才能判断「设备节点没出来」时该查哪一步。
① 模块加载或内置启动module_init 或 subsys_initcall② 平台 probe 入口platform_driver.probe():读 DT、映射寄存器③ MMC 核心 probemmc_of_parse(),分配 mmc_host,填 ops④ pwrseq 执行若 DT 有 mmc-pwrseq:上电、延时、pwrseq 的 pow_on⑤ 分配请求队列blk_alloc_queue() 并初始化 bio 结构⑥ 注册 hostmmc_add_host(),内核匹配 mmc_bus,调 host 的 probe⑦ 扫描与枚举mmc_scan() 到 mmc_rescan(),识别卡并读 EXT_CSD⑧ 注册块设备mmc_blk_alloc() 到 mmc_blk_alloc_req() 再 add_disk()断点排查判据:dmesg 里能看到「mmc0: new SD card」说明第 7 步成功;能看到 mmcblk0 说明第 8 步成功pwrseq 失败(第 4 步)是最常见的整条链断点,典型日志是 pwrseq 的 gpio 申请失败
图 10 · eMMC 驱动 probe 到 /dev/mmcblk0 出现的完整流程
「dmesg 里 mmc0 但没有 mmcblk0」的判定
这是最常见的「驱动没起来」误判。实际上有五种情况都能产生这个现象,按可能性排序:
- 探测到的是 SD 卡不是 eMMC :
mmc0: new SD card at addr 0x1说明 SoC 的 SD 控制器被先枚举了。查ls /sys/bus/mmc/devices/,确认 eMMC 挂在哪个 host 上。 - 硬件分区节点没使能 :只有
mmcblk0没有mmcblk0boot0, 说明EXT_CSD[224] HESW里 boot 分区使能位没置上(见 2.2 与 8.4 节)。 - EXT_CSD 读取失败 :dmesg 会有
error -84之类,容量识别为 0 或异常值。 - 块设备注册被
max_part或partscan影响 : 若 EXT_CSD 容量为 0,mmc_blk_alloc_req()可能只注册出空设备。 - 真的没注册 :
dmesg | grep -i mmc里若完全没有mmc_blk_alloc相关行,回到第 2 步检查 probe 是否被调用(-EPROBE_DEFER被吞)。
3.6 mmcblk 块设备层
mmcblk 把「协议栈的一次 ADTC 命令」抽象成「一次块设备 bio」。 它的设计是直通式 的:mmcblk_submit_bio() 直接调 mmc_blk_data_cmd() 发命令,没有额外的中间层。因此 eMMC 的性能特征基本等于器件特征, 这也是为什么「eMMC 随机写慢」这件事在软件层几乎无法优化。
| bio 特征 | 映射的 eMMC 命令 | 耗时特征(HS400 典型量级) | 备注 |
|---|---|---|---|
| 顺序大块读(1 MB 以上) | CMD18 多块读 |
1.5 ~ 3 GB/s 量级,接近总线上限 | 受 CPU 拷贝与文件系统影响明显 |
| 顺序大块写(1 MB 以上) | CMD25 多块写 |
600 MB/s ~ 1.2 GB/s,取决于是否回写 | 受 Cache 策略影响极大 |
| 4K 随机读 | CMD17 单块读 |
与顺序读接近(器件内部并行) | eMMC 的随机读延迟优势明显 |
| 4K 随机写 | CMD24 单块写 |
最差场景,可能与顺序写差一到两个数量级 | FTL 需读改写,落入写放大 |
| sync / fsync | Flush 之前的等待加写命令 R1b | 与 Cache 开关强相关 | 见第 10 章 |
| discard(fstrim) | CMD38 TRIM(无响应) |
异步,需轮询状态 | 对 eMMC 收益取决于 FTL 实现 |
为什么 eMMC 上的「写缓存」会毁掉 fsync 的意义
在 eMMC 上启用 Cache 后,write() 返回只代表数据进了器件 Cache。 fsync() 会通过 mmc_blk_flush() 触发 Cache Flush 语义, 但不同厂商对「Cache Flush」的实现质量差别很大:有的只是把 Cache 标记为无效 (回写放到后台做),有的才真正同步落到 NAND。
因此本文的建议是:写关键数据的分区(数据库、配置、计费数据)所在 eMMC, 在产品定义上就要明确是否开启 Cache;不确定时先关 Cache 跑通功能, 再在测试充分后开 Cache 做性能优化。
3.7 队列、中断与 DMA
eMMC 走的是「中断 + DMA」路径。理解这条路径对定位「偶发卡死」「高负载丢数据」很关键。
| 环节 | 机制 | 排查手段 | 常见问题 |
|---|---|---|---|
| 命令提交 | 驱动写寄存器并拉 CMD 起始脉冲 |
逻辑分析仪抓 CMD 线 | 命令索引被篡改说明信号完整性或时序有问题 |
| 数据传输 | DMA 搬数据(多为 SDMA / ADMA / IDMA) | 看 /proc/interrupts 里 MMC 中断计数 |
DMA 描述符配错会导致数据错乱但无 CRC 错误 |
| 完成中断 | 控制器置位 INT,驱动在中断里处理 |
dmesg 出现 Timeout waiting for hardware interrupt |
中断没来通常是时钟门控没开或共享中断没配好 |
| 超时恢复 | core 层 mmc_reset() 复位 host |
dmesg 出现 timeout 与 trying to reset card |
连续超时是硬件或时序问题,不是软件问题 |
| CQE(可选) | 硬件命令队列,控制器批量取命令 | debugfs caps 里的 CMDQ 位 |
使能后不稳定就关掉,见 9.5 节 |
3.8 编码规范与 remove 路径
eMMC 控制器的 remove 路径比多数块设备复杂:要先停队列、flush 块设备、 移除 pwrseq、释放 GPIO 与 regulator,顺序错了就会在 suspend 或模块卸载时 panic。
static int xxx_mmc_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mmc_host *mmc = dev_get_drvdata(dev);
struct xxx_host *host = mmc_get_drvdata(mmc);
/* 1. 先关卡:停止新的请求,等待 in-flight 完成 */
mmc_remove_host(mmc);
/* 2. 再拆块设备:flush 掉所有脏数据并注销 /dev/mmcblkN */
mmc_blk_remove(mmc);
mmc->card = NULL;
/* 3. 关电源时序(若用了 pwrseq) */
if (host->pwrseq)
mmc_pwrseq_power_off(&host->pwrseq);
/* 4. 释放资源:DMA 通道、中断、regulator、GPIO、寄存器映射 */
dma_release_channel(host->dma);
free_irq(host->irq, dev);
if (host->vq)
regulator_disable(host->vq);
gpiod_put(host->reset_gpiod);
devm_clk_put(dev, host->clocks[0]);
/* 5. 最后销毁 mmc_host 本身 */
mmc_free_host(mmc);
dev_set_drvdata(dev, NULL);
return 0;
}
/* 关键:remove 与 suspend 共用同一套「优雅下线」逻辑,不要重复写两份 */
static void xxx_mmc_shutdown(struct device *dev)
{
struct mmc_host *mmc = dev_get_drvdata(dev);
/* 无响应命令(例如在写卡时突然关电源)无法 abort,这里只能等超时 */
if (mmc->card)
mmc_shutdown(mmc);
}
写卡过程中直接断电,会留下「未完成命令」状态
eMMC 的写命令(CMD24 / CMD25)一旦发出,主机无法中途取消 ------ 设备树里 cd-gpios 那类「拔卡即中断」的机制在 eMMC 上不存在(不可移除器件)。所以:
① xxx_remove() 里不能假设命令能 abort,只能等超时;
② 产品必须明确定义「写操作过程中断电」的用户数据预期 ,并在文件系统上做保护 (ext4 的 journal 提交间隔、f2fs 的 checkpoint 策略);
③ 关键数据结构需要应用层双写加校验,不能只依赖文件系统。
第 4 章 · 控制器驱动:SDHCI 与厂商实现
绝大多数 SoC 的 eMMC 控制器都兼容 SDHCI 规范,Linux 用一套通用框架覆盖了它们。 本章先讲 SDHCI 通用部分,再按厂商拆解差异,最后给出「厂商私有补丁该怎么写」的范式。
4.1 SDHCI 寄存器组
SDHCI 寄存器按「能力寄存器 / 状态寄存器 / 寄存器组 / 时钟与复位 / 中断」五类划分。 驱动开发者需要记住的只有下面这十几个,其余可查 drivers/mmc/host/sdhci.h。
| 寄存器 | 偏移 | 作用 | 要点 |
|---|---|---|---|
| SDHCI_CAPABILITIES | 0x40 | 能力:支持的电压、总线宽度、模式、时钟频率 | 只读;它决定了这个控制器能做什么 ,与 DT 的 cap-* 属性取交集 |
| SDHCI_CAPABILITIES_1 | 0x44 | 能力扩展:HS200 / HS400 / CQE 支持 | 5.0 与 5.1 器件要用 HS400 就必须看这里 |
| SDHCI_ADMA_CAP | 0x58 | ADMA(增强 DMA)描述符能力 | 决定走 ADMA 还是 SDMA;不支持时性能降一档 |
| SDHCI_PRESENT_STATE | 0x24 | 当前状态:卡在位、busy、数据线状态 | 调试 HS400 训练失败必看:DAT 线状态不对说明时序没对齐 |
| SDHCI_CLOCK_CONTROL | 0x2C | 时钟使能、频率分频、稳态与突发时钟 | SDHCI_CLOCK_INT_STABLE 位要等置位 |
| SDHCI_SOFTWARE_RESET | 0x2F | 命令线、数据线、全部复位 | 分级复位顺序:先 CMD,再 DAT,再 ALL |
| SDHCI_ARGUMENT 与 CMD | 0x08 与 0x0C | 命令参数与命令寄存器 | 写 ARGUMENT 后必须立刻写 CMD,否则会被清零 |
| SDHCI_TRANSFER | 0x0E | 传输方式:单块或多块、读写、DMA 使能 | 多块加写加使能 DMA 三个位必须同时对 |
| SDHCI_BLOCK_SIZE 与 BLOCK_COUNT | 0x10 与 0x0F | 块大小(12 bit,0 表示 4096)与块数 | 块大小寄存器写 0 表示 4096,这是 4K 扇区必须注意的地方 |
| SDHCI_INT_STATUS 与 INT_ENABLE | 0x30 与 0x34 | 中断标志与使能 | 命令完成、数据完成、busy 完成要分别使能 |
| SDHCI_SDHC_CONTROL | 0x2D | 1.8 V 电压切换控制 | 与 CMD11 配合,控制器侧完成 IO 电平切换 |
| SDHCI_CMD_ARGS 与 XFER_PARAM | 0x28 与 0x0D | 64 bit 命令参数(CMD23 与 CMD18 的起始地址) | 仅 MMC_CAP_CMDQ 与部分控制器使用 |
SDHCI 能力寄存器的经验判读
SDHCI_CAPABILITIES 是纯硬件事实,DT 里写的 cap-* 是软件诉求, 实际生效的是两者交集。所以:
- DT 写了
cap-hs400但能力寄存器没置位,实际停在 HS200,日志里能看到; - 能力寄存器置位但 DT 没写,同样停在 HS200,因为没人申请;
- 排查「为什么没到 HS400」时,两个地方都要看,只查 DT 是最常见的疏漏。
4.2 SDHCI 通用驱动家族
Linux 里 SDHCI 通用框架的入口是 sdhci.c,往上分三路总线:
| 入口文件 | 总线 | 典型平台 | 驱动方需提供 |
|---|---|---|---|
sdhci.c 与 sdhci.h |
核心框架 | 全部 | 什么都不提供,只是能力与流程 |
sdhci-pci.c |
PCIe | x86、AMD、Intel SoC | PCI 枚举与电源管理 |
sdhci-pltfrm.c |
platform(含 DT) | 绝大多数 ARM SoC | DT 属性 :bus-width、cap-*、max-frequency、quirks |
sdhci-of-dwcmsh.c |
platform 加 OF | Rockchip、ZynqMP、紫光展锐部分 | DSR(驱动强度与采样点)、频率表、quirks |
sdhci-of-arasan.c |
platform 加 OF | Arasan 系列 IP | 时序参数、延迟调整 |
SoC 硬件 MMC 控制器SDHCI 寄存器组(能力见 0x40 与 0x44)host 目录下的厂商驱动quirks、DSR、频率表、pinctrl 申请sdhci.c 与 sdhci-pltfrm.c(通用)统一 probe、命令下发、中断处理、PMcore 目录与 block/mmcblk.c协议栈与块设备层,不随控制器变排查顺序:先看厂商驱动的 quirks 打印(dmesg 里的 quirks 列表),再往上查 core
图 11 · SDHCI 驱动家族的调用链:厂商驱动在上、通用框架在下
quirks 打印是 eMMC 调试的第一个必看点
每次 SDHCI 控制器 probe 成功,dmesg 里都会打印一行类似:
mmc0: SDHCI controller [d4200000.mmc] at ... 200 MHz, 4-bit, ADMA
mmc0: new SDHC card at address 59b1c74d [quirks=0x00008001: ...]
这个 quirks 列表说明 SoC 厂商主动声明了自己与规范不一致的地方 。 看到陌生的 quirk 位就去 include/linux/mmc/sdhci.h 查它的含义------ 这行日志能直接省掉大量「为什么这个平台的 eMMC 表现和别的平台不一样」的排查时间。
4.3 Rockchip dwcmsd
Rockchip 的 SDHCI 控制器在主线里是 sdhci-of-dwcmsh.c, 最需要注意的是它把 HS200 与 HS400 训练及 DSR(驱动强度、采样点)绑定在一起, 参数缺失会导致「能识别但切不到高速」。
| 关键 DT 属性或代码字段 | 作用 | 缺失或错误的后果 |
|---|---|---|
rockchip,grf-cmds-sent-delay? |
调整 GRF 时序延迟 | 偶发识别失败,表现为开机偶尔不识别 |
| ROCKCHIP_SDHCI_CAPABILITIES1 覆写 | 厂商代码里手动改能力寄存器 | HS400 使能后仍停在 HS200 |
rockchip,sd-freq-dn-msm 等 quirk |
按 SoC 版本微调频率 | 高频下 CRC 错误 |
dsn-in-t 等 DSN 延时字段 |
训练后的采样点延时 | HS200 训练成功但 HS400 失败 |
mmc-hs200-postpone-clk |
训练时先关时钟 | 训练期间偶发超时 |
mmc-hs400-enhanced-strobe |
启用 HS400EX 的 DS 支持 | DS 走线已留但仍走普通 HS400 |
4.4 Allwinner sunxi-mmc
全志(Allwinner)平台的 mmc-sunxi.c 是自研控制器,不走 SDHCI 框架。 它有两个特别容易踩的点:相位配置 与电源门控的时序要求。
| 项 | 说明 | 注意点 |
|---|---|---|
| 相位调整 | mmc-sunxi 用 DTB 的相位参数调整采样点 |
换料或换温度后偶发错误,需要重新扫相位,见第 7 章 |
| 时钟源切换 | 有些型号在 100 MHz 以上需要切到 PLL 时钟 | 不切则高频下大幅误码 |
| 电压域门控 | 支持的型号有独立的 VCCQ 门控 | 门控后必须先恢复电压再释放时钟 |
| 驱动私有 quirk | controller.h 里的 SUNXI_MMC_QUIRK_XXX |
不同 SoC 型号位定义不同,换型号必须重查 |
4.5 联发科 MSDC
MTK 的 mtk-sd.c 是自研控制器,复杂度主要来自上电相位校准 与电压切换时序。它也是「驱动自己不发 CMD1,而是复用 SD 路径再切回 eMMC」的典型实现。
- CMD1 由谁发 :MTK 驱动在
mtk_sd_probe_hw()里 用mmc_send_init_ocr()发完成,才注册块设备;自定义 pwrseq 时要注意 不要重复发 CMD1。 - 相位校准 :MSDC 在 HS200 与 HS400 前会跑一轮相位校准(
msdc_calibrate_phase), 耗时几十毫秒;若校准失败会降档,日志里会打印校准结果。 - 电压切换 :MTK 需要配合相应的 DT 属性;eMMC 场景关注的是
mmc-hs200-postpone-clk与 VCCQ 电压确认。
4.6 高通 sdhci-msm
高通平台的 SDHCI 增强版。实际项目中要关注两点:
- 电压缩放器(PMIC vdd-io) :eMMC 用 1.8 V IO 时需要 PMIC 支持
qcom,vdd-io-voltages属性列出可切电压,驱动据此切 GPIO 控制 vreg; 配置不对会在 CMD11 时切电压失败。 - 总线时钟来源 :部分 SoC 上 eMMC 总线时钟是独立的,需要在
sdhci-msm.c里确认clk_get拿到的是哪一路。
4.7 NXP USDHC 与 i.MX USDHC
NXP 的 USDHC 支持 eMMC 4.5 及以上,实际项目常见两个坑:
- USDHC 的总线宽度属性名与通用 DT 不同 ; 8 bit 支持需要 SoC 引脚配置正确,否则
PRESENT_STATE的 DAT 线数读出来是 4。 - HS400 支持的型号有限 :并非所有 i.MX 型号都有 USDHC3 与增强选通; DT 里写
cap-hs400也可能停在 HS200。查 SoC 手册确认增强选通引脚是否存在。
4.8 ZynqMP 与其它 SoC
ZynqMP 用 sdhci-of-dwcmsh.c,有个特殊点:SD 控制器与 eMMC 控制器 共用 zynqmp_sdhci_probe_hw() 的部分逻辑 ,改代码时要区分。 其余平台(紫光展锐、兆易创新、龙、全志、瑞芯微、晶晨等)多数已有主线支持, 少数仍需厂商 out-of-tree 驱动,此时先查 drivers/mmc/host/ 下 有没有同名文件(可能已经进主线了),再考虑移植 out-of-tree。
4.9 厂商私有补丁范式
当通用框架确实不够用(私有寄存器、私有时序、私有 quirk),需要在厂商驱动里加代码。 关键是把这些代码写成「可上游化」的形式,否则每次内核升级都要重打一遍。
/* 范式:所有平台私有逻辑用 quirk 位控制,主线逻辑保持通用 */
static const unsigned int xxx_mmc_of_quirks[] = {
/* 该 SoC 的 SDHCI 需要在切 HS400 前额外写 DSN 延时,否则训练失败 */
XXX_QUIRK_NEED_DSN_DELAY | (3 << XXX_QUIRK_DSN_SHIFT),
/* CMD6 busy 需要更长的超时时间 */
XXX_QUIRK_LONG_SWITCH_TIMEOUT | (2000 << XXX_QUIRK_TMO_SHIFT),
/* 硬件只在 3.3 V 下工作,不能切 1.8 V */
XXX_QUIRK_NO_1V8_SWITCH | 0,
};
static const struct mmc_of_quirk xxx_mmc_of_quirks[] = {
{ .compatible = "rockchip,rk3399-dwcmshc",
.quirks = xxx_mmc_of_quirks },
{ /* 新的 SoC 复用同一套 quirks,加 compatible 即可,不要复制代码 */
.compatible = "rockchip,rk3588-dwcmshc",
.quirks = xxx_mmc_of_quirks },
{}
};
/* 在 xxx_mmc_probe() 末尾按 quirk 生效,而不是在 DT 解析里硬编码 */
static void xxx_apply_quirks(struct mmc_host *mmc)
{
struct device *dev = mmc->dev;
int quirks = of_property_read_int(dev, "vendor,quirks", 0);
if (quirks & XXX_QUIRK_NEED_DSN_DELAY)
xxx_write_dsn_delay(mmc, (quirks >> XXX_QUIRK_DSN_SHIFT) & 0xff);
if (quirks & XXX_QUIRK_LONG_SWITCH_TIMEOUT)
mmc->max_switch_timeout = (quirks >> XXX_QUIRK_TMO_SHIFT) & 0xfff;
}
/* 写日志,方便现场定位「到底哪个 quirk 在起作用」 */
dev_info(dev, "quirks=0x%08x: %s%s%s\n", quirks,
(quirks & XXX_QUIRK_NEED_DSN_DELAY) ? "NEED_DSN_DELAY " : "",
(quirks & XXX_QUIRK_LONG_SWITCH_TIMEOUT) ? "LONG_SWITCH_TIMEOUT " : "",
(quirks & XXX_QUIRK_NO_1V8_SWITCH) ? "NO_1V8_SWITCH " : "");
判断「能不能上游化」的四条标准
① 是否只针对一个 SoC :针对单平台的逻辑用 quirk 位隔离,不写死在通用路径;
② 是否有 DT 表达 :能写成 DT 属性的就不要用 DT 里的私有 compatible 硬编码;
③ 是否有确定的判据 :加上 of_property_read_bool 判断, 这样其他平台完全不受影响;
④ 是否有日志:quirks 生效必须打日志,否则现场无法确认补丁是否起作用。
四条都满足的补丁,上游 review 通过率很高,后续内核升级基本无成本。
第 4 章自检
- dmesg 已记录
quirks=完整列表,陌生 quirk 位已查明含义 - 已核对
SDHCI_CAPABILITIES与 DT 声明的交集,确认目标模式在此范围内 - 若用 out-of-tree 驱动:已确认主线
drivers/mmc/host/中没有替代实现 - 私有逻辑已全部收敛到 quirk 位,且每个 quirk 都有日志输出
- 已记录当前内核版本下该平台的
quirks基线,升级内核后回归对比
第 5 章 · 设备树与分区设计细则
设备树是「一张板」与「一套软件」之间唯一的正式契约。 写错一个属性,症状可能是「不识别」「容量少一半」「掉电丢数据」这类看似无关的问题。
5.1 mmc 节点完整字段
下面是一个面向 eMMC 的完整 mmc 节点,逐项注释见下表。 实际项目里按需裁剪,不要照抄全量属性------多余的属性也会带来维护成本。
/* ---------- SoC 侧的 MMC 控制器节点(在 SoC 的 dtsi 里) ---------- */
&mmc0 {
status = "okay";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&mmc0_default>;
pinctrl-1 = <&mmc0_sleep>;
clocks = <&sdhci_in_clk>, <&sdhci_ahb_clk>;
clock-names = "in_clk", "ahb_clk";
clock-frequency = <200000000>;
max-frequency = <200000000>; /* 驱动用 mmc_of_parse 转成 host->max_freq */
bus-width = <8>; /* eMMC 常见 8 bit;1 与 4 也可 */
cd-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; /* 卡在位检测;eMMC 可省略 */
/* 复位若由 GPIO 承担则打开;由控制器自身复位则不需要 */
reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;
reset-delay-ms = <20>;
/* 能力声明:与硬件能力寄存器取交集 */
cap-mmc-highspeed; /* 52 MHz */
cap-hs200; /* 200 MHz 单线 */
cap-hs400; /* 200 MHz 双线 DDR */
cap-hs400-enhanced-strobe; /* 5.1 的 DS 增强选通 */
non-removable; /* eMMC 必备:没有插拔动作 */
mmc-hs200-postpone-clk; /* 训练时先关时钟(部分控制器需要)*/
mmc-hs400-postpone-clk;
vmmc-supply = <&vmmc_supply>; /* 3.3 V 主电源 */
};
/* ---------- 板级:eMMC 设备节点 ---------- */
&mmc0 {
status = "okay";
cap-sd-highspeed; /* 兼容 SD 卡时需要 */
sd-max-frequency = <200000000>;
mmc-pwrseq = <&mmc_pwrseq>;
keep-power-in-suspend; /* suspend 时不掉 VCC,见 13.2 节 */
/delete-property/ cd-gpios; /* eMMC 不可移除,删掉卡检测 */
};
/* ---------- 上电时序:电源时序 pwrseq ---------- */
mmc_pwrseq: mmc-pwrseq {
compatible = "mmc-pwrseq-simple";
reset-gpios = <&gpio3 5 GPIO_ACTIVE_LOW>, /* PMIC 的 VCC_EN,排第一位 */
<&gpio1 11 GPIO_ACTIVE_LOW>; /* eMMC RST_n */
post-power-on-delay-ms = <200>; /* reset 拉高后的延时 */
};
/* ---------- 分区:GPT 固定分区(子节点) ---------- */
&mmc0 {
partitions {
compatible = "fixed-partitions";
#size-cells = <1>;
#address-cells = <1>;
boot partition@0 {
label = "boot";
reg = <0x0 0x4000000>; /* 64 MB */
};
rootfs partition@4000000 {
label = "rootfs";
reg = <0x4000000 0x20000000>; /* 512 MB */
};
data partition@24000000 {
label = "data";
reg = <0x24000000 0x0>; /* 余量 */
};
};
};
| 属性 | 作用域 | 必填 | 作用与填写要点 |
|---|---|---|---|
status = "okay" |
SoC 与板级 | 是 | 板级必须覆盖 SoC 的 disabled;两处都写时板级优先 |
non-removable |
板级 | eMMC 必备 | 告诉 core 不做插拔检测与卡拔出处理 |
bus-width |
SoC | 是 | 1 / 4 / 8;写错时 PRESENT_STATE 读出的 DAT 线数不符,表现为读写错误 |
max-frequency |
SoC | 是 | 单位 Hz;不要超过 clock-frequency 与能力寄存器上限 |
cap-mmc-highspeed |
SoC | 建议 | 52 MHz 模式;即使只用 HS400 也建议打开,作为降级路径 |
cap-hs200 / cap-hs400 |
SoC | HS200 与 HS400 必备 | 与能力寄存器取交集;不写则到不了该模式 |
cap-hs400-enhanced-strobe |
SoC | HS400EX 必备 | 需要硬件真的接了 DS;接错会导致训练失败 |
mmc-hs200-postpone-clk |
SoC | 视平台 | 训练时先关时钟,规避部分控制器训练期间的竞争 |
cd-gpios |
SoC | SD 必备,eMMC 省略 | eMMC 保留会让部分平台反复尝试卡切换,浪费启动时间 |
reset-gpios 与 reset-delay-ms |
SoC | 视设计 | 复位脉宽与复位后延时;reset-delay-ms 缺失是冷启动偶发失败的常见原因 |
pinctrl-0 / pinctrl-1 |
SoC 与板级 | 是 | default 跑高频;sleep 用于 suspend 时把 IO 拉回安全电平 |
vmmc-supply |
SoC | 建议 | 对应 3.3 V 主电源;配合 regulator 门控方案 |
mmc-pwrseq |
板级 | 门控方案必备 | 见 5.6 节 |
keep-power-in-suspend |
板级 | 视设计 | suspend 时不给 VCC 断电;门控方案不要设这个,否则与门控逻辑冲突 |
partitions 子节点 |
板级 | 可选 | 见 5.2 节;不写则设备上没有分区节点 |
5.2 分区方案:GPT / MBR / eMMC 硬件分区
eMMC 有两层分区概念,混用是最常见的规划错误:
- 硬件分区 (Boot0 / Boot1 / RPMB):器件内部固化,绕过分区表, 由
EXT_CSD[224]使能与否决定是否上报; - 软件分区 (用户区内的 GPT 或 MBR 分区):落在
/dev/mmcblk0上, 由分区表描述。
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| GPT 加 PARTUUID 引用 | 支持大于 2 TB;分区 UUID 稳定,设备顺序变化不影响 | 需要 parted 或 sgdisk;部分老 bootloader 不认 |
推荐。所有现代量产项目 |
MBR 加 /dev/mmcblk0pN 引用 |
兼容性最好,U-Boot 老版本也认 | 无 UUID,多卡场景下 mmc0 与 mmc1 顺序会错 |
只有一个 eMMC 且 bootloader 很老的项目 |
GPT 加 /dev/mmcblk0pN |
容量大且引用直观 | 多卡场景高危:SD 卡插上后枚举成 mmc1,eMMC 变 mmc0,rootfs 挂错 | 不推荐 |
| 单分区(无分区表) | 简单,整个卡当 rootfs | 无法 OTA、无法 A/B、无独立数据区 | 仅数据采集类终端 |
| eMMC 硬件分区装 Bootloader | 独立于用户区,用户区损坏不影响启动 | 容量固定(各约 4 MB),需 mmc bootpart 写入 |
推荐:Boot0 与 Boot1 做 A/B |
「/dev/mmcblk0p2 挂不上」的根因几乎都是多卡顺序
Linux 按探测顺序分配 host 编号:先探测到的拿 mmc0 。 如果板子上有 SD 卡槽,插入 SD 卡时 eMMC 可能是 mmc1、SD 卡是 mmc0, 于是 root=/dev/mmcblk0p2 就指向了错误的设备。
两种正确解法:
① 设备树用 PARTUUID 引用 :root=PARTUUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, U-Boot 会把它解析成正确的 /dev/mmcblkNpM;
② 在 U-Boot 里固定 host 编号 ,并用 bootpart 显式指定分区号。
本文推荐第一种,唯一需要注意的是重刷分区表时 PARTUUID 会变, 产线流程必须把 PARTUUID 作为唯一标识记录在案。
5.3 U-Boot 与内核分区一致性
U-Boot 和内核共享同一份设备树(通常 U-Boot 拿到的是编译期传入的 dtb)。 一旦分区表定义不一致,典型症状是「U-Boot 能启动、内核挂不上 rootfs」。
| 一致性项 | U-Boot 侧 | 内核侧 | 不一致的表现 |
|---|---|---|---|
| 分区表 | 读同一份 dtb 的 partitions 子节点 |
同一份 | U-Boot printenv 显示分区大小对但 bootcmd 读不出来 |
bootargs 里的 root |
U-Boot 拼接并传给内核 | 内核按此挂载 | 内核 panic:VFS 无法挂载 root fs |
| 存储设备号 | mmc_set_devno 或 mmc dev |
按探测顺序 | U-Boot 从 mmc1 读,内核当 mmc0 用 |
| 硬件分区使能 | U-Boot 阶段已用 mmc bootpart 0 1 写入 |
内核读 EXT_CSD[224] |
U-Boot 能从 boot0 启动,内核看不到 mmcblk0boot0 |
| Cache 模式 | U-Boot 传给内核的 cache mode |
内核据此初始化 | 性能异常或掉电丢数据,见第 10 章 |
5.4 overlay 与多板型
多板型项目(同一 SoC 不同 PCB 载板)建议用编译期按板型选 dts, 而不是运行时 overlay:eMMC 涉及 pinctrl、regulator、reset 引脚, overlay 覆盖容易出现「属性没被完全替换」的隐性 bug。
| 做法 | 适用 | 风险 | 推荐度 |
|---|---|---|---|
| 按板型编译不同 dts | 板型在编译期已知 | 构建产物变多 | 推荐 |
| 单 dts 加多板型节点,用 dtsi include | 板型差异小 | 属性覆盖需用 /delete-property/,易漏 |
可用 |
| 运行时 overlay | 板型运行时才知道(如外接模组) | eMMC 相关属性覆盖后时序类属性可能不生效 | 不推荐用于 eMMC |
/delete-node/ 加重新定义 |
需要「取消继承」的属性 | 漏写会导致属性叠加(如 SoC 定义 4 bit,板级又写 8 bit) | 需谨慎 |
SoC 层:mmc 控制器节点status / clocks / clock-namespinctrl-0 / pinctrl-1max-frequency / bus-widthcap-* 能力声明reset-gpios / reset-delay-msvmmc-supply板级:覆盖与补充/delete-property/ 取消不 Needed 属性mmc-pwrseq 引用上电时序keep-power-in-suspendpartitions 子节点(GPT)boot / rootfs / data#address-cells 与 #size-cells属性合并的三条规则① 同名属性:板级覆盖 SoC(后定义生效)② 不同名属性:同时生效(cap-sd-highspeed 与 cap-mmc-highspeed 会共存)③ 需要「取消继承」时必须显式 /delete-property/ 或 /delete-node/,否则会残留叠加验证手段:dtc -I dtb -O dts 后直接看最终值,或启动后读 /sys/firmware/devicetree/base/
图 12 · eMMC 设备树节点结构与属性合并规则
属性叠加是 DT 最隐蔽的 bug 类型
DT 的属性定义不是覆盖而是合并 : SoC 的 dtsi 里写了 bus-width = <4>,板级 dts 里写 bus-width = <8>, 最终生效的是 8 (同名词后定义覆盖),这是对的。 但如果 SoC 里写的是 cap-sd-highspeed、板级又写了 cap-mmc-highspeed, 两者是不同属性名,会同时生效,导致 core 认为这张卡同时具备 SD 与 MMC 能力, 在某些版本上会走错初始化路径。
排查手段:dtc -I dts -O dtb 编译后 dtc -I dtb -O dts dtb.dtb | grep -A5 mmc@ 直接看最终值, 比反复读源码可靠得多。启动后也可以 cat /sys/firmware/devicetree/base/.../bus-width 复核。
5.5 pinctrl / regulator / reset / cd-gpios
这四类是「硬件与驱动握手」的部分,每个都有明确的时序要求。
| 资源 | DT 写法要点 | 时序要求 | 配错的表现 |
|---|---|---|---|
| pinctrl | pinctrl-0 跑高频、pinctrl-1 为 sleep;两组都要配,只配一组会在 suspend 时 IO 悬空 |
default 组在 probe 时应用;sleep 组在 suspend 时应用 | suspend 唤醒后首次访问失败(IO 电平不对) |
| regulator | vmmc-supply 指向固定 regulator;regulator-boot-on 让 suspend 保持供电 |
必须先有稳定供电才 probe;门控方案要配合 regulator-allow-set-load |
电压掉到 0(门控后没恢复),或 regulator_is_enabled 报错 |
| reset GPIO | reset-gpios 加 reset-delay-ms;pwrseq 的 post-power-on-delay-ms 也管延时 |
RST_n 拉低保持不小于 1 个时钟周期;拉高后不小于 1 ms 再发 CMD | 冷启动偶发不识别(热重启正常) |
| cd-gpios | eMMC 建议删除;SD 卡必须配对且极性要与卡座一致 | 探测阶段先于 CMD1 | eMMC 上留着会让驱动反复尝试切换,浪费启动时间 |
| WP(写保护)GPIO | wp-gpios(部分控制器支持);eMMC 写保护一般由厂商硬件决定 |
无 | 写入无报错但不落盘 |
5.6 mmc-pwrseq 与上电延时
当 eMMC 的上电需要外部协助(多个 GPIO 依次拉起、电源需要先升到某个电压档)时, 就要用 mmc-pwrseq。它是内核提供的标准机制(drivers/mmc/core/mmc-pwrseq.c)。
/* 标准写法:所有引脚一起拉起,post-power-on-delay-ms 统一延时 */
mmc_pwrseq: mmc-pwrseq {
compatible = "mmc-pwrseq-simple";
reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>, /* eMMC RST_n */
<&gpio3 5 GPIO_ACTIVE_LOW>; /* PMIC 的 VCC_EN */
post-power-on-delay-ms = <200>;
};
/* 带 GPIO 范围控制的写法(部分 SoC 需要 pinctrl 切换到 3.3 V 档)*/
mmc_pwrseq_x: mmc-pwrseq {
compatible = "mmc-pwrseq-simple";
reset-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>;
post-power-on-delay-ms = <300>;
pinctrl-0 = <&mmc_vddq_3v3>;
pinctrl-1 = <&mmc_vddq_3v3>;
};
硬件分区(器件内部固化,绕过分区表)Boot0 约 4 MB Boot1 约 4 MB RPMB 约 128 KB用户区(/dev/mmcblk0,GPT 或 MBR 分区表管理)p1 boot 64 MB p2 rootfs 512 MB p3 data 余量 p4 userdataU-Boot 引用方式load mmc 0:1 ${loadaddr} Imagemmc bootpart 0 1bootcmd 用 PARTUUID 指定 root高危写法(多卡场景)root=/dev/mmcblk0p2插 SD 卡后 mmc0 变成 SD 卡结果:挂错设备或直接起不来分区表操作全部通过 sgdisk 或 parted 完成,产线必须记录 PARTUUID 作为唯一标识重刷分区表会生成新的 PARTUUID,U-Boot 环境与设备树需同步更新
图 13 · eMMC 分区规划与 U-Boot 引用关系
pwrseq 配错是「开机偶发不识别」的头号原因
- 延时太短 :
post-power-on-delay-ms只写 10 ms, 而 PMIC 的 3.3 V 轨实际建立需要 100 ms 以上,器件在 VCC 未稳时收到 CMD1,OCR 阶段失败。 建议不小于 200 ms,冷启动问题优先怀疑这里。 - 引脚顺序没管 :
reset-gpios是数组, 内核按数组顺序依次拉起。如果 VCC_EN 必须在 RST_n 之前,就把它放第一位。 - 与 SoC 节点的 reset-gpios 冲突 :同一个 GPIO 在
mmc节点和 pwrseq 里都被申请,第二个会失败(-EBUSY)。 二选一,不要都写。 - procfs 里看不到 pwrseq :成功时
/sys/kernel/debug/mmc0/pwrseq有内容, 没有就是 pwrseq 根本没被执行,去 dmesg 里找 pwrseq 相关的错误。
本文档为 《原理方案设计》 系列文档之一,与《eMCP_电路设计指南》《eMMC 软件设计规范》 《Linux_SPI_NAND_驱动开发指南》配套使用。文中涉及的寄存器偏移、命令常量与内核接口 以 Linux mainline 为准;厂商私有部分请以对应 BSP 的手册与源码为准。 代码片段仅用于说明设计意图,实际移植时需按目标内核版本调整 API。
LINUX-EMMC-DRV-001 · V1.0 · 嵌入式文件系统系列 · 共 20 章 + 附录 A~F · 27 图
后续章节请下载「芯参谋」查看
......(篇幅原因, 还有22章· 详见芯参谋→方案设计)
打开路径:🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_eMMC_驱动开发指南
相关报告 · 1
- 瑞萨(Renesas)平台_SPI_NOR_eMMC_量产排查调试&解决方案
- 能读ID但读写失败_样机排查调试&解决方案
- 英飞凌(Infineon)_开发指南原理方案设计
- 基板管理控制器(BMC)_市场分析市场应用分析
- 大型无人机(Industrial Drone)_市场分析市场应用分析
相关报告 · 2
- 5G模组(5G Module)_市场分析市场应用分析
- 儿童玩具(Children Toy)_市场分析市场应用分析
- 多存储并联与通道均衡_技术探讨原理方案设计
- 博通(Broadcom)平台_SPI_NOR_NAND_eMMC_量产排查调试&解决方案
- 锐迪科(RDA)平台_SPI_NOR_量产排查调试&解决方案