3. DMA 传输类型详解:M2M、M2P、P2M 与 P2P
DMA 控制器支持的传输方向决定了数据流的路径,是驱动工程师配置传输描述符时首先要明确的参数。根据源地址与目标地址的物理属性,DMA 传输可分为四种基本类型:内存到内存(M2M)、内存到外设(M2P)、外设到内存(P2M)以及外设到外设(P2P)。每种类型在硬件信号、软件配置和性能特征上存在显著差异。
3.1 四种传输类型核心对比
| 传输类型 | 源地址域 | 目标地址域 | 典型场景 | 是否需要硬件握手 | 数据流方向 |
|---|---|---|---|---|---|
| M2M | 内存(DDR/SRAM) | 内存(DDR/SRAM) | 内存拷贝、帧缓冲搬运、数据搬移 | 否(软件触发) | 单向 |
| M2P | 内存(DDR/SRAM) | 外设 FIFO/寄存器 | LCD 显示刷新、音频播放、SPI 发送 | 是(外设请求) | 单向 |
| P2M | 外设 FIFO/寄存器 | 内存(DDR/SRAM) | 摄像头数据采集、UART 接收、ADC 采样 | 是(外设请求) | 单向 |
| P2P | 外设 FIFO/寄存器 | 外设 FIFO/寄存器 | 数字音频接口直通、硬件编解码桥接 | 是(双外设请求) | 单向或双向 |
工程要点:M2M 传输通常不需要外设硬件握手信号,由软件写触发寄存器启动;M2P 和 P2M 必须依赖外设发出的 DMA 请求信号(DMA_REQ)来驱动传输节拍;P2P 则需要两个外设的请求信号协同工作,对 DMA 控制器的通道仲裁和 FIFO 深度要求最高。
3.2 内存到内存(M2M)传输详解
M2M 是最简单的 DMA 传输模式,源和目标都在系统内存地址空间内。CPU 只需配置源地址、目标地址和传输长度,然后触发 DMA 通道启动。传输过程中不需要外设参与,DMA 控制器以内部总线时钟连续搬运数据。
- 触发方式:软件写通道使能寄存器,或写软件触发寄存器。
- 地址增长:源地址和目标地址均可独立配置为递增、递减或固定模式。
- 典型应用:Linux 内核中的
dma_memcpy、帧缓冲区的双缓冲切换、网络协议栈中的 skb 数据搬移。 - 性能瓶颈:总线带宽竞争。M2M 会同时占用读总线和写总线,在系统总线繁忙时可能影响 CPU 对其他内存的访问延迟。
// M2M 传输描述符配置示例(伪代码)
struct dma_slave_config config = {0};
config.src_addr = src_phys_addr; // 源内存物理地址
config.dst_addr = dst_phys_addr; // 目标内存物理地址
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.src_maxburst = 16; // 突发传输长度
config.dst_maxburst = 16;
dmaengine_slave_config(chan, &config);
desc = dmaengine_prep_dma_memcpy(chan, dst_phys_addr, src_phys_addr, size, flags);
dmaengine_submit(desc);
dma_async_issue_pending(chan);
3.3 内存到外设(M2P)与外设到内存(P2M)传输详解
M2P 和 P2M 是嵌入式系统中最常见的两种 DMA 传输类型,它们构成了 CPU 与 I/O 设备之间数据交换的高速通道。两者的核心区别在于数据流方向,但配置逻辑高度对称。
3.3.1 M2P 传输流程
- CPU 在内存中准备好待发送的数据缓冲区。
- 驱动配置 DMA 通道:源地址指向内存缓冲区,目标地址指向外设的数据寄存器地址(FIFO)。
- 外设(如 SPI、UART、I2S)准备好接收数据时,向 DMA 控制器发出硬件请求信号。
- DMA 控制器响应请求,从内存读取数据,写入外设寄存器。
- 传输完成后,DMA 控制器发出中断通知 CPU。
3.3.2 P2M 传输流程
- CPU 分配接收缓冲区并配置 DMA 通道:源地址指向外设数据寄存器,目标地址指向内存缓冲区。
- 外设(如 ADC、摄像头接口、UART)收到数据后,向 DMA 控制器发出请求。
- DMA 控制器从外设寄存器读取数据,写入内存缓冲区。
- 传输完成或缓冲区满时触发中断,CPU 处理数据。
常见陷阱:配置 M2P/P2M 时,外设地址必须使用物理地址(非虚拟地址),且地址宽度必须与外设 FIFO 宽度匹配。例如,32 位 FIFO 的外设应配置
addr_width = 4,否则会导致数据错位或丢失。此外,突发传输长度(maxburst)不应超过外设 FIFO 深度的一半,避免溢出。
3.4 外设到外设(P2P)传输详解
P2P 传输允许数据从一个外设直接流向另一个外设,无需经过内存中转。这种模式在低延迟、高吞吐的实时数据链路中具有不可替代的优势。
- 硬件要求:DMA 控制器必须支持双外设请求信号,且两个外设的时钟域和握手协议需要兼容。
- 配置要点:源和目标地址均为外设寄存器物理地址,通常地址增长模式设为固定(不递增)。
- 典型场景:数字麦克风数据直接送入 I2S 输出接口、硬件加密引擎与通信接口直连。
- 挑战:流控复杂。两个外设的数据速率可能不匹配,需要 DMA 控制器具备 FIFO 阈值管理和流量控制能力。
// P2P 传输描述符配置示例(伪代码)
struct dma_slave_config src_config = {0};
src_config.src_addr = i2s_rx_fifo_phys; // 源外设:I2S 接收 FIFO
src_config.dst_addr = codec_tx_fifo_phys; // 目标外设:音频编解码器发送 FIFO
src_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
src_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
src_config.src_maxburst = 8;
src_config.dst_maxburst = 8;
src_config.direction = DMA_DEV_TO_DEV; // 方向标记为外设到外设
dmaengine_slave_config(chan, &src_config);
desc = dmaengine_prep_slave_sg(chan, &sgl, 1, DMA_DEV_TO_DEV, flags);
dmaengine_submit(desc);
dma_async_issue_pending(chan);
3.5 四种传输类型的软件架构对比
下图展示了四种 DMA 传输类型在系统架构中的数据流路径,帮助理解 CPU、内存、外设和 DMA 控制器之间的交互关系。
3.6 传输类型选择决策树
在实际驱动开发中,选择哪种 DMA 传输类型取决于数据流的源和目标属性。以下决策树可以帮助快速确定正确的传输方向配置:
- 源地址是否在内存地址空间?
- 是 → 检查目标地址:目标在内存 → M2M;目标在外设 → M2P
- 否(源在外设) → 检查目标地址:目标在内存 → P2M;目标在外设 → P2P
- 是否需要硬件握手信号?
- M2M:不需要,软件触发即可。
- M2P/P2M/P2P:必须配置外设的 DMA 请求信号(DMA_REQ)和应答信号(DMA_ACK)。
- 地址增长模式如何配置?
- 内存端通常配置为递增模式(increment)。
- 外设端通常配置为固定模式(fixed),因为外设 FIFO 地址不变。
工程实践建议:在 Linux DMA Engine 框架中,
dmaengine_prep_slave_sg() 用于 M2P/P2M/P2P 传输,而 dmaengine_prep_dma_memcpy() 专用于 M2M。驱动开发时应根据传输类型选择正确的 API,避免将 M2M 描述符用于外设传输,否则会导致硬件握手信号缺失,传输无法启动。
3.7 传输类型与性能特征
| 传输类型 | 典型延迟 | 总线占用 | CPU 卸载率 | 适用数据量 |
|---|---|---|---|---|
| M2M | 中等(受总线仲裁影响) | 读总线 + 写总线 | 高(完全卸载) | 大块数据(≥1KB) |
| M2P | 低(外设请求驱动) | 读总线 | 高 | 流式数据(周期性) |
| P2M | 低(外设请求驱动) | 写总线 | 高 | 流式数据(周期性) |
| P2P | 极低(无内存中转) | 无内存总线访问 | 极高 | 实时数据流 |