3. Linux内核DMA引擎框架:dmaengine API介绍、dma_slave_config配置、环形缓冲区与链表模式
在嵌入式Linux音频系统中,DMA(Direct Memory Access)是连接音频外设(I2S、PCM接口)与系统内存的关键数据通路。Linux内核提供了统一的 dmaengine 框架,将不同SoC厂商的DMA控制器差异抽象为一套标准API。本章深入剖析dmaengine的核心接口、从机配置方法以及两种主流传输模式——环形缓冲区与链表模式,为后续音频性能调优奠定基础。
3.1 dmaengine API 体系
dmaengine框架位于 drivers/dma/ 目录,向上为音频驱动(如ASoC的PCM dmaengine插件)提供通用API,向下通过DMA控制器驱动操作硬件。核心API分为三个阶段:通道申请、传输控制、回调与释放。
| 阶段 | API函数 | 功能说明 |
|---|---|---|
| 通道申请 | dma_request_chan() | 根据设备树或名称请求DMA通道,返回dma_chan句柄 |
| 通道申请 | dma_release_channel() | 释放已申请的DMA通道 |
| 传输配置 | dmaengine_slave_config() | 配置从机参数(地址、宽度、方向、突发大小等) |
| 传输描述符 | dmaengine_prep_slave_sg() | 准备链表模式的scatter-gather传输描述符 |
| 传输描述符 | dmaengine_prep_dma_cyclic() | 准备环形缓冲区的周期传输描述符(音频最常用) |
| 传输提交 | dmaengine_submit() | 将描述符提交到DMA引擎的待处理队列 |
| 传输启动 | dma_async_issue_pending() | 触发DMA控制器开始执行已提交的传输 |
| 状态查询 | dmaengine_tx_status() | 查询传输状态,返回已传输字节数或剩余字节数 |
| 回调注册 | dma_async_tx_descriptor.tx_submit + callback | 在描述符中设置传输完成回调函数 |
关键提示: 音频流通常使用
dmaengine_prep_dma_cyclic() 而非 prep_slave_sg(),因为环形缓冲区天然适合周期性的音频采样传输,能避免频繁重新提交描述符的开销。
3.2 dma_slave_config 配置详解
dma_slave_config 结构体是连接DMA控制器与音频外设的桥梁。配置错误会导致数据错位、丢失甚至系统崩溃。核心字段如下:
struct dma_slave_config {
enum dma_transfer_direction direction;
phys_addr_t src_addr;
phys_addr_t dst_addr;
enum dma_slave_buswidth src_addr_width;
enum dma_slave_buswidth dst_addr_width;
u32 src_maxburst;
u32 dst_maxburst;
u32 slave_id;
bool device_fc;
};
| 字段 | 音频场景典型值 | 说明 |
|---|---|---|
direction | DMA_MEM_TO_DEV(播放)DMA_DEV_TO_MEM(录音) | 数据传输方向,决定源/目标地址的解析方式 |
src_addr / dst_addr | 音频外设FIFO的物理地址(如I2S的TX/RX寄存器) | 必须从设备树或平台数据中获取,不可硬编码 |
src_addr_width / dst_addr_width | DMA_SLAVE_BUSWIDTH_2_BYTES(16位音频)DMA_SLAVE_BUSWIDTH_4_BYTES(32位音频) | 必须与音频采样精度匹配,否则数据会移位 |
src_maxburst / dst_maxburst | 4、8、16(取决于FIFO深度和延迟要求) | 单次突发传输的数据单元数,影响总线占用和延迟 |
slave_id | SoC特定的外设请求线编号 | 用于DMA控制器识别请求源,通常从设备树获取 |
常见陷阱:
src_addr_width 和 dst_addr_width 必须与音频接口的硬件字长严格一致。例如,I2S设置为32位槽宽但只传输16位有效数据时,宽度应配置为4字节,否则DMA会按2字节传输导致高16位数据丢失或错位。
3.3 环形缓冲区模式(Cyclic Mode)
环形缓冲区是音频DMA传输的默认首选模式。其核心思想是:DMA控制器在内存中维护一个逻辑上首尾相连的缓冲区,当传输到达末尾时自动回绕到起始地址,无需软件干预。
环形缓冲区的工作流程:
- 驱动调用
dmaengine_prep_dma_cyclic(),传入缓冲区基地址、周期大小(period size)和周期数量。 - DMA控制器内部构建一个循环描述符链,每个周期对应一个硬件描述符。
- 传输启动后,DMA按顺序填充/读取每个周期,到达最后一个周期后自动跳回第一个周期地址。
- 每个周期传输完成时,DMA控制器触发中断或调用回调函数,驱动在回调中更新指针或处理数据。
性能优势: 环形缓冲区模式下,
dmaengine_submit() 只需调用一次,后续传输完全由硬件自动循环,消除了频繁提交描述符的用户态/内核态切换开销,是低延迟音频播放的关键。
3.4 链表模式(Scatter-Gather / Descriptor Chain)
链表模式适用于非连续物理内存的传输场景。当音频缓冲区由多个不连续的物理内存块(如通过 vmalloc 或 kmalloc 分配的碎片化内存)组成时,链表模式通过硬件描述符链实现零拷贝传输。
链表模式与环形缓冲区的对比:
| 特性 | 环形缓冲区(Cyclic) | 链表模式(SG) |
|---|---|---|
| 内存连续性要求 | 物理连续(或通过IOMMU映射为连续) | 支持不连续物理内存块 |
| 提交次数 | 一次提交,硬件自动循环 | 每次传输需重新提交描述符链 |
| 中断频率 | 每个周期产生一次中断 | 整个链传输完成产生一次中断(或每段中断) |
| 适用场景 | 音频流、连续周期性数据 | 大块非连续数据传输、文件I/O |
| CPU负载 | 低(硬件自动回绕) | 中等(需软件管理描述符链) |
| 延迟抖动 | 稳定、可预测 | 受描述符链长度和内存访问影响 |
音频场景选择建议: 绝大多数嵌入式音频驱动优先使用 环形缓冲区模式。仅当音频缓冲区必须使用非连续物理内存(如某些多媒体框架强制要求)且硬件支持SG时,才考虑链表模式。链表模式在音频中通常需要配合
dmaengine_prep_slave_sg() 和周期性的重新提交。
3.5 性能关键配置参数
在音频DMA传输中,以下参数直接影响延迟和吞吐量:
- 突发大小(maxburst): 增大突发值可提高总线利用率,但会增加延迟。音频场景建议4~16之间,需根据FIFO阈值和采样率调整。
- 周期大小(period size): 决定中断频率。周期越小,延迟越低,但CPU负载越高。典型值:64~1024帧。
- DMA通道优先级: 多通道场景下,音频通道应设置为高优先级,避免被其他DMA传输抢占导致XRUN。
- 缓存一致性: 使用
dma_map_single()或dma_alloc_coherent()确保DMA与CPU缓存的一致性,避免数据陈旧。
调试建议: 当音频出现噼啪声或断流时,首先检查
dma_slave_config 中的地址宽度和突发大小是否与硬件FIFO匹配。使用 dmaengine_tx_status() 返回的剩余字节数可以判断DMA是否落后于硬件指针。