4. DMA 传输模式:单次传输、块传输、请求传输、链式传输(Scatter-Gather)原理与对比

DMA 控制器支持多种传输模式,以适应不同外设带宽、数据粒度与系统延迟要求。选择错误的传输模式会导致总线占用过高、Cache 抖动加剧或外设 FIFO 溢出。本节从硬件调度机制、总线占用特征与 Cache 一致性影响三个维度,对比四种核心传输模式。

4.1 传输模式分类与核心特征

传输模式 触发方式 总线占用 适用场景 Cache 一致性风险
单次传输 单次请求触发 低(每次释放总线) 低速外设(UART、GPIO) 低(数据量小)
块传输 单次请求触发整块 高(连续占用总线) 高速块设备(MMC、NAND) 中(需整块同步)
请求传输 外设请求信号(DREQ) 动态(按需占用) 流式外设(I2S、SPI) 高(频繁中断)
链式传输 LLI 链表自动加载 低(分散传输) 非连续内存(网络、视频) 高(多段地址)
工程提示: 在嵌入式 Linux 中,dmaengine 框架通过 dma_slave_config 的 src_addr_width 和 dst_maxburst 参数隐式选择传输模式。块传输对应 DMA_PREP_INTERRUPT 标志,链式传输对应 DMA_CTRL_ACK 与 DMA_PREP_FENCE 组合。

4.2 单次传输(Single Transfer)

每次 DMA 请求只传输一个数据单元(字节、半字或字),传输完成后释放总线控制权。适用于外设 FIFO 深度小、数据到达间隔不确定的场景。

  • 调度特征: 每次传输需重新仲裁总线,延迟较高但公平性好。
  • Cache 影响: 单次传输数据量通常小于 Cache line 大小(64 字节),每次传输后需执行 dma_sync_single_for_cpu 或 dma_sync_single_for_device。
  • 典型外设: UART 16550、低速 SPI 从设备。
性能指标: 单次传输模式在 ARM Cortex-A72 上,DMA 请求延迟约 200–400 ns,有效带宽利用率仅 30%–50%,因频繁总线仲裁导致。
单次传输时序 DREQ 仲裁 传输 释放 外设请求 总线仲裁 1 数据单元 释放总线

4.3 块传输(Block Transfer)

一次 DMA 请求传输一个完整的数据块(通常为 512 字节、1 KB 或 4 KB),传输期间连续占用总线,直到块传输完成才释放。适用于高速块设备,如 eMMC、NAND Flash。

  • 调度特征: 块传输期间 CPU 与其它 DMA 通道无法访问总线,可能导致高优先级中断延迟增加。
  • Cache 影响: 块传输通常跨越多个 Cache line,需在传输前后执行整块 dma_map_single / dma_unmap_single,保证 Cache 与内存一致性。
  • 典型外设: eMMC 控制器、AHCI SATA、USB 批量端点。
一致性陷阱: 块传输模式下,若 DMA 写入内存后 CPU 读取同一地址,但 Cache 中仍存有旧数据(Cache 命中),将导致数据不一致。必须在 DMA 完成回调中调用 dma_sync_single_for_cpu 或 dma_unmap_single 使 Cache 失效。
块传输时序 DREQ 仲裁 连续传输 N 个数据单元 释放

4.4 请求传输(Demand Transfer / Request Transfer)

外设通过 DREQ 信号逐次请求数据传输,DMA 控制器每次响应后传输一个突发(burst),然后等待下一个 DREQ。适用于流式外设,数据以恒定速率到达。

  • 调度特征: 传输速率由外设 DREQ 频率决定,DMA 控制器在每次突发后释放总线,允许其他主设备介入。
  • Cache 影响: 突发大小通常为 4–16 字,与 Cache line 对齐时一致性开销最小;若不对齐,可能触发读-修改-写(RMW)操作。
  • 典型外设: I2S 音频接口、高速 SPI、Camera 接口。
优化建议: 请求传输模式下,将 maxburst 设置为 Cache line 大小(如 16 字 = 64 字节)的整数倍,可避免 DMA 写入时触发部分 Cache line 失效,减少一致性维护开销。
请求传输时序(DREQ 驱动) DREQ Burst DREQ Burst DREQ Burst 4–16 字 4–16 字 4–16 字

4.5 链式传输(Scatter-Gather / Linked List Transfer)

