2. Linux DMA子系统概述:DMA引擎框架、dmaengine API、流式DMA与一致性DMA的区别
DMA(Direct Memory Access)子系统是Linux内核中负责管理设备与内存之间直接数据传输的核心基础设施。它通过硬件DMA控制器(DMA Controller)在无需CPU逐字节干预的情况下完成数据搬移,从而大幅降低CPU负载、提升系统吞吐量。本章从DMA引擎框架、dmaengine API以及两种DMA映射模型(流式DMA与一致性DMA)三个维度展开,为后续的流式DMA映射API调用打下理论基础。
2.1 DMA引擎框架(DMA Engine Framework)
DMA引擎框架是Linux内核为DMA控制器驱动提供的一套通用抽象层。它将不同厂商、不同架构的DMA硬件(如Intel的IOAT、TI的EDMA、ARM的PL330等)封装为统一的“DMA设备”和“DMA通道”模型,使得上层驱动(如网络、存储、音频)无需关心底层硬件细节即可使用DMA传输能力。
DMA引擎框架的核心数据结构包括:
| 数据结构 | 作用 | 关键字段 |
|---|---|---|
struct dma_device |
描述一个DMA控制器设备,包含其能力集(capabilities)和操作函数指针 | dev, channels, device_alloc_chan_resources, device_prep_slave_sg |
struct dma_chan |
表示一个DMA通道,是驱动使用者与DMA引擎交互的句柄 | device, client, chan_id, private |
struct dma_async_tx_descriptor |
描述一次DMA传输事务,包含源地址、目的地址、长度、回调函数等 | tx_submit, callback, callback_param, cookie |
2.2 dmaengine API 核心接口
dmaengine API是驱动开发者直接调用的编程接口集合。它屏蔽了底层DMA控制器的差异,提供了一套标准化的通道管理、传输描述符构建、提交与同步机制。以下是最常用的API函数及其用途:
| API函数 | 功能 | 典型调用时机 |
|---|---|---|
dma_request_channel() |
根据掩码(mask)申请一个满足特定能力的DMA通道 | 驱动probe阶段 |
dma_release_channel() |
释放已申请的DMA通道 | 驱动remove阶段 |
dmaengine_prep_slave_sg() |
为slave DMA准备一个scatter-gather传输描述符 | 每次传输前 |
dmaengine_submit() |
将描述符提交到DMA引擎的待处理队列 | 准备描述符后 |
dma_async_issue_pending() |
触发DMA引擎开始处理已提交的待处理描述符 | 提交描述符后 |
dma_sync_wait() |
同步等待DMA传输完成(基于cookie轮询) | 需要同步时 |
dmaengine_terminate_sync() |
同步终止当前通道上的所有DMA传输 | 错误处理或停止时 |
dmaengine_prep_slave_sg() 之前,必须通过 dmaengine_slave_config() 配置通道的方向(DMA_MEM_TO_DEV / DMA_DEV_TO_MEM)、地址、宽度和突发长度等参数。这些配置直接影响DMA传输的正确性与效率。
一个典型的dmaengine传输流程如下:
- 通道申请: 调用
dma_request_channel()获取一个匹配的DMA通道。 - 从设备配置: 调用
dmaengine_slave_config()设置传输参数。 - 描述符准备: 调用
dmaengine_prep_slave_sg()构建传输描述符。 - 提交: 调用
dmaengine_submit()将描述符加入待处理队列,返回cookie。 - 触发: 调用
dma_async_issue_pending()启动传输。 - 完成通知: 通过回调函数或
dma_sync_wait()等待传输完成。
/* 示例:dmaengine slave传输核心步骤(伪代码) */
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
/* 1. 申请通道 */
chan = dma_request_channel(mask, filter_fn, filter_param);
if (!chan)
return -ENODEV;
/* 2. 配置从设备参数 */
struct dma_slave_config config = {
.direction = DMA_MEM_TO_DEV,
.src_addr = (dma_addr_t)mem_buf,
.dst_addr = dev_phys_reg,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 16,
.dst_maxburst = 16,
};
dmaengine_slave_config(chan, &config);
/* 3. 准备描述符 */
tx = dmaengine_prep_slave_sg(chan, sgl, sg_len, DMA_MEM_TO_DEV,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!tx)
return -ENOMEM;
tx->callback = my_dma_callback;
tx->callback_param = my_data;
/* 4. 提交 */
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie))
return -EIO;
/* 5. 触发 */
dma_async_issue_pending(chan);
/* 6. 等待完成(同步方式) */
dma_sync_wait(chan, cookie);
2.3 流式DMA与一致性DMA的区别
在Linux内核中,DMA映射分为两种模型:流式DMA映射(Streaming DMA Mapping) 和 一致性DMA映射(Consistent DMA Mapping / Coherent DMA Mapping)。两者在内存一致性模型、映射开销、适用场景上存在本质差异。
下表从多个维度详细对比两种DMA映射模型:
| 对比维度 | 流式DMA映射 | 一致性DMA映射 |
|---|---|---|
| 内存分配方式 | 使用常规内存(kmalloc / vmalloc),通过 dma_map_sg() 映射 |
使用 dma_alloc_coherent() 分配专用一致性内存 |
| Cache一致性 | 映射/取消映射时由驱动显式维护cache一致性 | 硬件保证一致性(非cacheable或硬件coherent) |
| 映射开销 | 每次传输都需要映射/取消映射,开销较大 | 一次分配,长期使用,运行时无额外映射开销 |
| 适用数据量 | 大块数据(KB ~ MB级别) | 小块数据(几十字节 ~ 几KB) |
| 典型用途 | 网络帧、磁盘数据、音频缓冲区 | DMA描述符环、状态寄存器、控制块 |
| API函数 | dma_map_sg() / dma_unmap_sg() / dma_sync_sg_for_cpu() / dma_sync_sg_for_device() |
dma_alloc_coherent() / dma_free_coherent() |
| 内存地址 | 返回dma_addr_t,CPU使用虚拟地址访问 | 同时返回CPU虚拟地址和dma_addr_t |
2.4 如何选择:流式DMA vs 一致性DMA
在实际驱动开发中,选择哪种DMA映射模型取决于以下三个关键因素:
- 数据生命周期: 如果数据在DMA传输完成后CPU只需要短暂访问(如网络收包后立即处理并释放),流式DMA更合适;如果CPU和外设需要长期共享同一块内存(如描述符环),一致性DMA更合适。
- 传输频率与数据量: 高频、小数据量的控制结构交互适合一致性DMA;低频、大数据量的批量传输适合流式DMA。
- 平台硬件特性: 支持硬件一致性(如ARM64的CCI/CMN互联)的平台可以降低一致性DMA的cache维护开销;老旧或低端SoC可能只有非cacheable的一致性方案,此时流式DMA反而可能获得更好的CPU访问性能。
本章为后续深入讲解流式DMA映射API(dma_map_sg、dma_sync_sg_for_cpu、dma_sync_sg_for_device 等)奠定了理论基础。理解DMA引擎框架的分层结构、dmaengine API的调用流程以及两种DMA映射模型的本质区别,是正确编写高性能DMA驱动的必要前提。