DMAengine (Direct Memory Access Engine) 是 Linux 内核中的一个重要框架,用于提供统一的 DMA 子系统接口。DMA 技术允许外设直接与系统内存进行数据传输,无需 CPU 直接参与,从而显著提高系统性能和效率。
DMAengine 框架基础
DMAengine 框架提供了一个抽象层,使各种 DMA 控制器驱动能够以统一的方式与内核交互。主要特点包括:
- 设备驱动模型:基于 Linux 设备驱动模型,提供统一注册和管理机制
- 异步操作支持:支持异步 DMA 传输,包括完成通知机制
- 通道抽象:提供 DMA 通道抽象,简化资源管理
- 传输描述符:使用传输描述符来描述 DMA 操作
核心组件
在 DMAengine 框架中,DMA 操作通常分为以下几个主要组成部分:DMA 设备 (dma_device),DMA 通道 (dma_chan),DMA 描述符 (dma_async_tx_descriptor),DMA 操作 (dmaengine_prep_* 系列函数)。
-
DMA 控制器结构体 (
struct dma_device) 这是 DMA 控制器的抽象表示,包含控制器的基本信息和支持的功能:struct dma_device { struct device dev; enum dma_transaction_type cap_mask; unsigned int max_xor; unsigned int max_pq; int chancnt; int privatecnt; struct list_head channels; struct list_head global_node; int (*device_alloc_chan_resources)(struct dma_chan *chan); void (*device_free_chan_resources)(struct dma_chan *chan); /* ... 其他回调函数 ... */ };dma_device是 DMA 控制器的主要接口,它管理硬件 DMA 引擎的能力,提供了支持的功能(如 DMA_MEMCPY、DMA_SLAVE)以及用于操作 DMA 通道的回调函数。每个设备(如 DMA 控制器)通常会有一个或多个 DMA 通道。dma_device中的一些重要成员:dev: 关联的设备(如struct device)。cap_mask: 支持的 DMA 能力,如 DMA_MEMCPY、DMA_SLAVE。device_alloc_chan_resources: 为 DMA 通道分配资源的函数。device_free_chan_resources: 释放 DMA 通道资源的函数。
-
DMA 通道结构体 (
struct dma_chan) 代表 DMA 控制器中的一个通道:struct dma_chan { struct dma_device *device; dma_cookie_t cookie; /* ... 其他成员 ... */ struct dma_chan_dev *dev; struct list_head device_node; struct kobject kobj; };每个
dma_device可以有一个或多个 DMA 通道。dma_chan是每个物理 DMA 通道的表示。一个通道通常与设备的硬件 DMA 引擎直接相关联。dma_chan的一些重要成员:device: 关联的dma_device。cookie: 用于标识 DMA 事务的唯一 ID。desc: 该通道的当前 DMA 描述符(任务)。device_node: 将dma_chan链接到dma_device的链表中。
-
DMA 传输描述符 (
struct dma_async_tx_descriptor) 描述一个 DMA 传输操作:struct dma_async_tx_descriptor { dma_cookie_t cookie; enum dma_ctrl_flags flags; dma_addr_t phys; struct dma_chan *chan; dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx); /* ... 其他成员和回调函数 ... */ };每个 DMA 传输操作都由一个描述符(
dma_async_tx_descriptor)表示。该描述符包含了 DMA 传输的详细信息,如源地址、目标地址、传输的大小等。dma_async_tx_descriptor的一些重要成员:callback: 完成时执行的回调函数。callback_param: 回调函数的参数。flags: DMA 操作的标志,通常用于控制操作的方式(如是否等待完成等)。
-
DMA 操作函数 (
dmaengine_prep_*)struct dma_async_tx_descriptor *dmaengine_prep_slave_sg( struct dma_chan *chan, struct scatterlist *sgl, // 分散/聚集列表 unsigned int sg_len, // 列表长度 enum dma_transfer_direction direction, unsigned long flags); /* ... 其他成员函数 ... */DMAEngine 提供了一些预备函数,这些函数用于准备 DMA 描述符,并提交到相应的通道进行处理。常见的操作函数有:
dmaengine_prep_memcpy: 用于内存到内存的数据复制操作。dmaengine_prep_slave_sg: 用于设置从设备到内存或从内存到设备的传输。dmaengine_prep_dma_cyclic: 用于周期性传输(例如音频流)。
DMAengine 工作流程
-
申请 DMA 通道
首先,驱动会使用
dma_request_chan()或dma_request_channel()来申请一个可用的 DMA 通道:struct dma_chan *chan; chan = dma_request_chan(dev, "tx"); if (IS_ERR(chan)) { pr_err("Failed to request DMA channel\n"); } -
准备 DMA 描述符
接下来,为 DMA 操作准备一个描述符,指定数据源、目标、大小等信息:
struct dma_async_tx_descriptor *desc; desc = dmaengine_prep_memcpy(chan, dest, src, len, DMA_PREP_INTERRUPT); if (!desc) { pr_err("Failed to prepare DMA descriptor\n"); } -
提交 DMA 描述符
将描述符提交到 DMA 引擎:
dmaengine_submit(desc); -
启动 DMA 传输 使用
dma_async_issue_pending()启动 DMA 传输:dma_async_issue_pending(chan); -
等待完成 通过 DMA 引擎的回调机制或等待通知来检查传输是否完成。例如,通过 dma_cookie_status() 来检查描述符的状态。
if (dma_cookie_status(chan, cookie, NULL) == DMA_COMPLETE) { pr_info("DMA transfer completed successfully\n"); } -
清理 完成操作后,释放 DMA 资源:
dma_release_channel(chan);
主要 API 函数
| 作用 | 函数 | 功能 |
|---|---|---|
| 控制器驱动 API | dma_async_device_register() |
注册 DMA 控制器 |
dma_async_device_unregister() |
注销 DMA 控制器 | |
| 客户端 API | dma_request_channel() |
请求分配 DMA 通道 |
dma_release_channel() |
释放 DMA 通道 | |
dmaengine_prep_dma_memcpy() |
准备内存到内存的 DMA 传输 | |
dmaengine_prep_slave_sg() |
准备从设备到内存或内存到设备的 DMA 传输 | |
maengine_submit() |
提交 DMA 传输描述符 | |
dma_async_issue_pending() |
启动挂起的 DMA 传输 | |
dma_async_is_tx_complete() |
检查传输是否完成 |
高级特性
- 散列表传输(
Scatter-Gather):支持不连续内存区域之间的传输 - 循环缓冲区:支持连续的循环 DMA 传输,适用于音频等应用
- 中断阈值:可配置中断触发阈值以平衡延迟和中断开销
- XOR/PQ 操作:支持 XOR 和 RAID6 PQ 计算
- 同步 API:提供阻塞式 API 简化使用
- 通道共享:一个物理 DMA 通道可以由多个虚拟通道共享,
vchan(虚拟通道)是为了更好地管理多个 DMA 任务而引入的抽象层。 - DMA 传输完成回调:使用回调函数来通知 DMA 操作是否成功完成,通常用于异步传输。
常见的 DMA 类型
- 内存到内存(
Memory-to-Memory, DMA_MEMCPY):将数据从一个内存位置复制到另一个内存位置。 - 内存到设备(
Memory-to-Device, DMA_MEM_TO_DEV):将数据从内存传输到设备,常见于发送数据到外设(如网络、音频、GPU)。 - 设备到内存(
Device-to-Memory, DMA_DEV_TO_MEM):从设备接收数据到内存,常见于接收设备数据(如从网卡接收数据到内存)。 - 从设备到设备(
Device-to-Device): 直接在两个设备之间进行 DMA 传输,这种方式较为少见,通常会通过内存作为中介。
使用场景
DMAengine 框架广泛应用于:
- 音频/视频处理:高速数据流传输
- 网络设备:网络数据包传输
- 存储控制器:磁盘数据传输
- SPI/I2C 控制器:外设数据传输
- 加密引擎:数据安全处理
开发示例
以下是一个简单的 DMA 内存复制操作示例:
#include <linux/dmaengine.h>
static void dma_complete_callback(void *completion)
{
complete(completion);
}
int sample_dma_memcpy(dma_addr_t dst, dma_addr_t src, size_t len)
{
struct dma_chan *chan;
struct dma_async_tx_descriptor *tx;
dma_cookie_t cookie;
enum dma_status status;
DECLARE_COMPLETION_ONSTACK(completion);
/* 1. 获取 DMA 通道 */
chan = dma_request_chan(NULL, "memcpy");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 2. 准备传输描述符 */
tx = dmaengine_prep_dma_memcpy(chan, dst, src, len, 0);
if (!tx) {
dma_release_channel(chan);
return -EINVAL;
}
/* 3. 设置完成回调 */
tx->callback = dma_complete_callback;
tx->callback_param = &completion;
/* 4. 提交并开始传输 */
cookie = dmaengine_submit(tx);
dma_async_issue_pending(chan);
/* 5. 等待完成 */
wait_for_completion(&completion);
/* 6. 检查状态 */
status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);
/* 7. 释放通道 */
dma_release_channel(chan);
return (status == DMA_COMPLETE) ? 0 : -EIO;
}
总结
DMAengine 框架是 Linux 内核中的一个强大子系统,它提供了统一的接口来管理和使用各种 DMA 控制器,使得驱动开发变得更加简单和标准化。通过减少 CPU 参与数据传输的负担,DMA 技术显著提高了系统性能,特别是在需要大量数据传输的应用场景中。
JINHU