3. DMA传输原理:DMA控制器工作模式、内存与设备间数据传输、DMA描述符链

DMA(Direct Memory Access)是音频驱动中实现高性能数据传输的核心硬件机制。它允许外设(如I2S控制器)与系统内存之间直接交换数据,无需CPU逐字节搬运,从而将CPU从繁重的数据拷贝任务中解放出来,专注于音频流控制、格式转换和上层应用逻辑。

3.1 DMA控制器工作模式

DMA控制器根据其内部寄存器结构和传输调度方式,主要分为以下两种工作模式:

模式 特点 适用场景 典型硬件
单块传输模式 一次配置传输一块数据,传输完成后产生中断,CPU重新配置下一块 小数据量、非实时、简单控制 早期DMA控制器、简单MCU
描述符链模式 通过链表组织多个传输描述符,DMA自动加载下一描述符,实现连续传输 音频流、高速数据采集、批量传输 PL330、DW_DMAC、TI EDMA
循环缓冲模式 描述符链首尾相连形成环形,DMA在缓冲区中循环传输,永不停止 音频播放/录音、实时数据流 ALSA dmaengine、环形DMA
关键概念:在Linux音频驱动中,描述符链模式和循环缓冲模式是主流选择。它们能够实现“零CPU干预”的连续数据传输,是环形缓冲区与DMA协同工作的基础。

3.2 内存与设备间数据传输流程

DMA传输的本质是将数据从源地址搬运到目的地址,整个过程由DMA控制器硬件自动完成。以音频播放为例,数据从内存(RAM)传输到I2S设备(外设)的典型流程如下:

  1. CPU初始化DMA通道:配置源地址(内存缓冲区)、目的地址(I2S数据寄存器)、传输宽度(16/32位)、突发长度等参数。
  2. CPU启动DMA传输:向DMA控制器写入控制寄存器,触发传输开始。
  3. DMA控制器读取内存数据:通过系统总线(如AXI、AHB)从内存地址读取音频样本。
  4. DMA控制器写入设备寄存器:将读取的数据写入I2S控制器的发送数据寄存器(TX FIFO)。
  5. 硬件握手信号:I2S控制器通过DMA请求信号(DMA_REQ)通知DMA控制器“准备好接收下一个数据”。
  6. 传输完成中断:当一块数据传输完毕,DMA控制器产生中断,CPU更新缓冲区指针或重新填充数据。
性能要点:突发传输(Burst Transfer)可以显著提高总线利用率。例如,一次突发传输4个32位字,比4次单字传输减少约60%的总线仲裁开销。在音频驱动中,通常设置突发长度为4或8。
DMA内存与设备间数据传输流程 CPU配置DMA通道 DMA读取内存数据 DMA写入设备FIFO 硬件握手信号 传输完成中断 系统总线(AXI/AHB) 负责数据搬运 DMA_REQ / DMA_ACK 硬件流控信号

3.3 DMA描述符链

描述符链(Descriptor Chain)是DMA控制器实现连续、复杂传输的核心数据结构。每个描述符(Descriptor)定义了一次传输的全部参数,多个描述符通过指针链接成链表,DMA控制器自动遍历执行。

3.3.1 描述符结构

一个典型的DMA描述符包含以下字段:

字段 说明 典型宽度
源地址 数据源的内存地址或外设地址 32/64位
目的地址 数据目标的内存地址或外设地址 32/64位
传输长度 本次传输的字节数 16/32位
控制标志 中断使能、传输方向、突发类型等 16/32位
下一描述符指针 指向链中下一个描述符的地址 32/64位
注意事项:描述符必须放置在非缓存(Non-Cacheable)或一致性(Coherent)内存区域。如果描述符被CPU缓存,DMA控制器可能读取到过时的数据,导致传输错误。在Linux中,通常使用 dma_alloc_coherent() 分配描述符内存。

3.3.2 描述符链的工作机制

描述符链的运作遵循“加载-执行-跳转”的硬件自动流水线:

  1. 加载首描述符:CPU将第一个描述符的地址写入DMA控制器的描述符指针寄存器,然后启动DMA。
  2. 执行传输:DMA控制器解析当前描述符,执行从源到目的的数据搬运。
  3. 检查下一指针:传输完成后,DMA控制器读取当前描述符中的“下一描述符指针”字段。
  4. 跳转或停止:如果下一指针有效(非NULL),DMA自动加载该描述符并继续传输;如果为NULL,则停止并产生中断。
  5. 循环模式:在音频驱动中,最后一个描述符的下一指针指向第一个描述符,形成环形链,实现无限循环传输。
DMA描述符链结构 描述符 0 源地址: buf0 目的地址: I2S_TX 长度: 1024字节 下一指针: → Desc1 描述符 1 源地址: buf1 目的地址: I2S_TX 长度: 1024字节 下一指针: → Desc2 描述符 2 源地址: buf2 目的地址: I2S_TX 长度: 1024字节 下一指针: → Desc0 环形描述符链(循环模式) Desc2 → Desc0,实现无限循环传输

3.3.3 描述符链在音频驱动中的典型配置

在ALSA音频驱动中,描述符链通常与环形缓冲区配合使用。以下是一个典型的4段环形描述符链配置示例:

/* 定义描述符结构(以PL330 DMA为例) */
struct dma_async_tx_descriptor *desc[4];
struct dma_slave_config config;

/* 配置DMA从设备参数 */
config.direction = DMA_MEM_TO_DEV;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.src_maxburst = 4;
config.dst_maxburst = 4;

dmaengine_slave_config(chan, &config);

/* 为环形缓冲区的4个period分别创建描述符 */
for (i = 0; i < 4; i++) {
    desc[i] = dmaengine_prep_slave_sg(
        chan,
        &ring_buf_sg[i],  /* 第i个period的scatterlist */
        1,                 /* 一个sg条目 */
        DMA_MEM_TO_DEV,
        DMA_PREP_INTERRUPT /* 使能传输完成中断 */
    );
}

/* 将描述符链接成环形链 */
for (i = 0; i < 4; i++) {
    desc[i]->callback = audio_period_complete;
    desc[i]->callback_param = &audio_data;
    /* 链接到下一个描述符,最后一个链接到第一个 */
    desc[i]->tx_submit(desc[i]);
}

/* 提交第一个描述符,启动传输 */
dmaengine_submit(desc[0]);
dma_async_issue_pending(chan);
协同优化要点:描述符链与环形缓冲区的协同体现在两个方面:
1. 地址映射:描述符中的源地址直接指向环形缓冲区的各个period,无需额外拷贝。
2. 中断负载均衡:每个描述符传输完成时产生中断,CPU在中断处理中更新写指针(播放)或读指针(录音),同时检查是否发生缓冲区溢出(XRUN)。
3. 批量提交:在音频流启动时一次性提交所有描述符,运行时DMA自动循环,CPU仅在period边界进行轻量级处理。

3.4 本章小结