4. dmaengine核心数据结构
在 Linux dmaengine 框架中,驱动开发者真正需要打交道的对象并不多,但每一个都承担着明确的职责边界。理解 dma_device、dma_chan、dma_desc 与 dma_slave_config 这四个核心结构,是后续做 DMA 驱动调优、性能测试和问题定位的基础。它们之间不是简单的包含关系,而是“控制器能力—通道实例—传输任务—从设备参数”的分层协作模型。
一、四个结构的职责定位
dma_device 代表一个 DMA 控制器整体,是 dmaengine 注册到系统中的顶层对象。它描述“这个控制器能做什么”:支持哪些传输模式(memcpy、slave_sg、cyclic 等)、有多少通道、是否支持 DMA_PRIVATE、最大传输长度、对齐要求等。一个 SoC 上可能有多个 DMA 控制器,每个控制器对应一个 dma_device。
dma_chan 是控制器对外暴露的通道实例,是客户端申请和使用的句柄。调用 dma_request_chan() 得到的就是它。通道既承载方向(MEM_TO_MEM、MEM_TO_DEV、DEV_TO_MEM),也承载运行时状态(idle、busy、paused)。性能调优中常说的“通道竞争”“通道绑定”都发生在这一层。
dma_desc 描述一次具体传输任务,是提交给硬件执行的描述符。它包含源地址、目的地址、长度、传输宽度、突发长度、回调函数等。对于 scatter-gather 场景,还会有 dma_sg 链表。描述符的生命周期管理(分配、提交、回收)直接影响吞吐和延迟。
dma_slave_config 是面向“设备到内存”或“内存到设备”传输的配置结构,用于告诉 DMA 控制器外设侧的硬件参数:方向、源/目的地址、地址宽度、突发长度、握手信号等。它通常由外设驱动在传输前通过 dmaengine_slave_config() 下发。
dma_device 是“控制器”,dma_chan 是“通道”,dma_desc 是“任务”,dma_slave_config 是“外设参数”。四者从抽象到具体,逐层收敛。
二、结构关系与数据流
下图展示了四个结构在 dmaengine 中的层次关系与典型调用路径。客户端先通过 dma_request_chan() 拿到 dma_chan,再用 dmaengine_slave_config() 配置外设参数,然后准备 dma_desc 并提交,最终由 dma_device 对应的控制器硬件执行。
三、关键字段与调优关注点
做性能测试时,不能只看 API 调用是否成功,而要关注结构中的关键字段是否与硬件能力匹配。下表列出四个结构中最影响性能的字段及其调优含义。
| 结构 | 关键字段 | 调优含义 |
|---|---|---|
dma_device |
cap_mask、max_xor、copy_align |
决定支持哪些传输模式与对齐约束,选错模式会导致回退到低效路径 |
dma_chan |
chan_id、device、cookie |
通道绑定与竞争分析;cookie 用于追踪传输完成顺序 |
dma_desc |
src_addr、dst_addr、len、callback |
描述符粒度影响吞吐;回调上下文影响延迟与 CPU 占用 |
dma_slave_config |
direction、src_addr_width、dst_addr_width、src_maxburst |
地址宽度与突发长度不匹配会导致 FIFO 欠载或总线利用率下降 |
四、典型场景与代码片段
以音频播放场景为例,外设驱动通常先申请通道,再配置 slave 参数,然后循环提交描述符。下面是一段简化后的调用序列:
struct dma_chan *chan;
struct dma_slave_config cfg;
struct dma_async_tx_descriptor *desc;
chan = dma_request_chan(dev, "tx");
if (IS_ERR(chan))
return PTR_ERR(chan);
memset(&cfg, 0, sizeof(cfg));
cfg.direction = DMA_MEM_TO_DEV;
cfg.dst_addr = i2s_base + I2S_TXDR;
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.dst_maxburst = 8;
dmaengine_slave_config(chan, &cfg);
desc = dmaengine_prep_slave_sg(chan, sgl, sg_len,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
desc->callback = audio_dma_done;
desc->callback_param = audio;
dmaengine_submit(desc);
dma_async_issue_pending(chan);
这段代码中,dst_maxburst = 8 与 I2S FIFO 深度直接相关。如果设置过大,可能超出外设承受能力;设置过小,则总线利用率不足。性能测试时应围绕这个参数做扫描,观察吞吐与 CPU 占用的变化。
/sys/kernel/debug/dmaengine/ 下可以查看通道状态与已提交描述符数量,结合 dma_chan 的 cookie 变化判断是否存在提交后长时间未完成的情况。
五、常见误区与复盘
- 误区一:把
dma_device当成通道使用。 实际上客户端永远拿不到dma_device,只能通过dma_chan操作。混淆两者会导致在错误层级查找能力标志。 - 误区二:忽略
dma_slave_config的方向一致性。 如果cfg.direction与dmaengine_prep_*传入的方向不一致,部分控制器会直接报-EINVAL,而不是自动纠正。 - 误区三:描述符提交后立即释放缓冲区。
dma_desc只是描述任务,硬件完成前源/目的缓冲区必须保持有效,否则会出现数据损坏或 IOMMU 报错。 - 误区四:只看 API 返回值,不看 cookie。
dmaengine_submit()返回的 cookie 是后续判断完成顺序的依据,忽略它会导致多通道场景下完成事件错配。
复盘时建议按“控制器能力是否匹配—通道是否争用—描述符粒度是否合理—slave 参数是否对齐”的顺序逐层排查。多数 DMA 性能问题并非出在硬件本身,而是这四个结构中的某个字段配置与预期不符。把这四个对象的关系理清,后续做吞吐、延迟和 CPU 占用测试时,才能快速定位到具体层级。