只读分享解决方案文档原理方案设计📅 2026-10-05🔖 V1.0✅ 长期有效
🔧解决方案&应用市场分析 -> 原理方案设计 -> 嵌入式驱动&系统开发 -> Linux_eMMC_驱动开发指南

Linux_eMMC_驱动开发指南

生成时间 2026-10-04 11:49:16 长期有效
同系列 · 原理方案设计

Linux eMMC 驱动开发指南

编制日期 2026-10-05

面向嵌入式 Linux 平台的 eMMC 存储器件 驱动开发、集成、调试与量产指南 —— 覆盖 SDHCI / 厂商控制器、MMC 子系统、设备树、速度模式切换、块设备 I/O 路径、Cache 一致性 与 RPMB 全链路。

文档编号LINUX-EMMC-DRV-001
版本V1.0
适用器件eMMC 4.41 / 4.51 / 5.0 / 5.1(153-ball BGA,含 eMCP / uMCP 合封形态)
适用内核Linux 4.19 / 5.4 / 5.10 / 5.15 / 6.1 / 6.6(主线 MMC 子系统)
适用平台ARM32 / ARM64 / RISC-V 嵌入式 SoC,SDHCI 兼容控制器与厂商私有 MMC 控制器
配套文档《eMCP_电路设计指南》《eMCP_软件设计规范》《Linux_SPI_NAND_驱动开发指南》
读者对象嵌入式 Linux BSP / 驱动工程师、存储产品经理、硬件与 PCB 工程师、产线与测试工程师

0.1 编写目的与读者对象

本文要解决的是「一颗 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 术语与缩略语

下表把全文会反复出现的名词一次性讲清,后文不再解释。标星号的三条是最容易误解的。

缩写 / 术语英文全称含义与工程含义
eMMCembedded MultiMediaCard焊在板上的不可移除存储器件,内置 FTL,对主机呈现为块设备
FTLFlash Translation Layer器件内部的地址翻译与磨损均衡层;主机不可见,也不可绕过
RPMBReplay Protected Memory Block带 HMAC 认证与防回放计数器的安全存储区,容量仅数百 KB
HS200High Speed 200eMMC 4.5 引入的单数据线 200 bit/s 模式,是 HS400 的过渡基础
HS400High Speed 400eMMC 5.0 引入的 DDR 双数据线模式,理论 400 MB/s 级别接口速率
HS400EXHS400 Enhanced StrobeeMMC 5.1 引入,用 DS 信号提供采样时钟,抑制 WR skew
DSData StrobeeMMC 输出的数据选通信号,控制器据此在数据眼中心采样
OCROperation Conditions Register卡上电后的能力与电压登记表,CMD1 用它做初始化握手(★)
CMD1Send Operating Conditions RegeMMC 专属初始化命令,循环发送直到 OCR 的 CCS 位为 1
CID / CSD / CSRCard IDentification / Card Specific Data / Card Status三张 128bit 寄存器;CSR 在 CMD13 查询
RCARelative Card AddressCMD3 分配的主机侧短地址,4 字节高 16 位为 RCA
EXT_CSDExtended Card Specific Data512 字节扩展寄存器,速率模式、Cache、分区配置、寿命全在这里(★)
CMD6SWITCH写 EXT_CSD 的专用命令,参数带 R1b,切模式与开关 Cache 全用它
ADTCAddressed Data Transfer Command带地址的数据传输类命令,如 CMD17/18/25/35
DCMDDevice Control Command设备自终止类命令,如 CMD6,地址固定为 0
rpmb 分区RePlay Protected Memory Block partitioneMMC 硬件分区之一,经 /dev/mmcblk0rpmb 或 TEE 访问
SDHCISD Host Controller InterfaceSD/MMC 控制器的标准寄存器与编程模型规范
CMDQCommand Queuing把读写命令放进硬件队列,控制器批量取指执行,见 9.5 节
CQECommand Queue Engine承载 CMDQ 的硬件引擎,寄存器组叫 CQEQ
fio-Linux 侧性能压测工具,用于建立基线与回归对比
GPT / MBRGUID Partition Table / Master Boot Record分区表格式;eMMC 量产推荐 GPT
PARTUUIDPartition UUIDGPT 分区唯一标识,设备树里用它而非 /dev/mmcblk0p2
Boot0 / Boot1Hardware partition 0/1eMMC 出厂即有的两个启动分区,不计入标称容量
BKOPSBackground Operations让 Cache 开关在安全时机自动生效的机制
WR skewWrite 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.19mmc_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.1DTS 顶层 mmc 节点属性清理,废弃一批老写法老设备树需迁移,见 5.4 节
6.6mmc-regulator 增加 regulator-allow-set-load电源门控方案需检查 13.3 节列出的新属性
版本相关的三条硬规则

