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 传输流程

  1. CPU 在内存中准备好待发送的数据缓冲区。
  2. 驱动配置 DMA 通道:源地址指向内存缓冲区,目标地址指向外设的数据寄存器地址(FIFO)。
  3. 外设(如 SPI、UART、I2S)准备好接收数据时,向 DMA 控制器发出硬件请求信号。
  4. DMA 控制器响应请求,从内存读取数据,写入外设寄存器。
  5. 传输完成后,DMA 控制器发出中断通知 CPU。

3.3.2 P2M 传输流程

  1. CPU 分配接收缓冲区并配置 DMA 通道:源地址指向外设数据寄存器,目标地址指向内存缓冲区。
  2. 外设(如 ADC、摄像头接口、UART)收到数据后,向 DMA 控制器发出请求。
  3. DMA 控制器从外设寄存器读取数据,写入内存缓冲区。
  4. 传输完成或缓冲区满时触发中断,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 控制器之间的交互关系。

DMA 四种传输类型数据流架构图 DMA 四种传输类型数据流架构 DMA 控制器 系统内存(DDR/SRAM) 外设(FIFO/寄存器) M2M M2P P2M P2P(双向) 图例说明: • M2M(绿色→蓝色→绿色):数据从内存到内存,DMA 控制器作为中转,无需外设参与。 • M2P(绿色→蓝色→红色):数据从内存到外设,外设通过硬件请求信号驱动传输。 • P2M(红色→蓝色→绿色):数据从外设到内存,外设主动产生请求,DMA 响应。 • P2P(红色↔蓝色↔红色):数据在外设之间直接传输,DMA 控制器协调双方向流控。

3.6 传输类型选择决策树

在实际驱动开发中,选择哪种 DMA 传输类型取决于数据流的源和目标属性。以下决策树可以帮助快速确定正确的传输方向配置:

  1. 源地址是否在内存地址空间?
    • 是 → 检查目标地址:目标在内存 → M2M;目标在外设 → M2P
    • 否(源在外设) → 检查目标地址:目标在内存 → P2M;目标在外设 → P2P
  2. 是否需要硬件握手信号?
    • M2M:不需要,软件触发即可。
    • M2P/P2M/P2P:必须配置外设的 DMA 请求信号(DMA_REQ)和应答信号(DMA_ACK)。
  3. 地址增长模式如何配置?
    • 内存端通常配置为递增模式(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 极低(无内存中转) 无内存总线访问 极高 实时数据流