4. DMA 传输模式:单次、块传输与描述符链对比

DMA 控制器支持多种传输模式,以适应不同外设和系统负载场景。理解单次传输(Single Transfer)、块传输(Block Transfer)和描述符链传输(Descriptor Chain)的差异,是编写高效 DMA 驱动的核心基础。三种模式在传输粒度、CPU 干预频率、内存布局和适用场景上存在本质区别。

4.1 三种传输模式核心概念

特性 单次传输 (Single) 块传输 (Block) 描述符链 (Descriptor Chain)
传输粒度 每次传输 1 个数据单元(字节/半字/字) 每次传输一个固定大小的数据块(如 512B、1KB) 每次传输一个由描述符定义的任意长度数据块
CPU 干预频率 极高:每传输一个单元触发一次中断 中等:每完成一个块触发一次中断 极低:整个描述符链完成后触发一次中断
内存连续性要求 源和目标地址必须连续 块内连续,块间可配置 每个描述符可指向任意不连续内存区域
硬件复杂度 最低 中等 最高(需要描述符表、链表遍历逻辑)
典型应用 低速外设(UART、GPIO) 存储设备(MMC、NAND Flash) 高速网络(Ethernet)、视频采集、音频流
工程提示: 在 Linux 内核中,dmaengine 框架通过 dma_slave_config 的 src_maxburst 和 dst_maxburst 字段控制单次/块传输的粒度。描述符链则通过 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_cyclic() 接口构建。

4.2 单次传输模式详解

单次传输是最基础的 DMA 模式。每次 DMA 请求只传输一个数据单元(通常为 1 字节、2 字节或 4 字节),然后释放总线。下一次传输需要重新发起 DMA 请求。

  • 优点: 实现简单,硬件资源占用少,适合数据量小、速率低的外设。
  • 缺点: 总线利用率极低,每次传输都需要重新仲裁,CPU 中断频率高。
  • 典型场景: UART 接收/发送、低速 SPI 数据采集、GPIO 状态读取。
// 单次传输配置示例(伪代码)
struct dma_slave_config config = {
    .direction = DMA_MEM_TO_DEV,
    .src_addr = mem_buf_phys,
    .dst_addr = uart_tx_reg,
    .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
    .src_maxburst = 1,   // 单次传输
    .dst_maxburst = 1,
};
dmaengine_slave_config(chan, &config);

4.3 块传输模式详解

块传输模式允许 DMA 控制器在一次总线占用中连续传输多个数据单元(一个块)。块大小由硬件寄存器配置,通常为 2 的幂次(如 16、64、256、512 字节)。

  • 优点: 总线利用率显著提升,中断频率降低,适合中等速率外设。
  • 缺点: 块内地址必须连续;块间切换仍需 CPU 或硬件自动重载。
  • 典型场景: MMC/SD 卡读写、NAND Flash 页传输、I2S 音频数据流。
// 块传输配置示例(伪代码)
struct dma_slave_config config = {
    .direction = DMA_DEV_TO_MEM,
    .src_addr = mmc_fifo_phys,
    .dst_addr = mem_buf_phys,
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 128,  // 每次传输 128 个 4 字节 = 512 字节块
    .dst_maxburst = 128,
};
dmaengine_slave_config(chan, &config);

4.4 描述符链传输模式详解

描述符链(也称为 scatter-gather 或链表模式)是最高效的 DMA 传输模式。每个描述符(Descriptor)包含源地址、目标地址、传输长度、控制标志以及指向下一个描述符的指针。DMA 控制器自动遍历链表,完成所有描述符定义的传输任务,仅在整条链完成后才触发中断。

  • 优点: CPU 干预最少,支持任意不连续内存布局,适合大数据量、高吞吐场景。
  • 缺点: 硬件实现复杂,描述符表需要维护在内存中,驱动设计难度较高。
  • 典型场景: 千兆以太网(SG DMA)、视频帧缓冲采集、高速数据采集卡。
// 描述符链传输配置示例(Linux dmaengine API)
struct scatterlist sg[3];
struct dma_async_tx_descriptor *tx;

// 初始化三个不连续的内存片段
sg_init_table(sg, 3);
sg_dma_address(&sg[0]) = buf1_phys; sg_dma_len(&sg[0]) = 1024;
sg_dma_address(&sg[1]) = buf2_phys; sg_dma_len(&sg[1]) = 2048;
sg_dma_address(&sg[2]) = buf3_phys; sg_dma_len(&sg[2]) = 512;

