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);

执行流程:

  1. DMA控制器从pending队列取出第一个描述符
  2. 配置硬件寄存器(源地址、目标地址、传输长度等)
  3. 启动DMA传输
  4. 当前描述符完成后,自动加载下一个描述符(如果支持硬件链表)
  5. 所有描述符完成后,触发完成中断(如果设置了 DMA_PREP_INTERRUPT)
性能提示: 在音频播放/录音的周期性传输中,建议在中断处理函数中先调用 dmaengine_submit 提交下一个周期的描述符,再调用 dma_async_issue_pending 触发。这样可以最大限度减少DMA停等时间,避免音频断流。

4.5 完整调用序列与状态机

下图展示了音频DMA传输中四个API的完整调用序列及描述符状态迁移:

DMA引擎API调用序列与描述符状态机 DMA引擎API调用序列与描述符状态机 dmaengine_slave_config dmaengine_prep_slave_sg dmaengine_submit dma_async_issue_pending 描述符状态 空闲(Idle) 已准备(Prepared) 已提交(Submitted) 执行中(In Progress) 完成(Completed) 回调通知 ⚠ 关键时序约束 1. config 必须在 prep 之前 2. submit 必须在 issue 之前 3. 中断中先 submit 再 issue

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是否卡死。