第2章 RK eMMC驱动核心数据结构深度解析
做RK平台存储驱动,绕不开几个核心数据结构。我刚开始接触这部分代码时,也被这些结构体绕得晕头转向。后来调了几个bug,才真正理解它们的设计意图。
说白了,mmc_host、mmc_card、mmc_request 这三兄弟,就是eMMC驱动的骨架。再加上RK私有的 rk_mmc_host,构成了我们日常打交道的全部家当。
2.1 mmc_host:主机控制器的抽象
mmc_host 代表一个MMC/SD主机控制器。在RK平台上,每个eMMC控制器对应一个 mmc_host 实例。你可以把它理解成「硬件控制器的软件代言人」。
核心字段速览:
struct device *parent— 父设备,通常是平台设备int index— 主机编号,RK3588上通常eMMC是0,SD卡是1struct mmc_host_ops *ops— 操作函数集,这是驱动的心脏unsigned int f_min / f_max— 时钟频率范围unsigned int caps / caps2— 能力标志位struct mmc_card *card— 当前连接的卡/设备struct mmc_request *mrq— 当前正在处理的请求
我习惯把 mmc_host 比作「插座」。它提供电力(时钟)、协议(命令通道)、数据通道。至于插上去的是什么设备,由 mmc_card 来描述。
// 驱动中获取 mmc_host 的典型方式
struct mmc_host *host = mmc_alloc_host(sizeof(struct rk_mmc_host), &pdev->dev);
if (!host) {
dev_err(&pdev->dev, "failed to alloc mmc host\n");
return -ENOMEM;
}
host->ops = &rk_mmc_ops; // 绑定RK平台的操作函数集
我的经验:调试时经常要打印 host->caps 确认能力位是否设置正确。RK3588的eMMC控制器如果不设置 MMC_CAP2_HS400,死活跑不上HS400模式。我曾经在这上面浪费了一整天。
2.2 mmc_card:设备的描述符
mmc_card 描述一个具体的MMC设备。在eMMC场景下,它就是板载的存储芯片。这个结构体在设备识别阶段被填充,之后驱动就靠它来了解「对面是谁」。
| 字段 | 含义 | RK平台常见值 |
|---|---|---|
unsigned int rca |
相对卡地址 | 0x0001(eMMC固定) |
unsigned int type |
设备类型 | MMC_TYPE_MMC |
struct mmc_ext_csd ext_csd |
EXT_CSD寄存器内容 | 包含分区大小、时序参数等 |
unsigned int csd[16] |
CSD寄存器原始数据 | 用于解析设备容量 |
struct device dev |
内核设备对象 | — |
为什么eMMC的RCA固定是0x0001?因为eMMC是内置设备,不需要像SD卡那样动态分配地址。这个细节在调试时很有用——如果你看到RCA不是1,那说明识别流程出了问题。
// 从 card 中获取关键信息的示例
struct mmc_card *card = host->card;
if (card->ext_csd.rev < 5) {
dev_warn(mmc_dev(host), "eMMC version too old: %d\n", card->ext_csd.rev);
}
// 检查是否支持HS400
if (card->ext_csd.card_type & EXT_CSD_CARD_TYPE_HS400_1_8V) {
dev_info(mmc_dev(host), "eMMC supports HS400 @ 1.8V\n");
}
注意:千万不要在中断上下文中直接访问 card->ext_csd。EXT_CSD的读取需要发送命令,而命令发送可能睡眠。我曾经在RK3568上踩过这个坑,导致内核调度器报错。
2.3 mmc_request:一次完整的命令交互
mmc_request 封装了一次完整的MMC总线事务。它包含一个或多个命令,以及可选的数据传输。理解这个结构体,就理解了eMMC驱动的执行流程。
一个典型的读写请求包含:
- CMD(命令) — 告诉设备做什么
- 数据(可选) — 读或写的数据块
- STOP(停止命令) — 多块传输结束时使用
struct mmc_request {
struct mmc_command *cmd; // 主命令
struct mmc_command *sbc; // 设置块计数命令(CMD23)
struct mmc_command *stop; // 停止命令(CMD12)
struct mmc_data *data; // 数据传输描述
struct completion completion; // 完成同步
// ... 其他字段
};
我举个例子。读一个扇区时,请求的构建流程是这样的:
- 填充
sbc为 CMD23,设置要读的块数 - 填充
cmd为 CMD18(多块读)或 CMD17(单块读) - 填充
data,指定数据缓冲区、方向、块大小 - 调用
mmc_start_request()提交请求
调试技巧:我经常在 mmc_request 的完成回调里打印 cmd->error 和 data->error。如果 cmd->error 非0,说明命令阶段就失败了;如果 data->error 非0但 cmd->error 为0,那问题出在数据传输阶段。这个二分法帮我快速定位了不下10个bug。
2.4 rk_mmc_host:RK平台的私有扩展
内核的通用 mmc_host 只提供了标准接口。RK平台有大量私有逻辑——DMA配置、时钟门控、延时链调整、自动门控等。这些都需要一个私有结构体来承载。
struct rk_mmc_host {
struct mmc_host *mmc; // 指向通用mmc_host
struct device *dev; // 平台设备
void __iomem *regs; // 寄存器基地址
struct clk *clk; // 时钟句柄
struct clk *clk_sample;// 采样时钟(HS400关键)
int irq; // 中断号
unsigned int id; // 控制器ID
// DMA相关
struct dma_chan *dma_chan; // DMA通道
dma_addr_t dma_addr; // DMA地址
int dma_dir; // DMA方向
// 延时链配置
u32 delay_line; // 当前延时值
struct mmc_tuning tuning_data;// 调优数据
// 状态跟踪
unsigned long flags; // 运行时标志
spinlock_t lock; // 自旋锁
};
这个结构体通过 mmc_alloc_host 的额外空间分配,与 mmc_host 绑定在一起:
// 分配时,mmc_host + rk_mmc_host 是一块连续内存
struct mmc_host *mmc = mmc_alloc_host(sizeof(struct rk_mmc_host), dev);
struct rk_mmc_host *rk_host = mmc_priv(mmc); // 通过 mmc_priv 获取
为什么RK要自己搞一套?说白了,通用框架管不了硬件细节。比如RK3588的eMMC控制器有4条延时链,每条链的调节粒度是1/8个时钟周期。这些参数放哪?只能放私有结构体里。
核心设计思路:
mmc_host— 通用框架,与协议层交互rk_mmc_host— 硬件细节,与寄存器交互- 两者通过
mmc_priv()宏建立联系 - 驱动中频繁使用
container_of来回转换
2.5 四者关系全景图
光说理论不够直观。我画了一张关系图,帮你理清这四个结构体怎么配合工作:
2.6 实际调试中的使用技巧
说了这么多理论,来点实际的。我在调试RK3588 eMMC驱动时,总结了一套「三板斧」:
第一板斧:检查host初始化
如果eMMC完全不工作,先看 rk_mmc_host 的寄存器基地址对不对。我遇到过两次设备树里reg属性写错的情况,导致驱动访问了错误的地址空间。
第二板斧:验证card识别
在 mmc_add_card 之后打印 card->ext_csd 的关键字段。重点关注 card_type 和 rev。如果 card_type 没有HS400标志位,说明设备树或硬件不支持。
第三板斧:跟踪request生命周期
在 rk_mmc_request 函数入口和中断处理函数里加trace。我习惯用 trace_printk 记录每个请求的cmd opcode和data长度。这样能快速判断是命令超时还是数据CRC错误。
嗯,这四个结构体搞清楚了,RK eMMC驱动的基本骨架就搭起来了。后面章节我们会深入每个结构体的具体使用场景,包括怎么配置延时链、怎么处理DMA、怎么调试HS400模式。但今天先把地基打牢——数据结构不理解,后面的代码看了也是白看。