2. DMA 控制器硬件架构:引擎内部结构、通道、请求线与仲裁机制
DMA(Direct Memory Access)控制器是系统总线上独立于 CPU 的数据搬运引擎。理解其硬件架构是编写高效 DMA 驱动的基石。本章深入剖析 DMA 控制器的核心组件:内部结构、通道(Channel)、请求线(Request Line)以及仲裁机制,并揭示它们如何协同工作以实现零 CPU 干预的数据传输。
2.1 DMA 引擎内部结构
一个典型的 DMA 控制器内部由多个功能模块组成,这些模块通过内部总线连接,协同完成从源地址到目的地址的数据搬运。下图展示了一个通用 DMA 引擎的软件架构视图。
架构要点: 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 设计中,请求线与通道的映射可以是固定的,也可以是可编程的。下图展示了一种典型的可编程映射结构。
2.4 仲裁机制
当多个通道同时有数据传输请求时,DMA 控制器内部的仲裁器决定哪个通道获得总线使用权。仲裁策略直接影响系统的实时性和吞吐量。
2.4.1 常见仲裁策略
| 策略 | 工作原理 | 优点 | 缺点 |
|---|---|---|---|
| 固定优先级 | 每个通道分配固定优先级编号,高优先级通道始终优先 | 实现简单,延迟可预测 | 低优先级通道可能被饿死 |
| 轮询(Round-Robin) | 所有通道按固定顺序轮流获得总线使用权 | 公平性好,无饿死 | 高优先级请求无法获得低延迟 |
| 加权轮询 | 每个通道分配权重,按权重比例分配总线时间片 | 兼顾公平与优先级 | 配置复杂,需要精确的权重计算 |
| 混合策略 | 高优先级通道使用固定优先级,其余通道使用轮询 | 兼顾实时性与公平性 | 硬件实现复杂度较高 |
2.4.2 仲裁粒度
仲裁可以在不同粒度上进行,这决定了通道获得总线后可以连续传输多少数据。
- 单次传输仲裁: 每次总线传输(如一个 32 位字)后重新仲裁。延迟最低,但总线利用率因频繁切换而下降。
- 突发传输仲裁: 通道获得总线后可以连续传输一个突发(Burst)的数据(如 4、8、16 个节拍),完成后才释放总线。突发大小是性能调优的关键参数。
- 数据块仲裁: 通道获得总线后可以完成整个 DMA 描述符指定的数据块,然后才切换。适用于大数据块传输,但会阻塞其他通道较长时间。
性能陷阱: 在网络驱动中,如果 DMA 仲裁粒度过大(如数据块仲裁),一个正在传输大帧的通道会长时间占用总线,导致其他通道(如音频、控制外设)的实时性要求无法满足。建议对网络通道使用突发传输仲裁,并设置合理的突发大小(通常为 8 或 16 个节拍)。
2.5 仲裁流程示例
下图展示了一个 4 通道 DMA 控制器在轮询仲裁策略下的传输流程。
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。