3. Linux DMA子系统概览
在Linux内核中,DMA(Direct Memory Access)子系统并不是一个单一驱动,而是一套分层的通用框架,核心就是dmaengine。它的设计目标是把“DMA控制器硬件差异”和“外设如何使用DMA”解耦:底层由SoC厂商实现DMA控制器驱动,上层由各类外设驱动(如UART、SPI、I2C、音频、存储、网络)通过统一API发起传输。理解dmaengine的组成、provider与client的角色划分,以及设备树绑定方式,是后续做DMA驱动调优和性能测试的基础。
一、dmaengine框架的整体组成
dmaengine框架大致可分为四层:client驱动层、dmaengine核心层、provider驱动层和硬件层。client驱动通过dma_request_chan()等接口申请通道,提交dma_async_tx_descriptor描述符;核心层负责通道管理、描述符队列、回调调度和生命周期管理;provider驱动则把具体DMA控制器的寄存器操作、通道仲裁、中断处理封装成struct dma_device和struct dma_chan。
从数据结构看,几个关键对象必须分清:
struct dma_device:描述一个DMA控制器实例,包含能力位图、通道列表和操作函数集。struct dma_chan:表示一条可用DMA通道,是client申请和释放的对象。struct dma_async_tx_descriptor:一次传输任务的描述符,包含回调、cookie和提交接口。struct dma_slave_config:用于配置外设侧地址、方向、总线宽度和握手信号。
二、provider与client的角色划分
Provider是DMA控制器的驱动,通常由SoC原厂或内核社区维护,例如dw-axi-dmac、pl330、imx-sdma、ti-edma等。Provider负责注册dma_device,实现device_prep_slave_sg、device_prep_dma_memcpy、device_issue_pending、device_terminate_all等回调,并在中断中推进传输状态。
Client是使用DMA的外设驱动,例如SPI控制器驱动、音频ASoC驱动、UART驱动或网络驱动。Client不直接操作DMA寄存器,而是通过dmaengine API申请通道、配置从设备参数、准备传输描述符并提交。典型调用链如下:
chan = dma_request_chan(dev, "tx");
dmaengine_slave_config(chan, &cfg);
desc = dmaengine_prep_slave_sg(chan, sgl, nents,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT);
desc->callback = my_dma_callback;
dmaengine_submit(desc);
dma_async_issue_pending(chan);
这里dma_request_chan()会依据设备树中的dmas和dma-names属性找到对应provider和通道。client与provider之间通过struct device和phandle建立关联,而不是硬编码通道号。
三、设备树绑定:把provider和client连起来
设备树是dmaengine框架中provider与client建立关系的桥梁。Provider节点通常包含#dma-cells属性,声明每个通道需要几个参数;client节点通过dmas和dma-names引用provider。例如:
dma0: dma-controller@40000000 {
compatible = "vendor,dma-ctrl";
reg = <0x40000000 0x1000>;
interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>;
#dma-cells = <2>;
dma-channels = <8>;
};
spi0: spi@40010000 {
compatible = "vendor,spi";
reg = <0x40010000 0x1000>;
dmas = <&dma0 3 0>,
<&dma0 4 0>;
dma-names = "tx", "rx";
};
其中&dma0 3 0表示使用dma0控制器的第3号通道,后面的0是provider自定义的请求线或优先级参数。dma-names必须与client驱动中dma_request_chan(dev, "tx")的字符串一致,否则会返回-EPROBE_DEFER或-ENODEV。
#dma-cells与dmas参数个数是否匹配、dma-names是否与驱动请求名一致。很多“DMA申请失败”其实不是硬件问题,而是设备树绑定错误。
四、典型场景与常见误区
以音频播放为例,I2S控制器作为client,通过dmas = <&dma0 5 0>申请一条TX通道,把内存中的PCM数据搬到I2S FIFO。provider在每次搬运完成后触发中断,client回调再提交下一段buffer,形成环形缓冲。若dma-names写成"playback"而驱动请求的是"tx",就会在probe阶段报错。
| 对象 | 角色 | 关键接口/属性 | 常见问题 |
|---|---|---|---|
| DMA控制器驱动 | provider | dma_device、#dma-cells |
通道注册失败、能力位缺失 |
| 外设驱动 | client | dma_request_chan、dma-names |
名称不匹配、方向配置错误 |
| 设备树 | 绑定描述 | dmas、dma-names |
phandle错误、参数个数不符 |
| dmaengine核心 | 中间层 | dma_async_tx_descriptor |
回调未触发、cookie丢失 |
复盘时建议按“provider是否就绪 → 设备树绑定是否正确 → client请求名是否匹配 → 描述符是否提交成功 → 中断与回调是否执行”的顺序排查。只有把dmaengine的框架组成、provider/client角色和设备树绑定关系理清,后续做DMA传输性能测试时,才能准确判断瓶颈是在硬件通道、驱动配置还是上层数据组织方式。