2. Linux DMA子系统概述
DMA(Direct Memory Access)子系统是Linux内核中负责管理直接内存访问的核心基础设施。它向上为设备驱动提供统一的数据传输接口,向下屏蔽不同硬件DMA控制器的差异。理解DMA子系统的架构层次、dmaengine框架以及关键配置结构体,是编写正确、高效DMA驱动的先决条件。
2.1 DMA引擎架构
Linux内核的DMA子系统采用分层架构设计,从底层的硬件寄存器操作到上层的驱动API,共分为三个主要层次:
| 层次 | 组件 | 职责 |
|---|---|---|
| 硬件层 | DMA控制器(如PL330、EDMA、Intel IOMMU) | 执行实际的内存搬运、链式传输、硬件同步 |
| 核心框架层 | dmaengine 核心(drivers/dma/dmaengine.c) |
提供统一的API、通道管理、事务队列调度 |
| 驱动使用层 | 外设驱动(SPI、I2C、Audio、GPU等) | 通过标准接口申请通道、配置传输、提交事务 |
架构要点: 硬件层与驱动使用层完全解耦。外设驱动无需关心底层DMA控制器是挂在AMBA总线还是PCIe总线上,只需通过
dmaengine API操作抽象的DMA通道(dma_chan)。
2.2 dmaengine框架
dmaengine 是Linux内核自2.6.38版本引入的通用DMA框架,旨在替代旧式的、与平台强绑定的DMA API。它定义了一套标准化的操作集合,使得DMA引擎驱动(provider)和DMA使用方(consumer)可以独立开发。
2.2.1 核心数据结构
框架围绕以下几个核心对象构建:
struct dma_device:描述一个DMA控制器硬件设备,包含其支持的能力(如设备树匹配、通道数量、传输类型)。struct dma_chan:代表一个抽象的DMA通道,是驱动使用方操作DMA的句柄。struct dma_async_tx_descriptor:描述一次DMA传输事务的描述符,包含源地址、目的地址、传输宽度、回调函数等。struct dma_slave_config:用于配置从设备(slave)与DMA控制器之间的接口参数,是本章重点。
2.2.2 典型使用流程
外设驱动使用 dmaengine 的典型步骤分为以下五个阶段:
- 请求通道:调用
dma_request_chan()或dma_request_slave_channel(),根据设备树或平台数据获取一个可用的DMA通道。 - 配置从设备参数:填充
struct dma_slave_config,并通过dmaengine_slave_config()下发到DMA引擎驱动。 - 准备传输描述符:调用
dmaengine_prep_slave_sg()(SG列表模式)或dmaengine_prep_dma_cyclic()(循环模式)等函数,获得传输描述符。 - 提交并启动:通过
dmaengine_submit()将描述符提交到硬件队列,再调用dma_async_issue_pending()启动传输。 - 等待完成:注册回调函数或使用
dma_wait_for_async_tx()等待传输结束。
最佳实践: 在步骤2中,务必确保
dma_slave_config 的 direction 字段与后续使用的传输方向一致。配置错误是DMA驱动中最常见的故障原因之一。
2.3 dma_slave_config 结构体
struct dma_slave_config 是连接外设驱动与DMA引擎驱动的桥梁。它定义了从设备(如SPI控制器、I2C控制器)与DMA控制器之间的硬件接口参数。该结构体定义在 include/linux/dmaengine.h 中。
2.3.1 结构体定义与字段解析
struct dma_slave_config {
enum dma_transfer_direction direction;
phys_addr_t src_addr;
phys_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
u32 src_port_window_size;
u32 dst_port_window_size;
bool device_fc;
unsigned int slave_id;
};
| 字段 | 类型 | 说明 |
|---|---|---|
direction |
enum dma_transfer_direction |
传输方向:DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV |
src_addr |
phys_addr_t |
源端物理地址(通常是从设备的FIFO地址) |
dst_addr |
phys_addr_t |
目的端物理地址 |
src_addr_width |
enum dma_slave_buswidth |
源端总线宽度:DMA_SLAVE_BUSWIDTH_1_BYTE、_2_BYTES、_4_BYTES、_8_BYTES |
dst_addr_width |
enum dma_slave_buswidth |
目的端总线宽度 |
src_maxburst |
u32 |
源端最大突发传输长度(以 src_addr_width 为单位) |
dst_maxburst |
u32 |
目的端最大突发传输长度 |
device_fc |
bool |
硬件流控标志:true 表示由外设硬件控制DMA请求 |
slave_id |
unsigned int |
从设备标识符(用于某些DMA控制器区分外设请求线) |
2.3.2 配置示例:SPI发送场景
以下代码展示了一个典型的SPI主控制器使用DMA进行内存到设备传输的配置过程:
#include <linux/dmaengine.h>
struct dma_slave_config config;
struct dma_chan *tx_chan;
/* 1. 请求DMA通道 */
tx_chan = dma_request_slave_channel(dev, "tx");
if (IS_ERR(tx_chan))
return PTR_ERR(tx_chan);
/* 2. 清空并填充配置结构体 */
memset(&config, 0, sizeof(config));
config.direction = DMA_MEM_TO_DEV;
config.dst_addr = spi->phys_base + SPI_TX_FIFO; /* SPI发送FIFO物理地址 */
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; /* 32位总线 */
config.dst_maxburst = 4; /* 每次突发4个字 */
/* 3. 下发配置到DMA引擎 */
ret = dmaengine_slave_config(tx_chan, &config);
if (ret < 0) {
dev_err(dev, "failed to configure tx channel\n");
dma_release_channel(tx_chan);
return ret;
}
常见错误: 许多开发者混淆了
src_addr 和 dst_addr 的赋值规则。对于 DMA_MEM_TO_DEV 方向,src_addr 是内存缓冲区地址(由DMA引擎自动处理),dst_addr 才是外设FIFO地址。反之,对于 DMA_DEV_TO_MEM,src_addr 是外设FIFO地址。不要手动填写内存侧的物理地址,否则会导致地址映射错误。
2.3.3 突发传输与总线宽度匹配
src_maxburst 和 dst_maxburst 是影响DMA传输效率的关键参数。它们定义了DMA控制器在一次硬件握手信号触发后,连续传输的数据单元个数。
- 匹配规则:
maxburst的值必须与外设FIFO的深度和DMA控制器的能力相匹配。例如,一个深度为8的32位FIFO,可以设置dst_maxburst = 8,dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES。 - 性能影响:过小的burst值会导致频繁的DMA请求和总线仲裁开销;过大的burst值可能造成外设FIFO溢出或欠载。
- 硬件限制:某些DMA控制器要求burst值必须是2的幂(1、2、4、8、16等),且不能超过硬件FIFO深度。
2.4 本章小结
Linux DMA子系统通过 dmaengine 框架实现了硬件无关的DMA操作抽象。本章重点介绍了:
- 三层架构:硬件层、核心框架层、驱动使用层,层间通过标准接口解耦。
- dmaengine框架:提供了从通道请求到传输完成的完整生命周期管理。
- dma_slave_config:作为配置核心,其字段的准确填写直接决定了DMA传输的正确性与效率。