第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卡是1
  • struct 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;  // 完成同步
    // ... 其他字段
};

我举个例子。读一个扇区时,请求的构建流程是这样的:

  1. 填充 sbc 为 CMD23,设置要读的块数
  2. 填充 cmd 为 CMD18(多块读)或 CMD17(单块读)
  3. 填充 data,指定数据缓冲区、方向、块大小
  4. 调用 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 四者关系全景图

光说理论不够直观。我画了一张关系图,帮你理清这四个结构体怎么配合工作:

RK eMMC驱动核心数据结构关系图 mmc_host 主机控制器抽象 ops / caps / f_min/f_max card指针 / mrq指针 rk_mmc_host RK私有扩展 regs / clk / irq / dma delay_line / tuning_data mmc_priv() mmc_card 设备描述符 rca / type / csd ext_csd(HS400/时序) host->card mmc_request 命令事务封装 cmd / sbc / stop data / completion host->mrq 硬件控制器 RK eMMC Controller 寄存器组 DMA引擎 时钟/复位 数据FIFO 命令通道 延时链 中断控制器 writel/readl 操作寄存器 数据流说明: 1. 上层文件系统发起读写请求 → 生成 mmc_request 2. mmc_host 通过 ops 回调将请求传递给 rk_mmc_host 3. rk_mmc_host 操作硬件寄存器,启动DMA传输 4. 硬件完成后触发中断 → 回调 mmc_request 的完成函数 5. mmc_card 提供设备能力信息,决定传输参数(如时序模式) 6. 整个流程中,mmc_host 是调度中心,rk_mmc_host 是执行者

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模式。但今天先把地基打牢——数据结构不理解,后面的代码看了也是白看。