4. 旧版 dmaengine API 回顾:回调机制与事务生命周期管理

在开始升级到新 API 之前,必须深入理解旧版 dmaengine 的核心机制。本章聚焦于 回调机制 与 事务生命周期,这是驱动开发者最容易出错、也是新旧 API 差异最大的两个领域。掌握这些概念,后续的移植工作才能有的放矢。

4.1 事务描述符与回调注册

旧版 API 中,一次 DMA 操作被抽象为一个 dma_async_tx_descriptor 结构体。该结构体不仅描述了传输的源、目的、长度等硬件参数,还内嵌了完成回调函数及其参数。

核心数据结构: struct dma_async_tx_descriptor 中的回调相关字段。
struct dma_async_tx_descriptor {
    dma_cookie_t cookie;                /* 事务的唯一标识 */
    enum dma_ctrl_flags flags;          /* 控制标志,如 DMA_PREP_INTERRUPT */
    dma_async_tx_callback callback;     /* 完成回调函数指针 */
    void *callback_param;               /* 回调函数的用户参数 */
    struct dma_chan *chan;              /* 所属的 DMA 通道 */
    /* ... 其他硬件描述字段 ... */
};

回调函数的类型定义如下:

typedef void (*dma_async_tx_callback)(void *callback_param);

驱动开发者通过 tx_submit 将描述符提交到硬件队列前,必须先设置好 callback 和 callback_param。这一设计将“传输描述”与“完成通知”紧密耦合在一起。

4.2 事务生命周期:从分配到完成

一个典型的旧版 DMA 事务经历以下五个阶段。理解每个阶段的状态转换,是正确使用 API 的前提。

关键认知: 旧版 API 中,事务的生命周期是线性的,且回调的触发时机严格绑定在硬件中断处理路径中。
旧版 DMA 事务生命周期 1. 分配 dma_prep_*() 2. 提交 tx_submit() 3. 挂起 等待硬件调度 4. 执行 硬件传输中 5. 完成 回调触发 中断服务例程 (ISR) 调用 callback(callback_param)

4.3 tx_submit 与 cookie 机制

tx_submit 是连接软件准备与硬件执行的桥梁。调用该函数后,描述符被移交给 DMA 引擎,引擎返回一个 dma_cookie_t 类型的 cookie,用于后续查询事务状态。

函数/宏 作用 返回值
tx_submit() 将描述符提交到通道的 pending 队列 dma_cookie_t (成功) / 负值 (失败)
dma_async_is_complete() 检查指定 cookie 对应的事务是否完成 DMA_COMPLETE / DMA_IN_PROGRESS / DMA_ERROR
dma_async_tx_descriptor_init() 初始化描述符,设置 cookie 为初始值 void
常见陷阱: 旧版 API 中,tx_submit 成功后,描述符的所有权转移给 DMA 引擎。驱动不能再修改描述符中的回调函数指针或参数,否则会导致竞态条件或野指针。

4.4 tx_status 与轮询模式

除了依赖回调,旧版 API 也支持通过 tx_status 轮询事务状态。这在不需要中断通知或需要超时控制的场景中非常有用。

enum dma_status dma_async_tx_status(
    struct dma_chan *chan,
    dma_cookie_t cookie,
    struct dma_tx_state *txstate
);

参数 txstate 是一个输出参数,用于返回更详细的状态信息,包括已传输的字节数(residue)和硬件 DMA 计数器的值。

状态码 含义 典型处理
DMA_COMPLETE 事务已完成 释放资源,触发后续处理
DMA_IN_PROGRESS 事务正在执行或等待执行 继续轮询或等待下一次中断
DMA_ERROR 事务执行出错 调用错误处理,重置通道

4.5 回调机制的实现细节

旧版 API 的回调触发点位于 DMA 引擎的中断处理函数中。典型的实现流程如下:

  1. 硬件中断到达:DMA 控制器完成一次传输,触发中断。
  2. 引擎 ISR 处理:调用 device_tx_status 确认完成状态,从完成队列中取出描述符。
  3. 回调执行:在 ISR 上下文中直接调用 desc->callback(desc->callback_param)。
  4. 描述符回收:将描述符归还给内存池或空闲链表。
性能影响: 旧版回调在中断上下文中执行,因此回调函数必须快速、不可睡眠。任何可能导致阻塞的操作(如 mutex_lock、kmalloc(GFP_KERNEL))都会导致系统崩溃。

4.6 旧版 API 的局限性总结

在进入新 API 之前,先总结旧版设计的主要痛点,这些正是新 API 要解决的核心问题。

方面 旧版 API 问题 后果
回调上下文 在 ISR 中直接执行 限制回调功能,容易导致死锁
生命周期管理 描述符所有权转移不明确 驱动容易误用已释放的描述符
状态查询 依赖 cookie 轮询,接口单一 无法高效支持批量完成通知
错误恢复 错误处理路径不统一 各驱动实现混乱,可移植性差