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通道到最终释放的完整生命周期:

DMA通道申请与释放完整流程 设备驱动 dma_request_channel() 过滤与通道匹配 分配通道资源 DMA数据传输 dma_release_channel() 过滤函数示例 bool my_filter(struct dma_chan *chan, void *param) { return chan->chan_id == (int)param; }

3.6 通道生命周期管理最佳实践

  1. 申请时机:在设备 probe() 函数中申请通道,在 remove() 中释放
  2. 错误处理:始终检查 dma_request_channel 的返回值,使用 IS_ERR_OR_NULL() 宏
  3. 过滤函数:对于多通道DMA控制器,务必提供过滤函数以绑定正确的硬件通道
  4. 引用计数:使用 dma_get_slave_channel 时,记得配对调用 dma_release_channel
  5. 调试接口:通过 /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