3. dmaengine 核心数据结构详解
dmaengine 子系统通过三个核心数据结构抽象了 DMA 控制器的硬件能力、通道状态以及传输描述符。理解 struct dma_device、struct dma_chan 和 struct dma_async_tx_descriptor 是编写 DMA 驱动和进行 DMA 传输的基础。本节将逐一剖析这三个结构体的关键字段与设计意图。
3.1 struct dma_device:DMA 控制器抽象
struct dma_device 是 dmaengine 框架对 DMA 控制器硬件的软件抽象。它包含了控制器的能力描述、操作函数集以及通道管理信息。驱动开发者需要填充该结构体并注册到内核。
dma_async_device_register() 将其注册到 dmaengine 核心。核心会遍历其 channels 链表,为每个通道创建对应的 dma_chan 实例。
3.1.1 关键字段解析
| 字段名 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向 DMA 控制器的硬件设备结构体,用于设备模型关联和电源管理。 |
channels |
struct list_head |
链表头,挂载该控制器下所有可用的 dma_chan 实例。 |
cap_mask |
dma_cap_mask_t |
位掩码,声明控制器支持的传输类型(如 MEMCPY、DEV_TO_MEM、CYCLIC 等)。 |
directions |
dma_cap_mask_t |
位掩码,声明支持的传输方向(DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM)。 |
src_addr_widths |
u32 |
位掩码,声明支持的源地址宽度(1、2、4、8 字节)。 |
dst_addr_widths |
u32 |
位掩码,声明支持的目的地址宽度。 |
max_burst |
u32 |
单次突发传输的最大长度(字节数),影响总线效率。 |
device_alloc_chan_resources |
函数指针 | 分配并初始化通道硬件资源(如描述符池、中断)。 |
device_free_chan_resources |
函数指针 | 释放通道硬件资源。 |
device_prep_dma_memcpy |
函数指针 | 准备一次内存到内存的拷贝传输,返回描述符。 |
device_prep_slave_sg |
函数指针 | 准备一次外设与内存间的分散/聚集传输。 |
device_issue_pending |
函数指针 | 将通道 pending 队列中的描述符提交给硬件执行。 |
cap_mask 或 directions,导致上层通过 dma_request_channel() 无法匹配到该控制器。务必在注册前正确初始化这些能力掩码。
3.1.2 操作函数集与能力声明
dma_device 中的函数指针构成了 DMA 驱动的操作接口。核心框架通过调用这些函数来与硬件交互。能力声明(cap_mask)则用于在通道请求阶段进行过滤,确保上层应用只能获取到支持所需传输类型的通道。
// 示例:初始化 dma_device 的能力和操作函数
struct dma_device *my_dma_dev;
// 设置支持 MEMCPY 和 SLAVE 传输
dma_cap_set(DMA_MEMCPY, my_dma_dev->cap_mask);
dma_cap_set(DMA_SLAVE, my_dma_dev->cap_mask);
// 设置支持双向传输
dma_cap_set(DMA_MEM_TO_DEV, my_dma_dev->directions);
dma_cap_set(DMA_DEV_TO_MEM, my_dma_dev->directions);
// 绑定操作函数
my_dma_dev->device_alloc_chan_resources = my_alloc_chan_resources;
my_dma_dev->device_free_chan_resources = my_free_chan_resources;
my_dma_dev->device_prep_dma_memcpy = my_prep_memcpy;
my_dma_dev->device_issue_pending = my_issue_pending;
// 注册到 dmaengine 核心
dma_async_device_register(my_dma_dev);
3.2 struct dma_chan:DMA 通道抽象
struct dma_chan 代表一个 DMA 通道。它可以是物理通道(硬件上独立的 DMA 引擎),也可以是虚拟通道(通过软件复用)。该结构体是上层驱动与底层硬件交互的桥梁。
3.2.1 关键字段解析
| 字段名 | 类型 | 说明 |
|---|---|---|
device |
struct dma_device * |
指向所属的 DMA 控制器设备。 |
cookie |
dma_cookie_t |
通道当前分配的最后一个描述符的 cookie 值,用于跟踪传输进度。 |
completed_cookie |
dma_cookie_t |
通道上最后一个完成的描述符的 cookie 值。 |
chan_id |
int |
通道在控制器内的唯一 ID,通常对应硬件通道编号。 |
private |
void * |
私有数据指针,驱动可用来关联自定义的通道上下文。 |
client_count |
atomic_t |
引用计数,记录当前有多少个客户端在使用该通道。 |
slave_config |
struct dma_slave_config |
外设配置信息,包括地址、宽度、突发大小等,通过 dmaengine_slave_config() 设置。 |
3.2.2 通道生命周期与状态管理
通道的生命周期由 dmaengine 核心管理。从 dma_request_channel() 获取通道,到 dma_release_channel() 释放通道。在传输过程中,通道的状态通过 cookie 机制来追踪。
- 分配: 上层调用
dma_request_channel(),核心根据 cap_mask 匹配并调用驱动的device_alloc_chan_resources。 - 配置: 调用
dmaengine_slave_config()设置外设参数。 - 使用: 通过
dmaengine_prep_*()准备描述符,然后dmaengine_submit()提交到 pending 队列。 - 释放: 调用
dma_release_channel(),核心调用驱动的device_free_chan_resources。
// 示例:获取并配置 DMA 通道
struct dma_chan *chan;
struct dma_slave_config config = {
.direction = DMA_MEM_TO_DEV,
.src_addr = (dma_addr_t)src_buf,
.dst_addr = (dma_addr_t)dst_reg,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 16,
.dst_maxburst = 16,
};
// 请求通道(假设 cap_mask 已定义)
chan = dma_request_channel(mask, filter_fn, filter_param);
if (IS_ERR(chan)) {
pr_err("Failed to request DMA channel\n");
return PTR_ERR(chan);
}
// 配置通道
dmaengine_slave_config(chan, &config);
3.3 struct dma_async_tx_descriptor:传输描述符
struct dma_async_tx_descriptor 是 dmaengine 中最核心的数据结构之一,它封装了一次 DMA 传输的所有信息,包括源地址、目的地址、传输长度、回调函数以及链表指针。
3.3.1 关键字段解析
| 字段名 | 类型 | 说明 |
|---|---|---|
chan |
struct dma_chan * |
指向该描述符所属的 DMA 通道。 |
cookie |
dma_cookie_t |
描述符的唯一标识符,由 dmaengine_submit() 分配。 |
flags |
unsigned long |
控制传输行为的标志位,如 DMA_PREP_INTERRUPT(使能传输完成中断)、DMA_CTRL_ACK(描述符使用后自动释放)。 |
callback |
dma_async_tx_callback |
传输完成后的回调函数指针。 |
callback_param |
void * |
传递给回调函数的参数。 |
tx_submit |
函数指针 | 将描述符提交到通道的 pending 队列,由 dmaengine_submit() 内部调用。 |
desc_free |
函数指针 | 释放描述符及其关联资源。 |
tx_list |
struct list_head |
链表节点,用于将多个描述符链接成传输链(DMA 链表传输)。 |
phys |
dma_addr_t |
描述符的物理地址(用于硬件链表模式)。 |
3.3.2 描述符生命周期与回调机制
描述符的生命周期从驱动调用 device_prep_*() 开始,到传输完成或调用 dmaengine_desc_free() 结束。回调函数是异步通知的关键,通常在 DMA 中断处理函数中被调用。
// 示例:准备、提交并设置回调
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
// 准备描述符(假设 chan 已配置)
tx = dmaengine_prep_slave_sg(chan, sgl, sg_len, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
if (!tx) {
pr_err("Failed to prepare slave sg\n");
return -ENOMEM;
}
// 设置回调
tx->callback = my_dma_callback;
tx->callback_param = my_data;
// 提交描述符
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
pr_err("Failed to submit descriptor\n");
return -EIO;
}
// 启动传输
dma_async_issue_pending(chan);
3.4 核心数据结构关系图
下图展示了 dma_device、dma_chan 和 dma_async_tx_descriptor 三者之间的层级与引用关系:
3.5 数据结构对比总结
| 特性 | dma_device | dma_chan | dma_async_tx_descriptor |
|---|---|---|---|
| 抽象层级 | 控制器级 | 通道级 | 传输任务级 |
| 主要职责 | 声明能力、提供操作函数集 | 管理通道状态、连接控制器与客户端 | 封装传输参数、管理回调与链表 |
| 生命周期 | 驱动加载到卸载 | 从请求到释放 | 从准备到完成或释放 |
| 关键操作 | dma_async_device_register() | dma_request_channel() / dma_release_channel() | dmaengine_submit() / dmaengine_desc_free() |
| 关联关系 | 包含 channels 链表 | 包含 device 指针 | 包含 chan 指针 |