链式传输通过内存中的链表(Linked List Item, LLI)描述多个不连续的源/目标地址段,DMA 控制器自动加载 LLI 并依次执行传输,无需 CPU 逐段干预。这是解决非连续物理内存 DMA 传输的核心方案。

  • 调度特征: 每个 LLI 传输完成后,DMA 控制器自动从内存加载下一个 LLI,支持循环、跳转和中断标志。
  • Cache 影响: 每个 LLI 指向的缓冲区可能位于不同 Cache line,需逐段执行 dma_map_sg / dma_unmap_sg,一致性维护复杂度最高。
  • 典型外设: 以太网控制器(描述符环)、视频编解码器、NVMe SSD。
关键约束: LLI 链表本身必须位于非 Cache 区域(或执行 dma_alloc_coherent 分配),否则 DMA 控制器读取 LLI 时可能读到 Cache 中的陈旧数据,导致传输地址错误。这是链式传输中最隐蔽的 Bug 来源。
链式传输(Scatter-Gather)架构 LLI 0 src=0x1000 LLI 1 src=0x2000 LLI 2 src=0x3000 DMA 引擎 加载 LLI DMA 引擎 加载 LLI DMA 引擎 加载 LLI 内存 0x1000 4 KB 内存 0x2000 8 KB 内存 0x3000 2 KB 非连续物理地址 LLI 链表需在 非 Cache 区域

4.6 四种模式对比总结

对比维度 单次传输 块传输 请求传输 链式传输
总线占用时间 短(1 数据单元) 长(整块) 中(突发长度) 分散(多段)
CPU 干预频率 高(每次传输) 低(每块一次) 中(每突发一次) 极低(链表自动)
Cache 同步粒度 单字/半字 整块 突发对齐 逐段
Linux API dmaengine_prep_slave_single dmaengine_prep_slave_sg(单段) dmaengine_prep_slave_sg(循环) dmaengine_prep_slave_sg(多段)
典型延迟 200–400 ns 1–10 µs(含仲裁) 500 ns–2 µs 2–20 µs(链表加载)
选型指南:
  • 外设 FIFO 深度 < 4 字 → 单次传输
  • 外设 FIFO 深度 ≥ 16 字且数据连续 → 块传输
  • 外设数据速率恒定且需低延迟 → 请求传输
  • 内存非连续且传输量大 → 链式传输

4.7 链式传输的 LLI 结构示例

以下代码展示一个典型的 Scatter-Gather LLI 链表初始化过程,使用 dma_alloc_coherent 分配 LLI 内存以避免 Cache 一致性问题。

struct lli_entry {
    u32 src_addr;
    u32 dst_addr;
    u32 next_lli;
    u32 control;
};

struct lli_entry *lli_base;
dma_addr_t lli_phys;

/* 分配非 Cache 的 LLI 内存 */
lli_base = dma_alloc_coherent(dev, NUM_LLI * sizeof(*lli_base),
                              &lli_phys, GFP_KERNEL);

/* 初始化第 0 个 LLI */
lli_base[0].src_addr = src_buf_phys_0;
lli_base[0].dst_addr = dst_buf_phys_0;
lli_base[0].next_lli = lli_phys + sizeof(*lli_base);  /* 指向下一个 LLI */
lli_base[0].control  = LLI_CTRL_TCI | (512 << LLI_TRANSFER_SIZE_SHIFT);

/* 初始化第 1 个 LLI */
lli_base[1].src_addr = src_buf_phys_1;
lli_base[1].dst_addr = dst_buf_phys_1;
lli_base[1].next_lli = 0;  /* 链表结束 */
lli_base[1].control  = LLI_CTRL_TCI | (1024 << LLI_TRANSFER_SIZE_SHIFT);

/* 配置 DMA 控制器从 lli_phys 开始加载 */
dma_engine_desc = dmaengine_prep_slave_sg(chan, &sg_table, 2,
                                          DMA_DEV_TO_MEM,
                                          DMA_PREP_INTERRUPT);
调试要点: 链式传输中,若 next_lli 指向的物理地址错误或 LLI 内容被 Cache 污染,DMA 控制器将加载非法描述符,导致总线错误(Bus Fault)。使用 dma_alloc_coherent 分配 LLI 内存是标准做法,也可使用 dma_pool 管理小尺寸 LLI 对象。