3. Linux DMA子系统概览
在Linux内核中,DMA(Direct Memory Access)并不是由各个外设驱动各自“直接操作寄存器”来完成,而是通过一套统一的dmaengine子系统进行抽象。这样做的目的,是让外设驱动只关心“我要搬什么数据、从哪搬到哪、搬完通知我”,而不必关心底层DMA控制器是哪个厂商、有多少通道、描述符如何排布。理解dmaengine框架,是后续调试DMA传输、中断协同与缓存一致性的基础。
一、dmaengine框架的分层结构
dmaengine框架自上而下大致分为四层:客户端驱动层、dmaengine核心层、DMA控制器驱动层和硬件DMA控制器。客户端驱动通常是SPI、I2C、UART、音频、存储等外设驱动,它们通过dma_request_chan()申请通道,通过dmaengine_prep_slave_sg()等接口提交传输请求。核心层负责通道管理、描述符队列、回调调度和生命周期管理。控制器驱动则实现struct dma_device和struct dma_chan,把核心层的请求翻译成具体寄存器操作。
二、DMA控制器驱动与通道申请释放
DMA控制器驱动在probe阶段会注册一个struct dma_device,并向核心层注册若干struct dma_chan。每个通道既可以用于内存到内存的memcpy,也可以用于内存与外设之间的slave传输。客户端驱动通过设备树中的dmas和dma-names属性,或者通过dma_request_chan()按名称申请通道。
struct dma_chan *chan;
chan = dma_request_chan(dev, "tx");
if (IS_ERR(chan)) {
dev_err(dev, "request dma chan failed: %ld\n", PTR_ERR(chan));
return PTR_ERR(chan);
}
/* 使用完成后必须释放 */
dma_release_channel(chan);
通道申请成功后,通常还需要调用dmaengine_slave_config()配置从机参数,例如外设FIFO地址、传输方向、地址宽度、突发长度等。这一步非常关键,因为很多DMA控制器只有在配置正确后才会真正启动传输。
-EBUSY或-ENODEV。在probe失败路径和remove路径中都要确保释放。
三、描述符与传输配置
dmaengine中的“描述符”是对一次或一组DMA传输的抽象。客户端驱动通过dmaengine_prep_slave_sg()、dmaengine_prep_dma_memcpy()、dmaengine_prep_dma_cyclic()等接口准备描述符,然后通过dmaengine_submit()提交到通道队列,最后调用dma_async_issue_pending()触发硬件执行。
| 接口 | 用途 | 典型场景 |
|---|---|---|
dmaengine_prep_slave_sg() |
准备散列表从机传输 | SPI/I2C 大块数据收发 |
dmaengine_prep_dma_memcpy() |
内存到内存拷贝 | 缓冲区搬运、加速 memcpy |
dmaengine_prep_dma_cyclic() |
循环传输 | 音频播放/采集、ADC 连续采样 |
dmaengine_submit() |
提交描述符到队列 | 所有传输类型 |
dma_async_issue_pending() |
启动挂起的传输 | 提交后触发硬件 |
dmaengine_terminate_sync() |
同步终止传输 | 错误恢复、模块卸载 |
描述符提交后会返回一个dma_cookie_t,客户端驱动可以用它来跟踪传输状态。完成回调callback通常在DMA中断上下文中被调用,因此回调函数中不能睡眠,只能做轻量级处理,例如唤醒等待队列或提交下一次传输。
四、典型场景与常见误区
以音频播放为例:音频驱动申请一个播放通道,配置从机地址为I2S TX FIFO,然后使用dmaengine_prep_dma_cyclic()准备循环描述符。DMA控制器按周期搬运PCM数据,每完成一个周期触发一次中断,驱动在回调中更新缓冲区指针。如果通道申请失败、描述符未正确提交或缓存未同步,就会出现“无声”“爆音”或“数据错位”等现象。
- 只调用
dmaengine_submit()却忘记dma_async_issue_pending(),导致传输永远不启动。 - 在回调中执行
msleep()或kmalloc(GFP_KERNEL),引发内核告警。 - 释放通道前未终止正在进行的传输,导致DMA访问已释放内存。
- 忽略
dma_slave_config中的地址宽度和突发长度,造成数据错位。
复盘时,可以按“通道申请 → 从机配置 → 描述符准备 → 提交 → 触发 → 中断回调 → 释放”这条链路逐项检查。dmaengine框架的价值在于把硬件差异封装在控制器驱动中,但客户端驱动仍然必须正确管理通道生命周期、描述符状态和缓存一致性,否则DMA传输依然会失败。