2. Linux DMA子系统架构:dmaengine框架概述、核心数据结构与驱动分层模型
Linux DMA子系统通过 dmaengine 框架为内核驱动和用户空间提供了统一的、与硬件无关的DMA操作接口。该框架屏蔽了不同DMA控制器(如Intel I/OAT、TI EDMA、PL330等)的硬件差异,使得驱动开发者无需关心底层寄存器细节,即可高效地发起和管理DMA传输。本章将深入剖析dmaengine框架的架构层次、核心数据结构以及驱动分层模型。
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控制器的实现差异。
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 |
用于链式传输的链表节点 |
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)的完整调用链:
- 通道请求: 上层调用
dma_request_channel()获取一个满足DMA_MEMCPY能力的通道。 - 准备描述符: 调用
dmaengine_prep_dma_memcpy(),框架内部调用device_prep_dma_memcpy函数指针,由控制器驱动创建并返回一个dma_async_tx_descriptor。 - 提交传输: 调用
dmaengine_submit()将描述符提交到通道的待处理队列。 - 触发执行: 调用
dma_async_issue_pending(),框架调用device_issue_pending,通知硬件开始DMA传输。 - 完成回调: 硬件传输完成后触发中断,控制器驱动在中断处理中调用
dma_cookie_complete()并执行描述符中注册的callback函数。
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控制器的利用率。这种模式称为“批量提交,一次触发”。