3. dmaengine 核心数据结构详解

dmaengine 子系统通过三个核心数据结构抽象了 DMA 控制器的硬件能力、通道状态以及传输描述符。理解 struct dma_device、struct dma_chan 和 struct dma_async_tx_descriptor 是编写 DMA 驱动和进行 DMA 传输的基础。本节将逐一剖析这三个结构体的关键字段与设计意图。

核心要点: dma_device 代表 DMA 控制器硬件,dma_chan 代表一个虚拟或物理的 DMA 通道,dma_async_tx_descriptor 代表一次具体的传输任务。三者形成“控制器 → 通道 → 描述符”的层级关系。

3.1 struct dma_device:DMA 控制器抽象

struct dma_device 是 dmaengine 框架对 DMA 控制器硬件的软件抽象。它包含了控制器的能力描述、操作函数集以及通道管理信息。驱动开发者需要填充该结构体并注册到内核。

注册流程: 驱动先分配并初始化 dma_device,然后调用 dma_async_device_register() 将其注册到 dmaengine 核心。核心会遍历其 channels 链表,为每个通道创建对应的 dma_chan 实例。

3.1.1 关键字段解析

字段名 类型 说明
dev struct device * 指向 DMA 控制器的硬件设备结构体,用于设备模型关联和电源管理。
channels struct list_head 链表头,挂载该控制器下所有可用的 dma_chan 实例。
cap_mask dma_cap_mask_t 位掩码,声明控制器支持的传输类型(如 MEMCPY、DEV_TO_MEM、CYCLIC 等)。
directions dma_cap_mask_t 位掩码,声明支持的传输方向(DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM)。
src_addr_widths u32 位掩码,声明支持的源地址宽度(1、2、4、8 字节)。
dst_addr_widths u32 位掩码,声明支持的目的地址宽度。
max_burst u32 单次突发传输的最大长度(字节数),影响总线效率。
device_alloc_chan_resources 函数指针 分配并初始化通道硬件资源(如描述符池、中断)。
device_free_chan_resources 函数指针 释放通道硬件资源。
device_prep_dma_memcpy 函数指针 准备一次内存到内存的拷贝传输,返回描述符。
device_prep_slave_sg 函数指针 准备一次外设与内存间的分散/聚集传输。
device_issue_pending 函数指针 将通道 pending 队列中的描述符提交给硬件执行。
常见错误: 驱动开发者常忘记设置 cap_mask 或 directions,导致上层通过 dma_request_channel() 无法匹配到该控制器。务必在注册前正确初始化这些能力掩码。

3.1.2 操作函数集与能力声明

dma_device 中的函数指针构成了 DMA 驱动的操作接口。核心框架通过调用这些函数来与硬件交互。能力声明(cap_mask)则用于在通道请求阶段进行过滤,确保上层应用只能获取到支持所需传输类型的通道。

// 示例:初始化 dma_device 的能力和操作函数
struct dma_device *my_dma_dev;

// 设置支持 MEMCPY 和 SLAVE 传输
dma_cap_set(DMA_MEMCPY, my_dma_dev->cap_mask);
dma_cap_set(DMA_SLAVE, my_dma_dev->cap_mask);

// 设置支持双向传输
dma_cap_set(DMA_MEM_TO_DEV, my_dma_dev->directions);
dma_cap_set(DMA_DEV_TO_MEM, my_dma_dev->directions);

// 绑定操作函数
my_dma_dev->device_alloc_chan_resources = my_alloc_chan_resources;
my_dma_dev->device_free_chan_resources  = my_free_chan_resources;
my_dma_dev->device_prep_dma_memcpy      = my_prep_memcpy;
my_dma_dev->device_issue_pending        = my_issue_pending;

// 注册到 dmaengine 核心
dma_async_device_register(my_dma_dev);

3.2 struct dma_chan:DMA 通道抽象

struct dma_chan 代表一个 DMA 通道。它可以是物理通道(硬件上独立的 DMA 引擎),也可以是虚拟通道(通过软件复用)。该结构体是上层驱动与底层硬件交互的桥梁。

3.2.1 关键字段解析

字段名 类型 说明
device struct dma_device * 指向所属的 DMA 控制器设备。
cookie dma_cookie_t 通道当前分配的最后一个描述符的 cookie 值,用于跟踪传输进度。
completed_cookie dma_cookie_t 通道上最后一个完成的描述符的 cookie 值。
chan_id int 通道在控制器内的唯一 ID,通常对应硬件通道编号。
private void * 私有数据指针,驱动可用来关联自定义的通道上下文。
client_count atomic_t 引用计数,记录当前有多少个客户端在使用该通道。
slave_config struct dma_slave_config 外设配置信息,包括地址、宽度、突发大小等,通过 dmaengine_slave_config() 设置。

3.2.2 通道生命周期与状态管理

