2. DMA引擎基础

2.1 DMA控制器工作原理

DMA(Direct Memory Access)控制器是嵌入式系统中负责在内存与外设之间、或内存与内存之间直接搬运数据的硬件引擎。CPU只需配置传输参数,DMA控制器便独立完成数据移动,从而将CPU从繁重的数据拷贝任务中解放出来,专注于音频算法与协议处理。

核心价值:在ALSA音频驱动中,DMA负责将音频采样数据从内存缓冲区搬运到I2S(Inter-IC Sound)发送FIFO,或将接收FIFO数据搬回内存。一次典型的48kHz/16位/双声道音频流,每秒需传输192,000字节,若由CPU逐字节搬运,将消耗大量算力。

DMA控制器内部架构

一个典型的DMA控制器包含以下关键模块:

模块 功能描述 音频场景作用
控制寄存器组 存储源地址、目的地址、传输长度、控制标志 配置音频缓冲区地址与采样大小
状态寄存器 指示传输完成、错误、空闲等状态 触发中断通知CPU缓冲区已空/满
地址生成器 自动递增/递减源和目的地址 顺序搬运音频帧,支持循环缓冲区
传输计数器 记录剩余待传输字节数 精确控制每次DMA burst大小
仲裁器 管理多个DMA通道的优先级与总线请求 保证音频DMA通道获得实时性保障
中断控制器 传输完成/错误时向CPU发送中断信号 触发ALSA period elapsed事件
关键概念:DMA传输的最小单位是burst(突发传输)。音频场景下,burst大小通常设置为与I2S FIFO深度匹配(如4×32位),以平衡总线占用与实时性。

DMA传输基本流程

  1. CPU配置DMA通道:设置源地址(内存缓冲区)、目的地址(外设FIFO)、传输宽度(8/16/32位)、burst大小。
  2. CPU启动DMA:写控制寄存器使能通道,DMA控制器开始工作。
  3. DMA请求总线:通过系统总线仲裁器获取总线控制权。
  4. DMA执行传输:从源地址读取数据,写入目的地址,地址生成器自动更新指针。
  5. 传输完成:计数器归零,状态寄存器置位,可选产生中断。
  6. CPU响应中断:在中断服务程序中更新缓冲区指针或重新配置下一轮传输。
DMA控制器工作原理流程图 CPU(ARM/RISC-V) ① 配置源/目的/长度 DMA控制器 地址生成器 | 计数器 | 仲裁器 ② 启动传输 系统总线仲裁器 内存缓冲区 I2S外设FIFO ③ DMA搬运数据 ④ 传输完成中断 ⑤ CPU更新缓冲区指针

2.2 DMA传输模式:单次传输 vs 循环传输

在ALSA音频驱动中,DMA传输模式的选择直接影响音频流的连续性与中断频率。两种模式各有适用场景:

特性 单次传输(Single-shot) 循环传输(Circular/Auto-reload)
传输行为 完成指定长度后停止,需CPU重新配置 完成后自动重置计数器,从头开始
中断频率 每次传输完成产生一次中断 可配置半满/全满中断,频率可控
CPU负载 高(频繁中断+重配置) 低(中断少,无重配置开销)
适用场景 非实时数据块传输、初始化阶段 连续音频流播放/录音
缓冲区管理 线性缓冲区,需手动切换 环形缓冲区,硬件自动回绕
ALSA典型用法 snd_pcm_period_elapsed 每次触发 半中断触发 period,全中断做同步
性能陷阱:若使用单次传输模式播放48kHz音频,每传输一个period(如1024帧)就产生一次中断并重配置DMA。在低延迟场景(period=64帧)下,中断频率高达750Hz,CPU将花费大量时间在中断上下文中,导致音频卡顿或xrun。

循环传输在ALSA中的实现

循环传输依赖DMA控制器的自动重载(auto-reload)能力。配置时需设置:

  • 源地址:环形缓冲区的起始地址
  • 目的地址:外设FIFO地址(固定不变)
  • 传输长度:整个环形缓冲区大小(如period_size × period_count)
  • 循环使能位:传输完成后自动重置地址和计数器
