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)或硬件特定的描述符链实现。
DMA传输方向三种模型 内存 (RAM) 外设 (Device) DMA搬运 ① 内存→外设 外设 (Device) 内存 (RAM) DMA搬运 ② 外设→内存 内存区域A 内存区域B DMA搬运 ③ 内存→内存 说明: ① 内存→外设:CPU写数据后需执行缓存回刷(dma_map_single + DMA_TO_DEVICE) ② 外设→内存:CPU读数据前需执行缓存失效(dma_map_single + DMA_FROM_DEVICE)

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:

  1. 判断数据最终流向: 数据是从内存到外设,还是从外设到内存?
  2. 判断CPU是否参与: CPU在DMA传输前是否写入了数据?CPU在DMA传输后是否要读取数据?
  3. 选择枚举值:
    • CPU写 → 设备读 → DMA_TO_DEVICE
    • 设备写 → CPU读 → DMA_FROM_DEVICE
    • CPU和设备都可能读写 → DMA_BIDIRECTIONAL(性能开销最大,慎用)
dma_data_direction 选择决策流程 确定DMA传输方向 数据最终流向? 内存 → 外设 是 外设 → 内存 是 CPU写数据后传输? CPU读数据前? DMA_TO_DEVICE 是 DMA_FROM_DEVICE 是 DMA_BIDIRECTIONAL 双向读写

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 参数传递