4. DMA通道申请与释放:通道分配策略、dma_cap_mask_t设置、dma_filter_fn回调函数、通道释放注意事项
DMA通道是系统中有限的硬件资源,正确申请与释放通道是DMA传输可靠性的基础。Linux内核通过dmaengine框架提供了一套标准化的通道管理接口,涵盖能力掩码配置、过滤回调、分配策略及资源回收等关键环节。本章深入剖析这些机制,帮助开发者避免资源泄漏与配置错误。
4.1 通道分配策略:从设备树到运行时选择
DMA通道的分配策略分为静态绑定与动态分配两种模式,内核根据设备树配置或驱动需求自动选择。
| 策略类型 | 分配时机 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 静态绑定 | 设备树解析阶段 | 专用DMA通道(如音频I2S) | 确定性高,无竞争 | 灵活性差,资源利用率低 |
| 动态分配 | 运行时调用dma_request_channel() | 通用DMA传输(如存储、网络) | 资源复用,适配多设备 | 可能因竞争导致分配失败 |
| 半动态分配 | 驱动初始化时预分配 | 实时性要求较高的场景 | 平衡灵活性与确定性 | 需要驱动自行管理池 |
实践建议:对于实时性敏感的外设(如摄像头ISP),优先使用静态绑定或半动态分配,避免运行时分配引入不可预测的延迟。
动态分配的核心函数为dma_request_channel(),其原型如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
该函数遍历系统中所有DMA控制器,依次调用过滤回调,返回第一个匹配的通道。分配策略遵循以下优先级:
- 检查
dma_cap_mask_t是否匹配控制器能力 - 调用
filter_fn进行设备级过滤 - 返回引用计数增加的通道指针
4.2 dma_cap_mask_t设置:能力掩码的精确配置
dma_cap_mask_t是一个位掩码类型,用于声明驱动所需的DMA传输能力。内核提供一组宏来操作该掩码:
| 宏/函数 | 作用 | 示例 |
|---|---|---|
dma_cap_zero(mask) |
清零所有位 | 初始化掩码 |
dma_cap_set(cap, mask) |
设置指定能力位 | 设置DMA_SLAVE |
dma_cap_clear(cap, mask) |
清除指定能力位 | 清除DMA_CYCLIC |
dma_has_cap(cap, mask) |
检查能力位是否置位 | 判断是否支持DMA_MEMCPY |
常见的能力类型定义在<linux/dmaengine.h>中:
enum dma_transaction_type {
DMA_MEMCPY, // 内存到内存拷贝
DMA_XOR, // XOR校验
DMA_PQ, // RAID P+Q
DMA_SLAVE, // 外设DMA传输
DMA_CYCLIC, // 循环传输(音频)
DMA_INTERLEAVE, // 交错传输
DMA_PRIVATE, // 私有通道
};
配置示例:申请一个支持SLAVE模式且支持循环传输的DMA通道
dma_cap_mask_t mask;
dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_CYCLIC, mask);
struct dma_chan *chan = dma_request_channel(mask, NULL, NULL);
if (!chan) {
pr_err("Failed to request DMA channel\n");
return -ENODEV;
}
常见错误:未调用
dma_cap_zero()直接设置位,导致掩码中包含随机垃圾位,可能匹配到不支持的控制器。务必先清零再设置。
4.3 dma_filter_fn回调函数:设备级精确匹配
当多个DMA控制器都支持相同能力时,dma_filter_fn回调允许驱动根据设备私有数据进一步筛选通道。回调原型如下:
typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);
回调接收两个参数:
chan:当前遍历到的DMA通道filter_param:驱动传入的私有参数(通常为设备指针或通道ID)
典型实现模式:
static bool my_dma_filter(struct dma_chan *chan, void *filter_param)
{
struct device *dev = filter_param;
struct dma_slave_map *map;
int ret;
// 获取DMA控制器的设备树映射信息
map = dma_get_slave_channel(chan);
if (!map)
return false;
// 检查是否匹配目标外设
ret = of_dma_match_channel(chan->device->dev, dev, map->slave_id);
return ret == 0;
}
// 使用时传入外设设备指针
struct dma_chan *chan = dma_request_channel(mask, my_dma_filter, my_dev);
性能提示:filter_fn回调在原子上下文中调用,不能睡眠。避免在回调中执行耗时的操作,如打印日志、获取互斥锁等。
4.4 通道释放注意事项:资源回收与状态清理
DMA通道使用完毕后必须释放,否则会导致资源泄漏。释放函数为dma_release_channel():
void dma_release_channel(struct dma_chan *chan);
释放时需注意以下关键点:
| 注意事项 | 说明 | 后果 |
|---|---|---|
| 确保传输已完成 | 释放前调用dma_sync_wait()或检查描述符状态 |
未完成传输导致数据损坏 |
| 释放所有描述符 | 调用dmaengine_terminate_sync()终止进行中的传输 |
描述符泄漏,DMA控制器状态异常 |
| 检查通道引用计数 | 确保没有其他模块持有该通道指针 | 双重释放导致内核崩溃 |
| 释放后置空指针 | 将chan指针置为NULL,防止悬空指针 | 野指针访问导致Oops |
| 考虑热插拔场景 | 在设备移除回调中释放通道 | 设备移除后通道变为孤儿 |
推荐的释放流程:
void my_dma_cleanup(struct my_dma_ctx *ctx)
{
struct dma_chan *chan = ctx->chan;
if (!chan)
return;
// 1. 终止所有进行中的传输
dmaengine_terminate_sync(chan);
// 2. 等待所有描述符完成
dma_sync_wait(chan, DMA_CTRL_ACK);
// 3. 释放通道
dma_release_channel(chan);
// 4. 置空指针
ctx->chan = NULL;
}
危险操作:不要在中断上下文中调用
dma_release_channel(),该函数可能睡眠等待。如果必须在中断中释放,使用工作队列延迟处理。
4.5 通道状态监控与调试
通过/sys/class/dma/接口可以监控通道使用情况:
# 查看所有DMA控制器
ls /sys/class/dma/
# 查看特定通道的状态
cat /sys/class/dma/dma0chan0/bytes_transferred
cat /sys/class/dma/dma0chan0/in_use
内核也提供了调试接口:
// 在驱动中打印通道信息
dev_info(chan->device->dev, "DMA channel %s allocated\n",
dma_chan_name(chan));
// 使用dmaengine的debugfs接口
mount -t debugfs none /sys/kernel/debug
cat /sys/kernel/debug/dmaengine/summary
调试技巧:当通道分配失败时,检查以下常见原因:
- dma_cap_mask_t中设置了控制器不支持的能力
- filter_fn回调返回false导致所有通道被过滤
- 系统中所有DMA通道已被其他驱动占用
- DMA控制器驱动未正确注册
通过本章的学习,开发者应能正确配置能力掩码、编写过滤回调、安全申请与释放DMA通道,为后续描述符链管理打下坚实基础。