4. DMA引擎API详解:dmaengine_slave_config、dmaengine_prep_slave_sg、dmaengine_submit、dma_async_issue_pending
在音频DMA驱动开发中,核心工作围绕DMA引擎的四个关键API展开。它们构成了从配置描述符、准备传输、提交请求到触发执行的完整链路。理解每个API的职责、参数含义及调用时序,是调试音频DMA传输的基础。
核心流程速览: 配置(
dmaengine_slave_config)→ 准备(dmaengine_prep_slave_sg)→ 提交(dmaengine_submit)→ 触发(dma_async_issue_pending)。四个步骤缺一不可,顺序不可颠倒。
4.1 dmaengine_slave_config:配置DMA传输参数
该函数用于配置DMA控制器的从设备(Slave)接口参数,包括源/目标地址、数据宽度、突发长度、传输方向等。通常在DMA通道申请成功后、准备描述符之前调用。
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);
关键结构体 dma_slave_config 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
direction |
enum dma_transfer_direction | 传输方向:DMA_MEM_TO_DEV(内存到设备,播放)或 DMA_DEV_TO_MEM(设备到内存,录音) |
src_addr / dst_addr |
dma_addr_t | 源/目标物理地址。对于音频外设,通常为FIFO地址 |
src_addr_width / dst_addr_width |
enum dma_slave_buswidth | 数据总线宽度,如 DMA_SLAVE_BUSWIDTH_2_BYTES(16位音频) |
src_maxburst / dst_maxburst |
u32 | 最大突发传输长度(以总线宽度为单位),通常与FIFO深度匹配 |
peripheral_config |
void * | 外设私有配置(可选),用于传递硬件特定参数 |
调试要点: 配置
src_maxburst 和 dst_maxburst 时,务必参考音频外设的FIFO深度。设置过大会导致FIFO溢出,过小则降低DMA效率。常见I2S控制器FIFO深度为8或16。
4.2 dmaengine_prep_slave_sg:准备SG描述符
该函数创建并返回一个DMA描述符,用于管理分散/聚合(Scatter-Gather)传输。音频驱动通常使用SG模式处理环形缓冲区或非连续内存块。
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan,
struct scatterlist *sgl,
unsigned int sg_len,
enum dma_transfer_direction dir,
unsigned long flags,
void *context);
参数详解:
- chan:已配置的DMA通道
- sgl:scatterlist链表头,描述内存缓冲区物理地址和长度
- sg_len:scatterlist条目数
- dir:传输方向,需与
dmaengine_slave_config一致 - flags:描述符标志位,常用
DMA_PREP_INTERRUPT(使能传输完成中断) - context:回调上下文指针,通常传递音频子流或DMA缓冲结构体
常见错误: 调用
dmaengine_prep_slave_sg 前未调用 dmaengine_slave_config,或 sgl 中的地址未进行DMA映射。这会导致描述符创建失败或传输数据错误。
4.3 dmaengine_submit:提交描述符到等待队列
将准备好的描述符提交到DMA通道的等待队列(pending queue)。该函数不会立即启动传输,而是将描述符加入队列等待触发。
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);
返回值: 返回一个cookie(dma_cookie_t),用于后续跟踪传输状态。可通过 dma_async_is_tx_complete 查询该cookie对应的传输是否完成。
关键行为:
- 描述符被加入通道的
pending链表 - 此时DMA硬件尚未开始搬运数据
- 多次调用
dmaengine_submit可累积多个描述符,形成传输队列
4.4 dma_async_issue_pending:触发DMA传输
该函数通知DMA控制器开始处理pending队列中的描述符。调用后,DMA硬件开始按顺序执行已提交的传输任务。
void dma_async_issue_pending(struct dma_chan *chan);
执行流程:
- DMA控制器从pending队列取出第一个描述符
- 配置硬件寄存器(源地址、目标地址、传输长度等)
- 启动DMA传输
- 当前描述符完成后,自动加载下一个描述符(如果支持硬件链表)
- 所有描述符完成后,触发完成中断(如果设置了
DMA_PREP_INTERRUPT)
性能提示: 在音频播放/录音的周期性传输中,建议在中断处理函数中先调用
dmaengine_submit 提交下一个周期的描述符,再调用 dma_async_issue_pending 触发。这样可以最大限度减少DMA停等时间,避免音频断流。
4.5 完整调用序列与状态机
下图展示了音频DMA传输中四个API的完整调用序列及描述符状态迁移:
4.6 音频驱动中的典型调用示例
以下代码片段展示了在音频DMA播放回调中,如何组合使用四个API完成一次周期传输:
static void audio_dma_period_elapsed(struct snd_pcm_substream *substream)
{
struct audio_dma_data *dma = snd_pcm_substream_chip(substream);
struct dma_async_tx_descriptor *desc;
struct scatterlist sg;
int ret;
/* 1. 配置DMA参数(仅在首次或参数变化时执行) */
struct dma_slave_config config = {
.direction = DMA_MEM_TO_DEV,
.src_addr = dma->buf_phys,
.dst_addr = dma->fifo_phys,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 8,
.dst_maxburst = 8,
};
dmaengine_slave_config(dma->chan, &config);
/* 2. 准备SG描述符 */
sg_init_table(&sg, 1);
sg_dma_address(&sg) = dma->buf_phys + dma->period_offset;
sg_dma_len(&sg) = dma->period_size;
desc = dmaengine_prep_slave_sg(dma->chan, &sg, 1,
DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT, NULL);
if (!desc) {
dev_err(dma->dev, "Failed to prepare DMA descriptor\n");
return;
}
/* 3. 设置完成回调 */
desc->callback = audio_dma_callback;
desc->callback_param = dma;
/* 4. 提交描述符 */
dma_cookie_t cookie = dmaengine_submit(desc);
/* 5. 触发传输 */
dma_async_issue_pending(dma->chan);
/* 更新偏移量,准备下一周期 */
dma->period_offset += dma->period_size;
if (dma->period_offset >= dma->buf_size)
dma->period_offset = 0;
}
4.7 调试检查清单
| 检查项 | 验证方法 | 常见问题 |
|---|---|---|
dmaengine_slave_config 返回值 |
检查返回0,非0表示配置失败 | 外设地址错误、突发长度超出硬件限制 |
dmaengine_prep_slave_sg 返回值 |
检查非NULL,NULL表示描述符创建失败 | SG条目数超限、DMA映射未完成、通道未配置 |
dmaengine_submit cookie |
检查cookie不为负数(dma_cookie_t 类型) |
描述符已被释放或通道处于错误状态 |
| 传输完成回调是否触发 | 在回调中添加调试打印或GPIO翻转 | 未设置 DMA_PREP_INTERRUPT 标志、中断未使能 |
| DMA传输数据正确性 | 使用逻辑分析仪抓取I2S/音频总线数据 | 地址对齐错误、数据宽度不匹配、突发长度配置不当 |
调试利器: 在
/sys/kernel/debug/dmaengine/ 下可以查看DMA通道状态、pending队列长度和已完成传输计数。使用 cat /sys/kernel/debug/dmaengine/*/chan*/state 快速定位DMA是否卡死。