3. 旧版 dmaengine API 回顾:关键操作函数分析

在旧版 dmaengine 框架中,DMA 驱动的使用遵循一套固定的流程:申请通道 → 准备传输描述符 → 提交传输请求 → 启动传输队列。本章将深入分析四个核心 API 函数,理解其参数、返回值及典型使用场景,为后续升级到新 API 奠定基础。

学习提示:旧版 API 的核心设计思想是“描述符驱动”——每个 DMA 传输任务被封装为一个描述符(descriptor),通过提交和下发机制实现异步传输。理解这一模型是掌握新旧 API 差异的关键。

3.1 整体流程概览

下图展示了旧版 dmaengine 中一次典型 DMA 传输的完整调用链,四个关键函数按顺序执行,构成“申请-准备-提交-下发”的生命周期。

旧版 dmaengine 传输流程(四个关键函数) dma_request_channel 申请 DMA 通道 dmaengine_prep_slave_sg 准备 SG 传输描述符 dmaengine_submit 提交到传输队列 dma_async_issue_pending 下发所有待处理请求 说明:旧版 API 中,dmaengine_submit 仅将描述符加入队列,必须调用 dma_async_issue_pending 才能真正启动硬件传输。 dmaengine_prep_slave_sg 返回的描述符可被多次提交,但通常建议每次传输重新准备。

3.2 dma_request_channel — 申请 DMA 通道

该函数是 DMA 驱动开发的入口,用于向 dmaengine 核心层请求一个满足特定能力的 DMA 通道。其原型如下:

struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
                                     dma_filter_fn filter_fn,
                                     void *filter_param);
参数/返回值 类型 说明
mask dma_cap_mask_t 位掩码,指定所需 DMA 能力(如 DMA_SLAVE、DMA_CYCLIC 等)
filter_fn dma_filter_fn 回调函数,用于进一步筛选通道(通常匹配设备树或硬件实例)
filter_param void * 传递给 filter_fn 的私有参数,通常为设备结构体指针
返回值 struct dma_chan * 成功返回通道指针,失败返回 NULL
典型用法:
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
chan = dma_request_channel(mask, my_filter_fn, &my_device);
if (!chan) {
    dev_err(dev, "Failed to request DMA channel\n");
    return -ENODEV;
}

3.3 dmaengine_prep_slave_sg — 准备 SG 传输描述符

该函数将上层提供的分散/聚集(SG)列表转换为 DMA 引擎可执行的硬件描述符。它是旧版 API 中最常用的传输准备接口。

struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
    struct dma_chan *chan,
    struct scatterlist *sgl,
    unsigned int sg_len,
    enum dma_transfer_direction dir,
    unsigned long flags,
    void *context);
参数 说明
chan 已申请的 DMA 通道
sgl SG 列表首地址,每个条目描述一段物理连续内存
sg_len SG 条目数量
dir 传输方向:DMA_MEM_TO_DEV / DMA_DEV_TO_MEM / DMA_MEM_TO_MEM
flags 控制标志,如 DMA_PREP_INTERRUPT(使能传输完成中断)
context 回调上下文指针,通常为 NULL
返回值 描述符指针,失败返回 NULL
常见陷阱:旧版 API 中,dmaengine_prep_slave_sg 返回的描述符默认是一次性的——传输完成后描述符自动释放。如果需要循环传输(如音频),应使用 dmaengine_prep_dma_cyclic 替代。

3.4 dmaengine_submit — 提交传输请求

将准备好的描述符提交到 DMA 通道的待处理队列中。该函数不会立即启动硬件传输,而是将描述符加入队列尾部等待下发。

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
项目 说明
参数 desc 由 prep 系列函数返回的描述符指针
返回值 DMA cookie(传输标识符),用于后续查询传输状态;失败返回 -EINVAL
行为 将描述符加入通道的 pending 链表,不操作硬件寄存器
关键设计:dmaengine_submit 的“延迟下发”机制允许驱动在提交多个传输后再统一启动,减少硬件交互次数。这在批量小数据场景中能显著提升性能。

3.5 dma_async_issue_pending — 下发所有待处理请求

该函数是真正“点燃” DMA 传输的触发器。调用后,dmaengine 核心会将 pending 队列中的所有描述符写入硬件寄存器,启动 DMA 引擎开始传输。

void dma_async_issue_pending(struct dma_chan *chan);
项目 说明
参数 chan 目标 DMA 通道
返回值 无
行为 将通道 pending 队列中的描述符全部写入硬件,启动传输
性能建议:在需要连续传输多个 SG 列表时,建议先依次调用 dmaengine_submit 提交所有描述符,最后只调用一次 dma_async_issue_pending。这样可以减少硬件寄存器访问次数,提高总线利用率。

3.6 四个函数的协作关系

下表总结了四个函数在 DMA 传输生命周期中的角色与协作方式:

阶段 函数 核心作用 是否操作硬件
1. 通道获取 dma_request_channel 从 dmaengine 核心获取可用通道 否(软件分配)
2. 描述符准备 dmaengine_prep_slave_sg 将 SG 列表转换为硬件描述符 否(内存操作)
3. 提交队列 dmaengine_submit 将描述符加入通道 pending 队列 否(链表操作)
4. 启动传输 dma_async_issue_pending 将队列描述符写入硬件,启动 DMA 是

从表中可以看出,前三个阶段均为软件层面的准备工作,只有最后一个阶段涉及硬件寄存器操作。这种设计使得旧版 dmaengine 能够高效地批量处理传输请求,但也带来了一个常见问题:开发者容易忘记调用 dma_async_issue_pending,导致传输“卡住”在 pending 队列中。

3.7 完整调用示例

以下代码片段展示了一个典型的 SPI 从设备使用旧版 DMA 进行接收的完整流程:

/* 1. 申请通道 */
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
chan = dma_request_channel(mask, spi_dma_filter, &spi->dev);

/* 2. 准备 SG 描述符(接收方向) */
desc = dmaengine_prep_slave_sg(chan, rx_sg, rx_sg_len,
                                DMA_DEV_TO_MEM,
                                DMA_PREP_INTERRUPT, NULL);
if (!desc) {
    dma_release_channel(chan);
    return -EINVAL;
}

/* 3. 设置回调(可选) */
desc->callback = spi_dma_rx_callback;
desc->callback_param = spi;

/* 4. 提交到队列 */
cookie = dmaengine_submit(desc);

/* 5. 下发启动 */
dma_async_issue_pending(chan);
升级注意:在新版 dmaengine API 中,dma_request_channel 已被 dma_request_chan 替代,dmaengine_prep_slave_sg 的参数和返回类型也有调整。第 5 章将详细对比这些差异。

3.8 本章小结

  • dma_request_channel:通过能力掩码和过滤函数获取 DMA 通道,是驱动初始化的第一步。
  • dmaengine_prep_slave_sg:将 SG 列表转换为硬件描述符,支持 MEM<->DEV 方向。
  • dmaengine_submit:将描述符加入 pending 队列,返回 cookie 用于状态查询。
  • dma_async_issue_pending:唯一操作硬件的函数,启动实际 DMA 传输。
  • 四个函数遵循“申请→准备→提交→下发”的严格顺序,缺一不可。