4. DMA API 详解:从通道申请到传输完成

在 Linux 内核中,DMA 引擎(dmaengine)框架提供了一套标准化的 API,用于管理 DMA 控制器并执行数据传输。理解从通道申请、描述符构建、提交到最终发起的完整流程,是进行 DMA 性能剖析的基础。本章将深入剖析四个核心 API 函数:dma_request_channel、dmaengine_prep_slave_sg、dmaengine_submit 和 dma_async_issue_pending。

核心流程概览: 一个典型的 DMA 传输事务遵循“申请通道 → 准备描述符 → 提交描述符 → 发起传输”的四步模型。任何一步的失误都可能导致传输失败或性能瓶颈。
DMA 传输四步核心 API 调用流程 dma_request_channel 获取 DMA 通道 dmaengine_prep_slave_sg 准备 SG 描述符 dmaengine_submit 提交到等待队列 dma_async_issue_pending 触发硬件执行 返回 dma_chan * 返回 struct dma_async_tx_descriptor * 返回 dma_cookie_t 用于后续状态查询

4.1 dma_request_channel:获取 DMA 通道

dma_request_channel 是 DMA 驱动与 DMA 引擎控制器建立连接的第一步。它根据指定的掩码(mask)和回调函数,从系统中可用的 DMA 通道中筛选并申请一个满足需求的通道。

#include <linux/dmaengine.h>

struct dma_chan *dma_request_channel(const dma_cap_mask_t *mask,
                                     dma_filter_fn filter_fn,
                                     void *filter_param);
参数 类型 说明
mask dma_cap_mask_t * DMA 能力掩码,指定所需传输类型(如 DMA_SLAVE、DMA_MEMCPY)
filter_fn dma_filter_fn 过滤回调函数,用于进一步筛选特定 DMA 控制器或通道
filter_param void * 传递给过滤回调的私有参数,通常为设备结构体指针
性能提示: 在性能测试场景中,建议在初始化阶段一次性申请通道,避免在热路径(hot path)上反复调用 dma_request_channel。通道申请涉及设备树解析和驱动绑定,开销较大。

4.2 dmaengine_prep_slave_sg:准备分散/聚集描述符

该函数用于为 Slave DMA 传输准备一个分散/聚集(SG)描述符。它将一个物理上可能不连续的内存缓冲区列表(scatterlist)转换为 DMA 控制器可以理解的硬件描述符链。

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 指向 scatterlist 数组的指针,描述源或目的缓冲区
sg_len scatterlist 中条目数量
dir 传输方向:DMA_MEM_TO_DEV 或 DMA_DEV_TO_MEM
flags 描述符标志,如 DMA_PREP_INTERRUPT(使能传输完成中断)
context 回调上下文参数,通常为 NULL
常见陷阱: 确保 scatterlist 中的缓冲区已经通过 dma_map_sg 进行映射,并且映射方向与 dir 参数一致。未映射或映射方向错误的缓冲区会导致 DMA 传输数据错误或硬件崩溃。

4.3 dmaengine_submit:提交描述符到等待队列

在描述符准备完成后,需要调用 dmaengine_submit 将其提交到 DMA 通道的等待队列中。该函数返回一个 cookie,用于后续跟踪传输状态。

dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
  • 返回值: dma_cookie_t 类型,是一个递增的整数标识符。
  • 作用: 将描述符加入通道的 pending 链表,但此时 DMA 硬件尚未开始执行。
  • 状态查询: 可通过 dma_async_is_tx_complete 或 dma_wait_for_async_tx 结合 cookie 查询传输是否完成。
性能关键点: dmaengine_submit 通常只进行链表操作,开销较小。但在高吞吐场景下,频繁提交大量小描述符可能导致描述符池耗尽,建议使用 SG 列表合并小传输。

4.4 dma_async_issue_pending:触发硬件执行

这是启动 DMA 传输的最后一步。调用该函数后,DMA 控制器开始处理 pending 队列中的描述符,实际的数据搬运由此开始。

void dma_async_issue_pending(struct dma_chan *chan);
  • 行为: 通知 DMA 硬件从 pending 队列中取出第一个(或一批)描述符开始执行。
  • 异步特性: 该函数立即返回,不等待传输完成。传输完成状态通过中断或轮询获取。
  • 批量提交优化: 可以多次调用 dmaengine_submit 提交多个描述符,然后一次性调用 dma_async_issue_pending,让 DMA 控制器连续处理多个传输,减少启动开销。
DMA 描述符生命周期状态机 已准备 prep_slave_sg 已提交 submit 已发起 issue_pending 已完成 中断/轮询 关键说明 • 从“已提交”到“已发起”之间,描述符在 pending 队列中等待。 • 多次 submit + 一次 issue_pending 可实现批量传输,降低软件开销。 • 传输完成后,描述符通常由驱动自动回收或通过回调释放。

4.5 完整调用示例与性能考量

以下是一个典型的 Slave DMA 传输代码片段,展示了四个 API 的串联使用:

struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
struct scatterlist sg;

// 1. 申请通道
chan = dma_request_channel(mask, my_filter, &my_device);
if (!chan) {
    pr_err("Failed to request DMA channel\n");
    return -ENODEV;
}

// 2. 准备 SG 描述符(假设 sg 已初始化并映射)
tx = dmaengine_prep_slave_sg(chan, &sg, 1, DMA_MEM_TO_DEV,
                              DMA_PREP_INTERRUPT, NULL);
if (!tx) {
    pr_err("Failed to prepare slave sg\n");
    dma_release_channel(chan);
    return -ENOMEM;
}

// 3. 提交描述符
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
    pr_err("Failed to submit DMA descriptor\n");
    dma_release_channel(chan);
    return -EIO;
}

// 4. 发起传输
dma_async_issue_pending(chan);

// 5. 等待传输完成(使用 cookie 查询)
if (dma_wait_for_async_tx(tx) < 0) {
    pr_err("DMA transaction timed out\n");
}
阶段 延迟/开销特征 优化建议
通道申请 高(ms 级),涉及设备枚举和驱动绑定 仅在初始化时调用一次,热路径复用
描述符准备 中(us 级),涉及内存分配和 SG 解析 使用预分配描述符池,减少动态分配
提交 低(ns 级),仅链表操作 批量提交多个描述符,分摊开销
发起传输 低(ns 级),写寄存器触发 合并多次 submit 后单次 issue_pending
性能剖析实践: 在测量 DMA 带宽和延迟时,应使用 ktime_get() 或 tracepoints 分别测量 dmaengine_submit 到 dma_async_issue_pending 的软件开销,以及从 issue_pending 到传输完成回调的硬件延迟。这有助于区分软件瓶颈和硬件瓶颈。