2. DMA传输基础原理
DMA(Direct Memory Access,直接存储器访问)是嵌入式音频系统中实现高效数据传输的核心硬件机制。在ALSA音频子系统中,DMA负责在音频外设(I2S控制器)与系统内存之间搬运音频样本数据,无需CPU逐字干预,从而大幅降低CPU占用率并提升音频流吞吐量。
核心认知: 在音频场景中,DMA将CPU从“逐字节搬运工”转变为“传输调度员”,使CPU能够专注于音频算法处理、策略调度和上层应用响应。
2.1 DMA控制器工作原理
DMA控制器是SoC内部独立于CPU的硬件模块,具备自己的地址生成、传输计数和总线仲裁逻辑。其基本工作流程如下:
- CPU配置DMA通道:软件设置源地址、目的地址、传输宽度、传输长度、触发源等参数。
- 外设触发请求:当音频外设(如I2S TX FIFO空或RX FIFO满)发出DMA请求信号。
- DMA控制器接管总线:DMA控制器向总线仲裁器申请总线控制权,获得授权后开始传输。
- 数据搬运:DMA控制器从源地址读取数据,写入目的地址,每次传输后自动更新地址指针和计数器。
- 传输完成通知:当计数器归零或达到预设条件时,DMA控制器向CPU发出中断或触发回调。
关键参数: 传输宽度(8/16/32位)、突发传输大小(burst size)、传输方向(内存→设备/设备→内存)、循环模式使能。
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缓冲区。
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控制器按链表顺序依次执行每个描述符的传输任务。
- 适用场景:非连续物理内存、音频多通道分离、复杂音频处理管线。
- 优点:支持虚拟地址到物理地址的灵活映射,减少内存碎片问题。
- 缺点:描述符管理增加软件复杂度,链表遍历带来额外延迟。
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控制器将自动在缓冲区起始和结束地址之间循环搬运数据,配合环形缓冲区实现无缝音频流。