4、DMA传输方向与类型:内存到内存(MEMCPY)、内存到设备(DEV_TO_MEM)、设备到内存(MEM_TO_DEV)、循环传输(CYCLIC)
DMA 传输的核心在于根据数据流向和传输模式选择正确的配置。Linux DMA Engine 框架抽象了四种标准传输类型,驱动开发者必须理解每种类型的语义、适用场景以及硬件约束,才能正确初始化描述符并处理完成回调。
4.1 四种传输类型概述
下表对比了四种 DMA 传输类型的核心特征、典型应用场景以及关键注意事项:
| 传输类型 | 数据流向 | 典型场景 | 关键约束 |
|---|---|---|---|
| MEMCPY | 内存 → 内存 | 数据拷贝、内存搬运、帧缓冲复制 | 源和目标必须都是内存地址;硬件可能要求对齐 |
| DEV_TO_MEM | 设备 → 内存 | 网卡接收、音频采集、摄像头数据捕获 | 源地址为设备 FIFO/寄存器,目标为内存缓冲区 |
| MEM_TO_DEV | 内存 → 设备 | 网卡发送、音频播放、LCD 显示刷新 | 源为内存缓冲区,目标为设备 FIFO/寄存器 |
| CYCLIC | 循环(设备 ↔ 内存) | 音频流、连续采样、无限循环播放 | 描述符链表形成环形;硬件必须支持自动重载 |
关键概念:MEMCPY 是唯一不涉及外设的纯内存传输类型,常用于 CPU 卸载。CYCLIC 是 MEM_TO_DEV 或 DEV_TO_MEM 的循环变体,描述符链表首尾相连,硬件自动重复执行,无需软件重新提交。
4.2 内存到内存 (MEMCPY)
MEMCPY 传输将数据从一块内存区域拷贝到另一块内存区域,完全由 DMA 控制器完成,不涉及任何外设。这是最简单的 DMA 传输类型,也是验证 DMA 控制器基本功能的首选测试用例。
在 Linux DMA Engine 中,MEMCPY 传输通过 dmaengine_prep_dma_memcpy() 接口创建描述符:
struct dma_async_tx_descriptor *desc;
dma_addr_t src_dma, dst_dma;
/* 将源和目标缓冲区映射到 DMA 地址 */
src_dma = dma_map_single(dev, src_buf, size, DMA_TO_DEVICE);
dst_dma = dma_map_single(dev, dst_buf, size, DMA_FROM_DEVICE);
/* 创建 MEMCPY 描述符 */
desc = dmaengine_prep_dma_memcpy(chan, dst_dma, src_dma, size, flags);
if (!desc) {
dev_err(dev, "Failed to prepare MEMCPY descriptor\n");
goto err_unmap;
}
desc->callback = memcpy_done_callback;
desc->callback_param = &cb_data;
/* 提交并启动传输 */
dmaengine_submit(desc);
dma_async_issue_pending(chan);
注意事项:MEMCPY 传输的源和目标地址必须使用
dma_map_single() 映射,且方向参数需根据实际读写操作选择。部分 DMA 控制器要求源和目标地址按 4 字节或 8 字节对齐,未对齐地址可能导致传输失败或性能下降。
4.3 内存到设备 (MEM_TO_DEV) 与设备到内存 (DEV_TO_MEM)
这两种类型是 DMA 传输中最常用的模式,分别对应数据从内存发送到外设(如网卡发送、音频播放)和从外设接收数据到内存(如网卡接收、音频采集)。
它们的核心区别在于数据流向和地址映射方向:
- MEM_TO_DEV:使用
dmaengine_prep_slave_sg()创建描述符,方向参数为DMA_MEM_TO_DEV。源地址是内存中的散列表(SG),目标地址是设备 FIFO 或寄存器。 - DEV_TO_MEM:同样使用
dmaengine_prep_slave_sg(),方向参数为DMA_DEV_TO_MEM。源地址是设备 FIFO,目标地址是内存中的散列表。
典型配置流程如下:
struct dma_slave_config config;
struct dma_async_tx_descriptor *desc;
/* 配置从设备参数 */
memset(&config, 0, sizeof(config));
config.direction = DMA_DEV_TO_MEM; /* 或 DMA_MEM_TO_DEV */
config.src_addr = pdev->phys_base + FIFO_REG; /* 设备 FIFO 物理地址 */
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
config.src_maxburst = 4; /* 每次突发传输 4 个数据单元 */
/* 应用配置到 DMA 通道 */
dmaengine_slave_config(chan, &config);
/* 创建 SG 描述符 */
desc = dmaengine_prep_slave_sg(chan, sgl, sg_len, config.direction, flags);
if (!desc) {
dev_err(dev, "Failed to prepare slave SG descriptor\n");
return -ENOMEM;
}
desc->callback = slave_done_callback;
dmaengine_submit(desc);
dma_async_issue_pending(chan);
4.4 循环传输 (CYCLIC)
循环传输是音频、连续采样等实时数据流场景的核心需求。它通过将多个描述符链接成环形链表,让 DMA 控制器自动循环执行,无需软件在每次传输完成后重新提交描述符。
循环传输的关键特征:
- 描述符链表形成环形:最后一个描述符的
next指针指向第一个描述符。 - 硬件自动重载:传输完成后自动跳转到链表头,继续下一轮传输。
- 周期性回调:每个周期(period)完成后触发中断,软件在回调中处理数据。
- 无终止条件:除非显式调用
dmaengine_terminate_sync(),否则传输永不停止。
创建循环传输描述符使用 dmaengine_prep_dma_cyclic() 接口:
struct dma_async_tx_descriptor *desc;
/* 创建循环传输描述符 */
desc = dmaengine_prep_dma_cyclic(chan, dma_buf, buf_size, period_size,
direction, flags);
if (!desc) {
dev_err(dev, "Failed to prepare cyclic descriptor\n");
return -ENOMEM;
}
/* 注册周期完成回调 */
desc->callback = cyclic_period_done;
desc->callback_param = &audio_data;
/* 启动循环传输 */
dmaengine_submit(desc);
dma_async_issue_pending(chan);
周期与缓冲区:
buf_size 是总缓冲区大小,period_size 是每个周期的大小。硬件将总缓冲区划分为多个周期,每个周期完成后触发一次中断。例如,音频场景中 buf_size = 4096,period_size = 1024,则每 1024 字节传输完成触发一次回调。
4.5 传输类型选择与硬件约束
并非所有 DMA 控制器都支持全部四种传输类型。驱动开发者在初始化阶段必须查询通道能力,避免使用不支持的传输类型:
struct dma_device *dma_dev = chan->device;
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_MEMCPY, mask); /* 检查 MEMCPY 支持 */
dma_cap_set(DMA_CYCLIC, mask); /* 检查 CYCLIC 支持 */
if (!dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) {
dev_warn(dev, "DMA controller does not support MEMCPY\n");
/* 回退到 CPU 拷贝 */
}
if (!dma_has_cap(DMA_CYCLIC, dma_dev->cap_mask)) {
dev_warn(dev, "DMA controller does not support CYCLIC\n");
/* 回退到手动重新提交 SG 描述符 */
}
下表总结了不同传输类型对硬件特性的依赖:
| 传输类型 | 必需硬件特性 | 可选硬件特性 |
|---|---|---|
| MEMCPY | 内存到内存传输引擎 | 对齐要求、最大传输长度 |
| DEV_TO_MEM | 外设接口、SG 支持 | 突发传输、地址宽度配置 |
| MEM_TO_DEV | 外设接口、SG 支持 | 突发传输、地址宽度配置 |
| CYCLIC | 描述符链表循环、自动重载 | 周期中断、硬件指针回绕 |
调试建议:在开发初期,先使用 MEMCPY 验证 DMA 控制器的基本功能,再逐步切换到 DEV_TO_MEM/MEM_TO_DEV 和 CYCLIC。MEMCPY 不依赖外设,可以独立测试 DMA 通道、描述符提交和中断回调的完整性。
4.6 传输方向与中断协同
不同传输类型的中断行为差异显著,驱动必须根据传输类型配置中断处理策略:
- MEMCPY:通常只需一次完成中断,回调中释放 DMA 映射并通知等待队列。
- DEV_TO_MEM / MEM_TO_DEV:每次 SG 列表传输完成触发中断,回调中处理数据并提交下一批描述符。
- CYCLIC:每个周期触发中断,回调中处理周期数据(如音频缓冲区填充),不能停止传输。
下图展示了 CYCLIC 传输的中断与数据流协同关系:
中断协同要点:在 CYCLIC 传输中,回调函数必须快速执行,避免阻塞后续中断。通常采用双缓冲或环形缓冲区机制:回调中仅更新缓冲区指针并触发数据消费,不进行耗时操作。对于音频场景,回调中通过
snd_pcm_period_elapsed() 通知 ALSA 框架,由框架在独立线程中处理数据。