① 结构体字段名随版本变化,mmc_host 在 4.x 与 6.x 之间成员差别很大, 直接照抄老代码的字段初始化会编译失败或语义错位;
② 本文所有内核配置项与 DT 属性都标注了「最低版本」,低于该版本时请先确认属性是否被解析;
③ 线上设备的内核版本一旦定了,就不要在补丁里引用新版本独有的 API,需要兼容时用 #if LINUX_版本号 Rev 1.4 >= 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 CMDeMMC 固定 1 bit CMD,数据线 1/4/8 bit 三档
供电VCC 3.3 V / VCCQ 1.8~3.3 V / VDDIO 1.8~3.3 VVCCQ 需与 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) + DAT[7: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/mmcblk0boot0Boot0 硬件分区绕过分区表Bootloader 存放区(主)
/dev/mmcblk0boot1Boot1 硬件分区绕过分区表Bootloader 备份区(A/B 方案)
/dev/mmcblk0rpmbRPMB 硬件分区需先 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 bitCLK 上升沿26 MHz约 25 MB/s(8 bit)Linux 里对应 MMC_TIMING_LEGACY / HIGH_SPEED
HS2004.51 bit(单线)CLK 上升沿 + 训练采样点200 MHz约 25 MB/s(时序余量大)需要 CMD21 训练;对应 MMC_TIMING_HS200
HS4005.02 bit(双 DDR)CLK 上下沿 + 训练采样点200 MHz约 200 MB/s需要 DS 或控制器采样点训练;MMC_TIMING_HS400
HS400EX5.12 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] 最高位是否为命令索引
R1bR1 + busy 信号(DAT0 拉低)CMD6 SWITCH、CMD1 电压切换后必须轮询等待 busy 结束;切模式可长达 2 s
R248 bit,含 CIDCMD2 ALL_SEND_CID、CMD3 分配 RCA读 128 bit CID
R332 bit,含 R1 状态内容CMD6 的 R1b 变体、SD 专有命令与 R1 相同的响应内容
R6 / R7R1 + 相对地址 / 电压窗口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() 填充。 驱动开发者最常打交道的字段如下。

字节字段名含义驱动里的用途
7REVEXT_CSD 版本判定支持 4.5 / 5.0 / 5.1 特性;mmc_ext_csd.rev
183SEC_FEATURE_SUPPORT安全特性集(AFU / RPMB)判断 RPMB 与认证特性是否可用
192DEVICE_TYPE卡类型:HS200 / HS400 / 5.1card->ext_csd.hs200 与 .hs400 的判定依据
196HS_200_TIMINGHS200 时序支持位与 DEVICE_TYPE 一起确认能否切 HS200
212DSTR_SUPPORTDS 信号支持位判定能否启用 HS400EX
215HS400_TUNING_BLOCK_THS400 训练块类型决定 CMD21 训练数据的解析方式
224HESWBoot / RPMB 硬件分区使能EXT_CSD_PARTITION_CONFIG,控制是否上报 boot/rpmb 节点
225HS_200_TUNING_BLOCK训练块地址训练块就在用户区里,需先由 U-Boot 预置
228 / 239CACHE_SIZE / CACHE_SIZE_E1Cache 大小(两处拼成完整值)mmc_cache_size()
234CACHE_UNIT_SIZECache 写粒度决定 flush / 失效的最小单位
241BKOPS后台操作支持与自陷使能控制 BKOPS_EN / BKOPS_DIS 自动生效时机
246CACHE_CTRLCache 使能与失效CACHE_ENABLE 位就是 cache_enable() 写的
249PARTITION_TABLE_SET分区表状态产线烧录后需确认都为 1(完成标记)
268 / 269PRE_EOL_LIFE_TIME_EST预估寿命 A / B / C健康度上报的数据来源,见 18.4 节
21/22/23C_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 里。

