2. DMA传输基础原理

DMA(Direct Memory Access,直接存储器访问)是嵌入式音频系统中实现高效数据传输的核心硬件机制。在ALSA音频子系统中,DMA负责在音频外设(I2S控制器)与系统内存之间搬运音频样本数据,无需CPU逐字干预,从而大幅降低CPU占用率并提升音频流吞吐量。

核心认知: 在音频场景中,DMA将CPU从“逐字节搬运工”转变为“传输调度员”,使CPU能够专注于音频算法处理、策略调度和上层应用响应。

2.1 DMA控制器工作原理

DMA控制器是SoC内部独立于CPU的硬件模块,具备自己的地址生成、传输计数和总线仲裁逻辑。其基本工作流程如下:

  1. CPU配置DMA通道:软件设置源地址、目的地址、传输宽度、传输长度、触发源等参数。
  2. 外设触发请求:当音频外设(如I2S TX FIFO空或RX FIFO满)发出DMA请求信号。
  3. DMA控制器接管总线:DMA控制器向总线仲裁器申请总线控制权,获得授权后开始传输。
  4. 数据搬运:DMA控制器从源地址读取数据,写入目的地址,每次传输后自动更新地址指针和计数器。
  5. 传输完成通知:当计数器归零或达到预设条件时,DMA控制器向CPU发出中断或触发回调。
关键参数: 传输宽度(8/16/32位)、突发传输大小(burst size)、传输方向(内存→设备/设备→内存)、循环模式使能。
DMA控制器工作原理流程图 CPU配置DMA通道 外设触发DMA请求 DMA获取总线控制权 DMA执行数据搬运 传输完成 → CPU中断 说明: CPU仅参与配置和完成通知两个阶段,实际数据搬运完全由DMA控制器自主完成,实现CPU零负担传输。

2.2 内存与设备间的数据传输

在ALSA音频系统中,DMA传输涉及两个主要端点:系统内存(RAM)和音频外设(I2S控制器FIFO)。根据音频数据流方向,分为两种基本场景:

传输方向 ALSA术语 源地址 目的地址 典型应用
播放(Playback) SNDRV_PCM_STREAM_PLAYBACK 内存中的音频缓冲区 I2S TX FIFO 扬声器输出、耳机播放
录音(Capture) SNDRV_PCM_STREAM_CAPTURE I2S RX FIFO 内存中的音频缓冲区 麦克风输入、线路录音

DMA控制器在传输过程中需要处理的关键地址映射关系:

  • 物理地址:DMA控制器直接操作的是总线物理地址,需要将内核虚拟地址转换为物理地址。
  • 总线地址:某些SoC中DMA控制器通过IOMMU或SMMU访问内存,需要额外的地址转换。
  • 外设地址:音频外设FIFO寄存器地址固定,由芯片手册定义,DMA直接读写该地址。
注意事项: 音频DMA缓冲区必须分配在DMA可访问的内存区域(DMA zone),且需要保证缓存一致性。在ARM架构中,通常使用dma_alloc_coherent()或dma_map_single()来管理DMA缓冲区。
内存与音频外设间的DMA数据传输 系统内存 (RAM) 音频缓冲区 (物理地址空间) dma_addr_t DMA控制器 地址生成 + 计数 总线仲裁 + 流控 音频外设 (I2S) TX FIFO / RX FIFO (外设寄存器地址) ioremap 映射 播放 播放 录音 录音 数据传输路径: 播放:内存缓冲区 → DMA控制器 → I2S TX FIFO → DAC → 扬声器 录音:麦克风 → ADC → I2S RX FIFO → DMA控制器 → 内存缓冲区

2.3 DMA传输模式

DMA控制器支持多种传输模式,以适应不同的音频应用场景。ALSA音频驱动中主要使用以下三种模式:

2.3.1 单次传输模式(Single Transfer Mode)

单次传输模式是最基本的DMA操作方式:CPU配置一次传输参数,DMA执行一次完整的数据搬运后停止,等待下一次配置。

  • 工作方式:每次传输完成后DMA通道自动关闭,需要CPU重新配置才能启动下一次传输。
  • 适用场景:非连续音频流、控制命令传输、低数据量场景。
  • 优点:实现简单,资源占用少。
  • 缺点:每次传输都需要CPU介入配置,不适合高吞吐量连续音频流。

