2、Linux DMA子系统架构:DMA Engine框架、dma_device与dma_chan结构体、DMA控制器抽象层

Linux内核的DMA Engine框架是驱动开发者与DMA控制器硬件之间的标准桥梁。它通过三层抽象——DMA Engine核心层、DMA控制器驱动层和DMA用户层(客户端)——实现了统一的API接口,使得不同厂商的DMA控制器(如Intel、TI、NXP、Xilinx等)能够以一致的方式被上层驱动调用。

核心设计思想:将硬件差异封装在控制器驱动内部,客户端驱动只需通过标准接口请求通道、提交传输描述符、等待完成回调,无需关心底层寄存器操作。

2.1 DMA Engine三层架构总览

下图展示了DMA Engine框架在Linux内核中的层次关系与数据流路径:

DMA Engine 三层架构与数据流 客户端驱动(Client Driver) dma_request_channel / dmaengine_prep_slave_sg DMA Engine 核心层(Core) dma_device / dma_chan / 通道管理与调度 DMA 控制器驱动(Controller Driver) 硬件寄存器操作 / 中断处理 / 描述符管理 DMA 控制器硬件(Hardware) PL330 / EDMA / SDMA / HDA 数据流方向 ① 客户端提交传输请求 ② 核心层分配通道与描述符 ③ 控制器驱动编程硬件 ④ 中断上报完成状态 回调路径 ④ 硬件中断触发 ③ 控制器驱动调用回调 ② 核心层通知客户端 ① 客户端处理完成事件 图:DMA Engine 三层架构,客户端 → 核心层 → 控制器驱动 → 硬件 虚线箭头表示中断回调路径,实线箭头表示传输请求下发路径
架构优势:这种分层设计使得DMA控制器驱动只需实现 dma_device 结构体中的回调函数,而客户端驱动则通过 dma_chan 句柄操作,两者完全解耦。更换DMA控制器硬件时,客户端驱动代码无需修改。

2.2 dma_device 结构体:控制器的能力描述

dma_device 是DMA控制器驱动向核心层注册的核心数据结构,它描述了控制器的硬件能力、支持的传输类型和操作函数指针。每个DMA控制器对应一个 dma_device 实例。

字段/成员 类型 说明
dev struct device * 指向DMA控制器的Linux设备结构体
channels struct list_head 该控制器管理的所有DMA通道链表头
cap_mask dma_cap_mask_t 位掩码,标识支持的DMA传输能力(如DMA_SLAVE、DMA_CYCLIC)
directions u32 位掩码,标识支持的传输方向(DMA_MEM_TO_MEM、DMA_MEM_TO_DEV等)
src_addr_widths u32 支持的源端数据宽度(1/2/4/8字节)
dst_addr_widths u32 支持的目的端数据宽度
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 函数指针 查询传输状态(已完成字节数、错误状态)
关键注册流程:控制器驱动在 probe 函数中填充 dma_device 结构体,调用 dma_async_device_register() 向核心层注册。核心层随后会遍历 channels 链表,为每个通道创建 dma_chan 实例并加入全局通道池。

2.3 dma_chan 结构体:传输通道的抽象

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链表 */
    struct kref refcount;           /* 引用计数 */
    struct dma_chan_dev *dev;       /* 对应的字符设备,用于用户空间访问 */
    const struct dma_chan_ops *ops; /* 通道操作函数集 */
    int chan_id;                    /* 通道ID */
    struct dma_slave_config cfg;    /* 从设备配置(地址、宽度、突发大小) */
};

客户端驱动通过 dma_request_channel() 获取一个满足特定能力要求的通道:

struct dma_chan *chan;
dma_cap_mask_t mask;

dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);  // 要求支持从设备传输

chan = dma_request_channel(mask, filter_fn, filter_param);
if (IS_ERR(chan)) {
    dev_err(dev, "Failed to request DMA channel\n");
    return PTR_ERR(chan);
}
通道生命周期:客户端驱动在初始化时请求通道,在卸载时通过 dma_release_channel() 释放。通道的分配策略由核心层根据 dma_cap_mask 和可选的过滤函数决定,支持按能力匹配和按设备匹配两种模式。

2.4 DMA控制器抽象层:从硬件到框架的映射

DMA控制器抽象层是连接具体硬件与DMA Engine框架的关键桥梁。不同的硬件实现通过填充 dma_device 中的函数指针,将硬件操作封装为标准接口。下表对比了三种常见DMA控制器的抽象实现差异:

