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控制器,依次调用过滤回调,返回第一个匹配的通道。分配策略遵循以下优先级:

  1. 检查dma_cap_mask_t是否匹配控制器能力
  2. 调用filter_fn进行设备级过滤
  3. 返回引用计数增加的通道指针
DMA通道动态分配流程图 dma_request_channel() 遍历DMA控制器列表 检查dma_cap_mask_t 调用filter_fn回调 匹配成功? 返回dma_chan指针 说明:若遍历所有控制器均未匹配,返回NULL;成功则通道引用计数+1

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);
dma_filter_fn回调匹配流程 dmaengine框架遍历通道 调用filter_fn(chan, param) 驱动解析chan私有数据 与filter_param比较 返回true/false 框架决定是否采纳
性能提示: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
调试技巧:当通道分配失败时,检查以下常见原因:
  1. dma_cap_mask_t中设置了控制器不支持的能力
  2. filter_fn回调返回false导致所有通道被过滤
  3. 系统中所有DMA通道已被其他驱动占用
  4. DMA控制器驱动未正确注册

通过本章的学习,开发者应能正确配置能力掩码、编写过滤回调、安全申请与释放DMA通道,为后续描述符链管理打下坚实基础。