2.3.2 循环传输模式(Circular / Auto-Reload Mode)

循环传输模式是音频DMA中最常用的模式。DMA控制器在完成一次传输后自动重新加载初始配置参数,持续进行数据传输,形成无限循环。

  • 工作方式:传输计数器归零后自动重置为初始值,地址指针回绕到起始位置。
  • 适用场景:连续音频播放/录音、环形缓冲区配合使用。
  • 优点:CPU只需初始配置,后续传输完全自主运行,中断频率低。
  • 缺点:需要配合环形缓冲区管理,防止数据覆盖或欠载。
ALSA实践: 在snd_pcm_hardware结构中,info字段包含SNDRV_PCM_INFO_MMAP和SNDRV_PCM_INFO_SYNC_START标志,驱动通过DMA循环模式实现音频缓冲区的连续填充和消费。

2.3.3 链式传输模式(Scatter-Gather / Linked-List Mode)

链式传输模式使用描述符链表(Descriptor Chain)管理多个不连续的内存块。每个描述符包含源地址、目的地址、传输长度和下一个描述符指针,DMA控制器自动遍历链表完成所有传输。

  • 工作方式:CPU构建描述符链表,DMA控制器按链表顺序依次执行每个描述符的传输任务。
  • 适用场景:非连续物理内存、音频多通道分离、复杂音频处理管线。
  • 优点:支持虚拟地址到物理地址的灵活映射,减少内存碎片问题。
  • 缺点:描述符管理增加软件复杂度,链表遍历带来额外延迟。
三种DMA传输模式对比 单次传输模式 CPU配置 → DMA传输 → 传输完成 → 停止 → CPU重新配置 → 再次启动 中断频繁,CPU负载高 循环传输模式 CPU初始配置 → DMA自动循环 → 地址回绕 → 持续传输 中断少,适合音频流 链式传输模式 CPU构建描述符链表 → DMA遍历链表 → 执行每个描述符 → 链表结束停止 灵活映射,复杂度高 模式对比总结 对比维度 单次传输 循环传输 链式传输 CPU干预频率 高(每次传输) 低(仅初始配置) 中(链表构建时) 内存连续性要求 连续 连续(回绕) 支持非连续 音频适用性 不推荐 最常用 高级场景 实现复杂度 低 中 高

2.4 DMA传输在ALSA中的代码示例

以下是一个简化的ALSA DMA传输配置代码片段,展示如何在音频驱动中初始化DMA通道并启动循环传输:

/* ALSA音频DMA通道初始化示例 */
static int audio_dma_channel_init(struct snd_pcm_substream *substream,
                                  struct dma_chan *dma_ch)
{
    struct dma_slave_config config;
    struct snd_pcm_runtime *runtime = substream->runtime;
    int ret;

    /* 配置DMA从设备参数 */
    memset(&config, 0, sizeof(config));
    config.direction = DMA_MEM_TO_DEV;  /* 播放方向 */
    config.src_addr = runtime->dma_addr; /* 内存缓冲区物理地址 */
    config.dst_addr = i2s_tx_fifo_phys;  /* I2S TX FIFO物理地址 */
    config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;  /* 32位采样 */
    config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    config.src_maxburst = 4;  /* 突发传输大小 */
    config.dst_maxburst = 4;

    ret = dmaengine_slave_config(dma_ch, &config);
    if (ret < 0)
        return ret;

    /* 准备循环传输描述符 */
    tx_desc = dmaengine_prep_dma_cyclic(dma_ch,
                                        runtime->dma_addr,
                                        snd_pcm_lib_buffer_bytes(substream),
                                        snd_pcm_lib_period_bytes(substream),
                                        DMA_MEM_TO_DEV,
                                        DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (!tx_desc)
        return -ENOMEM;

    /* 注册传输完成回调 */
    tx_desc->callback = audio_dma_complete_callback;
    tx_desc->callback_param = substream;

    /* 提交并启动DMA传输 */
    dmaengine_submit(tx_desc);
    dma_async_issue_pending(dma_ch);

    return 0;
}
关键API说明: dmaengine_prep_dma_cyclic() 是ALSA音频驱动中最核心的DMA API,它创建了一个循环传输描述符,DMA控制器将自动在缓冲区起始和结束地址之间循环搬运数据,配合环形缓冲区实现无缝音频流。

2.5 本章小结