1. DMA 基础概念
1.1 DMA 工作原理
直接存储器访问(Direct Memory Access, DMA)是一种允许硬件子系统直接读写系统内存的机制,无需 CPU 参与数据传输的每个字节。DMA 控制器作为独立的总线主控设备,在系统总线上与 CPU 并行工作,显著提升数据搬运效率。
核心思想:CPU 只需配置传输参数(源地址、目的地址、传输长度、触发条件),DMA 控制器自动完成剩余数据搬运,传输完成后通过中断通知 CPU。
1.2 DMA 与 CPU 数据搬运对比
传统 CPU 使用 PIO(Programmed I/O)模式逐字节搬运数据,而 DMA 将搬运任务卸载给专用硬件。下表从多个维度对比两种方式:
| 对比维度 | CPU 直接搬运(PIO) | DMA 搬运 |
|---|---|---|
| CPU 占用率 | 100%(搬运期间无法执行其他任务) | 仅配置和中断处理阶段占用(通常 <5%) |
| 吞吐量 | 受限于 CPU 主频和指令执行速度 | 可达数 Gbps(取决于总线带宽) |
| 延迟 | 低(无额外硬件调度开销) | 中等(需 DMA 请求-响应握手) |
| 适用场景 | 小数据量(<64 字节)、控制寄存器访问 | 大数据块(≥256 字节)、流式外设 |
| 硬件复杂度 | 无需额外 DMA 控制器 | 需要 DMA 引擎和中断控制器 |
| 功耗 | 高(CPU 持续活跃) | 低(CPU 可进入低功耗模式) |
工程经验:在嵌入式 Linux 系统中,当单次传输量超过 256 字节时,DMA 的收益开始显著。对于小于 64 字节的短数据,PIO 模式因省去 DMA 配置开销反而更快。
1.3 DMA 传输类型
根据数据流向和参与方,DMA 传输分为三种基本类型。每种类型在 Linux 内核中有不同的驱动实现路径和硬件配置方式。
1.3.1 内存到内存(Memory-to-Memory)
数据在系统内存的两个区域之间搬运,不涉及外设。典型应用包括:帧缓冲拷贝、加密/校验前的数据整理、用户态与内核态缓冲区同步。
/* Linux 内核 dmaengine API 示例:内存到内存传输 */
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
chan = dma_request_chan(dev, "memcpy");
tx = dmaengine_prep_dma_memcpy(chan, dst_phys, src_phys, size, DMA_CTRL_ACK);
cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);
注意:内存到内存 DMA 需要硬件支持。部分低端 DMA 控制器仅支持外设触发传输,无法发起内存到内存搬运。在申请通道前应通过
dma_get_slave_caps() 检查能力位。
1.3.2 内存到外设(Memory-to-Device)
数据从系统内存传输到外设的 FIFO 或寄存器。典型场景:LCD 显示数据刷新、音频播放 DMA 输出、网络 TX 描述符填充。
| 参数 | 说明 | 典型值 |
|---|---|---|
| 源地址 | 内存缓冲区物理地址 | 0x4800_0000 |
| 目的地址 | 外设数据寄存器地址 | 0x01C2_0000 (SPI_TX) |
| 传输宽度 | 单次总线传输的数据位宽 | 8/16/32 位 |
| 突发长度 | 连续传输次数(提高总线利用率) | 4/8/16 |
| 流控方式 | 由 DMA 还是外设控制传输节奏 | 外设流控(推荐) |
1.3.3 外设到内存(Device-to-Memory)
数据从外设 FIFO 读取到系统内存。典型场景:摄像头传感器数据采集、音频录音 DMA 输入、网络 RX 数据接收、UART 批量接收。
1.4 传输类型选择决策
在实际驱动开发中,选择哪种 DMA 传输类型取决于外设硬件设计、系统带宽需求和实时性要求。以下决策树可辅助判断:
- 数据是否涉及外设?
- 否 → 使用内存到内存(memcpy 通道)
- 是 → 进入下一步
- 数据流向?
- 内存 → 外设 → 使用内存到外设(slave TX 通道)
- 外设 → 内存 → 使用外设到内存(slave RX 通道)
- 是否需要循环传输?
- 是(如音频环形缓冲区)→ 配置 DMA 循环模式(DMA_CYCLIC)
- 否 → 配置单次传输模式
关键结论:DMA 的本质是将 CPU 从繁重的数据搬运中解放出来。理解三种传输类型的硬件特性和 Linux dmaengine 框架的对应关系,是后续章节编写 DMA 驱动的基石。在嵌入式 Linux 系统中,
dmaengine 子系统提供了统一的 API 抽象,屏蔽了不同 SoC DMA 控制器的硬件差异。