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的标准流程分为三步:

  1. 清零掩码:调用 dma_cap_zero(mask) 确保所有位为0。
  2. 设置能力位:调用 dma_cap_set(tx_type, mask) 置位所需能力。
  3. 传递掩码:将掩码作为参数传入 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:传递给过滤函数的私有参数。

通道匹配的内部流程如下:

dma_request_channel() 通道匹配流程 dma_request_channel() 遍历已注册DMA设备列表 dma_cap_mask_t 位掩码匹配 调用 filter_fn() 过滤 返回 dma_chan * 匹配失败处理 若遍历所有设备均未匹配 返回 NULL 或 ERR_PTR 调用者需检查 IS_ERR_OR_NULL()

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传输的前提。