4. DMA传输类型:内存到内存、内存到外设、外设到内存、循环传输
DMA(Direct Memory Access)控制器作为系统总线上的主设备,其核心价值在于无需CPU干预即可完成数据搬运。根据数据源与目的地的不同组合,DMA传输可划分为四种基本类型。理解每种类型的触发机制、传输方向、适用场景及配置要点,是驱动开发者设计高效数据通路的基础。
4.1 传输类型总览
下表从传输方向、典型触发源、硬件路径和常见应用四个维度,对四种DMA传输类型进行对比:
| 传输类型 | 数据方向 | 典型触发源 | 硬件路径 | 典型应用 |
|---|---|---|---|---|
| 内存到内存 (M2M) | 内存 → 内存 | 软件请求 | 系统总线 | 帧缓冲拷贝、数据搬移、内存清零 |
| 内存到外设 (M2P) | 内存 → 外设 | DMA请求或外设就绪 | 系统总线 → 外设总线 | LCD显示刷新、DAC输出、SPI发送 |
| 外设到内存 (P2M) | 外设 → 内存 | 外设硬件请求 | 外设总线 → 系统总线 | ADC采样、UART接收、摄像头数据捕获 |
| 循环传输 (Circular) | 双向或单向循环 | 硬件事件或定时器 | 系统总线 / 外设总线 | 音频流、连续数据采集、环形缓冲区 |
驱动开发提示: 在Linux DMA引擎框架中,
dma_transfer_type枚举定义了DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM和DMA_CYCLIC四种类型。循环传输在音频子系统中使用最为广泛,通常与dmaengine_prep_dma_cyclic()接口配合使用。
4.2 内存到内存传输 (M2M)
内存到内存传输是最纯粹的DMA搬运模式,源地址和目标地址均位于系统内存空间。该模式通常由软件显式启动,不需要外设硬件信号的参与。
4.2.1 工作原理
M2M传输的典型流程如下:
- CPU配置DMA控制器:源地址、目标地址、传输宽度、传输计数。
- CPU写入DMA使能寄存器或触发寄存器,启动传输。
- DMA控制器通过系统总线读取源内存数据,写入目标内存。
- 传输完成后,DMA控制器产生中断通知CPU。
关键特性: M2M传输不需要外设硬件握手信号,完全由软件控制。在Linux中,
dma_async_memcpy_buf_to_buf()是典型的M2M封装接口。该模式常用于帧缓冲的拷贝、内存区域清零或数据格式转换前的搬移。
4.2.2 内存到内存传输结构图
4.3 内存到外设传输 (M2P)
内存到外设传输将系统内存中的数据发送到外设设备,例如将帧缓冲数据发送到LCD控制器,或将音频样本发送到I2S DAC。该模式需要外设提供就绪信号或DMA请求信号来协调传输节奏。
4.3.1 硬件握手与流控
M2P传输的典型硬件连接包括:
- DMA请求线 (DMA_REQ): 外设向DMA控制器发出的传输请求信号。
- DMA应答线 (DMA_ACK): DMA控制器向外设发出的应答信号。
- 外设就绪信号: 部分外设通过状态寄存器指示是否可接收数据。
常见陷阱: 在M2P传输中,如果外设的FIFO深度较小且DMA响应延迟较大,可能导致外设FIFO下溢(Underflow)。驱动开发时应根据外设FIFO大小和DMA延迟合理设置突发传输大小(Burst Size)和阈值(Threshold)。
4.3.2 内存到外设传输流程图
4.4 外设到内存传输 (P2M)
外设到内存传输是数据采集系统的核心,外设(如ADC、UART、摄像头接口)产生数据后通过DMA直接存入内存,无需CPU逐字节读取。该模式对实时性和数据完整性要求较高。
4.4.1 配置要点
| 配置参数 | 说明 | 典型值 |
|---|---|---|
| 源地址 | 外设数据寄存器地址(固定) | 0x01C2_6000 (UART_RX) |
| 目标地址 | 内存缓冲区地址(递增) | 0x7000_0000 |
| 传输宽度 | 每次传输的数据位宽 | 8/16/32 bit |
| 突发长度 | 连续传输的数据单元数 | 4/8/16 |
| FIFO阈值 | 触发DMA请求的FIFO水位 | 半满 / 1/4满 |
驱动实现示例: 在Linux中,P2M传输通常通过
dmaengine_prep_slave_sg()或dmaengine_prep_dma_cyclic()接口配置。对于UART接收,典型的DMA描述符配置如下:
struct dma_async_tx_descriptor *txd;
struct dma_slave_config config;
config.direction = DMA_DEV_TO_MEM;
config.src_addr = uart_base + UART_RX_REG;
config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
config.src_maxburst = 4; /* 每次请求传输4字节 */
dmaengine_slave_config(chan, &config);
txd = dmaengine_prep_slave_sg(chan, sgt, nents, DMA_DEV_TO_MEM, flags);
4.4.2 外设到内存传输结构图
4.5 循环传输 (Circular / Cyclic)
循环传输是DMA的一种特殊模式,传输完成后自动重新加载初始配置,形成无限循环的数据流。该模式特别适合音频播放/录音、连续数据采集等需要稳定、无中断数据流的场景。
4.5.1 循环传输的核心机制
- 环形缓冲区: 内存中预先分配一块连续区域,DMA在传输到末尾后自动回绕到起始地址。
- 双缓冲/乒乓缓冲: 将环形缓冲区分为两个半区,CPU处理一个半区时DMA填充另一个半区。
- 周期中断: 每完成一个半区或指定大小的传输,DMA产生中断通知CPU处理数据。
- 无CPU干预: 配置完成后,DMA独立运行,CPU仅在周期中断时处理数据。
音频驱动中的循环传输: 在ALSA音频驱动中,
snd_pcm_period_elapsed()回调由DMA周期中断触发。典型的配置是:缓冲区大小=64KB,周期大小=8KB,DMA每传输8KB产生一次中断,CPU在中断中处理音频数据并更新指针。
4.5.2 循环传输环形缓冲区结构图
4.6 四种传输类型的对比与选型
在实际驱动开发中,选择合适的DMA传输类型直接影响系统性能和资源利用率。下表从多个维度进行对比:
| 对比维度 | M2M | M2P | P2M | Circular |
|---|---|---|---|---|
| 触发方式 | 软件触发 | 外设请求 | 外设请求 | 硬件事件 / 定时器 |
| 地址模式 | 源递增 → 目标递增 | 源固定 → 目标递增 | 源固定 → 目标递增 | 源/目标循环回绕 |
| 中断频率 | 传输完成一次 | 传输完成一次 | 传输完成一次 | 每个周期中断 |
| CPU负载 | 低(仅启动和完成) | 低 | 低 | 极低(仅周期处理) |
| 典型延迟 | 取决于内存带宽 | 取决于外设速度 | 取决于外设速度 | 取决于周期大小 |
| 适用场景 | 数据搬移、拷贝 | 显示、音频输出 | 数据采集、接收 | 音频流、连续采样 |
选型注意事项:
- M2M传输虽然简单,但会占用系统总线带宽,在高速系统中应避免与关键外设争抢总线。
- M2P和P2M传输需要正确配置外设的DMA请求信号极性、FIFO阈值和突发大小,否则可能导致数据丢失。
- 循环传输的周期大小选择至关重要:周期太小会导致频繁中断,增加CPU负载;周期太大会增加数据延迟。
- 部分DMA控制器不支持硬件循环模式,此时需要在驱动中通过软件重载描述符来模拟循环传输。
4.7 本章小结
DMA的四种基本传输类型构成了嵌入式系统中数据搬运的基础框架:
- 内存到内存 (M2M) 提供纯软件触发的数据搬移能力,适用于帧缓冲拷贝和内存初始化。
- 内存到外设 (M2P) 实现从系统内存到外设的数据输出,是显示和音频输出的核心。
- 外设到内存 (P2M) 完成从外设到系统内存的数据采集,是传感器和通信接口的基础。
- 循环传输 (Circular) 通过环形缓冲区和自动重载机制,实现无中断的连续数据流。