/* 循环传输配置示例(伪代码) */
dma_channel->src_addr     = (uint32_t)ring_buffer;
dma_channel->dst_addr     = (uint32_t)&i2s_regs->tx_fifo;
dma_channel->transfer_len = period_size * period_count;
dma_channel->ctrl         = DMA_CTRL_CIRCULAR | DMA_CTRL_INC_SRC;
dma_channel->burst_size   = 4;  /* 4 words per burst */
循环传输模式:环形缓冲区与DMA搬运 环形缓冲区(Ring Buffer) Period 0 已填充 Period 1 DMA正在搬运 Period 2 待填充 Period 3 空闲 DMA传输完成后自动回绕到Period 0 I2S TX FIFO DMA搬运

2.3 DMA描述符链

描述符链(Descriptor Chain)是DMA控制器的高级特性,允许将多个传输任务链接成链表,由硬件自动顺序执行。每个描述符包含下一次传输的配置信息,DMA完成当前传输后自动加载下一个描述符,无需CPU介入。

描述符结构

每个DMA描述符通常包含以下字段:

字段 位宽 说明
src_addr 32/64位 源数据地址(内存或外设)
dst_addr 32/64位 目的数据地址
transfer_len 16/32位 本次传输的字节数
next_desc 32/64位 下一个描述符的物理地址(NULL表示结束)
ctrl_flags 16/32位 中断使能、地址递增、传输宽度等控制位

描述符链在ALSA中的应用

在音频驱动中,描述符链常用于实现多缓冲区乒乓操作:

  • 描述符0:搬运缓冲区A → I2S FIFO,完成后产生中断
  • 描述符1:搬运缓冲区B → I2S FIFO,完成后产生中断
  • 描述符2:搬运缓冲区C → I2S FIFO,完成后产生中断
  • 描述符3:搬运缓冲区D → I2S FIFO,完成后产生中断
  • 回环:描述符3的next_desc指向描述符0,形成无限循环
优化技巧:描述符链配合中断聚合(interrupt coalescing)技术,可以只在关键节点(如半缓冲或整缓冲)产生中断,进一步降低CPU负载。例如每4个描述符才产生一次中断,CPU有更多时间处理音频算法。
/* 描述符链配置示例(伪代码) */
struct dma_descriptor desc[4];

/* 初始化描述符链 */
for (int i = 0; i < 4; i++) {
    desc[i].src_addr     = (uint32_t)&audio_buffers[i * PERIOD_SIZE];
    desc[i].dst_addr     = (uint32_t)&i2s_regs->tx_fifo;
    desc[i].transfer_len = PERIOD_SIZE;
    desc[i].ctrl_flags   = DMA_CTRL_INC_SRC | DMA_CTRL_INT_EN;
    desc[i].next_desc    = (uint32_t)&desc[(i + 1) % 4];  /* 循环链接 */
}

/* 启动DMA,从描述符0开始 */
dma_start_chain(&desc[0]);
DMA描述符链结构 描述符 0 src: 缓冲区A dst: I2S FIFO len: 1024字节 next → 描述符1 描述符 1 src: 缓冲区B dst: I2S FIFO len: 1024字节 next → 描述符2 描述符 2 src: 缓冲区C dst: I2S FIFO len: 1024字节 next → 描述符3 描述符 3 src: 缓冲区D dst: I2S FIFO len: 1024字节 next → 描述符0 回环:描述符3 → 描述符0(无限循环) DMA引擎 加载

2.4 三种传输模式对比总结

特性 单次传输 循环传输 描述符链
硬件复杂度 低 中 高
CPU干预频率 每次传输完成 仅半/全中断时 可配置(聚合中断)
缓冲区灵活性 低(线性) 中(环形) 高(多缓冲区乒乓)
适用音频场景 初始化、短数据块 连续播放/录音 多通道、低延迟、复杂路由
中断优化空间 小 中(调整period大小) 大(中断聚合、批量处理)
工程建议:对于大多数嵌入式Linux ALSA音频驱动,推荐使用循环传输 + 描述符链的组合方案。循环传输保证音频流的连续性,描述符链提供灵活的缓冲区管理能力。在低延迟场景(如专业音频设备)中,描述符链的乒乓操作可将延迟控制在1ms以内。