2. DMA控制器硬件架构
DMA(Direct Memory Access)控制器的硬件架构是理解驱动配置与性能调优的基础。本章深入剖析DMA引擎的内部结构、通道与请求线的映射关系,以及地址与计数寄存器的运作机制,为后续描述符链表和循环缓冲的实现奠定硬件认知。
2.1 DMA引擎内部结构
一个典型的DMA控制器由多个功能单元组成,这些单元协同工作以实现高效的数据搬运。下图展示了DMA引擎的核心内部结构:
架构要点: 现代DMA控制器(如ARM PL080、Xilinx AXI DMA)通常采用多通道 + 描述符链表架构。通道之间通过硬件仲裁器共享系统总线,描述符取指单元独立于数据传输路径,支持流水线操作。
2.2 通道与请求线
DMA通道是独立的硬件数据搬运路径,每个通道包含一组专用的寄存器集(源地址、目的地址、计数、控制)。请求线(DMA Request Line)是外设向DMA控制器发起传输请求的物理信号线。
2.2.1 通道属性对比
| 属性 | 硬件通道 | 虚拟通道 | 说明 |
|---|---|---|---|
| 寄存器集 | 独立物理寄存器 | 共享物理寄存器,软件分时复用 | 硬件通道延迟更低 |
| 并发能力 | 支持真正并行传输 | 仅支持分时复用 | 硬件通道适合高带宽外设 |
| 资源消耗 | 高(门电路多) | 低(共享逻辑) | 嵌入式SoC常用虚拟通道 |
| 典型应用 | 高速ADC、PCIe、以太网 | UART、SPI、I2C | 根据外设带宽需求选择 |
2.2.2 请求线映射机制
外设通过请求线向DMA控制器发送传输请求。请求线映射通常由硬件设计固定,或通过软件配置的请求选择寄存器(DMA Request Select)动态分配。
驱动配置要点: 在Linux DMA引擎驱动中,通过
dma_request_channel() 申请通道,并使用 dmaengine_slave_config() 设置请求线映射(如 slave_id 字段)。请求线编号通常对应SoC数据手册中的DMA Request ID。
2.3 地址与计数寄存器
每个DMA通道的核心寄存器组包括源地址寄存器(SAR)、目的地址寄存器(DAR)和传输计数寄存器(CTL/CCR)。这些寄存器直接控制数据搬运的源、目的和长度。
2.3.1 寄存器功能详解
| 寄存器 | 缩写 | 位宽 | 功能描述 |
|---|---|---|---|
| 源地址寄存器 | SAR | 32/64位 | 存储当前传输的源内存地址或外设地址 |
| 目的地址寄存器 | DAR | 32/64位 | 存储当前传输的目的内存地址或外设地址 |
| 传输计数寄存器 | CTL / CCR | 16/32位 | 记录剩余待传输的数据单元数量(通常以字节或字为单位) |
| 控制寄存器 | CR | 32位 | 配置传输方向、地址增减模式、数据宽度、中断使能等 |
| 状态寄存器 | SR | 32位 | 指示通道是否忙、传输完成、错误标志等 |
2.3.2 地址增减模式
地址寄存器支持三种增减模式,通过控制寄存器的相应位域配置:
- 固定模式(Fixed): 地址保持不变,适用于外设FIFO(如UART数据寄存器)的重复访问。
- 递增模式(Increment): 每次传输后地址增加数据宽度,适用于内存缓冲区。
- 递减模式(Decrement): 每次传输后地址减少数据宽度,用于特殊内存操作。
2.3.3 传输计数与自动重载
传输计数寄存器(CTL)在每次数据传输后递减。当CTL减至0时,DMA控制器根据配置执行以下操作之一:
- 单次传输完成: 触发完成中断,通道停止。
- 自动重载(Auto-Reload): 从描述符链表或重载寄存器重新加载计数和地址,实现循环缓冲。
- 链表跳转: 加载下一个描述符,继续传输。
性能陷阱: 在循环缓冲模式下,如果自动重载延迟过高(如中断响应不及时),可能导致FIFO溢出或数据丢失。建议使用双缓冲(Double Buffering)或描述符链表来消除重载间隙。
2.4 寄存器编程示例
以下代码展示了在裸机环境下配置DMA通道寄存器的典型流程:
/* DMA通道寄存器结构体定义 */
struct dma_channel_regs {
volatile uint32_t sar; /* 源地址寄存器 */
volatile uint32_t dar; /* 目的地址寄存器 */
volatile uint32_t ctl; /* 传输计数寄存器 */
volatile uint32_t cr; /* 控制寄存器 */
volatile uint32_t sr; /* 状态寄存器 */
};
/* 配置DMA通道进行内存到外设传输 */
void dma_channel_config(struct dma_channel_regs *ch,
uint32_t src_addr,
uint32_t dst_addr,
uint32_t length)
{
/* 1. 设置源地址(内存缓冲区) */
ch->sar = src_addr;
/* 2. 设置目的地址(外设FIFO地址) */
ch->dar = dst_addr;
/* 3. 设置传输计数(以字节为单位) */
ch->ctl = length;
/* 4. 配置控制寄存器:
* - 位[1:0]: 数据宽度 00=8bit, 01=16bit, 10=32bit
* - 位[3:2]: 源地址模式 00=递增, 01=固定
* - 位[5:4]: 目的地址模式 00=递增, 01=固定
* - 位[7]: 中断使能
* - 位[8]: 通道使能
*/
ch->cr = (2 << 0) | /* 32位数据宽度 */
(0 << 2) | /* 源地址递增 */
(1 << 4) | /* 目的地址固定 */
(1 << 7) | /* 使能传输完成中断 */
(1 << 8); /* 使能通道 */
}
/* 启动传输 */
void dma_channel_start(struct dma_channel_regs *ch)
{
ch->cr |= (1 << 8); /* 设置使能位启动传输 */
}
驱动开发提示: 在Linux DMA引擎框架中,上述寄存器操作被封装在
device_prep_slave_sg() 和 device_issue_pending() 等API中。理解底层寄存器有助于调试硬件相关问题和优化传输性能。
2.5 本章小结
DMA控制器的硬件架构决定了驱动设计的边界:
- 引擎内部结构 包含主控单元、通道阵列、描述符取指单元和数据缓冲,各单元协同实现高效传输。
- 通道与请求线 的映射关系决定了外设与DMA通道的绑定方式,驱动需正确配置请求选择寄存器。
- 地址与计数寄存器 是DMA传输的核心控制点,地址增减模式和自动重载机制直接影响循环缓冲的实现效率。