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传输完成的完整数据流与结构体关系:
3.5 驱动开发中的典型操作序列
- 注册阶段: 填充
dma_device的cap_mask和所有device_*回调,调用dma_async_device_register()。 - 通道分配: 核心调用
device_alloc_chan_resources,驱动分配硬件描述符池并初始化chan->private。 - 传输准备: 上层调用
device_prep_slave_sg,驱动分配dma_async_tx_descriptor,填充源/目的地址、长度、回调函数。 - 提交与触发: 调用
dmaengine_submit()将描述符加入通道队列,再调用device_issue_pending触发硬件启动。 - 完成处理: 硬件产生中断,驱动在中断处理中调用
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 指针未正确设置导致硬件链表断裂 |