4. dmaengine核心数据结构

dmaengine 是 Linux 内核中统一 DMA 控制器驱动的框架。它把“谁来做传输、用哪条通道、传输什么、怎么配置”抽象成四个核心数据结构:struct dma_device 描述控制器能力,struct dma_chan 描述一条可用通道,struct dma_async_tx_descriptor 描述一次具体传输,struct dma_slave_config 描述外设侧的硬件参数。理解这四者的分工与引用关系,是编写 DMA 驱动和排查传输问题的前提。

一、四层对象的分工

可以把 dmaengine 看成一条“能力—通道—任务—参数”的链路。dma_device 是控制器级别的对象,通常一个 DMA 控制器对应一个实例,它登记了 chancnt、channels、device_prep_dma_memcpy、device_prep_slave_sg、device_config、device_issue_pending 等回调,决定了这个控制器“能做什么”。dma_chan 是通道级别的对象,由 dma_device 在注册时批量创建,每个通道有独立的 chan_id、cookie 和 device 反向指针,决定了“用哪条路走”。dma_async_tx_descriptor 是传输级别的对象,由 device_prep_* 回调生成,携带源地址、目的地址、长度、回调函数和 tx_submit 提交入口,决定了“这一次搬什么”。dma_slave_config 是配置级别的对象,通过 dmaengine_slave_config() 下发,描述外设 FIFO 地址、传输方向、总线宽度、握手号等,决定了“外设怎么配合”。

dmaengine 四层核心数据结构关系 struct dma_device(控制器能力层) chancnt · channels · device_prep_dma_memcpy · device_prep_slave_sg device_config · device_issue_pending · device_terminate_all · dev 职责:登记控制器支持哪些传输类型与操作回调 struct dma_chan(通道层) chan_id · cookie · device(反向指针) chan->device->device_prep_*() 职责:标识一条物理/虚拟通道,承载提交队列 struct dma_slave_config(外设参数层) direction · src_addr · dst_addr src_addr_width · dst_addr_width 职责:描述外设 FIFO 与握手参数 struct dma_async_tx_descriptor(传输任务层) cookie · flags · tx_submit() · callback · callback_param phys · chan(所属通道)· next(描述符链) 职责:描述一次具体传输,可串联成描述符链 创建通道 配置下发 prep 生成 参数生效

二、关键字段与调用路径

驱动注册时,先填充 dma_device,再调用 dma_async_device_register()。框架会依据 chancnt 创建对应数量的 dma_chan,并把每个通道的 device 指针指回控制器。客户端驱动通过 dma_request_chan() 拿到通道句柄,随后调用 dmaengine_slave_config() 下发 dma_slave_config,再调用 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_memcpy() 得到描述符,最后用 dmaengine_submit() 提交、dma_async_issue_pending() 触发。

struct dma_chan *chan;
struct dma_slave_config cfg = {
    .direction = DMA_MEM_TO_DEV,
    .dst_addr = UART_TX_FIFO,
    .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
    .dst_maxburst = 16,
};
struct dma_async_tx_descriptor *tx;

chan = dma_request_chan(dev, "tx");
dmaengine_slave_config(chan, &cfg);
tx = dmaengine_prep_slave_sg(chan, sg, nents,
                             DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT);
tx->callback = tx_done_cb;
tx->callback_param = ctx;
dma_cookie_t cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);

三、描述符链与 cookie 机制

一次传输往往由多个描述符组成链。硬件支持链式描述符时,驱动在 device_prep_* 中把多个 dma_async_tx_descriptor 用 next 串起来,形成描述符链,一次提交即可让控制器连续搬运多段数据。每个描述符提交后都会获得一个递增的 cookie,客户端可用 dma_async_is_tx_complete() 或 dmaengine_tx_status() 查询完成状态。cookie 是判断“第几次传输是否完成”的唯一凭据,不能与通道号混淆。

四、四者对比清单

结构体 层级 核心字段 典型操作
dma_device 控制器 chancnt、channels、device_prep_* dma_async_device_register()
dma_chan 通道 chan_id、cookie、device dma_request_chan()
dma_async_tx_descriptor 传输 tx_submit、callback、next dmaengine_submit()
dma_slave_config 外设参数 direction、src/dst_addr、width dmaengine_slave_config()

五、典型场景与常见误区

以 UART 发送为例:客户端先申请 TX 通道,配置 DMA_MEM_TO_DEV 与 FIFO 地址,准备 SG 描述符并提交,传输完成后回调通知上层释放缓冲区。若把 dma_slave_config 的 direction 写反,数据会流向错误方向;若忘记调用 dma_async_issue_pending(),描述符只停留在队列中不会真正启动;若在回调中直接释放描述符而未确认 cookie 完成,可能触发 use-after-free。复盘时优先检查通道是否申请成功、配置是否下发、cookie 是否推进、回调是否被调用,这四步能覆盖大多数 dmaengine 传输异常。

记忆口诀:device 管能力,chan 管通路,descriptor 管任务,slave_config 管外设参数。四者各司其职,缺一不可。