1. DMA 基础概念

直接存储器访问(Direct Memory Access, DMA)是嵌入式与操作系统底层驱动中最重要的硬件加速机制之一。DMA 控制器允许外设与系统内存之间直接交换数据,而无需 CPU 逐字节参与搬运,从而将 CPU 从繁重的数据拷贝任务中解放出来,专注于计算与控制逻辑。

1.1 DMA 工作原理

DMA 传输的核心思想是:让硬件接管数据通路。CPU 只需配置传输参数(源地址、目的地址、传输长度、触发条件),然后启动 DMA 引擎,后续的数据搬运完全由 DMA 控制器在总线周期内自动完成。

关键流程: CPU 配置描述符 → DMA 控制器获取总线控制权 → 逐字节/逐字搬运 → 传输完成触发中断 → CPU 处理完成事件。
DMA 工作原理流程图 CPU 配置描述符 DMA 获取总线 数据搬运 完成中断 说明:CPU 仅在配置阶段和完成中断处理阶段参与,数据搬运阶段 CPU 可执行其他任务。 DMA 控制器通过总线仲裁获得控制权,以系统时钟速率逐字传输,传输完成后释放总线。 源地址:外设 FIFO / 内存 目的地址:内存 / 外设 FIFO 传输长度:字节/字/块 DMA 控制器内部包含地址寄存器、长度计数器、状态寄存器和控制逻辑 每次传输后地址递增/递减,计数器递减至零时触发完成中断

1.2 DMA 与 CPU 交互模式

DMA 与 CPU 共享系统总线,必须通过特定的交互机制协调对内存和外设的访问。根据总线占用方式和通知机制,主要分为以下三种模式:

交互模式 总线占用方式 CPU 阻塞程度 典型场景
周期窃取 DMA 在每个总线周期中“窃取”一个时钟周期 极低,CPU 几乎无感知 低速外设(UART、I2C)
突发传输 DMA 连续占用总线,传输完一个数据块后释放 中等,CPU 在突发期间被阻塞 高速外设(SDIO、以太网)
飞越模式 DMA 完全接管总线,外设与内存直接交换 高,CPU 完全挂起直到传输结束 大块数据拷贝(显示帧缓冲)
驱动开发提示: 在 Linux 内核中,dma_alloc_coherent() 分配的一致性内存适用于周期窃取模式;dma_map_single() 配合流式映射更适合突发传输。选择哪种模式取决于外设的实时性要求和系统总线的负载能力。
DMA 与 CPU 交互模式对比 周期窃取 (Cycle Stealing) • DMA 在每个总线周期插入 • CPU 仅延迟一个时钟 • 适合低速、连续性传输 • 对 CPU 性能影响最小 突发传输 (Burst) • DMA 连续占用总线 N 个周期 • CPU 在突发期间等待 • 适合高速、块状传输 • 总线利用率高 飞越模式 (Fly-By) • DMA 完全控制总线 • CPU 完全挂起 • 外设与内存直接交换 • 传输速率最高 选择原则:外设速率低 → 周期窃取;外设速率高且数据块大 → 突发传输;极致吞吐量 → 飞越模式。 CPU 开销:低 CPU 开销:中 CPU 开销:高

1.3 DMA 传输类型

根据数据传输的方向和参与方,DMA 传输可分为以下三种基本类型。理解这些类型是配置 DMA 描述符和编写驱动逻辑的前提。

  • 内存到内存 (Memory-to-Memory): 源和目标均为内存区域,常用于数据拷贝、缓冲区搬移、内存清零。需要 CPU 配置源地址和目的地址,DMA 控制器自动完成拷贝。
  • 内存到外设 (Memory-to-Peripheral): 数据从内存传输到外设的 FIFO 或寄存器,典型场景包括:LCD 显示数据发送、音频 DAC 数据输出、网络数据包发送。
  • 外设到内存 (Peripheral-to-Memory): 数据从外设接收 FIFO 传输到内存,典型场景包括:摄像头数据采集、音频 ADC 数据输入、网络数据包接收、存储设备读取。
传输类型 源端 目的端 典型外设 驱动 API 示例
内存→内存 内存 A 内存 B — dma_async_memcpy()
内存→外设 内存 外设 FIFO SPI、I2S、UART dmaengine_prep_slave_sg()
外设→内存 外设 FIFO 内存 ADC、SDIO、Camera dmaengine_prep_dma_cyclic()
注意事项: 内存到内存传输通常需要专用的 DMA 通道,且源和目的地址必须对齐到 DMA 控制器的对齐要求(常见为 4 字节或 8 字节)。外设到内存传输时,外设的触发信号极性、数据宽度和 FIFO 阈值必须与 DMA 控制器配置严格匹配,否则会导致数据错位或丢失。
DMA 三种传输类型架构 内存 → 内存 源内存 0x8000_0000 目的内存 0x9000_0000 内存 → 外设 内存 音频缓冲区 外设 FIFO I2S TX 外设 → 内存 外设 FIFO Camera CSI 内存 帧缓冲区 传输方向总结: • 内存→内存:数据拷贝、缓冲区搬移,无需外设参与 • 内存→外设:CPU 将数据写入外设,如音频播放、LCD 显示 • 外设→内存:外设数据采集到内存,如摄像头、麦克风、网络接收 DMA 控制器

1.4 DMA 传输的关键参数

在驱动开发中,配置 DMA 传输时需要明确以下参数,这些参数直接决定了传输的正确性和性能:

  • 数据宽度: 单次传输的位数,通常为 8 位、16 位、32 位或 64 位,必须与外设 FIFO 宽度匹配。
  • 突发长度: 一次总线仲裁中连续传输的数据单元个数,典型值为 4、8、16。突发长度越大,总线利用率越高,但 CPU 等待时间也越长。
  • 地址递增模式: 源地址和目的地址是否递增。内存侧通常递增,外设 FIFO 侧通常固定。
  • 传输方向: 读(外设→内存)、写(内存→外设)或双向。
  • 完成中断使能: 传输完成后是否触发中断,用于通知 CPU 处理数据。
性能优化要点: 在 Linux DMA Engine 框架中,struct dma_slave_config 结构体用于配置上述参数。合理设置 src_addr_width、dst_addr_width 和 src_maxburst、dst_maxburst 可以显著提升传输效率。例如,将突发长度从 1 增加到 8,总线利用率可提升 3~5 倍。
DMA 传输参数配置流程 数据宽度 突发长度 地址模式 传输方向 中断使能 配置顺序:先设置数据宽度和突发长度(硬件约束),再配置地址模式和传输方向(逻辑约束),最后使能中断。 错误配置会导致数据错位、总线超时或 DMA 传输失败,驱动中应通过 dmaengine_slave_config() 统一设置。 struct dma_slave_config cfg = { .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, .src_maxburst = 8 }; dmaengine_slave_config(chan, &cfg); // 配置 DMA 通道