2. Linux DMA引擎框架概述:dmaengine子系统架构、dma_device与dma_chan结构体、dmaengine核心API

Linux内核的DMA引擎框架(dmaengine)为设备驱动开发者提供了一套统一、抽象的DMA操作接口。该框架屏蔽了不同DMA控制器(DMAC)的硬件差异,使得外设驱动(如SPI、I2S、MMC)无需关心底层DMA寄存器的具体操作,只需通过标准API即可发起数据传输。本章将深入剖析dmaengine子系统的三层架构、核心数据结构以及关键API的设计哲学。

核心观点: dmaengine框架的核心目标是“解耦”——将DMA控制器驱动(提供能力)与DMA用户驱动(使用能力)分离,通过一套标准化的描述符和回调机制实现高效、异步的数据搬运。

2.1 dmaengine子系统架构

dmaengine子系统采用经典的分层设计,从上至下依次为:DMA用户层、核心框架层和DMA控制器驱动层。这种分层结构确保了代码的可移植性和可扩展性。

dmaengine子系统三层架构 dmaengine 子系统三层架构 DMA 用户层 (DMA User Layer) 外设驱动 (SPI, I2S, MMC, UART) / 内存到内存拷贝 (dmatest) 调用 dmaengine_prep_* / dmaengine_submit / dma_async_issue_pending 核心框架层 (Core Framework) dmaengine.c / dmaengine.h 通道管理、描述符生命周期、回调机制、事务队列 DMA 控制器驱动层 (Controller Driver) 实现 dma_device 结构体中的回调函数 如: pl330_driver, edma_driver, dw_dmac, ti-cppi 数据流方向:用户层 → 核心框架 → 控制器驱动;完成回调:控制器驱动 → 核心框架 → 用户层
架构设计要点: 核心框架层是“胶水代码”,它不直接操作硬件,而是维护一个全局的dma_device列表,并为用户层提供阻塞/非阻塞的DMA传输接口。控制器驱动层负责将核心框架的通用请求翻译为具体的DMA控制器寄存器操作。

2.2 核心数据结构:dma_device 与 dma_chan

dmaengine框架围绕两个核心结构体展开:dma_device 描述DMA控制器的能力,dma_chan 代表一个已分配的DMA通道实例。理解这两个结构体是开发DMA引擎驱动的第一步。

2.2.1 dma_device:DMA控制器的能力描述

struct dma_device 定义在 include/linux/dmaengine.h 中,是DMA控制器驱动向核心框架注册的“名片”。它包含了控制器支持的操作模式、通道列表、以及一系列函数指针(回调)。

字段/回调 类型/原型 说明
dev struct device * 指向DMA控制器的struct device,用于驱动模型绑定和电源管理
channels struct list_head 该控制器下所有可用DMA通道的链表头
cap_mask dma_cap_mask_t 位掩码,标识控制器支持哪些传输类型(如DMA_MEMCPY、DMA_SLAVE、DMA_CYCLIC)
directions u32 位掩码,标识支持的DMA方向(DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV)
device_alloc_chan_resources int (*)(struct dma_chan *) 分配通道资源(如中断、描述符内存池),在通道被用户请求时调用
device_free_chan_resources void (*)(struct dma_chan *) 释放通道资源,在通道被释放时调用
device_prep_dma_memcpy struct dma_async_tx_descriptor *(*)(struct dma_chan *, dma_addr_t, dma_addr_t, size_t, unsigned long) 准备一个内存到内存的拷贝描述符(DMA_MEMCPY模式)
device_prep_slave_sg struct dma_async_tx_descriptor *(*)(struct dma_chan *, struct scatterlist *, unsigned int, struct scatterlist *, unsigned int, unsigned long, void *) 准备一个外设与内存之间的SG列表传输描述符(DMA_SLAVE模式)
device_issue_pending void (*)(struct dma_chan *) 通知DMA控制器开始处理通道上已提交的待处理描述符
device_tx_status enum dma_status (*)(struct dma_chan *, dma_cookie_t, struct dma_tx_state *) 查询指定cookie对应的传输状态(进行中、完成、错误)
驱动开发注意: 并非所有回调都需要实现。例如,一个只支持DMA_SLAVE模式的控制器驱动可以不实现 device_prep_dma_memcpy。核心框架通过 cap_mask 来判断用户请求是否合法。

2.2.2 dma_chan:DMA通道的运行时实例

struct dma_chan 是用户驱动与DMA控制器驱动交互的“手柄”。每个 dma_chan 实例对应一个物理DMA通道(或虚拟通道)。

struct dma_chan {
    struct dma_device *device;      // 所属的DMA控制器
    dma_cookie_t cookie;            // 当前传输的cookie(用于状态查询)
    dma_cookie_t completed_cookie;  // 最近完成的传输cookie
    void *private;                  // 控制器驱动的私有数据
    struct list_head device_node;   // 挂入dma_device->channels链表
    unsigned int chan_id;           // 通道ID(在控制器内唯一)
    struct dma_chan_dev *dev;       // 对应的字符设备(用于sysfs)
    const struct dma_slave_config *slave_config; // 外设配置(slave模式时有效)
};

用户驱动通过 dma_request_channel() 或设备树解析获得 dma_chan 指针,之后所有操作(如准备描述符、提交、查询状态)都基于该指针进行。