// 准备描述符链传输
tx = dmaengine_prep_slave_sg(chan, sg, 3, DMA_MEM_TO_DEV,
                              DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
tx->callback = my_dma_callback;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
关键注意: 描述符链模式下,必须确保描述符表内存不被 CPU 缓存污染(使用 dma_alloc_coherent 或 dma_map_single 配合 DMA_TO_DEVICE 方向)。链表的最后一个描述符必须设置 next 指针为 NULL 或硬件特定的结束标志,否则 DMA 控制器会无限循环。

4.5 三种模式对比 SVG 架构图

下图从硬件执行流程角度对比三种传输模式,展示 CPU 中断频率和总线占用模式的差异。

DMA 传输模式对比:单次 vs 块 vs 描述符链 单次传输模式 CPU DMA 外设 每传输 1 个单元 → 中断 CPU 总线利用率低,中断频率高 块传输模式 CPU DMA 外设 每传输 1 个块(如 512B)→ 中断 CPU 总线利用率中等,中断频率适中 描述符链传输 CPU DMA 外设 整条链完成后 → 中断 CPU 总线利用率最高,中断频率最低 描述符链结构 Desc 0 buf0, len0 Desc 1 buf1, len1 END 关键工程选择原则: 1. 低速外设(UART、I2C)→ 单次传输,降低硬件复杂度 2. 中等速率块设备(MMC、NAND)→ 块传输,平衡效率与实现 3. 高速流式数据(以太网、视频)→ 描述符链,最大化吞吐量

4.6 传输模式选择决策树

在实际驱动开发中,选择哪种传输模式取决于以下关键因素:

  1. 数据速率: 低于 1 Mbps 可考虑单次传输;1-100 Mbps 适合块传输;高于 100 Mbps 必须使用描述符链。
  2. 内存连续性: 如果源/目标缓冲区物理连续,三种模式均可;如果不连续,只能选择描述符链。
  3. 中断延迟容忍度: 实时系统可能偏好块传输以控制最大延迟;吞吐优先系统选择描述符链。
  4. 硬件能力: 部分低端 DMA 控制器不支持描述符链,需查阅芯片手册确认。
性能数据参考: 在 ARM Cortex-A72 平台上,使用 1GB 数据搬运测试:单次传输模式 CPU 占用率约 85%,有效带宽约 200 MB/s;块传输(512B 块)CPU 占用率约 30%,带宽约 800 MB/s;描述符链(8 个描述符)CPU 占用率约 5%,带宽约 1.2 GB/s。实际数据因平台和总线架构而异。

4.7 描述符链传输的驱动实现要点

描述符链是三种模式中最复杂但也是性能最优的。以下是驱动开发中的关键实现步骤:

步骤 操作 注意事项
1 分配描述符表内存 使用 dma_alloc_coherent 保证描述符不被缓存
2 初始化描述符链表 设置每个描述符的 src/dst 地址、长度、控制标志
3 设置链表结束标志 最后一个描述符的 next 指针置 NULL 或硬件特定值
4 将链表基地址写入 DMA 控制器 通常通过 dmaengine_prep_slave_sg 或直接写寄存器
5 启动 DMA 传输 调用 dma_async_issue_pending 触发硬件遍历链表
6 等待完成中断 在回调函数中检查传输状态,释放或重用描述符
常见错误: 描述符链模式下,如果描述符表位于可缓存内存区域且未正确 flush,DMA 控制器可能读到过时的描述符数据,导致传输错误地址或长度。务必使用一致性内存分配或显式 cache 维护操作。

4.8 三种模式中断频率对比 SVG 图

下图以时间轴方式展示三种模式在传输相同数据量(如 1MB)时的中断触发点分布。

相同数据量(1MB)下中断频率对比 时间 → 单次传输(1 字节/次) 中断次数:1,048,576 次 块传输(512 字节/块) 中断次数:2,048 次 描述符链(8 个描述符) 中断次数:1 次(链完成后) 中断 单次 块 描述符链

4.9 实际工程中的混合使用策略

在复杂系统中,单一传输模式往往无法满足所有需求。常见的混合策略包括:

  • 启动阶段使用单次传输: 设备初始化时,使用单次传输读取设备状态寄存器,避免描述符链的初始化开销。
  • 正常运行时切换为描述符链: 设备就绪后,动态切换到描述符链模式以获取高吞吐。
  • 错误恢复使用块传输: 当描述符链传输出错时,回退到块传输模式进行数据重传,简化错误处理逻辑。
工程实践: 在 Linux 内核的 drivers/dma 目录下,许多 DMA 控制器驱动(如 pl330.c、edma.c)同时实现了单次、块和描述符链三种模式。驱动通过 device_prep_slave_sg、device_prep_dma_cyclic 和 device_prep_interleaved_dma 等回调函数区分不同传输模式。阅读这些源码是理解模式切换的最佳途径。