2. Linux DMA子系统概述:DMA Engine框架、dmaengine核心层、dmaengine驱动层、dmaengine API概览

DMA(Direct Memory Access)是嵌入式系统中实现高性能数据搬移的关键技术。Linux内核自2.6.x版本起引入了统一的DMA Engine框架,旨在解决以往DMA控制器驱动实现各异、API不统一的问题。本章将系统性地剖析DMA Engine框架的分层架构、核心数据结构以及对外提供的API接口,为后续的设备树配置与驱动开发奠定理论基础。

核心观点: Linux DMA Engine框架通过分层设计,将DMA控制器硬件差异封装在驱动层,向上层(如音频、视频、存储子系统)提供一套通用、异步、基于描述符的数据传输接口。

2.1 DMA Engine框架整体架构

DMA Engine框架采用经典的三层架构:用户层/内核子系统层、dmaengine核心层和DMA控制器驱动层。核心层作为枢纽,向下注册驱动,向上提供API。

DMA Engine 三层架构图 DMA Engine 框架三层架构 用户层 / 内核子系统层 音频子系统 (ASoC) | 视频子系统 (V4L2) | 加密子系统 (Crypto) | 存储子系统 (MMC/NAND) 调用 dmaengine API 发起异步传输请求 dmaengine 核心层 (drivers/dma/dmaengine.c) 核心数据结构:dma_device, dma_chan, dma_async_tx_descriptor 通道管理 | 描述符生命周期 | 回调机制 | 事务提交与同步 DMA 控制器驱动层 具体硬件实现:pl330 | edma | dw_dmac | bcm2835-dma | ti-cppi 实现 dma_device 操作集,操作硬件寄存器,管理硬件描述符 说明:箭头方向表示上层调用下层服务,核心层屏蔽硬件差异,提供统一接口。
架构要点: 核心层不直接操作硬件寄存器,而是通过驱动层注册的 dma_device 结构体中的函数指针来间接控制硬件。这种设计使得上层子系统无需关心底层是PL330还是EDMA。

2.2 dmaengine核心层详解

核心层是DMA Engine框架的“大脑”,负责管理通道、描述符和事务生命周期。其核心数据结构包括:

数据结构 文件位置 核心作用 关键字段
struct dma_device include/linux/dmaengine.h 描述一个DMA控制器硬件实例,包含其能力、通道列表和操作函数集 channels, cap_mask, device_alloc_chan_resources, device_prep_slave_sg
struct dma_chan include/linux/dmaengine.h 代表一个已分配的DMA通道,是上层与硬件交互的句柄 device, chan_id, slave, private
struct dma_async_tx_descriptor include/linux/dmaengine.h 描述一次DMA传输事务,包含源/目的地址、长度、回调函数等 tx_submit, callback, callback_param, cookie
struct dma_slave_config include/linux/dmaengine.h 配置从设备(如SPI、I2C)与DMA控制器之间的接口参数 direction, src_addr, dst_addr, src_maxburst, dst_maxburst
关键机制: 核心层维护一个全局的 dma_list 链表,所有注册的 dma_device 都会加入该链表。当上层调用 dma_request_channel() 时,核心层遍历该链表,根据 cap_mask(能力掩码)匹配最合适的通道。

2.3 dmaengine驱动层职责

驱动层是具体DMA控制器硬件的“翻译官”,负责将核心层的通用请求转化为硬件操作。一个典型的DMA控制器驱动需要实现以下核心回调函数:

  • 通道管理: device_alloc_chan_resources() 和 device_free_chan_resources(),负责分配/释放硬件通道相关的资源(如描述符池、中断)。
  • 传输准备: device_prep_slave_sg()(从设备SG传输)、device_prep_dma_memcpy()(内存拷贝)、device_prep_dma_cyclic()(循环传输,常用于音频)。
  • 事务控制: device_issue_pending()(触发硬件开始处理已提交的描述符)、device_tx_status()(查询传输状态)。
  • 配置: device_config(),用于接收来自上层的 dma_slave_config 配置。
