4. DMA 控制器硬件特性:SG-DMA、循环 DMA、内存到内存、外设到内存模式

DMA(Direct Memory Access)控制器的硬件能力直接决定了数据传输的吞吐量、CPU 卸载效率以及系统实时性。本章深入剖析四种核心 DMA 传输模式:SG-DMA(Scatter-Gather DMA)、循环 DMA(Cyclic DMA)、内存到内存(Mem-to-Mem) 和 外设到内存(Peripheral-to-Mem)。理解这些模式的硬件特性,是后续进行驱动性能优化和 CPU 负载均衡的基础。

4.1 四种 DMA 传输模式概览

下表从触发方式、数据流向、典型应用场景和 CPU 干预程度四个维度,对四种模式进行横向对比:

模式 触发方式 数据流向 典型应用 CPU 干预程度
SG-DMA 软件请求 / 外设事件 内存 ↔ 内存 / 外设 网络包收发、文件系统 I/O 低(仅配置描述符链表)
循环 DMA 硬件定时 / 外设连续请求 外设 → 内存 / 内存 → 外设 音频流、ADC 连续采样 极低(仅初始化)
内存到内存 软件显式启动 内存 → 内存 帧缓冲拷贝、数据搬移 中等(需等待完成)
外设到内存 外设硬件请求(DREQ) 外设 → 内存 UART 接收、SPI 数据采集 低(仅配置后等待中断)
性能提示: SG-DMA 和循环 DMA 是高性能驱动设计的核心。SG-DMA 通过描述符链表实现零散物理内存的连续传输,避免了内存拷贝;循环 DMA 则通过硬件自动回环,消除了频繁重配 DMA 通道的开销。

4.2 SG-DMA(Scatter-Gather DMA)

SG-DMA 是高性能 DMA 控制器的标志性特性。它允许 DMA 引擎通过一个描述符链表(Descriptor Chain)来管理多个不连续的物理内存缓冲区,实现一次请求完成多次传输。

4.2.1 核心硬件结构

SG-DMA 的核心是硬件描述符。每个描述符包含:源地址、目的地址、传输长度、控制标志(如中断使能、链尾标记)以及指向下一个描述符的指针。DMA 控制器自动从链表头部开始,依次处理每个描述符,直到遇到链尾标记。

关键寄存器与数据结构:
  • 描述符基址寄存器(DESC_BASE):指向链表首地址。
  • 当前描述符指针(CUR_DESC):硬件内部维护,指向正在处理的描述符。
  • 描述符完成中断:每个描述符或整个链表完成时可触发中断。

4.2.2 SG-DMA 传输流程 SVG 图

下图展示了 SG-DMA 从配置描述符链表到传输完成的完整硬件流程:

SG-DMA 描述符链表传输流程 CPU 配置描述符链表 DESC0 → DESC1 → DESC2 写入 DMA 启动寄存器 设置 DESC_BASE 地址 硬件自动取描述符 DMA 引擎读取 DESC0 执行数据传输 源 → 目的,长度 N 字节 更新状态 & 中断 可选:每描述符中断 取下一个描述符 DESC0 → DESC1 硬件自动流转 链表完成(链尾标记) 触发完成中断,DMA 停止 说明: SG-DMA 的硬件描述符链表存储在内存中,DMA 控制器通过总线主控(Bus Master)直接读取。CPU 只需配置链表头地址,后续传输完全由硬件自主完成。

4.2.3 驱动开发关键点

  • 描述符对齐:硬件通常要求描述符按 8 字节或 16 字节对齐,否则可能触发总线错误。
  • Cache 一致性:描述符链表所在内存必须使用 dma_alloc_coherent() 分配,或显式执行 dma_sync_single_for_device()。
  • 中断负载:建议仅在链表完成时触发中断,避免每个描述符都中断,减少 CPU 打扰。
/* SG-DMA 描述符结构体示例(以某 SoC 为例) */
struct sg_dma_desc {
    u32 src_addr;       /* 源物理地址 */
    u32 dst_addr;       /* 目的物理地址 */
    u32 len;            /* 传输长度 */
    u32 ctrl;           /* 控制位:中断使能、链尾标记等 */
    u32 next_desc;      /* 下一个描述符物理地址 */
    u32 status;         /* 硬件更新:传输状态 */
} __attribute__((aligned(16)));

4.3 循环 DMA(Cyclic DMA)

循环 DMA 专为连续、周期性的数据流设计。它使用一个环形缓冲区(Ring Buffer),DMA 控制器在传输完最后一个缓冲区后自动回绕到第一个,形成无限循环,无需 CPU 重新配置。

4.3.1 硬件特性

  • 自动回绕:硬件内部维护当前缓冲区索引,到达末尾后自动重置为 0。
  • 双缓冲 / 多缓冲:通常配合 2 个或 N 个缓冲区,一个被 DMA 填充时,CPU 处理另一个。
  • 半满 / 全满中断:硬件支持在缓冲区半满或全满时触发中断,实现流式处理。

