1. DMA 基础概念
直接存储器访问(Direct Memory Access, DMA)是嵌入式与操作系统底层驱动中最重要的硬件加速机制之一。DMA 控制器允许外设与系统内存之间直接交换数据,而无需 CPU 逐字节参与搬运,从而将 CPU 从繁重的数据拷贝任务中解放出来,专注于计算与控制逻辑。
1.1 DMA 工作原理
DMA 传输的核心思想是:让硬件接管数据通路。CPU 只需配置传输参数(源地址、目的地址、传输长度、触发条件),然后启动 DMA 引擎,后续的数据搬运完全由 DMA 控制器在总线周期内自动完成。
关键流程: CPU 配置描述符 → DMA 控制器获取总线控制权 → 逐字节/逐字搬运 → 传输完成触发中断 → CPU 处理完成事件。
1.2 DMA 与 CPU 交互模式
DMA 与 CPU 共享系统总线,必须通过特定的交互机制协调对内存和外设的访问。根据总线占用方式和通知机制,主要分为以下三种模式:
| 交互模式 | 总线占用方式 | CPU 阻塞程度 | 典型场景 |
|---|---|---|---|
| 周期窃取 | DMA 在每个总线周期中“窃取”一个时钟周期 | 极低,CPU 几乎无感知 | 低速外设(UART、I2C) |
| 突发传输 | DMA 连续占用总线,传输完一个数据块后释放 | 中等,CPU 在突发期间被阻塞 | 高速外设(SDIO、以太网) |
| 飞越模式 | DMA 完全接管总线,外设与内存直接交换 | 高,CPU 完全挂起直到传输结束 | 大块数据拷贝(显示帧缓冲) |
驱动开发提示: 在 Linux 内核中,
dma_alloc_coherent() 分配的一致性内存适用于周期窃取模式;dma_map_single() 配合流式映射更适合突发传输。选择哪种模式取决于外设的实时性要求和系统总线的负载能力。
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 控制器配置严格匹配,否则会导致数据错位或丢失。
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 倍。