3. DMA引擎核心数据结构:struct dma_device、struct dma_chan、struct dma_async_tx_descriptor详解
在Linux DMA引擎框架中,三个核心数据结构构成了驱动与子系统交互的基石:DMA控制器抽象(struct dma_device)、通道抽象(struct dma_chan)和传输描述符(struct dma_async_tx_descriptor)。理解这三者的关系与内部字段,是编写正确、高效DMA驱动的第一步。
核心关系:一个
dma_device 管理多个 dma_chan,每个通道通过提交 dma_async_tx_descriptor 来发起数据传输。
3.1 struct dma_device:DMA控制器抽象
struct dma_device 代表一个物理DMA控制器(如PL330、Intel IOAT、DMA Engine for PCIe等)。驱动开发者需要填充该结构体,并将其注册到内核DMA引擎框架中。
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向DMA控制器的struct device,用于设备模型关联 |
channels |
struct list_head |
该控制器下所有dma_chan的链表头 |
cap_mask |
dma_cap_mask_t |
位掩码,声明控制器支持的操作类型(如DMA_MEMCPY、DMA_SG) |
directions |
dma_cap_mask_t |
支持的传输方向(DEV_TO_MEM、MEM_TO_DEV、MEM_TO_MEM) |
copy_align |
int |
内存拷贝对齐要求(2的幂次),如4表示16字节对齐 |
device_alloc_chan_resources |
函数指针 | 分配通道资源回调 |
device_free_chan_resources |
函数指针 | 释放通道资源回调 |
device_prep_dma_memcpy |
函数指针 | 准备内存拷贝传输描述符 |
device_issue_pending |
函数指针 | 通知硬件开始处理待提交的描述符 |
驱动开发提示:注册
dma_device 时,必须调用 dma_async_device_register()。该函数会将设备加入全局DMA设备列表,并暴露给DMA引擎用户(如ALSA、MMC、SPI子系统)。
3.2 struct dma_chan:通道抽象
struct dma_chan 代表一个独立的DMA通道。每个通道可以独立配置源地址、目的地址、传输宽度和突发长度。用户通过 dma_request_channel() 获取通道,使用完毕后通过 dma_release_channel() 释放。
| 字段 | 类型 | 说明 |
|---|---|---|
device |
struct dma_device * |
指向所属的DMA控制器 |
cookie |
dma_cookie_t |
通道当前传输的cookie值,用于跟踪传输状态 |
chan_id |
int |
通道在控制器内的索引ID |
private |
void * |
驱动私有数据指针,通常指向硬件通道寄存器基址 |
client_count |
int |
当前使用该通道的客户端数量 |
slave_config |
struct dma_slave_config |
从设备配置(地址、宽度、流控模式等) |
3.3 struct dma_async_tx_descriptor:传输描述符
每个DMA传输请求被封装为一个 struct dma_async_tx_descriptor。描述符包含传输所需的全部信息:源地址、目的地址、传输长度、回调函数以及链表指针(用于支持描述符链)。
| 字段 | 类型 | 说明 |
|---|---|---|
chan |
struct dma_chan * |
该描述符所属的DMA通道 |
cookie |
dma_cookie_t |
传输的唯一标识符,由 dma_cookie_assign() 分配 |
callback |
dma_async_tx_callback |
传输完成后的回调函数指针 |
callback_param |
void * |
回调函数的参数 |
tx_list |
struct list_head |
用于将多个描述符链接成传输链 |
phys |
dma_addr_t |
描述符的物理地址(硬件DMA引擎需要直接访问) |
flags |
unsigned long |
控制标志(如DMA_PREP_INTERRUPT、DMA_CTRL_ACK) |
3.4 描述符生命周期与传输流程
一个典型的DMA传输请求经历以下步骤:
- 准备描述符:调用
device_prep_dma_memcpy()等函数,返回struct dma_async_tx_descriptor *。 - 提交描述符:调用
dmaengine_submit(),将描述符加入通道的提交队列,并分配cookie。 - 触发传输:调用
dma_async_issue_pending(),通知硬件开始处理队列中的描述符。 - 等待完成:通过
dma_async_is_tx_complete()轮询,或在回调中等待通知。 - 释放描述符:传输完成后,框架自动回收描述符(若设置了DMA_CTL_ACK标志)。
常见陷阱:描述符的生命周期管理极易出错。如果未正确设置
DMA_CTL_ACK 标志,或者驱动在传输未完成时释放描述符内存,会导致DMA引擎访问已释放的内存,引发内核崩溃。
3.5 数据结构协作示例
以下代码片段展示了三个核心数据结构在实际驱动中的协作方式:
/* 1. 注册DMA设备 */
static struct dma_device my_dma_dev = {
.dev = &pdev->dev,
.cap_mask = { { DMA_MEMCPY } },
.directions = { { DMA_MEM_TO_MEM } },
.copy_align = 4, /* 16字节对齐 */
.device_alloc_chan_resources = my_alloc_chan_resources,
.device_free_chan_resources = my_free_chan_resources,
.device_prep_dma_memcpy = my_prep_dma_memcpy,
.device_issue_pending = my_issue_pending,
};
dma_async_device_register(&my_dma_dev);
/* 2. 用户获取通道并提交传输 */
struct dma_chan *chan = dma_request_channel(mask, filter_fn, NULL);
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
tx = dmaengine_prep_dma_memcpy(chan, dst_phys, src_phys, size, DMA_CTRL_ACK);
tx->callback = my_callback;
tx->callback_param = my_data;
cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);
/* 3. 等待传输完成 */
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) != DMA_COMPLETE)
cpu_relax();
关键设计原则:DMA引擎框架采用“准备-提交-触发”三段式设计,将描述符的构建与硬件执行解耦。这种设计允许驱动在中断上下文之外批量准备描述符,然后一次性触发,提高吞吐量。
3.6 数据结构对比总结
| 数据结构 | 职责 | 生命周期 | 由谁创建 | 由谁销毁 |
|---|---|---|---|---|
dma_device |
DMA控制器抽象 | 设备存在期间 | 驱动probe | 驱动remove |
dma_chan |
DMA通道抽象 | 从分配到释放 | 框架/驱动 | 框架/驱动 |
dma_async_tx_descriptor |
单次传输描述 | 从准备到完成 | 驱动prep回调 | 框架自动回收 |