3. DMA描述符核心数据结构:dma_async_tx_descriptor、dma_slave_config、scatterlist解析
在Linux DMA引擎子系统中,描述符管理是连接上层业务与底层硬件DMA控制器的桥梁。本章深入剖析三个核心数据结构:dma_async_tx_descriptor(传输描述符)、dma_slave_config(从设备配置)和 scatterlist(分散/聚集列表)。理解这些结构的内存布局、字段语义及协作关系,是编写高带宽、低延迟DMA驱动的必备基础。
核心观点:描述符是DMA传输的“指令包”,配置结构是“设备参数卡”,scatterlist是“内存碎片地图”。三者协同,决定了传输的吞吐量上限和CPU卸载效率。
3.1 dma_async_tx_descriptor:传输描述符
dma_async_tx_descriptor 是DMA引擎框架中最核心的运行时对象,代表一次异步传输事务。它封装了硬件通道操作、完成回调、链式管理和依赖关系。
3.1.1 结构体定义与关键字段
struct dma_async_tx_descriptor {
dma_cookie_t cookie; /* 传输cookie,用于状态查询 */
enum dma_status (*tx_status)(struct dma_async_tx_descriptor *tx,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*callback)(void *callback_param); /* 完成回调 */
void *callback_param; /* 回调参数 */
struct dma_chan *chan; /* 所属DMA通道 */
dma_addr_t phys; /* 描述符的物理地址(硬件可见) */
struct list_head tx_list; /* 用于链式描述符链表 */
struct dma_async_tx_descriptor *next; /* 依赖链中的下一个描述符 */
struct dma_async_tx_descriptor *parent; /* 依赖链中的父描述符 */
unsigned long flags; /* 传输标志(DMA_PREP_*) */
};
| 字段 | 类型 | 说明 |
|---|---|---|
cookie |
dma_cookie_t |
每次提交传输时分配的唯一标识,用于 dma_async_is_tx_complete() 查询状态 |
callback |
函数指针 | 传输完成后的异步通知回调,在软中断上下文中执行 |
phys |
dma_addr_t |
描述符本身的物理地址,硬件DMA引擎通过该地址获取描述符内容 |
tx_list |
struct list_head |
用于将多个描述符链接成硬件链表,实现连续传输无需CPU干预 |
next / parent |
指针 | 构建依赖DAG(有向无环图),实现描述符间的同步与排序 |
flags |
unsigned long |
控制传输行为,如 DMA_PREP_INTERRUPT(完成时触发中断) |
3.1.2 描述符生命周期与状态机
描述符从分配到释放经历明确的状态转换,驱动开发者必须理解每个阶段的约束:
- 分配(Allocated): 通过
dmaengine_prep_slave_sg()等API创建,此时描述符尚未提交。 - 提交(Submitted): 调用
dmaengine_submit()将描述符加入通道的提交队列,cookie被赋值。 - 激活(Issued): 调用
dma_async_issue_pending()后,描述符被硬件DMA引擎读取并执行。 - 完成(Completed): 硬件传输结束,回调函数被执行,描述符可被释放或重用。
- 释放(Freed): 调用
dmaengine_desc_free()归还内存池。
性能要点: 高带宽场景下,应使用描述符池(
dma_pool)预分配,避免在热路径中频繁kmalloc/kfree。描述符的 phys 地址在池分配时固定,硬件可直接DMA读取。
3.2 dma_slave_config:从设备配置结构
dma_slave_config 用于描述DMA从设备(如I2C、SPI、UART控制器)的硬件接口参数。在调用 dmaengine_slave_config() 时,驱动将该结构传递给DMA引擎,以配置通道的传输方向、数据宽度、地址递增策略和流控模式。
3.2.1 结构体定义
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 src_port_window_size; /* 源端FIFO窗口大小(可选) */
u32 dst_port_window_size; /* 目的端FIFO窗口大小 */
bool device_fc; /* 硬件流控使能 */
unsigned long slave_id; /* 从设备ID(用于多路复用) */
};
3.2.2 关键配置参数详解
| 参数 | 典型值 | 对性能的影响 |
|---|---|---|
src_addr_width |
1(字节)、2(半字)、4(字) | 匹配设备FIFO宽度,不匹配会导致数据错位或性能下降 |
src_maxburst |
4、8、16 | 决定每次DMA请求传输的数据量,增大可提高总线利用率,但可能增加延迟 |
device_fc |
true / false | 硬件流控使能时,设备通过DMA请求信号控制传输节奏,减少CPU干预 |
direction |
DMA_MEM_TO_DEV / DMA_DEV_TO_MEM |
决定DMA引擎内部地址递增逻辑和通道方向 |
常见错误: 配置
src_addr_width 为4字节,但设备FIFO实际只支持1字节访问,会导致DMA传输数据错位。务必查阅设备手册确认总线宽度。
3.3 scatterlist:分散/聚集列表
scatterlist 是Linux内存管理中用于描述非连续物理内存块的链表结构。在DMA传输中,它允许将多个不连续的缓冲区组织成一个逻辑传输单元,硬件DMA引擎通过链表自动遍历所有片段,实现“分散-聚集”DMA(SG-DMA)。
3.3.1 结构体定义
struct scatterlist {
unsigned long page_link; /* 低位包含标志位,高位指向struct page */
unsigned int offset; /* 在page内的偏移量 */
unsigned int length; /* 该片段的字节长度 */
dma_addr_t dma_address; /* DMA映射后的总线地址(由dma_map_sg填充) */
unsigned int dma_length; /* DMA映射后的实际长度(可能因对齐调整) */
};
3.3.2 SG-DMA传输流程
- 构建scatterlist数组: 分配
struct scatterlist数组,调用sg_init_table()初始化。 - 填充片段: 使用
sg_set_page()或sg_set_buf()设置每个片段的page、offset和length。 - DMA映射: 调用
dma_map_sg()将物理地址转换为总线地址,结果写入dma_address。 - 提交传输: 调用
dmaengine_prep_slave_sg()生成描述符,传入scatterlist和片段数。 - 解除映射: 传输完成后调用
dma_unmap_sg()释放DMA映射。
性能关键:
dma_map_sg() 可能合并相邻物理页,返回的映射片段数可能少于原始片段数。驱动应使用返回值作为实际片段数,而非原始 nents。
3.3.3 scatterlist与描述符的协作
在 dmaengine_prep_slave_sg() 内部,DMA引擎驱动遍历scatterlist数组,为每个片段生成硬件描述符条目,并通过 tx_list 链接成硬件链表。整个过程对上层驱动透明:
struct dma_async_tx_descriptor *tx;
struct scatterlist *sgl;
int nents, i;
/* 构建并映射scatterlist */
nents = dma_map_sg(chan->device->dev, sgl, nents_orig, dir);
/* 提交SG传输 */
tx = dmaengine_prep_slave_sg(chan, sgl, nents, dir, DMA_PREP_INTERRUPT);
if (!tx) {
dev_err(dev, "Failed to prepare slave sg\n");
dma_unmap_sg(chan->device->dev, sgl, nents_orig, dir);
return -ENOMEM;
}
tx->callback = my_callback;
tx->callback_param = my_data;
dmaengine_submit(tx);
dma_async_issue_pending(chan);
3.4 三种数据结构的协作关系
在实际驱动中,三个结构体协同完成一次完整的DMA传输:
| 阶段 | 使用的结构 | 操作 |
|---|---|---|
| 初始化 | dma_slave_config |
配置通道参数(方向、宽度、突发、流控) |
| 内存准备 | scatterlist |
构建分散缓冲区列表,调用 dma_map_sg() |
| 描述符创建 | dma_async_tx_descriptor |
dmaengine_prep_slave_sg() 生成描述符 |
| 传输提交 | dma_async_tx_descriptor |
dmaengine_submit() + issue_pending() |
| 完成处理 | dma_async_tx_descriptor |
回调函数中调用 dma_unmap_sg() |
设计模式: 高带宽驱动通常维护一个描述符池(
dma_pool)和一个scatterlist缓存,避免在每次传输时动态分配。描述符的 callback 中释放scatterlist映射并回收描述符到池中,形成零分配热路径。
3.5 本章小结
- dma_async_tx_descriptor 是DMA传输的运行时对象,管理状态、回调、链式和依赖关系。理解其生命周期(分配→提交→激活→完成→释放)是避免资源泄漏和竞态的关键。
- dma_slave_config 定义设备接口参数,必须在传输前正确配置。宽度、突发和流控的匹配直接影响传输正确性和性能。
- scatterlist 实现非连续内存的DMA传输,通过
dma_map_sg()映射后,硬件可自动遍历所有片段。驱动应使用映射后的实际片段数。 - 三种结构体通过
dmaengine_slave_config()→dmaengine_prep_slave_sg()→dmaengine_submit()形成完整的传输流水线。