1. DMA 基础概念
1.1 DMA 工作原理
直接存储器访问(Direct Memory Access, DMA) 是一种允许硬件子系统(外设、存储器、加速器)直接读写系统内存,而无需 CPU 参与每个数据传输周期的技术。DMA 的核心思想是将数据搬运任务从 CPU 卸载给专用的 DMA 控制器(DMAC),从而释放 CPU 去执行更重要的计算与控制逻辑。
核心价值: 在高速外设(如网卡、NVMe SSD、GPU、摄像头)与内存之间,DMA 可将数据传输吞吐量提升 10~100 倍,同时将 CPU 占用率从接近 100% 降至接近 0%。
DMA 传输的基本工作流程包含以下 5 个步骤:
- CPU 配置 DMA 控制器: CPU 设置源地址、目标地址、传输长度、传输方向、触发源等参数到 DMA 控制器的寄存器中。
- DMA 控制器获取总线控制权: 当外设或软件触发 DMA 请求时,DMAC 向总线仲裁器申请总线控制权。
- DMAC 执行数据传输: 获得总线控制权后,DMAC 直接驱动地址总线和数据总线,在源和目标之间搬运数据,无需 CPU 介入。
- 传输完成中断: 当传输计数器归零时,DMAC 向 CPU 发送中断信号,通知传输完成。
- CPU 处理后续数据: CPU 在中断服务程序中处理已就绪的数据缓冲区。
1.2 DMA 与 CPU 的交互
DMA 并非完全取代 CPU,而是与 CPU 协同工作。两者在系统总线、缓存一致性、中断处理等方面存在复杂的交互关系。下表总结了 DMA 与 CPU 在关键维度上的分工与协作:
| 交互维度 | CPU 角色 | DMA 角色 | 关键机制 |
|---|---|---|---|
| 传输控制 | 初始化 DMA 描述符、启动传输 | 执行实际数据搬运 | 描述符环(Descriptor Ring) |
| 总线仲裁 | 释放总线控制权 | 请求并获取总线控制权 | 周期窃取(Cycle Stealing) |
| 缓存一致性 | 刷出/无效化缓存行 | 直接访问物理内存 | Cache Coherency Protocol |
| 传输完成通知 | 响应中断、处理数据 | 触发中断或更新状态位 | 中断 / Polling |
| 错误处理 | 解析错误状态、恢复传输 | 置位错误标志、中止传输 | DMA 错误中断 |
缓存一致性陷阱: 在 ARM/Linux 环境中,如果 CPU 缓存了 DMA 目标内存区域的数据,而 DMA 直接写入了物理内存,CPU 可能读到过时的缓存数据。驱动开发者必须使用
dma_map_single() / dma_unmap_single() 等 API 来保证一致性。
1.3 DMA 传输类型
根据数据传输的源和目标设备类型,DMA 传输可分为以下三种基本类型。理解这些类型对于正确选择 DMA 通道和配置传输参数至关重要。
1.3.1 内存到内存(Memory-to-Memory, M2M)
- 场景: 数据拷贝、内存整理、加密/解密加速、图像处理中的帧拷贝。
- 特点: 源和目标都是系统内存;不需要外设触发,通常由软件直接启动;传输速率受内存带宽限制。
- 典型硬件: 通用 DMAC(如 ARM PL080、DW DMAC)。
1.3.2 内存到外设(Memory-to-Device, M2D)
- 场景: 向 UART 发送数据、向 SPI DAC 写入波形、向 LCD 控制器刷新帧缓冲。
- 特点: 源是内存,目标是外设 FIFO/寄存器;通常由外设的“请求”信号触发;传输粒度通常为字或半字。
- 典型硬件: 专用 DMA 通道(如 STM32 DMA、Xilinx AXI DMA)。
1.3.3 外设到内存(Device-to-Memory, D2M)
- 场景: 从 UART 接收数据、从 ADC 采集样本、从摄像头捕获图像帧。
- 特点: 源是外设 FIFO/寄存器,目标是内存;外设数据就绪时触发 DMA 请求;需要处理数据溢出保护。
- 典型硬件: 与 M2D 共用同一 DMA 控制器,仅方向位不同。
| 传输类型 | 源 | 目标 | 触发方式 | 典型带宽 | Linux 驱动场景 |
|---|---|---|---|---|---|
| M2M | 内存 | 内存 | 软件触发 | 内存带宽的 60~90% | 加密加速、数据搬运 |
| M2D | 内存 | 外设 | 外设请求 | 外设接口速率 | SPI 发送、LCD 刷新 |
| D2M | 外设 | 内存 | 外设请求 | 外设接口速率 | UART 接收、音频采集 |
驱动开发常见误区: 初学者容易混淆 M2M 和 M2D 的触发方式。在 Linux 内核中,
struct dma_slave_config 的 direction 字段必须与硬件实际连接一致。如果外设是 SPI TX,方向应设为 DMA_MEM_TO_DEV;如果是 SPI RX,应设为 DMA_DEV_TO_MEM。错误的方向配置会导致 DMA 传输无法启动或数据错乱。