信号组引脚电气角色驱动侧关联设计红线
CLK1 根主控输出给卡的时钟,HS400 下 200 MHzmmc_set_ios 里配置输出时钟50 欧姆阻抗;长度匹配 DAT 差分对在 5 mm 以内
CMD1 根双向命令与响应,串行命令索引与 CRC 校验必须上拉(卡侧或主控侧二选一),禁止浮空
DAT[7:0]8 根HS200 单线 / HS400 双线 DDR总线宽度切换与采样点训练HS400 必须按 2 组 4 根差分对布线,不要单端走 8 根
DS1 根卡输出数据选通(HS400EX)DT 属性 mmc-hs400-enhanced-strobe需从 SoC 引到卡 DS 球;走线与 CLK 平行等长
RST_n1 根低有效复位,主控输出reset-gpios 与 mmc_reset()不能悬空;必须有确定的上电默认电平
CMD_DS1 根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 / DAT[7:0]DS / RST_neMMC 器件FTL + CacheNAND 阵列PMIC / LDOVCC / VCCQ / VDDIOGPIO 门控VCC_EN 开关控制低功耗CLK / CMD / DAT[7: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 明确规定。时序不满足的典型表现是 「冷启动偶尔不识别,热重启正常」——因为热重启时电源已经稳定,反而绕过了问题。

电源域典型电压作用上电时序要求关机时序要求
VCC3.0 ~ 3.6 VNAND 阵列、FTL 内核、PLL最先;稳定后至少等 300 ms 且 CLK 可提供才能发 CMD1最后断;需等器件完成 Cache 落盘(见 10.3 节)
VCCQ1.7 ~ 2.0 / 2.7 ~ 3.6 V接口域(CMD / DAT / CLK)与 VCC 无强制先后,但必须先于或同时于第一次命令有效与 VCC 同步或先断
VDDIO1.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_CSD[246]),写命令的 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 根差分对(DAT[3:0] 一组、DAT[7: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 浮空、上拉缺失、信号完整性
DAT[7: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.15.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.cmmcblk_submit_bio()、mmcblk_data_cmd()bio 如何转成 ADTC 命令;flush 与 discard 如何映射
块设备 ioctldrivers/mmc/block/mmc_ioctl.cmmc_blk_ioctl()、mmc_erase()、mmc_ffs()CMD38 erase 是无响应命令,靠状态查询等完成
核心drivers/mmc/core/core.cmmc_send_init_ocr()、mmc_set_ios()OCR 循环、IO 设置如何下发给 host
核心drivers/mmc/core/mmc_ops.cmmc_erase()、mmc_set_erase_size()模式切换辅助函数怎么拼 CMD6 参数
核心drivers/mmc/core/mmc_ext_csd.cmmc_read_ext_csd()、mmc_select_timing()模式切换与卡状态判定的绝大部分逻辑在这里
核心drivers/mmc/core/mmc_card.cmmc_init_card()、mmc_manage_cache()整卡初始化顺序、Cache 开关时机
主机接口include/linux/mmc/card.hmmc_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.revEXT_CSD 版本版本判断错则用 5.1 才有的字段,切 5.0 器件时失败
mmc_card.ext_csd.hs4005.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」的判定

这是最常见的「驱动没起来」误判。实际上有五种情况都能产生这个现象,按可能性排序:

  1. 探测到的是 SD 卡不是 eMMC:mmc0: new SD card at addr 0x1 说明 SoC 的 SD 控制器被先枚举了。查 ls /sys/bus/mmc/devices/,确认 eMMC 挂在哪个 host 上。
  2. 硬件分区节点没使能:只有 mmcblk0 没有 mmcblk0boot0, 说明 EXT_CSD[224] HESW 里 boot 分区使能位没置上(见 2.2 与 8.4 节)。
  3. EXT_CSD 读取失败:dmesg 会有 error -84 之类,容量识别为 0 或异常值。
  4. 块设备注册被 max_part 或 partscan 影响: 若 EXT_CSD 容量为 0,mmc_blk_alloc_req() 可能只注册出空设备。
  5. 真的没注册: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 / fsyncFlush 之前的等待加写命令 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() 复位 hostdmesg 出现 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_CAPABILITIES0x40能力:支持的电压、总线宽度、模式、时钟频率只读;它决定了这个控制器能做什么,与 DT 的 cap-* 属性取交集
SDHCI_CAPABILITIES_10x44能力扩展:HS200 / HS400 / CQE 支持5.0 与 5.1 器件要用 HS400 就必须看这里
SDHCI_ADMA_CAP0x58ADMA(增强 DMA)描述符能力决定走 ADMA 还是 SDMA;不支持时性能降一档
SDHCI_PRESENT_STATE0x24当前状态:卡在位、busy、数据线状态调试 HS400 训练失败必看:DAT 线状态不对说明时序没对齐
SDHCI_CLOCK_CONTROL0x2C时钟使能、频率分频、稳态与突发时钟SDHCI_CLOCK_INT_STABLE 位要等置位
SDHCI_SOFTWARE_RESET0x2F命令线、数据线、全部复位分级复位顺序:先 CMD,再 DAT,再 ALL
SDHCI_ARGUMENT 与 CMD0x08 与 0x0C命令参数与命令寄存器写 ARGUMENT 后必须立刻写 CMD,否则会被清零
SDHCI_TRANSFER0x0E传输方式:单块或多块、读写、DMA 使能多块加写加使能 DMA 三个位必须同时对
SDHCI_BLOCK_SIZE 与 BLOCK_COUNT0x10 与 0x0F块大小(12 bit,0 表示 4096)与块数块大小寄存器写 0 表示 4096,这是 4K 扇区必须注意的地方
SDHCI_INT_STATUS 与 INT_ENABLE0x30 与 0x34中断标志与使能命令完成、数据完成、busy 完成要分别使能
SDHCI_SDHC_CONTROL0x2D1.8 V 电压切换控制与 CMD11 配合,控制器侧完成 IO 电平切换
SDHCI_CMD_ARGS 与 XFER_PARAM0x28 与 0x0D64 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.cPCIex86、AMD、Intel SoCPCI 枚举与电源管理
sdhci-pltfrm.cplatform(含 DT)绝大多数 ARM SoCDT 属性:bus-width、cap-*、max-frequency、quirks
sdhci-of-dwcmsh.cplatform 加 OFRockchip、ZynqMP、紫光展锐部分DSR(驱动强度与采样点)、频率表、quirks
sdhci-of-arasan.cplatform 加 OFArasan 系列 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

全志平台的 mmc-sunxi.c 是自研控制器,不走 SDHCI 框架。 它有两个特别容易踩的点:相位配置与电源门控的时序要求。

项说明注意点
相位调整mmc-sunxi 用 DTB 的相位参数调整采样点换料或换温度后偶发错误,需要重新扫相位,见第 7 章
时钟源切换有些型号在 100 MHz 以上需要切到 PLL 时钟不切则高频下大幅误码
电压域门控支持的型号有独立的 VCCQ 门控门控后必须先恢复电压再释放时钟
驱动私有 quirkcontroller.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 基线,升级内核后回归对比

本文档为 《原理方案设计》 系列文档之一,与《eMCP_电路设计指南》《eMMC 软件设计规范》 《Linux_SPI_NAND_驱动开发指南》配套使用。文中涉及的寄存器偏移、命令常量与内核接口 以 Linux mainline 为准;厂商私有部分请以对应 BSP 的手册与源码为准。 代码片段仅用于说明设计意图,实际移植时需按目标内核版本调整 API。

LINUX-EMMC-DRV-001 · V1.0 · 嵌入式文件系统系列 · 共 20 章 + 附录 A~F · 27 图

扫码打开本页
微信扫码 · 展会可扫

再分享给同事

同事可直接打开这份资料《Linux_eMMC_驱动开发指南》,在线阅读;完整章节请下载芯参谋。