3. dmaengine 核心 API:通道申请、释放、描述符准备、提交与触发
DMA 传输的完整生命周期由五个核心 API 串联而成:通道申请 → 描述符准备 → 提交 → 触发 → 通道释放。本章逐一拆解每个 API 的语义、参数陷阱与典型用法,并配合内联 SVG 展示调用流程。
3.1 dma_request_channel —— 获取 DMA 通道
函数原型:
struct dma_chan *dma_request_channel(const dma_cap_mask_t *mask,
dma_filter_fn fn,
void *fn_param);
该函数是驱动获取 DMA 通道的唯一入口。内核根据 mask 指定的能力掩码(如 DMA_SLAVE、DMA_MEMCPY)遍历系统中所有 DMA 控制器,对每个候选通道调用 fn 过滤函数,返回第一个匹配的通道。
| 参数 | 说明 | 常见值 / 注意事项 |
|---|---|---|
| mask | DMA 能力位掩码 | 使用 dma_cap_set(DMA_SLAVE, mask) 设置 |
| fn | 过滤回调函数 | 通常传入 dma_filter_match 或自定义匹配函数 |
| fn_param | 过滤参数 | 常为设备树中的 dmas 属性解析出的 struct of_phandle_args |
最佳实践: 在设备树匹配的驱动中,优先使用
dma_request_slave_channel(dev, name) 封装函数,它内部自动完成 mask 设置和 of 过滤,代码更简洁。
3.2 dma_release_channel —— 归还 DMA 通道
函数原型:
void dma_release_channel(struct dma_chan *chan);
通道使用完毕后必须释放,否则会导致 DMA 控制器资源泄漏。释放操作会将通道状态重置,并归还到空闲池中供其他驱动复用。
注意: 释放前必须确保该通道上所有已提交的描述符都已完成或已终止。未完成的描述符会导致 DMA 控制器状态不一致,甚至触发硬件错误。
3.3 dmaengine_prep_slave_single —— 准备单次传输描述符
函数原型:
struct dma_async_tx_descriptor *dmaengine_prep_slave_single(
struct dma_chan *chan,
dma_addr_t buf,
size_t len,
enum dma_transfer_direction dir,
unsigned long flags);
该 API 为 单次缓冲区传输 准备一个 DMA 描述符。描述符是 DMA 引擎的最小传输单元,包含源地址、目的地址、传输长度、方向、回调函数等元数据。
| 参数 | 含义 | 典型值 |
|---|---|---|
| buf | 外设侧 DMA 地址(物理地址) | 通过 dma_map_single 获得 |
| len | 传输字节数 | 必须 ≤ 通道支持的 max_sg_len |
| dir | 传输方向 | DMA_MEM_TO_DEV / DMA_DEV_TO_MEM |
| flags | 描述符控制标志 | DMA_PREP_INTERRUPT 使能传输完成中断 |
关键点: 描述符此时只是“准备好”,并未提交到硬件队列。可以同时准备多个描述符,构建描述符链。
3.4 dmaengine_submit —— 提交描述符到传输队列
函数原型:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
将描述符提交到 DMA 通道的 软件传输队列 中。提交后返回一个 cookie(传输标识符),可用于后续查询传输状态。多个描述符可以按顺序提交,形成链式传输。
| 返回值 | 含义 |
|---|---|
cookie > 0 | 提交成功,cookie 为该传输的唯一标识 |
cookie < 0 | 提交失败(如队列满、描述符无效) |
链式传输技巧: 调用
dmaengine_submit 前,通过 desc->callback = my_callback 设置每个描述符的完成回调,实现逐段处理。
3.5 dma_async_issue_pending —— 触发 DMA 传输
函数原型:
void dma_async_issue_pending(struct dma_chan *chan);
该函数是 DMA 传输的 启动开关。调用后,DMA 控制器开始处理通道队列中已提交的所有描述符。如果队列中有多个描述符,硬件会自动按顺序执行,直到队列为空。
常见错误: 忘记调用
dma_async_issue_pending,导致描述符一直停留在软件队列中,硬件从未启动传输。这是 DMA 驱动调试中最容易忽略的问题。
3.6 核心 API 调用流程总览
下图展示了从通道申请到传输完成的完整调用链,以及各 API 在软件与硬件层面的作用范围:
3.7 典型代码片段:完整传输流程
/* 1. 申请通道 */
struct dma_chan *chan = dma_request_slave_channel(dev, "rx");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 2. 配置从设备参数(略,详见第4章) */
dmaengine_slave_config(chan, &config);
/* 3. 准备描述符 */
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_single(chan, buf_dma, len,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT);
if (!desc)
goto err_free_chan;
/* 4. 设置完成回调 */
desc->callback = my_transfer_done;
desc->callback_param = my_data;
/* 5. 提交到队列 */
dma_cookie_t cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie))
goto err_free_desc;
/* 6. 触发传输 */
dma_async_issue_pending(chan);
/* 7. 等待完成(轮询或等待回调) */
wait_for_completion(&my_data->done);
/* 8. 释放通道 */
dma_release_channel(chan);
调试建议: 在
dma_async_issue_pending 之后,可以通过 dmaengine_tx_status(chan, cookie, &state) 查询传输进度,state.residue 表示剩余未传输的字节数。
3.8 API 对比速查表
| API | 作用 | 调用时机 | 返回值 |
|---|---|---|---|
dma_request_channel | 获取 DMA 通道 | 驱动 probe 阶段 | struct dma_chan * |
dma_release_channel | 释放 DMA 通道 | 驱动 remove 阶段 | void |
dmaengine_prep_slave_single | 准备单次传输描述符 | 每次传输前 | struct dma_async_tx_descriptor * |
dmaengine_submit | 提交描述符到队列 | 描述符准备后 | dma_cookie_t |
dma_async_issue_pending | 触发硬件传输 | 提交描述符后 | void |