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 来发起数据传输。
DMA引擎三层数据结构关系图 struct dma_device dma_chan[0] 通道0 dma_chan[1] 通道1 dma_chan[N] 通道N dma_async_tx_descriptor dma_async_tx_descriptor 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 从设备配置(地址、宽度、流控模式等)
DMA通道状态机 空闲 (IDLE) 忙碌 (BUSY) 暂停 (PAUSED) tx_submit pause resume complete terminate_all 通道状态转换由DMA引擎框架管理,驱动通过回调感知状态变化

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传输请求经历以下步骤:

  1. 准备描述符:调用 device_prep_dma_memcpy() 等函数,返回 struct dma_async_tx_descriptor *。
  2. 提交描述符:调用 dmaengine_submit(),将描述符加入通道的提交队列,并分配cookie。
  3. 触发传输:调用 dma_async_issue_pending(),通知硬件开始处理队列中的描述符。
  4. 等待完成:通过 dma_async_is_tx_complete() 轮询,或在回调中等待通知。
  5. 释放描述符:传输完成后,框架自动回收描述符(若设置了DMA_CTL_ACK标志)。
常见陷阱:描述符的生命周期管理极易出错。如果未正确设置 DMA_CTL_ACK 标志,或者驱动在传输未完成时释放描述符内存,会导致DMA引擎访问已释放的内存,引发内核崩溃。
描述符生命周期流程图 准备描述符 提交描述符 触发传输 等待完成 释放描述符 关键API调用序列 chan->device->device_prep_dma_memcpy() → dmaengine_submit() → dma_async_issue_pending() → dma_async_is_tx_complete() / callback → 框架自动回收描述符

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回调 框架自动回收