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 描述符生命周期与状态机

描述符从分配到释放经历明确的状态转换,驱动开发者必须理解每个阶段的约束:

  1. 分配(Allocated): 通过 dmaengine_prep_slave_sg() 等API创建,此时描述符尚未提交。
  2. 提交(Submitted): 调用 dmaengine_submit() 将描述符加入通道的提交队列,cookie被赋值。
  3. 激活(Issued): 调用 dma_async_issue_pending() 后,描述符被硬件DMA引擎读取并执行。
  4. 完成(Completed): 硬件传输结束,回调函数被执行,描述符可被释放或重用。
  5. 释放(Freed): 调用 dmaengine_desc_free() 归还内存池。
性能要点: 高带宽场景下,应使用描述符池(dma_pool)预分配,避免在热路径中频繁kmalloc/kfree。描述符的 phys 地址在池分配时固定,硬件可直接DMA读取。
dma_async_tx_descriptor 生命周期状态机 Allocated Submitted Issued Completed Freed dmaengine_submit() issue_pending() 硬件传输完成 dmaengine_desc_free() 返回内存池 ⚠ 回调在软中断上下文中执行,不能调用可能导致睡眠的函数(如mutex_lock)。

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传输数据错位。务必查阅设备手册确认总线宽度。
dma_slave_config 与设备接口映射 DMA控制器 src_addr_width: 4 src_maxburst: 8 DMA请求/数据总线 从设备(SPI/I2C) FIFO宽度: 4字节 突发能力: 8 dma_slave_config direction, burst, width dmaengine_slave_config() 💡 配置必须与设备硬件特性严格匹配:宽度不匹配会导致数据错位,burst过大可能造成设备FIFO溢出。 建议在probe阶段调用slave_config,运行时通过dmaengine_slave_config()动态调整。

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传输流程

  1. 构建scatterlist数组: 分配 struct scatterlist 数组,调用 sg_init_table() 初始化。
  2. 填充片段: 使用 sg_set_page() 或 sg_set_buf() 设置每个片段的page、offset和length。
  3. DMA映射: 调用 dma_map_sg() 将物理地址转换为总线地址,结果写入 dma_address。
  4. 提交传输: 调用 dmaengine_prep_slave_sg() 生成描述符,传入scatterlist和片段数。
  5. 解除映射: 传输完成后调用 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);
scatterlist → 硬件描述符链表映射 sg[0] page=0x1000 len=256 sg[1] page=0x2000 len=512 sg[2] page=0x3000 len=128 sg[3] page=0x4000 len=64 HW Desc 0: addr=0x1000 len=256 HW Desc 1: addr=0x2000 len=512 HW Desc 2: addr=0x3000 len=128 HW Desc 3: addr=0x4000 len=64 🔗 硬件描述符通过next指针链接,DMA引擎自动从Desc 0遍历到Desc 3,无需CPU逐段发起传输。 dma_map_sg() 可能合并相邻物理页,导致硬件描述符数 ≤ scatterlist片段数。

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映射并回收描述符到池中,形成零分配热路径。
三种数据结构协作架构 dma_slave_config 方向 / 宽度 / 突发 设备地址 / 流控 scatterlist page / offset / length dma_address / dma_length dma_async_tx_descriptor cookie / callback / phys tx_list / next / parent slave_config() prep_slave_sg() 💡 协作流程: 1. 先调用 dmaengine_slave_config() 配置通道参数(dma_slave_config) 2. 构建 scatterlist 并映射,然后 prep_slave_sg() 生成描述符 3. 提交描述符后,硬件自动遍历 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() 形成完整的传输流水线。