3. DMA驱动核心数据结构:struct dma_device、struct dma_chan、struct dma_async_tx_descriptor详解

在Linux DMA引擎(dmaengine)框架中,驱动开发的核心是理解并正确使用三个关键数据结构:struct dma_device(DMA控制器抽象)、struct dma_chan(DMA通道抽象)和struct dma_async_tx_descriptor(传输描述符)。它们构成了从硬件控制器到上层用户请求的完整抽象链。

核心认知: 这三个结构体分别对应“谁来做”(控制器)、“用哪个资源做”(通道)、“做什么”(传输任务)。理解它们的关系是编写正确DMA驱动的第一步。

3.1 struct dma_device:DMA控制器的软件抽象

struct dma_device 代表一个物理DMA控制器(如PL330、Intel IOAT、TI EDMA等)。它注册到内核的dmaengine核心,并暴露控制器能力与操作函数集。

成员 类型/说明 关键作用
dev struct device * 关联硬件设备,用于DMA API和电源管理
channels struct list_head 链表头,挂载该控制器下所有dma_chan
cap_mask dma_cap_mask_t 位掩码,声明支持的传输类型(MEMCPY/SG/SLAVE等)
directions 位掩码 支持的DMA方向(DEV_TO_MEM/MEM_TO_DEV/MEM_TO_MEM)
src_addr_widths 位掩码 支持的源地址宽度(1/2/4/8字节)
dst_addr_widths 位掩码 支持的目的地址宽度
device_alloc_chan_resources 回调函数 分配并初始化通道硬件资源
device_free_chan_resources 回调函数 释放通道硬件资源
device_prep_dma_memcpy 回调函数 准备内存拷贝传输描述符
device_prep_slave_sg 回调函数 准备外设与内存间的SG传输描述符
device_issue_pending 回调函数 触发通道开始执行排队的描述符
device_tx_status 回调函数 查询传输状态(已完成/进行中/错误)
驱动注册流程: 驱动先填充 dma_device 的 cap_mask 和回调函数,然后调用 dma_async_device_register() 将其注册到dmaengine核心。核心会遍历 channels 链表,为每个通道创建cdev并暴露给用户空间。

3.2 struct dma_chan:通道资源与状态管理

struct dma_chan 是DMA通道的运行时抽象。每个物理DMA通道对应一个 dma_chan 实例,它连接了控制器、传输描述符队列和上层使用者。

struct dma_chan {
    struct dma_device *device;      // 所属DMA控制器
    dma_cookie_t cookie;            // 当前传输的cookie(用于状态查询)
    struct list_head global_node;   // 全局通道链表节点
    struct list_head free_node;     // 空闲通道链表节点
    struct list_head devices_node;  // 控制器内通道链表节点
    struct dma_chan_dev *dev;       // 字符设备,用于用户空间访问
    int chan_id;                    // 通道ID(在控制器内唯一)
    struct dma_slave_config slave_config; // 外设配置(地址、宽度、流控)
    void *private;                  // 驱动私有数据(硬件寄存器基址等)
};
成员 功能说明
cookie 递增的传输标识符,用于 dma_async_is_tx_complete() 查询
slave_config 保存外设的DMA请求参数(如SPI TX FIFO地址、数据宽度)
private 驱动自定义数据,通常指向硬件通道描述符或寄存器映射
dev 字符设备接口,用户态可通过 /dev/dma* 操作通道
常见陷阱: 在 device_alloc_chan_resources 回调中,必须正确初始化 chan->private 并分配硬件描述符池。如果 private 为NULL,后续的 device_prep_slave_sg 将无法访问硬件寄存器,导致内核崩溃。

3.3 struct dma_async_tx_descriptor:传输任务的完整描述

这是每次DMA传输请求的“任务单”。它描述了源地址、目的地址、传输长度、回调函数以及依赖关系。dmaengine框架通过它实现异步传输和链式操作。

struct dma_async_tx_descriptor {
    dma_cookie_t cookie;                    // 传输cookie
    enum dma_ctrl_flags flags;              // 控制标志(DMA_PREP_INTERRUPT等)
    dma_addr_t src;                         // 源物理地址(MEMCPY模式)
    dma_addr_t dst;                         // 目的物理地址(MEMCPY模式)
    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;               // 用于挂载到通道的提交队列
    enum dma_status status;                 // 传输状态
};
字段 用途 典型值/行为
cookie 唯一标识一次传输 由 dma_cookie_assign() 分配
flags 控制传输行为 DMA_PREP_INTERRUPT 使能完成中断
next 硬件链表指针 用于DMA硬件自动加载下一个描述符(如PL330的LLI)
callback 中断下半部回调 在tasklet或直接中断上下文中调用
status 运行时状态 DMA_IN_PROGRESS / DMA_COMPLETE / DMA_ERROR
链式传输(DMA Chaining): 通过 next 指针将多个描述符连接成链表。硬件完成当前传输后自动加载下一个描述符,无需CPU干预。这是实现高速连续传输的关键技术。

3.4 三者关系与数据流

下图展示了从控制器注册到一次DMA传输完成的完整数据流与结构体关系:

DMA核心数据结构关系与传输数据流 struct dma_device DMA控制器抽象 cap_mask / channels / 回调函数集 struct dma_chan DMA通道抽象 cookie / slave_config / private dma_async_tx_descriptor 传输任务描述符 src/dst/len/callback/next channels链表 提交队列 传输流程:alloc_chan → prep_slave_sg → submit → issue_pending → 中断回调 硬件DMA控制器 PL330 / EDMA / IOAT 硬件通道寄存器 CSR / SAR / DAR / CCR 硬件描述符链表 LLI / BD / 硬件链表 驱动操作 配置 提交 next指针 关键设计要点 ① dma_device 通过 channels 链表管理多个 dma_chan ② dma_chan 的 private 指向硬件通道寄存器,slave_config 保存外设参数

3.5 驱动开发中的典型操作序列

  1. 注册阶段: 填充 dma_device 的 cap_mask 和所有 device_* 回调,调用 dma_async_device_register()。
  2. 通道分配: 核心调用 device_alloc_chan_resources,驱动分配硬件描述符池并初始化 chan->private。
  3. 传输准备: 上层调用 device_prep_slave_sg,驱动分配 dma_async_tx_descriptor,填充源/目的地址、长度、回调函数。
  4. 提交与触发: 调用 dmaengine_submit() 将描述符加入通道队列,再调用 device_issue_pending 触发硬件启动。
  5. 完成处理: 硬件产生中断,驱动在中断处理中调用 dma_cookie_complete() 标记完成,并执行描述符中的 callback。
缓存一致性注意事项: 当使用 device_prep_slave_sg 时,驱动必须在准备描述符前调用 dma_map_sg() 确保CPU缓存与DMA数据一致。在回调函数中,应调用 dma_unmap_sg() 释放映射。忽略缓存操作是DMA传输数据损坏的首要原因。

3.6 三个结构体的协作总结

结构体 生命周期 核心职责 典型错误
dma_device 驱动加载→卸载 声明能力、提供操作函数集 未正确设置 cap_mask 导致上层无法匹配
dma_chan 通道打开→关闭 管理通道状态、保存外设配置 private 未初始化导致空指针访问
dma_async_tx_descriptor 一次传输 描述传输参数、链式连接、回调通知 next 指针未正确设置导致硬件链表断裂