2. DMA 控制器硬件架构:引擎内部结构、通道、请求线与仲裁机制

DMA(Direct Memory Access)控制器是系统总线上独立于 CPU 的数据搬运引擎。理解其硬件架构是编写高效 DMA 驱动的基石。本章深入剖析 DMA 控制器的核心组件:内部结构、通道(Channel)、请求线(Request Line)以及仲裁机制,并揭示它们如何协同工作以实现零 CPU 干预的数据传输。

2.1 DMA 引擎内部结构

一个典型的 DMA 控制器内部由多个功能模块组成,这些模块通过内部总线连接,协同完成从源地址到目的地址的数据搬运。下图展示了一个通用 DMA 引擎的软件架构视图。

DMA 引擎内部结构框图 DMA 引擎内部结构框图 DMA 主控单元 (控制逻辑 + 状态机) 地址生成器 (源/目的地址递增) 传输计数器 (剩余字节数) 通道仲裁器 (优先级/轮询调度) 数据缓冲 FIFO (暂存/宽度匹配) 寄存器组 (配置/状态寄存器) 系统总线接口 (AHB/AXI/PCIe) 外设接口 (请求线/握手信号) 中断控制器 (传输完成/错误) 💡 关键说明: • 主控单元协调所有模块,根据配置的传输模式(单次/突发/链表)驱动状态机。 • 地址生成器支持固定地址、递增地址和链表地址模式,适应不同外设需求。 • 数据缓冲 FIFO 用于桥接不同总线宽度,并支持突发传输以提升总线利用率。
架构要点: DMA 引擎内部各模块通过专用硬件逻辑并行工作。主控单元在单个时钟周期内即可完成地址计算、总线仲裁和 FIFO 控制,这是 DMA 能够达到接近总线理论带宽的关键。

2.2 通道(Channel)

通道是 DMA 控制器中独立的数据传输路径。每个通道包含一组专用的寄存器(源地址、目的地址、传输计数、控制配置),可以独立配置并执行一次 DMA 传输。多通道设计允许 DMA 控制器同时服务多个外设或内存区域。

2.2.1 通道内部寄存器映射

寄存器名称 位宽 功能描述
SAR (Source Address Register) 32/64 位 存储源数据地址,支持传输后自动递增/递减或保持不变
DAR (Destination Address Register) 32/64 位 存储目的数据地址,支持传输后自动递增/递减或保持不变
CTR (Transfer Count Register) 16/32 位 记录待传输的数据量(字节/字/突发数),每完成一次传输递减
CCR (Channel Control Register) 32 位 配置传输方向(内存→外设/外设→内存/内存→内存)、传输宽度、突发大小、中断使能等
CSR (Channel Status Register) 32 位 反映通道当前状态:空闲、运行、暂停、完成、错误等

2.2.2 通道工作模式

  • 单次传输模式: 配置完成后启动一次传输,传输指定数量的数据后自动停止。适用于短数据块或控制类外设。
  • 自动请求模式: 通道在完成一次传输后自动重新加载配置并等待下一次触发,适用于周期性数据流(如音频采样)。
  • 链表(Scatter-Gather)模式: 通道通过读取内存中的描述符链表来获取多个传输任务,无需 CPU 逐次重新配置。这是网络驱动中零拷贝技术的核心硬件支持。
驱动开发提示: 在 Linux 内核中,struct dma_chan 是对硬件通道的软件抽象。驱动通过 dma_request_channel() 申请通道,通过 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_cyclic() 配置传输描述符。理解硬件通道的寄存器布局有助于优化描述符提交和回调处理。

2.3 请求线(Request Line)

请求线是外设向 DMA 控制器发起数据传输请求的硬件信号线。每个外设(如 UART、SPI、I2S、以太网 MAC)通常拥有独立的请求线,连接到 DMA 控制器的特定通道或仲裁器。

2.3.1 请求线类型

类型 信号行为 典型应用
硬件请求线 (HW Request) 外设通过专用 GPIO 或 SoC 内部互联线直接触发 DMA 传输 高速 ADC 采样、以太网帧接收
软件触发 (SW Trigger) CPU 通过写寄存器来启动 DMA 传输 内存拷贝、数据校验
定时器触发 (Timer Trigger) 由片上定时器产生周期性请求 音频播放、PWM 波形生成

2.3.2 请求线与通道的映射关系

在 SoC 设计中,请求线与通道的映射可以是固定的,也可以是可编程的。下图展示了一种典型的可编程映射结构。

请求线与通道映射关系图 请求线与通道映射关系图 UART0 RX SPI1 TX I2S0 RX ETH TX 可编程映射矩阵 (MUX / Crossbar) 请求线 0 → 通道 2 请求线 1 → 通道 0 请求线 2 → 通道 3 请求线 3 → 通道 1 由 DMA 控制器寄存器 (DMAC_REQ_MAP) 配置 通道 0 通道 1 通道 2 通道 3 💡 可编程映射允许驱动在运行时动态绑定外设请求到空闲通道,提高硬件资源利用率。 例如:当 UART 空闲时,其请求线可被重新映射给需要更高吞吐量的以太网控制器。

2.4 仲裁机制

