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调用打下理论基础。

核心要点: Linux DMA子系统将硬件DMA控制器抽象为统一的“DMA引擎”,通过dmaengine API向驱动开发者提供设备无关的通道申请、传输控制与回调机制。流式DMA与一致性DMA是两种不同的内存映射模型,分别适用于不同场景下的数据一致性与性能需求。

2.1 DMA引擎框架(DMA Engine Framework)

DMA引擎框架是Linux内核为DMA控制器驱动提供的一套通用抽象层。它将不同厂商、不同架构的DMA硬件(如Intel的IOAT、TI的EDMA、ARM的PL330等)封装为统一的“DMA设备”和“DMA通道”模型,使得上层驱动(如网络、存储、音频)无需关心底层硬件细节即可使用DMA传输能力。

Linux DMA引擎框架分层架构 驱动使用者(网络/存储/音频) dmaengine API(dma_request_channel / dmaengine_prep_slave_sg / dmaengine_submit) DMA引擎核心(dma_device / dma_chan / dma_async_tx_descriptor) DMA控制器驱动(IOAT / EDMA / PL330 / 自定义) DMA控制器硬件(寄存器 / 描述符 / 中断) 说明:驱动使用者通过dmaengine API调用,经引擎核心调度到底层硬件驱动,最终控制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传输流程如下:

  1. 通道申请: 调用 dma_request_channel() 获取一个匹配的DMA通道。
  2. 从设备配置: 调用 dmaengine_slave_config() 设置传输参数。
  3. 描述符准备: 调用 dmaengine_prep_slave_sg() 构建传输描述符。
  4. 提交: 调用 dmaengine_submit() 将描述符加入待处理队列,返回cookie。
  5. 触发: 调用 dma_async_issue_pending() 启动传输。
  6. 完成通知: 通过回调函数或 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 vs 一致性DMA 对比图 流式DMA映射(Streaming DMA) 特点 • 映射开销大(每次传输需cache维护) • 适合大数据块、短时、频繁传输 • 需要调用 dma_map_sg / dma_unmap_sg 典型场景:网络收发包、块设备读写 数据流从外设→内存→外设 一致性DMA映射(Coherent DMA) 特点 • 映射开销小(一次分配,长期使用) • 适合小数据块、控制结构、长期共享 • 使用 dma_alloc_coherent / dma_free_coherent 典型场景:DMA描述符环、控制寄存器 CPU与外设共享控制状态 核心区别:Cache一致性维护策略 流式DMA:映射时执行cache flush/invalidate,取消映射时再次维护cache一致性 一致性DMA:分配时建立非cacheable或硬件一致性内存区域,无需运行时维护 性能权衡:流式DMA适合批量传输,一致性DMA适合频繁小数据访问

下表从多个维度详细对比两种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
常见误区: 一致性DMA并不意味着“不需要同步”。在非硬件一致性平台上(如大多数ARM SoC),一致性DMA内存实际上是“非cacheable”的,CPU访问速度较慢。流式DMA虽然需要手动维护cache,但可以利用cache加速CPU对数据的访问。选择哪种模型需要根据数据访问频率、数据量和实时性要求综合权衡。

2.4 如何选择:流式DMA vs 一致性DMA

在实际驱动开发中,选择哪种DMA映射模型取决于以下三个关键因素:

  • 数据生命周期: 如果数据在DMA传输完成后CPU只需要短暂访问(如网络收包后立即处理并释放),流式DMA更合适;如果CPU和外设需要长期共享同一块内存(如描述符环),一致性DMA更合适。
  • 传输频率与数据量: 高频、小数据量的控制结构交互适合一致性DMA;低频、大数据量的批量传输适合流式DMA。
  • 平台硬件特性: 支持硬件一致性(如ARM64的CCI/CMN互联)的平台可以降低一致性DMA的cache维护开销;老旧或低端SoC可能只有非cacheable的一致性方案,此时流式DMA反而可能获得更好的CPU访问性能。
经验法则: 对于网络驱动、存储驱动等需要处理大量数据流的场景,优先使用流式DMA映射;对于需要长期驻留的控制结构(如DMA描述符表、USB请求块),优先使用一致性DMA映射。两种模型可以在同一个驱动中混合使用,分别管理不同类型的数据缓冲区。

本章为后续深入讲解流式DMA映射API(dma_map_sg、dma_sync_sg_for_cpu、dma_sync_sg_for_device 等)奠定了理论基础。理解DMA引擎框架的分层结构、dmaengine API的调用流程以及两种DMA映射模型的本质区别,是正确编写高性能DMA驱动的必要前提。