4. DMA通道管理:dma_request_channel()、dma_release_channel()、通道选择策略与dma_cap_mask_t
DMA通道是系统中有限的硬件资源,驱动开发者必须掌握通道的申请、释放与选择策略。本章深入剖析dmaengine框架提供的核心通道管理接口,并揭示dma_cap_mask_t位掩码在通道匹配中的关键作用。
4.1 通道生命周期与核心API
一个DMA通道从申请到释放的完整生命周期包含三个关键阶段:能力声明、通道申请、通道释放。下表总结了三个核心API的职责与调用时机:
| API函数 | 头文件/源 | 功能描述 | 典型调用时机 |
|---|---|---|---|
dma_cap_zero() |
<linux/dmaengine.h> |
清零能力掩码,初始化位图 | 构造通道请求前 |
dma_request_channel() |
dmaengine.c |
根据掩码和过滤函数申请通道 | probe()或设备打开时 |
dma_release_channel() |
dmaengine.c |
释放已占用的通道,归还给框架 | remove()或设备关闭时 |
4.2 dma_cap_mask_t:能力声明位图
dma_cap_mask_t是一个与体系结构无关的位掩码类型,用于描述DMA设备支持的能力集合。每个位对应一种DMA传输类型或特性,例如DMA_SLAVE、DMA_CYCLIC、DMA_INTERLEAVE等。
关键数据结构:
dma_cap_mask_t 在 include/linux/dmaengine.h 中定义为 DECLARE_BITMAP(dma_cap_mask_t, DMA_MAX),本质上是一个无符号长整型数组,位宽由 DMA_MAX 决定。
使用dma_cap_mask_t的标准流程分为三步:
- 清零掩码:调用
dma_cap_zero(mask)确保所有位为0。 - 设置能力位:调用
dma_cap_set(tx_type, mask)置位所需能力。 - 传递掩码:将掩码作为参数传入
dma_request_channel()。
#include <linux/dmaengine.h>
dma_cap_mask_t mask;
struct dma_chan *chan;
/* 1. 清零掩码 */
dma_cap_zero(mask);
/* 2. 设置所需能力:SLAVE + CYCLIC */
dma_cap_set(DMA_SLAVE, mask);
dma_cap_set(DMA_CYCLIC, mask);
/* 3. 申请通道,NULL表示不使用自定义过滤函数 */
chan = dma_request_channel(mask, NULL, NULL);
if (IS_ERR_OR_NULL(chan)) {
pr_err("Failed to request DMA channel\n");
return -ENODEV;
}
最佳实践: 如果驱动同时需要多种能力(如SLAVE模式下的循环传输),务必在掩码中同时置位
DMA_SLAVE 和 DMA_CYCLIC。仅设置 DMA_CYCLIC 而遗漏 DMA_SLAVE 可能导致通道匹配失败。
4.3 dma_request_channel():通道匹配与过滤机制
dma_request_channel() 的函数原型如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
参数说明:
- mask:能力位掩码,框架据此筛选具备指定能力的DMA设备。
- filter_fn:可选的自定义过滤回调函数,用于进一步匹配通道(如匹配DMA请求线编号)。
- filter_param:传递给过滤函数的私有参数。
通道匹配的内部流程如下:
4.4 通道选择策略:从掩码到具体通道
当系统中存在多个DMA控制器,或一个控制器拥有多个通道时,dma_request_channel() 的选择策略直接影响驱动性能与可靠性。常见的策略包括:
| 策略 | 实现方式 | 适用场景 | 注意事项 |
|---|---|---|---|
| 能力优先 | 仅设置能力掩码,不传过滤函数 | 驱动只关心能力,不关心具体通道编号 | 框架可能返回任意满足能力的通道 |
| 设备绑定 | 通过过滤函数匹配 dma_device 指针 |
多DMA控制器系统中绑定特定控制器 | 需提前获取目标设备的 dma_device |
| 请求线匹配 | 过滤函数匹配 filter_param 中的请求线号 |
外设DMA请求线固定(如SPI、I2S) | 需查阅芯片手册确认请求线映射 |
| 通道号指定 | 过滤函数匹配 chan_id |
调试或特殊硬件约束 | 降低驱动可移植性,不推荐 |
过滤函数模板: 过滤函数原型为
bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param)。返回 true 表示接受该通道,false 表示拒绝。
/* 示例:通过设备树 phandle 匹配特定 DMA 控制器 */
static bool my_dma_filter(struct dma_chan *chan, void *filter_param)
{
struct device_node *np = filter_param;
struct dma_device *dma_dev = chan->device;
/* 匹配 DMA 控制器的 device_node */
return dma_dev->dev->of_node == np;
}
/* 使用示例 */
struct device_node *dma_np = of_parse_phandle(pdev->dev.of_node, "dmas", 0);
chan = dma_request_channel(mask, my_dma_filter, dma_np);
4.5 dma_release_channel():安全释放通道
通道使用完毕后,必须调用 dma_release_channel() 将其归还给框架,否则会导致通道泄漏,影响其他驱动使用。
void dma_release_channel(struct dma_chan *chan);
释放通道时的注意事项:
- 先终止传输:调用
dmaengine_terminate_sync()确保所有进行中的描述符已完成或终止。 - 释放描述符:确保所有已提交的描述符已被释放或回收。
- 置空指针:释放后将
chan指针置为NULL,防止悬空指针。
static void my_device_release_dma(struct my_priv *priv)
{
if (priv->dma_chan) {
/* 1. 终止所有传输 */
dmaengine_terminate_sync(priv->dma_chan);
/* 2. 释放通道 */
dma_release_channel(priv->dma_chan);
/* 3. 置空指针 */
priv->dma_chan = NULL;
}
}
常见错误: 在
dma_release_channel() 之后继续访问 chan 指针,或在传输未终止时直接释放通道,可能导致内核崩溃或DMA控制器状态不一致。
4.6 多通道系统中的分配策略对比
在嵌入式多核或异构系统中,多个DMA通道的分配策略直接影响系统吞吐量。下表对比了三种典型分配策略:
| 策略 | 分配方式 | 优点 | 缺点 |
|---|---|---|---|
| 轮询分配 | 按顺序依次分配通道 | 实现简单,负载均衡 | 无法感知通道实际负载 |
| 优先级分配 | 高优先级请求优先分配低编号通道 | 保证关键任务通道可用 | 低优先级任务可能饿死 |
| 亲和性分配 | 绑定到特定CPU或DMA控制器 | 减少跨片访问延迟 | 配置复杂,灵活性降低 |
实战建议: 对于大多数嵌入式Linux驱动,使用
dma_request_channel() 的默认行为(能力优先+无过滤函数)即可满足需求。仅在需要绑定特定DMA控制器或请求线时,才实现自定义过滤函数。
4.7 本章小结
DMA通道管理是dmaengine驱动开发的基石。通过 dma_cap_mask_t 精确声明能力需求,借助 dma_request_channel() 的过滤机制灵活匹配通道,并在使用完毕后通过 dma_release_channel() 安全释放,构成了完整的通道生命周期管理闭环。掌握这些接口,是后续实现环形缓冲区与高效DMA传输的前提。