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 技术显著提高了系统性能,特别是在需要大量数据传输的应用场景中。