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 过滤函数,返回第一个匹配的通道。

参数说明常见值 / 注意事项
maskDMA 能力位掩码使用 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 在软件与硬件层面的作用范围:

dmaengine 核心 API 调用流程 dma_request_channel dmaengine_slave_config dmaengine_prep_slave_single dmaengine_submit dma_async_issue_pending 传输完成回调 / 轮询状态 dma_release_channel 软件层面 通道资源分配 传输参数配置 描述符构建 硬件层面 DMA 控制器执行 地址 / 长度加载 总线传输

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