DMA驱动层回调函数调用流程 DMA驱动层核心回调函数调用流程 上层子系统 dma_request_channel() device_alloc_chan_resources device_prep_slave_sg() device_issue_pending() 说明:驱动层回调函数由核心层在合适的时机调用,驱动开发者只需填充 dma_device 结构体中的函数指针。
驱动开发注意: 驱动层必须正确处理描述符的生命周期管理。描述符在 device_prep_* 中创建,在 tx_submit 后进入提交队列,传输完成后由中断处理函数调用回调,最后释放。内存泄漏或野指针是常见问题。

2.4 dmaengine API概览

dmaengine API是上层子系统与DMA Engine交互的唯一途径。根据使用场景,API可分为以下几类:

API类别 函数原型 功能描述 使用阶段
通道管理 dma_request_channel(mask, filter_fn, filter_param) 根据能力掩码和过滤函数请求一个DMA通道 驱动初始化
dma_release_channel(chan) 释放已分配的DMA通道 驱动卸载
传输配置 dmaengine_slave_config(chan, config) 配置从设备接口参数(地址、宽度、突发长度) 传输前配置
dmaengine_prep_slave_sg(chan, sgl, sg_len, dir, flags) 准备一个从设备SG(分散/聚集)传输描述符 传输前准备
dmaengine_prep_dma_cyclic(chan, buf_addr, buf_len, period_len, dir, flags) 准备一个循环传输描述符(常用于音频流) 传输前准备
事务提交与控制 dmaengine_submit(desc) 将描述符提交到硬件待处理队列,返回cookie 准备完成后
dma_async_issue_pending(chan) 触发DMA控制器开始处理已提交的描述符 提交之后
状态查询与同步 dma_async_is_tx_complete(chan, cookie, state) 查询指定cookie对应的传输是否完成 轮询或中断中
dma_sync_wait(chan, cookie) 同步等待指定传输完成(阻塞调用) 需要同步时
典型使用模式: 请求通道 → 配置从设备参数 → 准备描述符 → 提交描述符 → 触发传输 → (中断回调或轮询)等待完成 → 释放描述符。这个模式在音频、SPI、UART等驱动中反复出现。

2.5 典型传输流程示例

以下是一个典型的从设备SG传输代码片段,展示了dmaengine API的完整调用序列:

/* 1. 请求DMA通道 */
struct dma_chan *chan = dma_request_channel(mask, filter_fn, NULL);
if (!chan) {
    dev_err(dev, "Failed to request DMA channel\n");
    return -ENODEV;
}

/* 2. 配置从设备参数 */
struct dma_slave_config config = {
    .direction = DMA_DEV_TO_MEM,
    .src_addr = (dma_addr_t)phy_fifo_addr,
    .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
    .src_maxburst = 4,
};
dmaengine_slave_config(chan, &config);

/* 3. 准备SG传输描述符 */
struct scatterlist sg;
sg_init_one(&sg, buf, BUFFER_SIZE);
struct dma_async_tx_descriptor *desc;
desc = dmaengine_prep_slave_sg(chan, &sg, 1, DMA_DEV_TO_MEM,
                               DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc) {
    dev_err(dev, "Failed to prepare slave sg\n");
    goto err;
}

/* 4. 设置回调 */
desc->callback = my_dma_callback;
desc->callback_param = my_data;

/* 5. 提交描述符 */
dma_cookie_t cookie = dmaengine_submit(desc);

/* 6. 触发传输 */
dma_async_issue_pending(chan);

/* 7. 等待完成(或在回调中处理) */
dma_sync_wait(chan, cookie);
关键点: dmaengine_submit() 只是将描述符加入队列,真正的硬件启动由 dma_async_issue_pending() 触发。两者必须成对调用。回调函数在中断上下文中执行,应尽量简短。

2.6 本章小结

本章从宏观到微观,系统梳理了Linux DMA Engine框架的完整脉络:

  • 三层架构明确了核心层与驱动层的职责边界,核心层提供通用API,驱动层负责硬件适配。
  • 核心数据结构(dma_device、dma_chan、dma_async_tx_descriptor)是理解框架的钥匙。
  • 驱动层回调是硬件操作的“最后一公里”,开发者需要根据硬件手册实现这些回调。
  • API概览提供了从通道请求到传输完成的完整工具链,是后续实战编码的基础。