3. 旧版 dmaengine API 回顾:关键操作函数分析
在旧版 dmaengine 框架中,DMA 驱动的使用遵循一套固定的流程:申请通道 → 准备传输描述符 → 提交传输请求 → 启动传输队列。本章将深入分析四个核心 API 函数,理解其参数、返回值及典型使用场景,为后续升级到新 API 奠定基础。
学习提示:旧版 API 的核心设计思想是“描述符驱动”——每个 DMA 传输任务被封装为一个描述符(descriptor),通过提交和下发机制实现异步传输。理解这一模型是掌握新旧 API 差异的关键。
3.1 整体流程概览
下图展示了旧版 dmaengine 中一次典型 DMA 传输的完整调用链,四个关键函数按顺序执行,构成“申请-准备-提交-下发”的生命周期。
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 传输。
- 四个函数遵循“申请→准备→提交→下发”的严格顺序,缺一不可。