3. DMA 传输描述符:struct dma_async_tx_descriptor、描述符生命周期、tx_submit 与 issue_pending
DMA 传输描述符是驱动与 DMA 引擎之间的核心契约。它封装了一次传输所需的所有参数:源地址、目的地址、传输长度、硬件链路指针、完成回调以及状态标志。理解描述符的结构与生命周期,是正确使用 DMA 引擎的前提。
3.1 描述符核心结构:struct dma_async_tx_descriptor
该结构体定义在 <linux/dmaengine.h> 中,是 DMA 事务的抽象表示。每个描述符对应一个或多个硬件描述符(取决于引擎是否支持 scatter-gather)。
| 字段 | 类型 | 说明 |
|---|---|---|
cookie |
dma_cookie_t |
提交后由引擎分配的传输标识符,用于查询状态 |
flags |
unsigned long |
控制位:DMA_PREP_INTERRUPT(使能完成中断)、DMA_CTRL_ACK 等 |
callback |
dma_async_tx_callback |
传输完成后的回调函数指针(在 tasklet 或中断上下文中调用) |
callback_param |
void * |
回调函数的私有参数,通常指向请求者的上下文 |
tx_submit |
dma_cookie_t (*)(struct dma_async_tx_descriptor *) |
将描述符提交到硬件待处理队列的方法 |
desc_free |
int (*)(struct dma_async_tx_descriptor *) |
释放描述符内存的方法,由驱动在不再需要时调用 |
chan |
struct dma_chan * |
该描述符所属的 DMA 通道 |
phys |
dma_addr_t |
硬件描述符链表的物理地址(用于链式 DMA) |
tx_list |
struct list_head |
用于将多个描述符链接成软件链表(环形缓冲区或散列集合) |
关键设计原则:描述符的
callback 和 callback_param 在 tx_submit 之前设置,提交后引擎拥有描述符的所有权。驱动不应在传输完成前修改描述符内容。
3.2 描述符生命周期
一个描述符从创建到释放经历四个明确阶段。环形缓冲区设计需要精确管理每个阶段的状态转换。
- 预备(Prepared):调用
device_prep_dma_*系列函数(如dmaengine_prep_slave_sg())获得描述符。此时描述符属于驱动,可以设置回调、参数和 flags。 - 提交(Submitted):调用
tx_submit将描述符移交给 DMA 引擎的软件待处理队列。引擎返回一个cookie,驱动用此 cookie 跟踪传输状态。 - 触发(Issued):调用
issue_pending通知引擎将待处理队列中的描述符写入硬件寄存器或链表,启动实际 DMA 传输。 - 完成(Completed):硬件传输结束,引擎调用回调(如果设置了
DMA_PREP_INTERRUPT),驱动在回调中释放描述符或将其归还到空闲池。
环形缓冲区中的生命周期管理:在环形缓冲区设计中,描述符通常从预分配的池中取出(预备),提交后进入软件环形队列,触发后硬件开始处理。完成回调将描述符归还到空闲池,避免频繁的 kmalloc/kfree。这种零分配策略对高吞吐量场景至关重要。
3.3 tx_submit:提交描述符
tx_submit 是描述符的方法,调用后将描述符加入 DMA 引擎的软件待处理队列。该函数是原子操作,不能在中断上下文之外睡眠。
struct dma_async_tx_descriptor *desc;
dma_cookie_t cookie;
desc = dmaengine_prep_slave_sg(chan, sgt, nents, dir, DMA_PREP_INTERRUPT);
if (!desc) {
dev_err(dev, "Failed to prepare DMA descriptor\n");
return -ENOMEM;
}
desc->callback = my_dma_callback;
desc->callback_param = my_priv;
/* 提交描述符,获得 cookie */
cookie = desc->tx_submit(desc);
if (dma_submit_error(cookie)) {
dev_err(dev, "Failed to submit DMA descriptor\n");
desc->desc_free(desc);
return -EIO;
}
| 行为 | 说明 |
|---|---|
| 原子提交 | 将描述符追加到通道的 pending 链表尾部,返回递增的 cookie |
| 所有权转移 | 提交后驱动不应再修改描述符的地址/长度字段,引擎可能随时读取 |
| 错误处理 | 返回 -ENOMEM 或 -EINVAL 时,驱动必须手动调用 desc_free 释放描述符 |
| 中断使能 | 若需要完成回调,必须在预备时设置 DMA_PREP_INTERRUPT 标志 |
3.4 issue_pending:触发传输
issue_pending 是通道的方法,调用后 DMA 引擎将待处理队列中的所有描述符写入硬件,启动实际传输。该函数通常不阻塞,但具体行为取决于硬件实现。
/* 触发所有待处理的描述符 */
dma_async_issue_pending(chan);
/* 或者直接调用通道方法 */
chan->device->device_issue_pending(chan);
常见陷阱:部分 DMA 引擎(如某些 ARM PL08x)在
issue_pending 时只写入第一个描述符,后续描述符通过硬件链表自动加载。驱动必须确保描述符的 phys 字段指向有效的硬件描述符链表,否则传输会在第一个描述符完成后停止。
3.5 描述符状态查询
驱动通过 cookie 查询传输状态,常用函数为 dma_async_is_tx_complete() 或 dmaengine_tx_status()。
| 函数 | 返回值 | 使用场景 |
|---|---|---|
dma_async_is_tx_complete() |
DMA_COMPLETE / DMA_IN_PROGRESS / DMA_PAUSED / DMA_ERROR |
轮询模式,非中断驱动 |
dmaengine_tx_status() |
同上,同时返回剩余字节数 | 需要知道传输进度的场景 |
enum dma_status status;
status = dmaengine_tx_status(chan, cookie, NULL);
if (status == DMA_COMPLETE) {
/* 传输完成,可以重用描述符 */
} else if (status == DMA_ERROR) {
/* 传输出错,需要复位通道 */
}
3.6 环形缓冲区中的描述符管理策略
在环形缓冲区设计中,描述符的生命周期与缓冲区槽位绑定。典型策略如下:
- 预备阶段:从空闲池获取描述符,设置源/目的地址指向环形缓冲区当前槽位。
- 提交阶段:调用
tx_submit,将描述符加入软件待处理队列,同时将槽位标记为“提交中”。 - 触发阶段:调用
issue_pending,硬件开始处理。此时槽位数据正在被 DMA 读取或写入。 - 完成阶段:回调中将描述符归还空闲池,槽位标记为“空闲”,唤醒等待的生产者或消费者。
性能建议:在环形缓冲区中,
tx_submit 和 issue_pending 应尽量批量调用。每次提交多个描述符后统一触发一次 issue_pending,减少硬件寄存器访问次数,提高总线利用率。