4. Linux DMA引擎框架:dmaengine API概述、dma_device结构体、dma_chan通道管理
Linux内核自2.6.32版本起引入了统一的DMA引擎框架(dmaengine),旨在解决不同DMA控制器驱动接口碎片化的问题。该框架为设备驱动开发者提供了一套标准化的API,用于申请DMA通道、配置传输参数、提交传输请求以及异步等待传输完成。本章将深入剖析dmaengine框架的核心组件:API体系、dma_device结构体以及dma_chan通道管理机制。
4.1 dmaengine API概述
dmaengine API分为两个层次:消费者API(供外设驱动使用)和提供者API(供DMA控制器驱动实现)。消费者API是驱动开发者最常接触的接口,其核心操作流程如下:
- 通道申请:通过
dma_request_chan()或dma_request_slave_channel()获取一个DMA通道。 - 描述符准备:调用
dmaengine_prep_slave_sg()、dmaengine_prep_dma_cyclic()或dmaengine_prep_interleaved_dma()创建传输描述符。 - 回调绑定:通过
dmaengine_submit()将描述符提交到硬件队列前,设置callback和callback_param。 - 传输启动:调用
dma_async_issue_pending()触发硬件开始搬运。 - 同步等待:使用
dma_sync_wait()或DMA完成回调获取传输结果。
| API函数 | 功能描述 | 典型使用场景 |
|---|---|---|
dma_request_chan() |
通过设备树或平台数据请求DMA通道 | 驱动初始化时获取通道 |
dmaengine_prep_slave_sg() |
准备分散/聚合传输描述符 | 帧缓冲与显示控制器之间的数据搬运 |
dmaengine_prep_dma_cyclic() |
准备循环传输描述符 | 音频流、连续刷新显示缓冲区 |
dmaengine_submit() |
将描述符提交到DMA引擎的待处理队列 | 提交传输请求 |
dma_async_issue_pending() |
启动队列中所有待处理的传输 | 触发DMA控制器开始工作 |
dma_sync_wait() |
同步等待DMA传输完成 | 阻塞等待传输结束 |
dma_release_channel() |
释放DMA通道 | 驱动卸载或通道不再使用时 |
dmaengine_prep_slave_sg()配合DMA_MEM_TO_DEV方向,将帧缓冲数据搬运到显示控制器的FIFO。对于需要连续刷新的场景(如高帧率显示),dmaengine_prep_dma_cyclic()可以避免重复提交描述符的开销。
4.2 dma_device结构体深度解析
dma_device是DMA控制器驱动向内核注册的核心结构体,它描述了DMA控制器的硬件能力和操作函数集。该结构体定义在include/linux/dmaengine.h中,是连接硬件与框架的桥梁。
struct dma_device {
unsigned int chancnt; /* 通道数量 */
unsigned int max_burst; /* 最大突发传输长度 */
u32 src_addr_widths; /* 支持的源地址宽度位掩码 */
u32 dst_addr_widths; /* 支持的目的地址宽度位掩码 */
u32 directions; /* 支持的数据传输方向位掩码 */
u32 residue_granularity; /* 剩余数据量粒度 */
struct list_head channels; /* 通道链表头 */
struct dma_cap_mask_t cap_mask; /* 能力掩码 */
/* 操作函数指针 */
int (*device_alloc_chan_resources)(struct dma_chan *chan);
void (*device_free_chan_resources)(struct dma_chan *chan);
struct dma_async_tx_descriptor *(*device_prep_slave_sg)(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_transfer_direction direction,
unsigned long flags, void *context);
struct dma_async_tx_descriptor *(*device_prep_dma_cyclic)(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_transfer_direction direction,
unsigned long flags);
int (*device_config)(struct dma_chan *chan,
struct dma_slave_config *config);
int (*device_terminate_all)(struct dma_chan *chan);
enum dma_status (*device_tx_status)(struct dma_chan *chan,
dma_cookie_t cookie,
struct dma_tx_state *txstate);
void (*device_issue_pending)(struct dma_chan *chan);
};
- cap_mask:使用
dma_cap_set()宏设置,声明该DMA设备支持的操作类型(如DMA_SLAVE、DMA_CYCLIC)。 - directions:位掩码表示支持
DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV中的哪些方向。 - src_addr_widths / dst_addr_widths:定义支持的地址宽度,如
DMA_SLAVE_BUSWIDTH_4_BYTES。 - device_config:配置DMA通道的从设备参数,包括源/目的地址、突发大小、地址宽度等。
4.3 dma_chan通道管理
dma_chan是DMA通道的抽象,代表一个物理DMA通道或虚拟通道。每个dma_chan实例关联到一个dma_device,并维护传输状态、回调函数和客户端私有数据。通道管理涉及分配、配置、使用和释放的全生命周期。
4.3.1 通道状态与生命周期
| 阶段 | 状态 | 关键操作 | 说明 |
|---|---|---|---|
| 空闲 | IDLE | 通道未被使用 | 等待客户端申请 |
| 已分配 | ALLOCATED | dma_request_chan() |
客户端获得通道所有权 |
| 已配置 | CONFIGURED | device_config() |
设置从设备参数(地址、宽度、突发大小) |
| 传输中 | PAUSED / ACTIVE | dma_async_issue_pending() |
硬件正在执行数据传输 |
| 完成 | COMPLETED | 回调或dma_sync_wait() |
传输结束,可重新配置或释放 |
| 释放 | RELEASED | dma_release_channel() |
通道归还给系统 |
4.3.2 通道管理核心流程
以下代码片段展示了显示驱动中典型的DMA通道管理流程:
/* 1. 申请DMA通道 */
struct dma_chan *dma_chan;
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_chan = dma_request_channel(mask, filter_fn, &filter_param);
if (IS_ERR(dma_chan)) {
dev_err(dev, "Failed to request DMA channel\n");
return PTR_ERR(dma_chan);
}
/* 2. 配置从设备参数 */
struct dma_slave_config config = {
.direction = DMA_MEM_TO_DEV,
.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES,
.src_maxburst = 16,
.dst_maxburst = 16,
.dst_addr = display_fifo_phys_addr,
};
dmaengine_slave_config(dma_chan, &config);
/* 3. 准备传输描述符 */
struct dma_async_tx_descriptor *tx;
tx = dmaengine_prep_slave_sg(dma_chan, sgl, sg_nents,
DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT, NULL);
if (!tx) {
dev_err(dev, "Failed to prepare slave sg\n");
dma_release_channel(dma_chan);
return -ENOMEM;
}
/* 4. 设置完成回调 */
tx->callback = dma_done_callback;
tx->callback_param = completion;
/* 5. 提交并启动传输 */
dma_cookie_t cookie = dmaengine_submit(tx);
dma_async_issue_pending(dma_chan);
/* 6. 等待传输完成(同步方式) */
dma_sync_wait(dma_chan, cookie);
/* 7. 释放通道 */
dma_release_channel(dma_chan);
- 在显示驱动中,DMA通道通常在
probe()函数中申请,在remove()函数中释放,避免频繁申请/释放带来的开销。 - 对于多缓冲区显示场景,建议使用
dmaengine_prep_dma_cyclic()配合DMA完成中断,实现无CPU干预的连续帧刷新。 - 通道配置(
dmaengine_slave_config)应在每次传输前根据当前显示参数(分辨率、像素格式)重新配置,确保地址宽度和突发大小匹配。
4.4 虚拟通道与多通道调度
在支持多通道的DMA控制器中,dma_device的channels链表管理着所有可用通道。dmaengine框架引入了虚拟通道(virtual channel)的概念,允许将多个DMA请求映射到有限的物理通道上。虚拟通道通过virt-dma辅助模块实现,核心数据结构为virt_dma_chan:
struct virt_dma_chan {
struct dma_chan chan; /* 基类通道 */
struct tasklet_struct task; /* 任务软中断 */
struct list_head desc_allocated; /* 已分配描述符链表 */
struct list_head desc_submitted; /* 已提交描述符链表 */
struct list_head desc_issued; /* 已下发描述符链表 */
struct list_head desc_completed; /* 已完成描述符链表 */
spinlock_t lock; /* 自旋锁保护链表操作 */
};
- 解耦:将传输描述符的管理从硬件中断上下文中解耦到tasklet中处理。
- 排队:支持多个传输描述符排队,由底层调度器决定下一个执行哪个传输。
- 周期传输:对于显示刷新场景,虚拟通道可以无缝支持
dmaengine_prep_dma_cyclic()的循环描述符管理。
在显示驱动开发中,理解虚拟通道的链表管理机制至关重要。当使用dmaengine_submit()提交描述符时,描述符首先进入desc_submitted链表;调用dma_async_issue_pending()后,描述符移至desc_issued链表;硬件传输完成后,描述符移至desc_completed链表,并在tasklet中调用用户注册的回调函数。
dmaengine_desc_pool_create()在通道初始化时预分配固定数量的描述符,减少内存碎片和分配延迟。