2. DMA传输方向:内存到外设、外设到内存、内存到内存,以及dma_data_direction枚举详解
在DMA(直接内存访问)工程实战中,传输方向是决定缓存同步策略、API调用方式和硬件配置的核心要素。Linux内核通过枚举类型 dma_data_direction 统一抽象了三种基本传输方向,理解其语义是正确使用 dma_map_single 系列函数的前提。
2.1 三种DMA传输方向模型
根据数据流在系统总线上的移动路径,DMA传输可划分为以下三种经典模型:
| 传输方向 | 数据流描述 | 典型场景 | 缓存同步要求 |
|---|---|---|---|
| 内存 → 外设 | CPU/内存写入数据,DMA搬运至外设(如网卡、显卡) | 网络数据发送、显示帧缓冲更新、音频播放 | 写缓存回刷(Clean/Flush) |
| 外设 → 内存 | 外设产生数据,DMA搬运至内存供CPU读取 | 网络数据接收、摄像头图像采集、磁盘读取 | 缓存失效(Invalidate) |
| 内存 → 内存 | 数据在内存不同区域之间搬运,不经过CPU | 大块数据拷贝、帧缓冲复制、加密/压缩加速 | 双向同步(回刷+失效) |
实战提示: 内存到内存的DMA传输在嵌入式异构系统(如DSP+ARM、FPGA+SoC)中非常常见,但Linux标准DMA API对其支持有限,通常需要借助DMA引擎框架(dmaengine)或硬件特定的描述符链实现。
2.2 dma_data_direction 枚举详解
Linux内核在 <linux/dma-mapping.h> 中定义了 dma_data_direction 枚举,用于向DMA API明确告知传输方向,从而触发正确的缓存同步操作。该枚举包含四个成员:
enum dma_data_direction {
DMA_BIDIRECTIONAL = 0, /* 双向:内存与外设之间双向传输 */
DMA_TO_DEVICE = 1, /* 方向:内存 → 外设(CPU写,设备读) */
DMA_FROM_DEVICE = 2, /* 方向:外设 → 内存(设备写,CPU读) */
DMA_NONE = 3, /* 无方向:调试或未初始化状态 */
};
2.3 枚举值与缓存同步策略映射
每个枚举值对应一组特定的缓存同步操作,这是驱动开发者必须牢记的映射关系:
| 枚举值 | 数值 | 缓存同步操作 | dma_map_single 行为 | dma_unmap_single 行为 |
|---|---|---|---|---|
DMA_TO_DEVICE |
1 | 写回(Clean) | 将CPU缓存中脏数据写回内存,确保设备可见 | 通常无需操作(设备已读取) |
DMA_FROM_DEVICE |
2 | 失效(Invalidate) | 使CPU缓存行失效,强制从内存重新读取 | 使CPU缓存行失效(防止读到陈旧数据) |
DMA_BIDIRECTIONAL |
0 | 写回 + 失效 | 先写回所有脏数据,再使缓存失效 | 先写回所有脏数据,再使缓存失效 |
DMA_NONE |
3 | 无操作 | 仅做合法性检查,不执行缓存同步 | 仅做合法性检查,不执行缓存同步 |
常见错误: 初学者常将
DMA_FROM_DEVICE 用于发送方向,导致设备读取到陈旧数据(缓存未失效)。反之,将 DMA_TO_DEVICE 用于接收方向,可能导致CPU读取到设备尚未写入的脏缓存数据。务必根据数据最终消费者确定方向。
2.4 方向选择实战决策树
在实际驱动开发中,可以通过以下决策流程快速确定正确的 dma_data_direction:
- 判断数据最终流向: 数据是从内存到外设,还是从外设到内存?
- 判断CPU是否参与: CPU在DMA传输前是否写入了数据?CPU在DMA传输后是否要读取数据?
- 选择枚举值:
- CPU写 → 设备读 →
DMA_TO_DEVICE - 设备写 → CPU读 →
DMA_FROM_DEVICE - CPU和设备都可能读写 →
DMA_BIDIRECTIONAL(性能开销最大,慎用)
- CPU写 → 设备读 →
2.5 方向误用的典型后果
在嵌入式Linux驱动开发中,方向选择错误会导致难以调试的数据一致性问题。以下表格总结了不同误用场景的后果:
| 实际方向 | 错误枚举值 | 后果描述 | 调试现象 |
|---|---|---|---|
| 内存→外设 | DMA_FROM_DEVICE |
CPU缓存未写回,设备读取到陈旧数据 | 网卡发送错误数据包、显示花屏 |
| 外设→内存 | DMA_TO_DEVICE |
CPU缓存未失效,CPU读取到缓存中的陈旧数据 | 网络接收数据重复、图像采集出现残影 |
| 双向传输 | DMA_TO_DEVICE |
设备写入的数据在CPU缓存中不可见 | 共享内存通信数据丢失 |
| 单向传输 | DMA_BIDIRECTIONAL |
额外的缓存同步开销,性能下降30%~50% | DMA传输速率远低于预期 |
工程最佳实践:
- 优先使用
DMA_TO_DEVICE和DMA_FROM_DEVICE,避免使用DMA_BIDIRECTIONAL除非确实需要双向访问。 - 在
dma_map_single和dma_unmap_single中使用相同的dma_data_direction值。 - 对于内存到内存的DMA传输,通常使用
DMA_BIDIRECTIONAL或通过dmaengine框架的DMA_MEM_TO_MEM标志处理。
2.6 方向与硬件DMA引擎的对应关系
不同SoC的DMA控制器硬件寄存器中,传输方向通常通过以下方式配置:
| 硬件DMA控制器 | 方向配置方式 | 与dma_data_direction映射 |
|---|---|---|
| PL330 (ARM) | 源/目标类型寄存器(SRC/DST) | SRC=内存, DST=外设 → DMA_TO_DEVICE |
| EDMA (TI) | 传输参数RAM (PaRAM) 中的OPT字段 | SYNC=1, SRC=外设 → DMA_FROM_DEVICE |
| DMA-Engine (通用) | struct dma_slave_config 的 direction 字段 | 直接使用 dma_data_direction 枚举值 |
| Intel IOMMU | DMA映射页表属性 | 通过 dma_map_page 的 dir 参数传递 |