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 地址、传输方向、总线宽度、握手号等,决定了“外设怎么配合”。
二、关键字段与调用路径
驱动注册时,先填充 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 传输异常。