1. DMA 基础概念
1.1 DMA 的定义
DMA(Direct Memory Access,直接存储器访问) 是一种允许硬件子系统(外设或内存控制器)在不经过 CPU 的情况下,直接与系统内存进行数据读写的技术。在嵌入式 Linux 系统中,DMA 控制器作为总线上的独立主设备,能够发起总线事务,完成数据搬运任务。
核心要点:DMA 的本质是“CPU 授权下的数据旁路传输”——CPU 只需配置传输参数,后续数据流动由 DMA 控制器自主完成,传输结束后通过中断通知 CPU。
| 对比维度 | CPU 直接搬运 | DMA 搬运 |
|---|---|---|
| 数据路径 | 外设 → CPU 寄存器 → 内存 | 外设 → 总线 → 内存(旁路 CPU) |
| CPU 占用 | 全程占用,无法执行其他任务 | 仅配置和收尾阶段介入 |
| 传输速率 | 受限于 CPU 指令执行速度 | 接近总线带宽,适合高速外设 |
| 适用场景 | 小数据量、低频操作 | 大数据块、流式数据、实时采集 |
1.2 DMA 与 CPU 的交互模式
DMA 与 CPU 的协同工作遵循“配置-启动-传输-通知”的四步模型。整个交互过程可抽象为以下阶段:
- CPU 配置阶段:CPU 设置 DMA 控制器的源地址、目的地址、传输长度、传输宽度、突发大小等参数。
- CPU 启动阶段:CPU 写 DMA 控制器的启动寄存器(或使能通道),DMA 控制器获得总线控制权。
- DMA 自主传输阶段:DMA 控制器在总线仲裁器授权下,逐字或逐块搬运数据,CPU 可并行执行其他任务。
- DMA 完成通知阶段:传输完成或出错时,DMA 控制器触发中断,CPU 响应中断进行后续处理(如数据校验、缓冲区切换)。
设计要点:在嵌入式 Linux 驱动中,DMA 传输通常与 CPU 的 cache 一致性管理紧密配合。DMA 直接操作物理内存,而 CPU 可能使用 cache 缓存同一区域,驱动需要通过 dma_map_single() / dma_sync_single_for_cpu() 等 API 保证数据一致性。
1.3 DMA 在嵌入式系统中的作用
在嵌入式 Linux 系统中,DMA 承担着数据搬运加速器和CPU 负载卸载器的双重角色。其核心价值体现在以下方面:
- 降低 CPU 占用率:高速外设(如千兆以太网、USB 3.0、MIPI CSI 摄像头)的数据吞吐可达数百 MB/s,若由 CPU 逐字搬运,CPU 将完全被数据移动占据,无法处理协议栈、控制逻辑等关键任务。DMA 将 CPU 从数据搬运中解放出来。
- 提升数据传输吞吐量:DMA 控制器通常支持突发传输(burst transfer),一次总线事务可传输多个数据字,总线利用率远高于 CPU 单次 load/store 指令。
- 支持后台数据流动:DMA 可在 CPU 执行中断服务程序或上下文切换时继续传输,实现“传输与计算重叠”。
- 降低实时任务延迟:音频编解码、软件定义无线电等实时应用要求数据以固定间隔送达,DMA 的硬件定时触发模式可保证确定性延迟。
常见误区:DMA 并非“零开销”——每次 DMA 传输仍需 CPU 进行配置和中断处理,且 DMA 描述符的维护、cache 刷新操作都会消耗 CPU 周期。对于小于 64 字节的极短传输,CPU 直接搬运反而更快。
1.4 DMA 传输的基本要素
一次完整的 DMA 传输由以下五个基本要素构成,驱动开发者在配置 DMA 时必须明确指定每一项:
| 要素 | 说明 | 典型配置值 |
|---|---|---|
| 源地址 | 数据读取的起始物理地址,可以是外设 FIFO 地址或内存缓冲区地址 | 0x5000_0000(外设基址) |
| 目的地址 | 数据写入的目标物理地址 | 0x8000_0000(DDR 地址) |
| 传输宽度 | 每次总线事务传输的数据位宽 | 8/16/32/64 位 |
| 突发长度 | 一次总线仲裁中连续传输的数据单元个数 | 4/8/16(AHB 总线典型值) |
| 传输模式 | 单次传输、循环传输、描述符链传输 | 循环模式(音频流常用) |
驱动开发提示:在 Linux DMA Engine 框架中,上述要素通过 struct dma_slave_config 和 struct dma_async_tx_descriptor 描述。配置时需特别注意源/目的地址的递增属性——外设 FIFO 通常地址固定(不递增),而内存缓冲区地址递增。
/* Linux DMA Engine 配置示例(伪代码) */
struct dma_slave_config config = {
.direction = DMA_DEV_TO_MEM, // 外设到内存方向
.src_addr = 0x50000000, // 外设 FIFO 地址
.dst_addr = 0x80000000, // 内存缓冲区地址
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 8, // 突发长度 8
.dst_maxburst = 8,
};
dmaengine_slave_config(chan, &config);