2. Linux DMA子系统架构
Linux DMA 子系统通过一套统一的框架——DMA Engine,将不同厂商、不同硬件特性的 DMA 控制器抽象为标准的软件接口。本章深入剖析该框架的核心数据结构与分层设计,帮助开发者理解 DMA 通道的申请、配置与传输控制机制。
2.1 DMA Engine 框架分层
DMA Engine 框架位于内核 drivers/dma/ 目录下,采用经典的三层架构:
- DMA Engine 核心层:提供通用 API(如
dma_request_channel()、dmaengine_prep_slave_sg()),管理通道生命周期与传输描述符。 - DMA 控制器驱动层:实现
struct dma_device中的回调函数,直接操作硬件寄存器。 - DMA 用户层:包括 ALSA、MMC、SPI 等子系统,通过核心层 API 发起 DMA 传输请求。
关键设计原则: DMA Engine 将“通道分配”与“传输控制”解耦。用户驱动只需关注通道句柄和描述符,无需了解底层寄存器细节。
2.2 核心结构体:dma_device
struct dma_device 是 DMA 控制器驱动的“身份证”,定义在 include/linux/dmaengine.h 中。它描述了控制器的能力、支持的传输类型以及所有操作回调。
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向 DMA 控制器的 struct device,用于设备模型关联 |
channels |
struct list_head |
该控制器下所有 DMA 通道的链表头 |
cap_mask |
dma_cap_mask_t |
位掩码,声明支持的传输类型(如 MEMCPY、SLAVE、CYCLIC) |
directions |
u32 |
位掩码,声明支持的传输方向(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM) |
max_burst |
u32 |
单次突发传输的最大字节数 |
device_alloc_chan_resources |
回调函数指针 | 分配通道硬件资源,返回描述符数量 |
device_prep_slave_sg |
回调函数指针 | 准备散聚(SG)传输描述符 |
device_issue_pending |
回调函数指针 | 将待处理描述符提交给硬件执行 |
device_tx_status |
回调函数指针 | 查询传输状态(已完成/进行中/错误) |
驱动注册示例: 在 probe 函数中,驱动填充
struct dma_device 后调用 dma_async_device_register() 将其注册到核心层。核心层自动将 channels 链表中的每个通道加入全局通道池。
2.3 通道抽象:dma_chan
struct dma_chan 代表一个已分配的 DMA 通道实例。用户驱动通过 dma_request_channel() 获得该结构体指针,后续所有传输操作都基于它进行。
struct dma_chan {
struct dma_device *device; /* 所属 DMA 控制器 */
dma_cookie_t cookie; /* 当前传输的 cookie 值 */
struct list_head global_node; /* 全局通道链表节点 */
void *private; /* 控制器驱动私有数据 */
const char *name; /* 通道名称,用于调试 */
int chan_id; /* 通道 ID,在控制器内唯一 */
struct dma_chan_dev *dev; /* 通道对应的字符设备,用于 sysfs */
};
通道的生命周期管理遵循以下流程:
- 申请:用户驱动调用
dma_request_channel(),核心层遍历全局通道池,匹配cap_mask和过滤函数。 - 配置:通过
dmaengine_slave_config()设置源/目标地址、数据宽度、突发大小等参数。 - 使用:准备描述符 → 提交 → 等待完成回调。
- 释放:调用
dma_release_channel(),核心层将通道归还到空闲池。
2.4 DMA 控制器抽象层:统一硬件差异
不同 SoC 的 DMA 控制器在寄存器布局、通道数量、传输粒度上差异巨大。DMA Engine 通过以下机制实现抽象:
- 能力掩码(cap_mask):用户驱动通过
dma_has_cap()查询控制器是否支持 MEMCPY、SLAVE、CYCLIC 等操作。 - 虚拟通道(virt-dma):核心层提供
struct virt_dma_chan辅助实现,将硬件通道与软件描述符队列解耦,简化驱动开发。 - 描述符重用:通过
dmaengine_desc_set_reuse()和dmaengine_desc_get_reuse()减少内存分配开销,适用于环形缓冲区场景。
常见陷阱: 部分 DMA 控制器不支持 SG 散聚传输,此时
device_prep_slave_sg 回调返回 NULL。驱动必须检查返回值并回退到逐个缓冲区传输,否则会导致内核崩溃。