4.3.2 循环 DMA 环形缓冲区结构 SVG 图

循环 DMA 环形缓冲区结构 Buffer 0 DMA 正在写入 Buffer 1 CPU 可读取 Buffer 2 等待写入 Buffer 3 等待写入 自动回绕 外设数据流 说明: DMA 写指针(WP)循环递增,CPU 读指针(RP)追赶处理。当 WP 追上 RP 时,表示缓冲区溢出(Overrun),驱动需及时处理。

4.3.3 驱动开发关键点

  • 缓冲区大小选择:必须大于外设最大传输单元,避免溢出。音频场景通常选择 2 的幂次大小以优化地址计算。
  • 中断频率:半满中断可降低延迟,但增加 CPU 负载;全满中断吞吐量高,但延迟增大。需根据实时性要求权衡。
  • 停止与恢复:循环 DMA 停止后,重新启动时需清空 FIFO 并重置指针,防止数据错位。

4.4 内存到内存模式(Mem-to-Mem)

内存到内存模式用于在系统内存的两个区域之间高速拷贝数据,常用于帧缓冲更新、加密引擎输入准备等场景。与 CPU 使用 memcpy() 相比,DMA 可以释放 CPU 核心用于其他计算任务。

4.4.1 硬件特性

  • 双通道传输:DMA 控制器同时从源地址读取和向目的地址写入,总线利用率高。
  • 突发传输(Burst Transfer):支持 4、8、16 字节突发,显著提高内存带宽利用率。
  • 无外设同步:传输完全由软件触发,无需等待外设请求信号。

4.4.2 性能对比:DMA vs CPU memcpy

指标 DMA Mem-to-Mem CPU memcpy()
CPU 占用率 仅启动和完成中断时占用 传输期间 100% 占用
小数据量延迟(< 64 字节) 较高(DMA 启动开销大) 极低
大数据量吞吐(> 1 MB) 高(利用突发传输) 中等(受 Cache 影响)
Cache 污染 无(直接访问内存) 严重(污染 L1/L2 Cache)
性能陷阱: 对于小于 256 字节的拷贝,DMA 的启动延迟(配置寄存器 + 中断响应)可能超过 CPU 直接拷贝的时间。建议在驱动中设置阈值,小数据量使用 CPU 拷贝,大数据量使用 DMA。

4.5 外设到内存模式(Peripheral-to-Mem)

这是最常用的 DMA 模式,用于将外设(如 UART、SPI、I2S、ADC)的数据直接传输到内存,无需 CPU 逐字节读取。外设通过硬件请求线(DREQ)向 DMA 控制器发起传输请求。

4.5.1 硬件握手流程

  1. 外设数据就绪:外设接收 FIFO 达到预设阈值(如半满),或单字节就绪。
  2. 断言 DREQ:外设拉高 DMA 请求信号。
  3. DMA 仲裁:DMA 控制器仲裁优先级,获得总线控制权。
  4. 单次 / 突发传输:DMA 从外设数据寄存器读取数据,写入内存缓冲区。
  5. 撤销 DREQ:外设检测到数据被读取,撤销请求信号。
  6. 重复:直到传输计数归零或收到停止指令。

4.5.2 外设到内存模式硬件握手 SVG 图

外设到内存模式硬件握手时序 外设(UART/SPI) FIFO 数据就绪 DMA 控制器 仲裁 & 传输 系统内存 接收缓冲区 DREQ 数据总线 握手时序说明: 1. 外设 FIFO 达到阈值 → 拉高 DREQ 信号。 2. DMA 控制器采样到 DREQ → 发起总线仲裁 → 读取外设数据寄存器。 3. 外设检测到数据被读取 → 撤销 DREQ → 准备下一字节。 4. 重复步骤 1-3,直到传输计数完成或软件停止。

4.5.3 驱动开发关键点

  • FIFO 阈值匹配:外设 FIFO 阈值应与 DMA 突发大小匹配。例如,SPI FIFO 深度 16 字节,设置阈值为 8 字节,DMA 使用 8 字节突发。
  • DREQ 极性:部分外设支持高电平有效或低电平有效,需在 DMA 控制器中正确配置极性。
  • 超时处理:外设长时间未产生数据时,DMA 可能挂起。驱动应实现超时定时器,强制复位 DMA 通道。
/* 外设到内存 DMA 配置示例(伪代码) */
struct dma_slave_config config = {
    .direction = DMA_DEV_TO_MEM,
    .src_addr = (phys_addr_t)&spi_regs->dr,  /* 外设数据寄存器地址 */
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 4,                        /* 每次突发 4 字节 */
    .dst_addr = (phys_addr_t)buf_phys,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .dst_maxburst = 4,
};
dmaengine_slave_config(chan, &config);

4.6 本章小结