3. DMA 传输类型详解:内存到内存、内存到外设、外设到内存、外设到外设
DMA(Direct Memory Access)控制器作为系统总线上的主设备,其核心价值在于无需 CPU 干预即可完成数据搬运。根据数据源与目标的地址空间归属,DMA 传输可划分为四种基本模式。理解每种模式的硬件路径、Cache 一致性影响以及适用场景,是进行嵌入式系统性能优化与驱动调试的基础。
3.1 传输模式分类总览
下表从数据流向、典型硬件路径、Cache 一致性风险以及常见应用四个维度,对四种 DMA 传输模式进行对比:
| 传输模式 | 数据流向 | 典型路径 | Cache 一致性风险 | 典型应用 |
|---|---|---|---|---|
| 内存到内存 (M2M) | DDR → DDR | 源缓冲区 → DMA → 目标缓冲区 | 高(CPU 可能同时访问源/目标) | 帧缓冲拷贝、数据搬移、内存校验 |
| 内存到外设 (M2P) | DDR → 外设 FIFO | 内存 → DMA → 外设寄存器/FIFO | 中(CPU 写源缓冲区后需 clean) | LCD 显示、音频播放、网络发包 |
| 外设到内存 (P2M) | 外设 FIFO → DDR | 外设寄存器/FIFO → DMA → 内存 | 中(CPU 读目标缓冲区前需 invalidate) | 摄像头采集、音频录音、网络收包 |
| 外设到外设 (P2P) | 外设 FIFO → 外设 FIFO | 源外设 → DMA → 目标外设 | 低(不经过内存,无 Cache 问题) | 数字信号处理、协议转换、数据重定向 |
dmaengine_prep_dma_memcpy() 接口;M2P 和 P2M 使用 dmaengine_prep_slave_sg() 接口;P2P 模式则依赖硬件是否支持 DMA_DEV_TO_DEV 传输类型,且通常需要专用的 DMA 请求线连接。
3.2 内存到内存 (M2M) 传输模式详解
M2M 模式是最纯粹的 DMA 传输形式,数据源和目标均在系统主存(DDR SDRAM)中。DMA 控制器从源地址读取数据,写入目标地址,整个过程不涉及外设寄存器。
3.2.1 硬件路径与 Cache 一致性挑战
M2M 传输的最大陷阱在于 Cache 一致性问题。CPU 通过 Cache 访问内存,而 DMA 直接访问物理内存(总线主控)。当 CPU 修改了源缓冲区但数据仍停留在 Cache 中(写回策略),DMA 读取到的将是过时的物理内存数据。
dma_map_single(..., DMA_TO_DEVICE) 或手动 clean Cache,DMA 可能搬运“脏”数据。反之,传输完成后若未 invalidate 目标缓冲区的 Cache,CPU 读到的可能是旧数据。
3.2.2 驱动实现要点
在 Linux DMA Engine 框架下,M2M 传输的典型代码路径如下:
/* 申请 DMA 通道 */
struct dma_chan *chan = dma_request_chan(dev, "memcpy");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 准备 DMA 传输描述符 */
struct dma_async_tx_descriptor *tx;
tx = dmaengine_prep_dma_memcpy(chan, dst_phys, src_phys, size, DMA_CTRL_ACK);
if (!tx) {
dma_release_channel(chan);
return -ENOMEM;
}
/* 提交并等待传输完成 */
tx->callback = my_callback;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
/* 等待完成(同步模式) */
wait_for_completion(&done);
dma_get_slave_caps() 查询 DMA 控制器支持的地址对齐要求和最大传输长度,避免因对齐问题导致性能降级。
3.3 内存到外设 (M2P) 传输模式详解
M2P 模式是嵌入式系统中应用最广泛的 DMA 传输类型。数据从内存缓冲区流向外设的 FIFO 或寄存器,典型场景包括 LCD 显示控制器从帧缓冲区读取像素数据、I2S 控制器从音频缓冲区读取采样数据。
3.3.1 硬件路径与流控机制
M2P 传输通常由外设的 DMA 请求信号触发。外设 FIFO 空时,向 DMA 控制器发送请求,DMA 从内存读取数据写入 FIFO。这种“按需传输”机制称为 流控(Flow Control)。
3.3.2 Cache 一致性处理策略
对于 M2P 传输,CPU 是数据生产者,外设是消费者。CPU 写入内存缓冲区后,必须确保数据被写回到物理内存,DMA 才能读到最新数据。
- 推荐做法: 使用
dma_map_single(dev, buf, size, DMA_TO_DEVICE),该 API 内部执行 clean Cache 操作。 - 手动做法: 在 ARM 上调用
__cpuc_flush_dcache_area(buf, size)或使用dma_sync_single_for_device()。 - 性能陷阱: 频繁的 Cache clean 操作会降低性能。对于周期性传输(如音频),建议使用 DMA 环形缓冲区,并采用双缓冲机制,在后台缓冲填充时 clean 前台缓冲。
3.4 外设到内存 (P2M) 传输模式详解
P2M 是 M2P 的逆过程。外设产生数据(如摄像头传感器输出像素、ADC 采样值),DMA 将数据从外设 FIFO 搬运到内存缓冲区。CPU 作为数据消费者,需要读取内存中的数据进行处理。
3.4.1 传输触发与数据一致性
P2M 传输同样由外设的 DMA 请求触发。外设 FIFO 达到预设阈值(如半满)时,向 DMA 发送请求,DMA 将数据读入内存。
3.4.2 驱动中的 Cache 操作顺序
正确的 P2M 驱动流程如下:
- 传输前: invalidate 目标缓冲区(清除 Cache 中的旧数据,防止 DMA 写入时 Cache 与内存不一致)。
- 提交传输: 配置 DMA 描述符并启动。
- 传输完成: 再次 invalidate 目标缓冲区(确保 CPU 看到 DMA 写入的新数据)。
/* 传输前:invalidate 目标缓冲区 */
dma_sync_single_for_device(dev, buf_phys, size, DMA_FROM_DEVICE);
/* 提交 DMA 传输 */
desc = dmaengine_prep_slave_sg(chan, &sgt, 1, DMA_DEV_TO_MEM, flags);
dmaengine_submit(desc);
dma_async_issue_pending(chan);
/* 等待传输完成 */
wait_for_completion(&done);
/* 传输后:再次 invalidate,确保 CPU 可见 */
dma_sync_single_for_cpu(dev, buf_phys, size, DMA_FROM_DEVICE);
3.5 外设到外设 (P2P) 传输模式详解
P2P 模式是最少见的 DMA 传输类型,数据不经过主存,直接从源外设 FIFO 搬运到目标外设 FIFO。这种模式对 Cache 一致性无影响,但硬件要求极高。
3.5.1 硬件约束与适用场景
P2P 传输需要满足以下条件:
- 源外设和目标外设必须连接到同一个 DMA 控制器的不同请求线。
- DMA 控制器必须支持 双请求线同时工作,且能处理两个外设的流控信号。
- 两个外设的数据宽度和传输速率必须匹配,否则需要 FIFO 或缓冲区进行速率适配。
3.5.2 驱动配置示例
在 Linux 中,P2P 传输通常需要配置两个 slave 通道(一个用于源,一个用于目标),并使用 DMA_DEV_TO_DEV 传输方向:
struct dma_slave_config src_config = {
.direction = DMA_DEV_TO_DEV,
.src_addr = src_fifo_phys,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 4,
};
struct dma_slave_config dst_config = {
.direction = DMA_DEV_TO_DEV,
.dst_addr = dst_fifo_phys,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_maxburst = 4,
};
dmaengine_slave_config(chan_src, &src_config);
dmaengine_slave_config(chan_dst, &dst_config);
3.6 四种模式的选择决策树
在实际工程中,选择哪种 DMA 传输模式取决于数据路径和硬件能力。以下决策树可帮助快速判断:
- 数据是否经过内存?
- 是 → 进入步骤 2。
- 否 → 使用 P2P 模式(需硬件支持)。
- 源和目标是否都在内存?
- 是 → 使用 M2M 模式(如 memcpy 加速)。
- 否 → 进入步骤 3。
- 数据流向?
- 内存 → 外设 → 使用 M2P 模式(如显示、音频输出)。
- 外设 → 内存 → 使用 P2M 模式(如摄像头、音频输入)。
掌握这四种传输模式的硬件路径、Cache 一致性要求和驱动接口差异,是后续章节中调试 DMA 传输错误和优化系统性能的基础。下一章将深入 DMA 描述符链与环形缓冲区的工程实现。