2. Linux DMA子系统架构:dmaengine框架概述、核心数据结构与驱动分层模型

Linux DMA子系统通过 dmaengine 框架为内核驱动和用户空间提供了统一的、与硬件无关的DMA操作接口。该框架屏蔽了不同DMA控制器(如Intel I/OAT、TI EDMA、PL330等)的硬件差异,使得驱动开发者无需关心底层寄存器细节,即可高效地发起和管理DMA传输。本章将深入剖析dmaengine框架的架构层次、核心数据结构以及驱动分层模型。

核心观点: dmaengine框架的核心设计哲学是“通道抽象”与“描述符驱动”。它将硬件DMA通道抽象为dma_chan,将一次传输任务抽象为dma_async_tx_descriptor,通过标准API实现异步传输控制。

2.1 dmaengine框架概述

dmaengine框架位于Linux内核的 drivers/dma/ 目录下,向上为各子系统(如MMC、SPI、ALSA、V4L2等)提供通用DMA服务,向下对接各类DMA控制器驱动。其核心目标是实现“一次编写,到处运行”的DMA传输能力。

框架的主要职责包括:

  • 通道管理: 动态分配、释放和复用DMA通道。
  • 传输描述符管理: 创建、提交、回收传输描述符。
  • 回调机制: 在DMA传输完成或出错时,异步通知上层驱动。
  • 硬件抽象: 提供统一的 struct dma_device 接口,屏蔽不同DMA控制器的实现差异。
dmaengine框架架构图 dmaengine 框架架构图 上层消费者 (MMC, SPI, ALSA, Crypto) dmaengine 核心层 (dmaengine.c) 硬件抽象层 (struct dma_device 接口) 具体DMA控制器驱动 (pl330.c, ioatdma.c, tegra210-adma.c) DMA 硬件控制器
设计要点: 框架通过 dma_request_channel() 和 dma_release_channel() 管理通道生命周期。上层驱动在初始化时请求通道,在卸载时释放通道,框架负责底层硬件通道的分配与调度。

2.2 核心数据结构

dmaengine框架围绕三个核心数据结构构建:dma_device、dma_chan 和 dma_async_tx_descriptor。它们分别代表DMA控制器、DMA通道和一次DMA传输任务。

2.2.1 struct dma_device — DMA控制器抽象

dma_device 是DMA控制器的软件抽象,包含控制器的能力描述、操作函数指针和通道列表。每个DMA控制器驱动在初始化时,需要填充该结构体并注册到框架中。

成员 类型 描述
cap_mask dma_cap_mask_t 控制器支持的能力位图(如DMA_MEMCPY、DMA_SLAVE、DMA_CYCLIC)
channels list_head 该控制器下所有DMA通道的链表头
device_alloc_chan_resources 函数指针 分配并初始化通道资源
device_free_chan_resources 函数指针 释放通道资源
device_prep_dma_memcpy 函数指针 准备一次内存到内存的拷贝传输描述符
device_prep_slave_sg 函数指针 准备一次从设备到内存(或反向)的SG传输描述符
device_issue_pending 函数指针 触发硬件开始执行已提交的传输描述符

2.2.2 struct dma_chan — DMA通道抽象

dma_chan 代表一个逻辑DMA通道,是上层驱动与硬件通道之间的桥梁。它包含指向所属 dma_device 的指针、通道ID、以及用于异步传输完成通知的等待队列。

struct dma_chan {
    struct dma_device *device;      /* 所属的DMA控制器 */
    dma_cookie_t cookie;            /* 当前传输的cookie */
    dma_cookie_t completed_cookie;  /* 最后完成的传输cookie */
    int chan_id;                    /* 通道ID */
    struct list_head device_node;   /* 挂入dma_device的channels链表 */
    struct dma_chan_percpu *local;  /* 每CPU统计信息 */
    int client_count;               /* 使用该通道的客户端数量 */
    struct list_head free_slots;    /* 空闲描述符槽位 */
    /* ... 其他成员 */
};

2.2.3 struct dma_async_tx_descriptor — 传输任务抽象

该结构体描述一次具体的DMA传输任务,包括源地址、目的地址、传输长度、回调函数等。它是框架实现异步传输的核心。

