3. Linux DMA子系统概览
在 Linux 内核中,DMA 并不是由每个驱动各自“裸操作寄存器”来完成,而是被抽象成了一套通用子系统——dmaengine。它的核心价值在于:把“谁提供 DMA 能力”和“谁使用 DMA 能力”解耦。SoC 厂商实现 DMA 控制器驱动,外设驱动只通过统一的 DMAEngine API 申请通道、准备描述符、提交传输并等待完成。这样,同一份 SPI、UART、音频或存储驱动,可以在不同平台上复用,而不必关心底层是哪种 DMA 控制器。
一、dmaengine 框架的分层结构
dmaengine 框架自上而下可分为四层:客户端驱动层、DMAEngine 核心层、DMA 控制器驱动层和硬件层。客户端驱动调用 dma_request_chan() 等接口;核心层负责通道分配、描述符生命周期管理和回调调度;控制器驱动实现 struct dma_device 与 struct dma_chan 的具体操作;硬件层则是真实的 DMA 引擎与总线。
二、DMAEngine API 的核心调用流程
一次典型的 DMA 传输遵循“申请通道 → 配置参数 → 准备描述符 → 提交 → 等待完成 → 释放通道”的流程。以内存到内存的 memcpy 为例:
struct dma_chan *chan;
struct dma_device *dev;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
struct completion done;
chan = dma_request_chan(&pdev->dev, "tx");
dev = chan->device;
tx = dev->device_prep_dma_memcpy(chan, dst, src, len,
DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
tx->callback = my_dma_callback;
tx->callback_param = &done;
cookie = tx->tx_submit(tx);
dma_async_issue_pending(chan);
wait_for_completion(&done);
dma_release_channel(chan);
其中 dma_cookie_t 是描述符的序号,用于追踪传输状态;dma_async_issue_pending() 才是真正“踢”硬件开始工作的动作。很多初学者只调用 tx_submit() 就等待完成,结果永远等不到回调,原因就是漏掉了 issue_pending。
三、通道管理与设备树 DMA 配置
DMA 通道是稀缺资源,核心层通过 dma_request_chan() 按名称或按 phandle 申请。设备树中通常用 dmas 和 dma-names 描述外设与 DMA 控制器的连接关系:
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
dmas = <&dma0 5 0>, <&dma0 6 0>;
dma-names = "tx", "rx";
};
dma0: dma-controller@20000000 {
compatible = "vendor,dma-v2";
reg = <0x20000000 0x10000>;
interrupts = <0 30 4>;
#dma-cells = <3>;
dma-channels = <16>;
};
驱动中通过 dma_request_chan(&pdev->dev, "tx") 即可拿到对应通道,无需硬编码通道号。这种“名字匹配”机制让同一驱动在不同板级配置下都能正确工作。
| API | 作用 | 常见误区 |
|---|---|---|
| dma_request_chan() | 按名称申请 DMA 通道 | 忘记检查 ERR_PTR 返回值 |
| dmaengine_prep_slave_sg() | 准备外设散列表传输 | sg 数量超过控制器上限 |
| dmaengine_submit() | 提交描述符到待处理队列 | 误以为已启动硬件 |
| dma_async_issue_pending() | 触发硬件执行 | 漏调用导致无回调 |
| dmaengine_tx_status() | 查询传输残量与状态 | 在回调外轮询浪费 CPU |
| dma_release_channel() | 释放通道资源 | 在回调中释放造成死锁 |
四、典型场景与常见误区复盘
以音频播放为例:I2S 控制器通过 dma-names = "tx" 申请播放通道,使用 dmaengine_prep_dma_cyclic() 准备环形缓冲区描述符,实现循环播放。当周期完成时,DMA 控制器产生中断,核心层调度回调,驱动在回调中填充下一段音频数据。这里的关键是周期长度与回调延迟的匹配:周期太短会导致中断风暴,周期太长则会出现音频断续。
常见误区:一是在中断上下文里调用可能睡眠的 dma_request_chan();二是多个外设共用同一通道导致传输互相踩踏;三是忘记在 remove() 中释放通道,造成通道泄漏,后续设备申请失败。排查时可用 cat /sys/kernel/debug/dmaengine/summary 查看通道占用与传输统计。
实践建议:优先使用 devm_dma_request_chan() 这类托管接口,让内核自动处理释放;在设备树中为每个方向单独命名通道;对高带宽场景,尽量使用 SG 或 cyclic 模式,减少描述符提交次数,从而降低 CPU 占用。
理解 dmaengine 的分层与 API 调用顺序,是后续设计 DMA 描述符管理、实现高带宽传输的基础。只有把通道申请、描述符准备、提交与回调这条链路走通,才能在具体外设驱动中稳定地发挥 DMA 的吞吐能力。