当多个通道同时有数据传输请求时,DMA 控制器内部的仲裁器决定哪个通道获得总线使用权。仲裁策略直接影响系统的实时性和吞吐量。

2.4.1 常见仲裁策略

策略 工作原理 优点 缺点
固定优先级 每个通道分配固定优先级编号,高优先级通道始终优先 实现简单,延迟可预测 低优先级通道可能被饿死
轮询(Round-Robin) 所有通道按固定顺序轮流获得总线使用权 公平性好,无饿死 高优先级请求无法获得低延迟
加权轮询 每个通道分配权重,按权重比例分配总线时间片 兼顾公平与优先级 配置复杂,需要精确的权重计算
混合策略 高优先级通道使用固定优先级,其余通道使用轮询 兼顾实时性与公平性 硬件实现复杂度较高

2.4.2 仲裁粒度

仲裁可以在不同粒度上进行,这决定了通道获得总线后可以连续传输多少数据。

  • 单次传输仲裁: 每次总线传输(如一个 32 位字)后重新仲裁。延迟最低,但总线利用率因频繁切换而下降。
  • 突发传输仲裁: 通道获得总线后可以连续传输一个突发(Burst)的数据(如 4、8、16 个节拍),完成后才释放总线。突发大小是性能调优的关键参数。
  • 数据块仲裁: 通道获得总线后可以完成整个 DMA 描述符指定的数据块,然后才切换。适用于大数据块传输,但会阻塞其他通道较长时间。
性能陷阱: 在网络驱动中,如果 DMA 仲裁粒度过大(如数据块仲裁),一个正在传输大帧的通道会长时间占用总线,导致其他通道(如音频、控制外设)的实时性要求无法满足。建议对网络通道使用突发传输仲裁,并设置合理的突发大小(通常为 8 或 16 个节拍)。

2.5 仲裁流程示例

下图展示了一个 4 通道 DMA 控制器在轮询仲裁策略下的传输流程。

轮询仲裁流程示例 轮询仲裁流程示例(4 通道) t0 t1 t2 t3 t4 通道 0 突发 通道 1 突发 通道 2 突发 通道 3 突发 通道 0 突发 通道 1 突发 通道 2 空闲 通道 3 突发 💡 流程说明: 1. 第一轮:通道 0→1→2→3 依次获得总线,每个通道传输一个突发(Burst)。 2. 第二轮:通道 0 和 1 仍有数据,通道 2 无请求(空闲),仲裁器跳过通道 2,直接服务通道 3。 3. 轮询仲裁保证每个有请求的通道都能在固定周期内获得服务,避免饿死。 4. 实际硬件中,仲裁器在每个突发传输完成后立即重新仲裁,延迟在纳秒级。

2.6 驱动开发中的硬件抽象

在 Linux 内核中,DMA 引擎的硬件细节被抽象为 dma_device 和 dma_chan 结构体。驱动开发者通过标准 API 操作硬件,无需直接操作寄存器。

/* Linux 内核 DMA 引擎驱动核心数据结构示例 */
struct dma_device {
    unsigned int    channels;       /* 支持的通道数 */
    enum dma_cap_mask_t cap_mask;   /* 能力掩码 */
    struct list_head channels;      /* 通道链表 */

    /* 通道操作函数指针 */
    int (*device_alloc_chan_resources)(struct dma_chan *chan);
    void (*device_free_chan_resources)(struct dma_chan *chan);
    struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
        struct dma_chan *chan, struct scatterlist *sgl,
        unsigned int sg_len, enum dma_transfer_direction direction,
        unsigned long flags, void *context);
    int (*device_issue_pending)(struct dma_chan *chan);
    enum dma_status (*device_tx_status)(struct dma_chan *chan,
        dma_cookie_t cookie, struct dma_tx_state *txstate);
};

/* 通道结构体 */
struct dma_chan {
    struct dma_device *device;      /* 所属 DMA 设备 */
    dma_cookie_t cookie;            /* 传输 cookie */
    /* ... 其他成员 */
};
实践建议: 在编写网络驱动时,优先使用 dmaengine_prep_slave_sg() 进行描述符提交。该函数内部会处理硬件通道的 SAR/DAR/CTR 寄存器配置,并利用 Scatter-Gather 能力实现零拷贝。避免直接操作 DMA 寄存器,以保持代码的可移植性。

2.7 本章小结

  • DMA 引擎由主控单元、地址生成器、传输计数器、数据缓冲 FIFO、通道仲裁器和寄存器组等模块构成,各模块通过内部总线协同工作。
  • 通道是独立的数据传输路径,每个通道包含 SAR、DAR、CTR、CCR、CSR 等寄存器,支持单次、自动请求和链表三种工作模式。
  • 请求线是外设与 DMA 控制器之间的硬件握手信号,通过可编程映射矩阵灵活绑定到不同通道。
  • 仲裁机制决定多通道并发时的总线分配策略,固定优先级、轮询、加权轮询和混合策略各有适用场景。
  • 仲裁粒度(单次/突发/数据块)直接影响系统实时性和总线利用率,网络驱动中推荐使用突发传输仲裁。
  • Linux 内核通过 dma_device 和 dma_chan 抽象硬件细节,驱动开发者应优先使用标准 DMA engine API。