成员 描述
cookie 传输的唯一标识符,用于跟踪传输状态
callback 传输完成后的回调函数指针
callback_param 回调函数的参数
tx_submit 将描述符提交到硬件待处理队列
desc_free 释放描述符及其关联资源
phys 描述符的物理地址(用于硬件访问)
chan 指向所属通道的指针
tx_list 用于链式传输的链表节点
dmaengine核心数据结构关系图 核心数据结构关系图 struct dma_device - cap_mask - channels 链表头 - device_* 函数指针 - 全局唯一ID struct dma_chan - device (所属dma_device) - cookie / completed_cookie - chan_id - client_count dma_async_tx_descriptor - cookie (传输标识) - callback / callback_param - tx_submit / desc_free - phys (硬件地址) 关系说明 1个 dma_device 包含 N 个 dma_chan 1个 dma_chan 管理 M 个描述符

2.3 驱动分层模型

dmaengine框架采用经典的分层设计,从上到下依次为:DMA引擎层(核心API)、DMA设备层(硬件抽象接口)和 DMA控制器驱动层(硬件实现)。这种分层模型使得上层驱动无需关心底层硬件细节,同时也便于新DMA控制器的适配。

2.3.1 分层职责

层次 文件/模块 主要职责
引擎层 (Engine Layer) dmaengine.h, dmaengine.c 提供标准API(dma_async_memcpy_buf_to_buf, dmaengine_submit, dma_async_issue_pending),管理通道和描述符生命周期
设备层 (Device Layer) struct dma_device 定义硬件无关的接口契约,包括能力声明、通道操作、传输准备和提交函数指针
控制器驱动层 (Controller Driver) pl330.c, ioatdma.c 等 实现具体的硬件操作,如配置DMA寄存器、处理中断、管理硬件描述符队列

2.3.2 典型传输流程

一次典型的DMA传输从上层驱动发起,经过框架层层传递,最终由硬件执行。以下是一个内存拷贝(memcpy)的完整调用链:

  1. 通道请求: 上层调用 dma_request_channel() 获取一个满足 DMA_MEMCPY 能力的通道。
  2. 准备描述符: 调用 dmaengine_prep_dma_memcpy(),框架内部调用 device_prep_dma_memcpy 函数指针,由控制器驱动创建并返回一个 dma_async_tx_descriptor。
  3. 提交传输: 调用 dmaengine_submit() 将描述符提交到通道的待处理队列。
  4. 触发执行: 调用 dma_async_issue_pending(),框架调用 device_issue_pending,通知硬件开始DMA传输。
  5. 完成回调: 硬件传输完成后触发中断,控制器驱动在中断处理中调用 dma_cookie_complete() 并执行描述符中注册的 callback 函数。
驱动分层模型与传输流程 驱动分层模型与典型传输流程 引擎层 (Engine Layer) — dmaengine.h / dmaengine.c dma_request_channel() → dmaengine_prep_dma_memcpy() → dmaengine_submit() → dma_async_issue_pending() 设备层 (Device Layer) — struct dma_device 接口 device_alloc_chan_resources → device_prep_dma_memcpy → device_issue_pending 控制器驱动层 (Controller Driver) — pl330.c / ioatdma.c 配置寄存器 → 构建硬件描述符 → 启动DMA → 中断处理 → 回调通知 传输流程步骤 ① 请求通道 → ② 准备描述符 → ③ 提交传输 → ④ 触发执行 → ⑤ 完成回调 (上层驱动) (引擎层) (设备层) (控制器驱动) (硬件)
注意事项: 在编写DMA控制器驱动时,必须正确实现 device_issue_pending 函数。该函数是触发硬件开始传输的关键入口,如果实现不当(例如未正确写入启动寄存器),会导致传输永远无法开始,上层驱动将一直等待完成回调。

2.4 关键API与使用模式

为了帮助读者快速上手,下面列出几个最常用的dmaengine API及其典型使用模式:

API 功能 典型调用时机
dma_request_channel() 请求一个满足指定能力的DMA通道 驱动初始化时
dmaengine_prep_slave_sg() 准备一次从设备到内存的SG传输描述符 每次传输前
dmaengine_submit() 将描述符提交到通道的待处理队列 准备描述符后立即调用
dma_async_issue_pending() 触发硬件开始执行待处理队列中的传输 提交描述符后调用
dma_sync_wait() 同步等待当前传输完成(阻塞) 需要同步结果时
dma_release_channel() 释放DMA通道 驱动卸载时
性能提示: 在批量传输场景中,建议使用 dmaengine_submit() 提交多个描述符后,再统一调用一次 dma_async_issue_pending()。这样可以减少硬件启动开销,提高DMA控制器的利用率。这种模式称为“批量提交,一次触发”。