RK eMMC命令与数据传输机制:CMD/ADMA描述符链、DMA传输模式、PIO模式、命令超时处理
好,咱们进入第四章。这一章讲的是eMMC通信的核心——命令怎么发,数据怎么传。说白了,就是Host和eMMC设备之间怎么“对话”。很多驱动问题,比如卡死在某个命令上、数据传输到一半断了、或者速度上不去,根子都在这里。
我个人习惯,拿到一个新平台,先把命令传输和数据传输这两条路径理清楚。路径通了,剩下的就是调参和修修补补。
4.1 命令传输:从Host到Device的“指令通道”
eMMC协议里,命令是通过CMD线发送的。Host发一个命令,Device回一个响应。看起来简单,但实际工程里,命令超时是最常见的故障之一。
命令传输的基本流程:
- Host构造命令结构体(包含命令索引、参数、响应类型)
- 写入命令寄存器,触发发送
- 等待设备响应(硬件自动接收,或中断通知)
- 检查响应状态,判断命令是否成功
我曾经遇到过一个案子,板子启动时总是在初始化阶段卡死。抓日志发现是发送CMD1(查询电压范围)后没收到响应。查了一圈,发现是命令超时时间设得太短,设备上电慢,还没来得及回。嗯,这里要注意:命令超时时间不能一刀切,不同阶段、不同设备,超时值要区别对待。
4.1.1 命令超时处理机制
为什么会超时?无非三种情况:
- 设备没响应——设备挂了、没上电、或者CMD线断了
- 响应被噪声干扰——信号完整性差,CRC校验失败
- Host没等到中断——中断配置错误,或者DMA没完成
我建议在驱动里实现一个分级超时策略:
| 命令类型 | 典型超时值 | 说明 |
|---|---|---|
| CMD0(复位) | 100ms | 设备复位需要时间 |
| CMD1(初始化) | 1s | 设备上电初始化可能较慢 |
| CMD6(切换模式) | 500ms | 模式切换涉及内部操作 |
| CMD18(多块读) | 根据块数动态计算 | 数据量大时超时要放宽 |
避坑指南:我曾经在调试一个eMMC读写异常时,发现CMD6(切换高速模式)总是超时。后来用示波器抓CMD线,发现设备确实回了响应,但Host的中断没触发。原因是中断控制器里该中断被屏蔽了。所以,超时不一定就是设备的问题,先检查中断路径。
4.2 数据传输模式:DMA vs PIO
数据传输是eMMC性能的关键。RK平台支持两种模式:DMA(直接内存访问)和PIO(程序控制输入输出)。你想想看,PIO模式下CPU要亲自搬数据,DMA模式下CPU只需要告诉硬件“你去搬”,然后就可以干别的了。
4.2.1 PIO模式
PIO模式,说白了就是CPU一个一个字节地读写数据寄存器。优点是实现简单,不需要复杂的DMA描述符链。缺点是CPU占用率高,速度上不去。
- 适用于:小数据量传输(比如读取设备CID、CSD寄存器)
- 缺点:大数据量时CPU被拖死,系统响应变慢
- 我建议:只在初始化阶段或调试时用PIO,正常读写用DMA
4.2.2 DMA传输模式
DMA模式是主流。RK平台的DMA控制器支持ADMA(高级DMA),它使用描述符链来管理数据传输。描述符链是什么?就是一个链表,每个节点描述一段内存的地址、长度和下一个节点的指针。
个人经验:我在项目中遇到过DMA传输到一半卡死的情况。查到最后发现是描述符链的最后一个节点没有设置“结束标志”。硬件不知道传输结束,就一直等。所以,描述符链的结尾一定要正确标记。
4.3 ADMA描述符链详解
ADMA描述符链是RK eMMC控制器实现高效数据传输的核心。每个描述符是一个16字节的结构体,包含:
| 偏移 | 字段 | 说明 |
|---|---|---|
| 0x00 | 属性(Valid/End/Int/Act) | 控制位:有效、结束、中断、操作类型 |
| 0x04 | 数据长度 | 本次传输的字节数 |
| 0x08 | 数据地址 | 内存缓冲区的物理地址 |
| 0x0C | 下一个描述符地址 | 指向链中下一个描述符 |
描述符链的工作流程:
- Host在内存中构建描述符链
- 将链首地址写入控制器的ADMA寄存器
- 启动DMA传输
- 硬件自动遍历描述符链,完成所有数据传输
- 遇到End标志或错误时停止,触发中断
关键点:描述符链的地址必须是物理地址,不能是虚拟地址。因为DMA控制器直接访问内存,不经过MMU。很多新手在这里翻车,传了个虚拟地址进去,DMA直接访问了错误的内存区域。
4.4 核心逻辑流程图
下面这张图展示了从命令发送到数据传输完成的完整流程。我把它画出来,方便你对照代码理解。
4.5 实际调试中的常见问题
最后,我总结几个我在RK平台上实际踩过的坑:
- 描述符链地址不对齐:ADMA要求描述符链首地址按32字节对齐。我遇到过因为结构体成员对齐问题,导致地址没对齐,DMA直接报错。
- Cache一致性问题:DMA传输的数据在内存中,CPU可能通过Cache访问同一块内存。如果不做Cache刷新或使用DMA一致性内存,数据会乱掉。我建议在分配DMA缓冲区时使用
dma_alloc_coherent。 - 中断丢失:如果中断处理函数里没有清中断标志位,或者中断优先级太低被其他中断淹没了,会导致传输完成但Host不知道。我曾经在调试时发现中断状态寄存器里已经置位了,但CPU没进中断处理函数——查到最后是中断控制器里该中断被屏蔽了。
调试小技巧:当怀疑DMA传输有问题时,先切到PIO模式试试。如果PIO模式能正常工作,那问题大概率出在DMA描述符链或DMA控制器配置上。这个排除法我屡试不爽。