2. DMA控制器硬件架构:DMA通道、请求线、仲裁机制、传输宽度与突发传输
DMA(Direct Memory Access)控制器是嵌入式系统中负责在外设与存储器之间、或存储器与存储器之间直接搬运数据的硬件引擎。理解其内部架构是驱动移植与性能调优的基础。本节从五个核心维度拆解DMA控制器的硬件组成与运行原理。
2.1 DMA通道(Channel)
DMA通道是独立的数据搬运路径,每个通道包含一组寄存器(源地址、目的地址、传输计数、控制配置),可独立配置并服务于一个外设或一次传输任务。多通道设计允许并发处理多个DMA请求。
关键特性:
- 独立性:每个通道拥有自己的配置寄存器组,互不干扰。
- 优先级可编程:软件可为每个通道分配优先级(高/低/固定轮询)。
- 传输粒度:支持字节(8-bit)、半字(16-bit)、字(32-bit)传输宽度。
- 循环模式:支持自动重载传输计数,用于连续数据流(如音频ADC)。
2.2 DMA请求线(Request Line)
外设通过硬件请求线向DMA控制器发出数据传输请求。每条请求线对应一个外设事件(如UART接收缓冲区非空、定时器溢出、ADC转换完成)。DMA控制器检测到请求后,根据配置启动一次传输。
| 请求类型 | 触发条件 | 典型应用 |
|---|---|---|
| 硬件请求 | 外设硬件信号电平/边沿 | UART RX、SPI TX、I2C数据就绪 |
| 软件触发 | 写寄存器启动位 | 存储器到存储器拷贝 |
| 外设事件 | 定时器比较匹配、ADC转换结束 | 定时触发ADC采样搬运 |
| 链式请求 | 前一个DMA传输完成 | 描述符链表自动加载下一段传输 |
驱动适配要点:在Linux DMA引擎框架中,
dma_request_channel() 函数通过匹配 dma_slave_map 表将外设请求线与DMA通道绑定。设备树中需正确声明 dmas 和 dma-names 属性。
2.3 仲裁机制(Arbitration)
当多个DMA通道同时请求总线访问时,仲裁器决定哪个通道获得总线控制权。仲裁策略直接影响实时性和吞吐量。
常见陷阱:在I2C高速传输(400kHz以上)场景中,若DMA通道优先级设置不当,低优先级通道可能因频繁被抢占而导致I2C时钟拉伸超时,引发总线错误。建议将I2C DMA通道设为最高优先级。
2.4 传输宽度(Transfer Width)
传输宽度定义DMA每次总线访问搬运的数据位宽。源和目的宽度可以不同,硬件自动进行对齐与填充。
| 宽度枚举 | 位宽 | 典型外设 | 地址递增步长 |
|---|---|---|---|
DMA_SLAVE_BUSWIDTH_1_BYTE |
8-bit | UART、I2C、低速SPI | 1 |
DMA_SLAVE_BUSWIDTH_2_BYTES |
16-bit | 音频I2S、16位ADC | 2 |
DMA_SLAVE_BUSWIDTH_4_BYTES |
32-bit | 高速SPI、并行LCD、以太网 | 4 |
DMA_SLAVE_BUSWIDTH_8_BYTES |
64-bit | DDR控制器、高性能加速器 | 8 |
宽度不匹配处理:当源宽度为8-bit、目的宽度为32-bit时,DMA控制器自动将4个字节打包为一个字写入目的地址。驱动中需通过
struct dma_slave_config 的 src_addr_width 和 dst_addr_width 字段分别配置。
2.5 突发传输(Burst Transfer)
突发传输允许DMA在一次总线仲裁授权后连续传输多个数据单元,减少总线仲裁开销,提高有效带宽。突发长度(Burst Length)表示一次突发中传输的数据单元个数。
2.6 综合配置示例
以下代码展示在Linux DMA引擎框架中配置I2C从设备DMA传输的典型流程,涵盖通道、请求线、传输宽度与突发长度设置:
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
struct dma_slave_config config = {
.direction = DMA_DEV_TO_MEM, // I2C接收:外设→内存
.src_addr = (phys_addr_t)i2c_base + 0x10, // I2C数据寄存器地址
.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE, // I2C每次1字节
.src_maxburst = 4, // 突发长度4,匹配I2C FIFO深度
.dst_addr = (phys_addr_t)buf_phys, // 目的内存物理地址
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, // 内存按32-bit对齐
.dst_maxburst = 4,
};
struct dma_chan *chan;
chan = dma_request_channel(mask, filter_fn, "i2c0_rx");
if (!chan) {
dev_err(dev, "Failed to request DMA channel\n");
return -ENODEV;
}
dmaengine_slave_config(chan, &config);
struct dma_async_tx_descriptor *tx;
tx = dmaengine_prep_slave_sg(chan, sgl, nr_sg, DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
tx->callback = dma_rx_callback;
tx->callback_param = priv;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
工程实践建议:
- 在I2C驱动中,建议将
src_maxburst设置为I2C控制器FIFO深度的一半,避免FIFO溢出。 - 使用
dma_get_slave_caps()查询控制器支持的突发长度范围,不同IP核差异较大。 - 对于多通道并发场景,合理分配优先级并启用轮询模式可避免低优先级通道饿死。