2. 旧版 dmaengine API 回顾:核心数据结构详解

在开始从旧版 dmaengine 向新 API 升级之前,必须透彻理解旧版 API 的三大核心数据结构:dma_chan、dma_slave_config 和 dma_async_tx_descriptor。它们是整个 DMA 传输的基石,也是升级过程中需要重点替换或适配的对象。

升级提示: 新版 API 虽然重构了接口,但底层硬件抽象和传输描述的概念并未改变。理解旧版结构体,能帮助你更清晰地定位新 API 中对应的替代方案。

2.1 dma_chan:DMA 通道的抽象

dma_chan 是旧版 dmaengine 中代表一个 DMA 通道的核心结构体。它封装了通道的硬件能力、当前状态、以及操作函数集。驱动开发者通过它来发起和控制 DMA 传输。

关键字段解析:
struct dma_chan {
    struct dma_device *device;      // 所属 DMA 控制器设备
    dma_cookie_t      cookie;       // 当前传输的 cookie(用于跟踪)
    int               chan_id;      // 通道 ID(硬件索引)
    struct list_head  global_node;  // 全局通道链表节点
    struct list_head  slave_node;   // 从设备通道链表节点
    void              *private;     // 通道私有数据(通常指向 dma_slave_chan)
    int               client_count; // 绑定到此通道的客户端数量
};

该结构体在旧版 API 中扮演着“句柄”的角色。当外设(如 SPI、I2C、UART)请求一个 DMA 通道时,dmaengine 会返回一个 dma_chan 指针。后续的配置、提交、回收操作都围绕这个指针进行。

升级注意点: 在新版 API 中,dma_chan 结构体被保留但内部字段大幅简化,private 字段的使用方式发生变化,不再推荐直接访问内部链表节点。升级时需检查所有对 dma_chan 内部字段的直接访问。
dma_chan 核心结构关系 dma_chan dma_device *device dma_cookie_t cookie void *private list_head global_node list_head slave_node 说明:旧版 dma_chan 直接暴露内部链表和私有数据,新版 API 将其封装为不透明句柄

2.2 dma_slave_config:从设备传输配置

dma_slave_config 是旧版 API 中用于配置 DMA 传输参数的桥梁结构体。它定义了外设(slave)与 DMA 控制器之间的硬件连接参数,包括地址、方向、数据宽度和突发大小等。

结构体定义:
struct dma_slave_config {
    enum dma_transfer_direction direction; // 传输方向:DMA_MEM_TO_DEV / DMA_DEV_TO_MEM
    dma_addr_t src_addr;                   // 源物理地址(外设侧)
    dma_addr_t dst_addr;                   // 目的物理地址(外设侧)
    enum dma_slave_buswidth src_addr_width; // 源数据宽度(字节)
    enum dma_slave_buswidth dst_addr_width; // 目的数据宽度(字节)
    u32 src_maxburst;                      // 源突发传输长度(单位:数据宽度)
    u32 dst_maxburst;                      // 目的突发传输长度(单位:数据宽度)
    u32 slave_id;                          // 从设备 ID(用于硬件握手信号路由)
};

该结构体通过 dmaengine_slave_config() 函数传递给 DMA 引擎。配置的正确性直接影响传输的稳定性和性能。常见错误包括:地址未对齐、突发长度超过硬件 FIFO 深度、数据宽度不匹配等。

字段 典型值 说明
direction DMA_MEM_TO_DEV 内存到外设(如 SPI 发送)
src_addr 0x4802A000 外设数据寄存器物理地址
src_addr_width DMA_SLAVE_BUSWIDTH_4_BYTES 32 位数据宽度
src_maxburst 8 每次突发传输 8 个数据单元
slave_id 0x1A 硬件握手信号 ID(平台相关)
升级注意点: 新版 API 将 dma_slave_config 扩展为 dma_slave_config(名称相同但字段不同),新增了 peripheral_config 和 peripheral_size 字段用于传递平台特定配置。旧版中通过 slave_id 传递的硬件路由信息,在新版中可能被移入 peripheral_config 或设备树属性中。
dma_slave_config 配置流程 外设驱动 填充 slave_config dmaengine_slave_config() DMA 控制器寄存器配置 硬件握手信号建立 传输准备就绪

2.3 dma_async_tx_descriptor:传输描述符

dma_async_tx_descriptor 是旧版 API 中描述一次 DMA 传输的完整元数据。它包含了传输的物理地址、长度、回调函数、以及用于链接多个传输的链表节点。该结构体是异步传输的核心,驱动通过它来提交、回收和跟踪传输状态。

结构体定义:
struct dma_async_tx_descriptor {
    dma_cookie_t       cookie;          // 传输 cookie(用于状态查询)
    enum dma_ctrl_flags flags;          // 控制标志(如 DMA_PREP_INTERRUPT)
    dma_addr_t         src;             // 源物理地址
    dma_addr_t         dst;             // 目的物理地址
    size_t             len;             // 传输长度(字节)
    struct dma_async_tx_descriptor *next; // 链表下一个描述符(用于链式传输)
    struct dma_chan    *chan;           // 所属通道
    dma_async_tx_callback callback;     // 传输完成回调函数
    void               *callback_param; // 回调函数参数
    struct list_head   tx_list;         // 用于挂接到通道的待处理队列
};

描述符的生命周期如下:

  1. 创建: 通过 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_cyclic() 等函数创建。
  2. 提交: 通过 dmaengine_submit() 将描述符提交到通道的待处理队列。
  3. 启动: 通过 dma_async_issue_pending() 触发 DMA 控制器开始处理。
  4. 完成: 传输完成后,回调函数被调用,描述符被回收或重新使用。
性能关键点: 旧版 API 中,描述符的 next 指针支持硬件链表(LLI),允许一次提交多个描述符实现连续传输。这是实现高吞吐量 DMA 的关键机制。在新版 API 中,这一机制被 dmaengine_prep_dma_chain() 或 struct dma_async_tx_descriptor 的 tx_submit 回调替代。
dma_async_tx_descriptor 生命周期 创建 (prep) 提交 (submit) 启动 (issue) 完成 (callback) 关键:描述符在 submit 后进入待处理队列,issue_pending 后才真正开始硬件传输 待处理队列 (pending) 硬件执行中 (active) 完成 (done)

2.4 三大结构体的协作关系

在实际的 DMA 传输中,这三个结构体协同工作:

  • dma_chan 提供通道上下文,是传输的“容器”。
  • dma_slave_config 配置通道的硬件参数,是传输的“调谐器”。
  • dma_async_tx_descriptor 描述单次传输的细节,是传输的“任务单”。
结构体 角色 创建方式 升级影响
dma_chan 通道句柄 dma_request_channel() 内部字段简化,私有数据访问方式改变
dma_slave_config 硬件配置 驱动手动填充 新增 peripheral_config 字段,slave_id 可能废弃
dma_async_tx_descriptor 传输描述 dmaengine_prep_*() 系列函数 回调机制保留,链表管理方式更新
升级注意点: 旧版 API 中,这三个结构体的内部字段被大量直接访问(如 chan->private、desc->cookie)。在新版 API 中,推荐使用访问器函数(如 dma_cookie_status())替代直接字段访问,以提高代码的可移植性和兼容性。