2. Linux DMA引擎框架概述:dmaengine子系统架构、dma_device与dma_chan结构体、dmaengine核心API
Linux内核的DMA引擎框架(dmaengine)为设备驱动开发者提供了一套统一、抽象的DMA操作接口。该框架屏蔽了不同DMA控制器(DMAC)的硬件差异,使得外设驱动(如SPI、I2S、MMC)无需关心底层DMA寄存器的具体操作,只需通过标准API即可发起数据传输。本章将深入剖析dmaengine子系统的三层架构、核心数据结构以及关键API的设计哲学。
2.1 dmaengine子系统架构
dmaengine子系统采用经典的分层设计,从上至下依次为:DMA用户层、核心框架层和DMA控制器驱动层。这种分层结构确保了代码的可移植性和可扩展性。
2.2 核心数据结构:dma_device 与 dma_chan
dmaengine框架围绕两个核心结构体展开:dma_device 描述DMA控制器的能力,dma_chan 代表一个已分配的DMA通道实例。理解这两个结构体是开发DMA引擎驱动的第一步。
2.2.1 dma_device:DMA控制器的能力描述
struct dma_device 定义在 include/linux/dmaengine.h 中,是DMA控制器驱动向核心框架注册的“名片”。它包含了控制器支持的操作模式、通道列表、以及一系列函数指针(回调)。
| 字段/回调 | 类型/原型 | 说明 |
|---|---|---|
| dev | struct device * |
指向DMA控制器的struct device,用于驱动模型绑定和电源管理 |
| channels | struct list_head |
该控制器下所有可用DMA通道的链表头 |
| cap_mask | dma_cap_mask_t |
位掩码,标识控制器支持哪些传输类型(如DMA_MEMCPY、DMA_SLAVE、DMA_CYCLIC) |
| directions | u32 |
位掩码,标识支持的DMA方向(DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV) |
| device_alloc_chan_resources | int (*)(struct dma_chan *) |
分配通道资源(如中断、描述符内存池),在通道被用户请求时调用 |
| device_free_chan_resources | void (*)(struct dma_chan *) |
释放通道资源,在通道被释放时调用 |
| device_prep_dma_memcpy | struct dma_async_tx_descriptor *(*)(struct dma_chan *, dma_addr_t, dma_addr_t, size_t, unsigned long) |
准备一个内存到内存的拷贝描述符(DMA_MEMCPY模式) |
| device_prep_slave_sg | struct dma_async_tx_descriptor *(*)(struct dma_chan *, struct scatterlist *, unsigned int, struct scatterlist *, unsigned int, unsigned long, void *) |
准备一个外设与内存之间的SG列表传输描述符(DMA_SLAVE模式) |
| device_issue_pending | void (*)(struct dma_chan *) |
通知DMA控制器开始处理通道上已提交的待处理描述符 |
| device_tx_status | enum dma_status (*)(struct dma_chan *, dma_cookie_t, struct dma_tx_state *) |
查询指定cookie对应的传输状态(进行中、完成、错误) |
device_prep_dma_memcpy。核心框架通过 cap_mask 来判断用户请求是否合法。
2.2.2 dma_chan:DMA通道的运行时实例
struct dma_chan 是用户驱动与DMA控制器驱动交互的“手柄”。每个 dma_chan 实例对应一个物理DMA通道(或虚拟通道)。
struct dma_chan {
struct dma_device *device; // 所属的DMA控制器
dma_cookie_t cookie; // 当前传输的cookie(用于状态查询)
dma_cookie_t completed_cookie; // 最近完成的传输cookie
void *private; // 控制器驱动的私有数据
struct list_head device_node; // 挂入dma_device->channels链表
unsigned int chan_id; // 通道ID(在控制器内唯一)
struct dma_chan_dev *dev; // 对应的字符设备(用于sysfs)
const struct dma_slave_config *slave_config; // 外设配置(slave模式时有效)
};
用户驱动通过 dma_request_channel() 或设备树解析获得 dma_chan 指针,之后所有操作(如准备描述符、提交、查询状态)都基于该指针进行。
2.3 dmaengine核心API
dmaengine核心API是用户驱动与框架交互的主要途径。这些API封装了描述符的创建、提交、执行以及状态查询等关键操作。根据使用场景,可以将其分为三类:通道管理、传输控制、状态查询。
2.3.1 通道管理API
- dma_request_channel():根据掩码和过滤函数请求一个满足条件的DMA通道。这是用户驱动获取通道的唯一入口。
- dma_release_channel():释放已分配的DMA通道,归还给框架。
- dma_get_slave_channel():在已经知道具体通道的情况下获取通道(通常用于设备树解析后的通道绑定)。
2.3.2 传输控制API
传输控制API遵循“准备-提交-执行”的三步曲模式,这是dmaengine框架的核心设计模式。
| 步骤 | API函数 | 作用 |
|---|---|---|
| 1. 准备 | dmaengine_prep_slave_sg()dmaengine_prep_dma_memcpy()dmaengine_prep_dma_cyclic() |
创建一个DMA传输描述符(struct dma_async_tx_descriptor),配置源地址、目的地址、长度、方向等参数。此时尚未提交到硬件。 |
| 2. 提交 | dmaengine_submit() |
将准备好的描述符提交到通道的待处理队列中。返回一个cookie(dma_cookie_t),用于后续查询传输状态。 |
| 3. 执行 | dma_async_issue_pending() |
通知DMA控制器开始处理通道上所有已提交但尚未执行的描述符。此函数通常不会阻塞,DMA传输在后台进行。 |
// 典型的使用模式(DMA Slave模式)
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
// 1. 准备描述符
tx = dmaengine_prep_slave_sg(chan, sgl, sg_len, direction, DMA_PREP_INTERRUPT, NULL);
if (!tx) {
dev_err(dev, "Failed to prepare slave sg\n");
return -ENOMEM;
}
// 设置完成回调
tx->callback = my_dma_callback;
tx->callback_param = my_data;
// 2. 提交描述符
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
dev_err(dev, "Failed to submit DMA descriptor\n");
return -EIO;
}
// 3. 触发执行
dma_async_issue_pending(chan);
2.3.3 状态查询与同步API
- dma_async_is_tx_complete():查询指定cookie的传输是否完成。返回
DMA_COMPLETE、DMA_IN_PROGRESS或DMA_ERROR。 - dma_sync_wait():阻塞等待指定cookie的传输完成。适用于需要同步的场景(如内存到内存拷贝)。
- dmaengine_terminate_sync():立即终止通道上的所有传输,并等待硬件停止。常用于错误恢复或设备关闭。
dma_sync_wait()。对于内存到内存的批量拷贝,可以使用 dmaengine_async_device_issue_pending() 配合 dma_wait_for_async_tx() 实现高效的同步等待。
2.4 描述符生命周期与回调机制
每个DMA传输描述符(struct dma_async_tx_descriptor)都经历“创建 → 提交 → 执行 → 完成 → 释放”的生命周期。核心框架通过引用计数管理描述符的内存,并在传输完成时调用用户注册的回调函数。
2.5 关键数据结构关系总结
下表总结了dmaengine框架中几个关键数据结构之间的关系与职责,帮助开发者快速建立全局视图。
| 数据结构 | 所属层次 | 主要职责 | 生命周期 |
|---|---|---|---|
dma_device |
控制器驱动层 | 描述控制器能力,提供回调函数指针 | 驱动加载时创建,卸载时销毁 |
dma_chan |
核心框架层 | 代表一个已分配的DMA通道,连接用户与控制器 | 用户请求时创建,释放时销毁 |
dma_async_tx_descriptor |
核心框架层 | 描述一次DMA传输的配置、回调、cookie | prep_*时创建,回调完成后释放 |
dma_slave_config |
用户层 | 配置外设的DMA参数(地址、宽度、突发长度) | 由用户驱动栈上分配,配置后不再使用 |
dma_device 回调函数原型和 dma_chan 的使用模式。理解“准备-提交-执行”的三步曲是掌握dmaengine框架的关键。