控制器类型 典型硬件 抽象层实现特点 关键回调差异
PL330(ARM) ARM PrimeCell PL330 微码引擎,描述符链由硬件自动加载 device_prep_slave_sg 构建微码指令序列
EDMA(TI) TI OMAP/Davinci 参数RAM表,支持链接传输和PaRAM集 device_prep_slave_sg 配置PaRAM条目
SDMA(NXP i.MX) NXP i.MX系列 脚本引擎,支持复杂传输模式 device_prep_slave_sg 加载BD表

抽象层的核心工作包括:

  • 描述符管理:将上层提交的传输请求转换为硬件可执行的描述符(Descriptor),并管理描述符链表的入队、出队和回收。
  • 中断处理:注册硬件中断处理函数,在传输完成或出错时调用核心层的回调机制,通知客户端驱动。
  • 通道状态维护:跟踪每个通道的忙/闲状态、当前传输进度、错误计数等。
  • 电源管理:在系统休眠时保存DMA控制器状态,唤醒时恢复。
DMA控制器抽象层:从硬件到框架的映射 硬件实现层 PL330 微码引擎 EDMA PaRAM 表 SDMA BD 表 HDA 链表描述符 DMA 中断控制器 时钟与电源管理 寄存器映射 控制器抽象层 dma_device 结构体填充 描述符分配与回收 中断处理与回调 通道状态管理 电源管理回调 DMA Engine API 实现 dma_chan 生命周期管理 DMA Engine 框架 dma_request_channel dmaengine_prep_slave_sg dmaengine_submit dma_async_issue_pending dma_async_is_tx_complete dmaengine_terminate_sync dmaengine_synchronize 图:抽象层作为桥梁,将不同硬件实现映射为统一的DMA Engine API

2.5 描述符管理与传输生命周期

DMA传输的核心是描述符(Descriptor),它封装了一次传输所需的所有参数。DMA Engine框架使用 dma_async_tx_descriptor 结构体表示描述符:

struct dma_async_tx_descriptor {
    dma_cookie_t cookie;                    /* 传输cookie */
    enum dma_ctrl_flags flags;              /* 控制标志 */
    dma_addr_t src;                         /* 源地址(物理地址) */
    dma_addr_t dst;                         /* 目的地址(物理地址) */
    size_t len;                             /* 传输长度 */
    struct dma_chan *chan;                  /* 所属通道 */
    dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
    dma_async_tx_callback callback;         /* 传输完成回调 */
    void *callback_param;                   /* 回调参数 */
    struct list_head tx_list;               /* 用于链式传输 */
};

一次典型的DMA传输生命周期包含以下步骤:

  1. 准备描述符:客户端调用 dmaengine_prep_slave_sg() 或 dmaengine_prep_dma_memcpy(),控制器驱动分配并初始化描述符。
  2. 提交描述符:调用 dmaengine_submit() 将描述符加入通道的传输队列,返回一个cookie用于后续状态查询。
  3. 触发传输:调用 dma_async_issue_pending() 通知硬件开始处理队列中的描述符。
  4. 等待完成:通过 dma_async_is_tx_complete() 轮询或注册回调函数等待传输完成。
  5. 清理资源:传输完成后,控制器驱动回收描述符内存,调用回调通知客户端。
常见陷阱:描述符的 tx_submit 函数必须在 dma_async_issue_pending 之前调用,否则描述符不会被硬件感知。此外,回调函数在中断上下文中执行,不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL))。

2.6 通道能力匹配与过滤机制

当多个DMA控制器共存于系统中时,客户端驱动需要通过过滤函数精确匹配所需的通道。过滤函数的原型为:

bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);

典型的过滤实现示例:

static bool my_dma_filter(struct dma_chan *chan, void *param)
{
    struct device *dev = param;
    struct dma_device *dma_dev = chan->device;

    // 检查DMA控制器是否与我们的设备匹配
    if (dma_dev->dev->parent == dev) {
        // 进一步检查通道是否支持所需方向
        if (chan->private) {
            struct my_dma_chan_data *data = chan->private;
            return data->direction == DMA_MEM_TO_DEV;
        }
    }
    return false;
}

// 使用过滤函数请求通道
chan = dma_request_channel(mask, my_dma_filter, my_device);
最佳实践:在嵌入式系统中,通常使用设备树(Device Tree)中的 dmas 和 dma-names 属性来静态绑定DMA通道。内核提供了 dma_request_slave_channel() 函数,可直接通过设备树名称获取通道,无需手动编写过滤函数。