4. DMA API 详解:从通道申请到传输完成
在 Linux 内核中,DMA 引擎(dmaengine)框架提供了一套标准化的 API,用于管理 DMA 控制器并执行数据传输。理解从通道申请、描述符构建、提交到最终发起的完整流程,是进行 DMA 性能剖析的基础。本章将深入剖析四个核心 API 函数:dma_request_channel、dmaengine_prep_slave_sg、dmaengine_submit 和 dma_async_issue_pending。
核心流程概览: 一个典型的 DMA 传输事务遵循“申请通道 → 准备描述符 → 提交描述符 → 发起传输”的四步模型。任何一步的失误都可能导致传输失败或性能瓶颈。
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 控制器连续处理多个传输,减少启动开销。
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 到传输完成回调的硬件延迟。这有助于区分软件瓶颈和硬件瓶颈。