3. dmaengine API详解(上):通道申请与释放流程
在DMA驱动开发中,通道(Channel)是数据传输的核心抽象。dmaengine框架提供了标准化的API用于通道的申请、配置与释放。本章聚焦于dma_request_channel、dma_release_channel和dma_get_slave_channel三个关键接口,深入剖析其内部机制与使用规范。
学习提示:通道申请是DMA传输的“入场券”。错误的申请时机或遗漏释放会导致系统资源泄漏,甚至DMA传输失败。请务必理解每个API的适用场景与生命周期。
3.1 核心API速览
下表列出了本章涉及的三个核心API及其功能定位:
| API函数 | 头文件 | 功能描述 | 典型调用者 |
|---|---|---|---|
dma_request_channel |
<linux/dmaengine.h> |
根据掩码和过滤函数,从系统中动态分配一个满足条件的DMA通道 | DMA引擎使用者(如音频、SPI驱动) |
dma_release_channel |
<linux/dmaengine.h> |
释放之前申请的DMA通道,归还给系统 | DMA引擎使用者 |
dma_get_slave_channel |
<linux/dmaengine.h> |
获取一个已经绑定到特定从设备(slave)的专用通道 | DMA控制器驱动内部 |
3.2 dma_request_channel:通用通道申请
dma_request_channel 是dmaengine框架中最常用的通道申请接口。它允许调用者通过掩码(mask)和过滤函数(filter_fn)来精确选择所需的DMA通道。
3.2.1 函数原型
struct dma_chan *dma_request_channel(const dma_cap_mask_t *mask,
dma_filter_fn filter_fn,
void *filter_param);
3.2.2 参数详解
| 参数 | 类型 | 说明 |
|---|---|---|
mask |
const dma_cap_mask_t * |
DMA能力掩码,用于指定需要的DMA传输类型(如DMA_SLAVE、DMA_MEMCPY等) |
filter_fn |
dma_filter_fn |
过滤回调函数,用于进一步筛选通道(如匹配设备树中的通道ID) |
filter_param |
void * |
传递给过滤函数的私有参数,通常为设备结构体指针或通道索引 |
3.2.3 典型使用示例
#include <linux/dmaengine.h>
dma_cap_mask_t mask;
struct dma_chan *chan;
/* 初始化掩码并设置DMA_SLAVE能力 */
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
/* 申请通道:过滤函数为NULL表示接受任意匹配掩码的通道 */
chan = dma_request_channel(&mask, NULL, NULL);
if (IS_ERR_OR_NULL(chan)) {
pr_err("Failed to request DMA channel\n");
return -ENODEV;
}
/* 使用通道... */
/* 释放通道 */
dma_release_channel(chan);
关键点:当
filter_fn 为 NULL 时,内核会返回第一个满足掩码条件的通道。对于多通道DMA控制器,建议提供过滤函数以绑定到正确的硬件通道。
3.3 dma_release_channel:通道释放
通道使用完毕后,必须调用 dma_release_channel 归还资源。该函数会执行以下操作:
- 检查通道是否正在使用中(如有未完成的传输,会打印警告)
- 将通道状态标记为可用
- 将通道归还到DMA控制器的空闲通道池
- 减少通道的引用计数
3.3.1 函数原型
void dma_release_channel(struct dma_chan *chan);
常见错误:
- 忘记释放通道导致资源泄漏(可通过
/sys/kernel/debug/dmaengine/summary检查) - 在DMA传输未完成时释放通道,可能导致DMA控制器访问已释放的内存
- 重复释放同一个通道,引发内核崩溃
3.4 dma_get_slave_channel:从设备专用通道获取
dma_get_slave_channel 与 dma_request_channel 不同,它用于获取一个已经预绑定到特定从设备的通道。该API通常在DMA控制器驱动的 device_alloc_chan_resources 回调内部使用,或者由已经通过其他方式获得通道引用的代码调用。
3.4.1 函数原型
struct dma_chan *dma_get_slave_channel(struct dma_chan *chan);
3.4.2 使用场景
- DMA控制器驱动内部:在
of_dma_simple_xlate等翻译函数中,根据设备树解析出的通道索引获取对应的通道 - 通道复用:当多个子系统共享同一个物理DMA通道时,通过该API增加引用计数
3.4.3 与 dma_request_channel 的对比
| 对比维度 | dma_request_channel |
dma_get_slave_channel |
|---|---|---|
| 调用者 | DMA引擎使用者(外设驱动) | DMA控制器驱动或通道持有者 |
| 通道来源 | 从全局通道池中搜索匹配 | 基于已有通道引用获取 |
| 过滤机制 | 支持掩码+过滤函数 | 无过滤,直接操作通道指针 |
| 引用计数 | 创建新引用 | 增加已有通道的引用计数 |
| 典型返回值 | 新分配的通道或NULL | 传入的通道指针或NULL |
3.5 通道申请与释放完整流程
下图展示了从设备驱动申请DMA通道到最终释放的完整生命周期:
3.6 通道生命周期管理最佳实践
- 申请时机:在设备
probe()函数中申请通道,在remove()中释放 - 错误处理:始终检查
dma_request_channel的返回值,使用IS_ERR_OR_NULL()宏 - 过滤函数:对于多通道DMA控制器,务必提供过滤函数以绑定正确的硬件通道
- 引用计数:使用
dma_get_slave_channel时,记得配对调用dma_release_channel - 调试接口:通过
/sys/kernel/debug/dmaengine/channels查看当前通道分配状态
性能调优提示:通道申请是一个相对耗时的操作(可能涉及设备树解析、DMA控制器驱动初始化等)。对于高频DMA传输场景,建议在初始化阶段一次性申请通道并复用,避免在热路径中反复申请和释放。
3.7 常见问题与避坑指南
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 通道申请失败 | dma_request_channel 返回 NULL |
检查DMA控制器驱动是否加载;确认掩码设置正确;检查设备树中DMA通道配置 |
| 通道被占用 | 过滤函数匹配不到可用通道 | 使用 dma_cap_set 设置正确的能力位;检查其他驱动是否占用了该通道 |
| 释放后野指针 | 释放通道后继续使用通道指针 | 在 dma_release_channel 后将通道指针置为 NULL |
| 引用计数不平衡 | 通道无法被再次分配 | 确保每次 dma_get_slave_channel 都对应一次 dma_release_channel |