通道的生命周期由 dmaengine 核心管理。从 dma_request_channel() 获取通道,到 dma_release_channel() 释放通道。在传输过程中,通道的状态通过 cookie 机制来追踪。

  • 分配: 上层调用 dma_request_channel(),核心根据 cap_mask 匹配并调用驱动的 device_alloc_chan_resources。
  • 配置: 调用 dmaengine_slave_config() 设置外设参数。
  • 使用: 通过 dmaengine_prep_*() 准备描述符,然后 dmaengine_submit() 提交到 pending 队列。
  • 释放: 调用 dma_release_channel(),核心调用驱动的 device_free_chan_resources。
// 示例:获取并配置 DMA 通道
struct dma_chan *chan;
struct dma_slave_config config = {
    .direction = DMA_MEM_TO_DEV,
    .src_addr = (dma_addr_t)src_buf,
    .dst_addr = (dma_addr_t)dst_reg,
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 16,
    .dst_maxburst = 16,
};

// 请求通道(假设 cap_mask 已定义)
chan = dma_request_channel(mask, filter_fn, filter_param);
if (IS_ERR(chan)) {
    pr_err("Failed to request DMA channel\n");
    return PTR_ERR(chan);
}

// 配置通道
dmaengine_slave_config(chan, &config);

3.3 struct dma_async_tx_descriptor:传输描述符

struct dma_async_tx_descriptor 是 dmaengine 中最核心的数据结构之一,它封装了一次 DMA 传输的所有信息,包括源地址、目的地址、传输长度、回调函数以及链表指针。

3.3.1 关键字段解析

字段名 类型 说明
chan struct dma_chan * 指向该描述符所属的 DMA 通道。
cookie dma_cookie_t 描述符的唯一标识符,由 dmaengine_submit() 分配。
flags unsigned long 控制传输行为的标志位,如 DMA_PREP_INTERRUPT(使能传输完成中断)、DMA_CTRL_ACK(描述符使用后自动释放)。
callback dma_async_tx_callback 传输完成后的回调函数指针。
callback_param void * 传递给回调函数的参数。
tx_submit 函数指针 将描述符提交到通道的 pending 队列,由 dmaengine_submit() 内部调用。
desc_free 函数指针 释放描述符及其关联资源。
tx_list struct list_head 链表节点,用于将多个描述符链接成传输链(DMA 链表传输)。
phys dma_addr_t 描述符的物理地址(用于硬件链表模式)。

3.3.2 描述符生命周期与回调机制

描述符的生命周期从驱动调用 device_prep_*() 开始,到传输完成或调用 dmaengine_desc_free() 结束。回调函数是异步通知的关键,通常在 DMA 中断处理函数中被调用。

// 示例:准备、提交并设置回调
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;

// 准备描述符(假设 chan 已配置)
tx = dmaengine_prep_slave_sg(chan, sgl, sg_len, DMA_MEM_TO_DEV,
                              DMA_PREP_INTERRUPT);
if (!tx) {
    pr_err("Failed to prepare slave sg\n");
    return -ENOMEM;
}

// 设置回调
tx->callback = my_dma_callback;
tx->callback_param = my_data;

// 提交描述符
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
    pr_err("Failed to submit descriptor\n");
    return -EIO;
}

// 启动传输
dma_async_issue_pending(chan);

3.4 核心数据结构关系图

下图展示了 dma_device、dma_chan 和 dma_async_tx_descriptor 三者之间的层级与引用关系:

dmaengine 核心数据结构关系图 dmaengine 核心数据结构关系图 struct dma_device dev: struct device * channels: list_head cap_mask: dma_cap_mask_t device_alloc_chan_resources device_issue_pending struct dma_chan device: dma_device * cookie: dma_cookie_t completed_cookie private: void * slave_config dma_async_tx_desc chan: dma_chan * cookie: dma_cookie_t callback callback_param tx_list: list_head channels 链表 chan 指针 说明: 1. dma_device 通过 channels 链表管理多个 dma_chan 实例。 2. dma_chan 通过 device 指针反向引用所属的 dma_device。 3. dma_async_tx_descriptor 通过 chan 指针关联到通道,并通过 tx_list 支持链表传输。 典型传输流程 ① dma_request_channel() → 获取 dma_chan ② dmaengine_prep_*() → 创建 dma_async_tx_descriptor ③ dmaengine_submit() → 提交描述符 → dma_chan.cookie 更新

3.5 数据结构对比总结

特性 dma_device dma_chan dma_async_tx_descriptor
抽象层级 控制器级 通道级 传输任务级
主要职责 声明能力、提供操作函数集 管理通道状态、连接控制器与客户端 封装传输参数、管理回调与链表
生命周期 驱动加载到卸载 从请求到释放 从准备到完成或释放
关键操作 dma_async_device_register() dma_request_channel() / dma_release_channel() dmaengine_submit() / dmaengine_desc_free()
关联关系 包含 channels 链表 包含 device 指针 包含 chan 指针
设计哲学: dmaengine 通过这种分层设计,实现了硬件无关性。上层驱动只需操作 dma_chan 和描述符,无需关心底层 DMA 控制器的具体寄存器细节。这种抽象使得代码可移植性大大增强。