dma_device 与 dma_chan 关系 dma_device 与 dma_chan 关系 struct dma_device dev: struct device * cap_mask: DMA_MEMCPY | DMA_SLAVE directions: MEM_TO_DEV | DEV_TO_MEM channels: list_head device_prep_slave_sg() device_issue_pending() device_tx_status() channels 链表 struct dma_chan (chan0) device: &dma_device chan_id: 0, private: pl330_chan_data struct dma_chan (chan1) device: &dma_device chan_id: 1, private: pl330_chan_data 一个 dma_device 管理多个 dma_chan,每个 dma_chan 对应一个物理通道。 用户驱动通过 dma_request_channel() 获取 dma_chan,然后通过 dma_chan->device 访问控制器回调。

2.3 dmaengine核心API

dmaengine核心API是用户驱动与框架交互的主要途径。这些API封装了描述符的创建、提交、执行以及状态查询等关键操作。根据使用场景,可以将其分为三类:通道管理、传输控制、状态查询。

2.3.1 通道管理API

  • dma_request_channel():根据掩码和过滤函数请求一个满足条件的DMA通道。这是用户驱动获取通道的唯一入口。
  • dma_release_channel():释放已分配的DMA通道,归还给框架。
  • dma_get_slave_channel():在已经知道具体通道的情况下获取通道(通常用于设备树解析后的通道绑定)。

2.3.2 传输控制API

传输控制API遵循“准备-提交-执行”的三步曲模式,这是dmaengine框架的核心设计模式。

步骤 API函数 作用
1. 准备 dmaengine_prep_slave_sg()
dmaengine_prep_dma_memcpy()
dmaengine_prep_dma_cyclic()
创建一个DMA传输描述符(struct dma_async_tx_descriptor),配置源地址、目的地址、长度、方向等参数。此时尚未提交到硬件。
2. 提交 dmaengine_submit() 将准备好的描述符提交到通道的待处理队列中。返回一个cookie(dma_cookie_t),用于后续查询传输状态。
3. 执行 dma_async_issue_pending() 通知DMA控制器开始处理通道上所有已提交但尚未执行的描述符。此函数通常不会阻塞,DMA传输在后台进行。
// 典型的使用模式(DMA Slave模式)
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;

// 1. 准备描述符
tx = dmaengine_prep_slave_sg(chan, sgl, sg_len, direction, DMA_PREP_INTERRUPT, NULL);
if (!tx) {
    dev_err(dev, "Failed to prepare slave sg\n");
    return -ENOMEM;
}

// 设置完成回调
tx->callback = my_dma_callback;
tx->callback_param = my_data;

// 2. 提交描述符
cookie = dmaengine_submit(tx);
if (dma_submit_error(cookie)) {
    dev_err(dev, "Failed to submit DMA descriptor\n");
    return -EIO;
}

// 3. 触发执行
dma_async_issue_pending(chan);

2.3.3 状态查询与同步API

  • dma_async_is_tx_complete():查询指定cookie的传输是否完成。返回 DMA_COMPLETE、DMA_IN_PROGRESS 或 DMA_ERROR。
  • dma_sync_wait():阻塞等待指定cookie的传输完成。适用于需要同步的场景(如内存到内存拷贝)。
  • dmaengine_terminate_sync():立即终止通道上的所有传输,并等待硬件停止。常用于错误恢复或设备关闭。
性能提示: 在大多数外设驱动中,推荐使用异步模式(设置回调函数),避免在中断上下文或原子上下文中调用 dma_sync_wait()。对于内存到内存的批量拷贝,可以使用 dmaengine_async_device_issue_pending() 配合 dma_wait_for_async_tx() 实现高效的同步等待。

2.4 描述符生命周期与回调机制

每个DMA传输描述符(struct dma_async_tx_descriptor)都经历“创建 → 提交 → 执行 → 完成 → 释放”的生命周期。核心框架通过引用计数管理描述符的内存,并在传输完成时调用用户注册的回调函数。

DMA描述符生命周期 DMA描述符生命周期 1. 创建 (prep_*) 2. 提交 (submit) 3. 执行 (issue_pending) 4. 完成 (回调) 描述符在回调执行后由框架自动释放(除非设置了DMA_CTRL_ACK标志),用户驱动不应手动释放。

2.5 关键数据结构关系总结

下表总结了dmaengine框架中几个关键数据结构之间的关系与职责,帮助开发者快速建立全局视图。

数据结构 所属层次 主要职责 生命周期
dma_device 控制器驱动层 描述控制器能力,提供回调函数指针 驱动加载时创建,卸载时销毁
dma_chan 核心框架层 代表一个已分配的DMA通道,连接用户与控制器 用户请求时创建,释放时销毁
dma_async_tx_descriptor 核心框架层 描述一次DMA传输的配置、回调、cookie prep_*时创建,回调完成后释放
dma_slave_config 用户层 配置外设的DMA参数(地址、宽度、突发长度) 由用户驱动栈上分配,配置后不再使用
学习路径建议: 在阅读具体DMA控制器驱动(如pl330.c、edma.c)之前,务必先熟悉本节介绍的 dma_device 回调函数原型和 dma_chan 的使用模式。理解“准备-提交-执行”的三步曲是掌握dmaengine框架的关键。