3. DMA控制器硬件抽象:核心数据结构详解

在Linux内核DMA引擎子系统中,dma_device、dma_chan_percpu和dma_cap_mask_t构成了DMA控制器硬件抽象的三根支柱。它们分别负责描述控制器全局属性、通道级统计计数以及能力位图。理解这三个结构体是编写DMA引擎驱动的第一步。

核心要点:dma_device代表一个DMA控制器硬件实例;dma_chan_percpu提供每个CPU核上的通道统计信息;dma_cap_mask_t用位图标记控制器支持的操作类型(如MEMCPY、XOR、PQ等)。

3.1 dma_device结构体:DMA控制器的“身份证”

dma_device定义在include/linux/dmaengine.h中,是DMA引擎驱动最核心的数据结构。每个DMA控制器硬件实例对应一个dma_device对象,它包含了控制器的能力描述、通道列表、操作函数集以及硬件参数。

字段类型说明
devstruct device *指向DMA控制器的通用设备结构体,用于设备模型关联
channelsstruct list_head该控制器下所有DMA通道的链表头
cap_maskdma_cap_mask_t能力位图,标记控制器支持哪些DMA操作类型
copy_alignunsigned intMEMCPY操作的对齐要求(2的幂次)
fill_alignunsigned intMEMSET操作的对齐要求
dev_idint控制器在系统中的唯一ID,用于sysfs接口
device_alloc_chan_resources函数指针分配通道资源的回调函数
device_free_chan_resources函数指针释放通道资源的回调函数
device_prep_dma_memcpy函数指针准备MEMCPY传输描述符的回调
device_issue_pending函数指针触发通道开始执行待处理传输
驱动开发提示:在probe函数中,驱动需要先填充dma_device的cap_mask、对齐参数和所有回调函数指针,然后调用dma_async_device_register()将设备注册到DMA引擎核心框架。
dma_device 结构体核心字段关系图 dma_device cap_mask (能力位图) channels (通道链表) 回调函数集 device_alloc_chan_resources device_prep_dma_memcpy device_issue_pending 说明: 每个DMA控制器硬件实例对应一个dma_device,驱动通过填充该结构体向内核注册控制器能力。

3.2 dma_chan_percpu结构体:每CPU通道统计

dma_chan_percpu是DMA引擎中用于统计每个CPU核上通道使用情况的结构体。它嵌入在dma_chan结构体中,以percpu变量的形式存在,避免多核竞争。

struct dma_chan_percpu {
    /* 统计计数 */
    unsigned long   memcpy_count;   /* MEMCPY操作完成次数 */
    unsigned long   bytes_transferred;  /* 已传输字节总数 */
    
    /* 时间戳 */
    unsigned long   timestamp;      /* 最后一次操作的时间戳 */
};
字段用途更新时机
memcpy_count记录该CPU上通过该通道完成的MEMCPY操作次数每次传输完成回调中递增
bytes_transferred累计传输的字节数,用于带宽统计传输完成时累加实际长度
timestamp记录最后一次传输完成的时间点传输完成时写入jiffies
注意事项:dma_chan_percpu是percpu变量,驱动代码中必须使用this_cpu_ptr()或per_cpu_ptr()宏来访问,不能直接解引用。错误的使用方式会导致数据竞争或访问越界。
dma_chan_percpu 多核统计架构 CPU 0 dma_chan_percpu memcpy_count: 1024 bytes_transferred: 524288 timestamp: 0x3F2A1B CPU 1 dma_chan_percpu memcpy_count: 896 bytes_transferred: 458752 timestamp: 0x3F2A1C CPU 2 dma_chan_percpu memcpy_count: 1152 bytes_transferred: 589824 timestamp: 0x3F2A1D dma_chan (共享通道描述符) 每个CPU核拥有独立的dma_chan_percpu实例,避免多核统计时的锁竞争。通过percpu变量机制实现无锁访问。

3.3 DMA通道能力描述:dma_cap_mask_t

dma_cap_mask_t是一个位图类型,用于标记DMA控制器支持哪些传输操作类型。它定义在include/linux/dmaengine.h中,本质上是DECLARE_BITMAP的封装。

/* 能力位图类型定义 */
typedef DECLARE_BITMAP(dma_cap_mask_t, DMA_MAX);

/* 常用能力标志枚举 */
enum dma_transaction_type {
    DMA_MEMCPY,         /* 内存到内存拷贝 */
    DMA_XOR,            /* XOR校验计算 */
    DMA_PQ,             /* P+Q RAID校验 */
    DMA_XOR_VAL,        /* XOR校验验证 */
    DMA_PQ_VAL,         /* P+Q校验验证 */
    DMA_MEMSET,         /* 内存填充 */
    DMA_INTERRUPT,      /* 中断触发(无数据传输) */
    DMA_SLAVE,          /* 外设到内存/内存到外设 */
    DMA_CYCLIC,         /* 循环传输(音频流等) */
    DMA_INTERLEAVE,     /* 交错传输 */
    /* ... 其他类型 */
};
能力标志典型应用场景硬件要求
DMA_MEMCPY内存拷贝加速、数据搬运支持内存到内存的DMA传输
DMA_SLAVESPI/UART/I2C外设数据收发支持外设触发和流控制
DMA_XORRAID5校验计算硬件XOR引擎
DMA_PQRAID6 P+Q校验硬件Galois域计算引擎
DMA_CYCLIC音频播放、波形生成支持环形缓冲区自动循环
DMA_INTERLEAVE多通道数据交织传输支持多源多目标交错
位图操作API:内核提供了一组宏来操作dma_cap_mask_t:dma_cap_set()设置能力位,dma_cap_clear()清除能力位,dma_has_cap()检查是否支持某能力。驱动在注册设备前必须正确设置cap_mask。
dma_cap_mask_t 能力位图结构 MEMCPY XOR PQ SLAVE CYCLIC INTER ... ... ... 驱动probe时:dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask) 消费者:dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask) ? 使用 : 跳过 典型能力组合场景: 通用DMA:MEMCPY + SLAVE RAID加速:XOR + PQ + VAL 音频:SLAVE + CYCLIC

3.4 三者的协作关系

在DMA引擎驱动中,dma_device、dma_chan_percpu和dma_cap_mask_t协同工作,构成完整的硬件抽象层:

  1. dma_device作为顶层容器,包含cap_mask和通道链表
  2. dma_cap_mask_t在dma_device中标记控制器能力,供消费者查询
  3. 每个dma_chan包含percpu的dma_chan_percpu统计信息
  4. 消费者通过dma_request_channel()传入cap_mask过滤条件,内核自动匹配
最佳实践:编写DMA引擎驱动时,建议按照以下顺序初始化:
1. 分配dma_device并清零
2. 使用dma_cap_set()设置所有支持的能力位
3. 填充对齐参数和回调函数指针
4. 初始化通道链表并添加所有dma_chan
5. 为每个dma_chan分配percpu的dma_chan_percpu
6. 调用dma_async_device_register()注册
/* 驱动初始化示例片段 */
static int my_dma_probe(struct platform_device *pdev)
{
    struct dma_device *dma_dev;
    struct my_dma_chan *my_chan;
    int i, ret;

    dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
    if (!dma_dev)
        return -ENOMEM;

    /* 1. 设置能力位图 */
    dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask);
    dma_cap_set(DMA_SLAVE, dma_dev->cap_mask);

    /* 2. 设置对齐参数 */
    dma_dev->copy_align = DMAENGINE_ALIGN_8_BYTES;

    /* 3. 填充回调函数 */
    dma_dev->device_alloc_chan_resources = my_alloc_chan_resources;
    dma_dev->device_free_chan_resources = my_free_chan_resources;
    dma_dev->device_prep_dma_memcpy = my_prep_dma_memcpy;
    dma_dev->device_issue_pending = my_issue_pending;

    /* 4. 初始化通道 */
    INIT_LIST_HEAD(&dma_dev->channels);
    for (i = 0; i < MY_DMA_NUM_CHANNELS; i++) {
        my_chan = &my_dma_data->channels[i];
        my_chan->chan.device = dma_dev;
        /* 为每个通道分配percpu统计 */
        my_chan->chan.percpu = alloc_percpu(struct dma_chan_percpu);
        list_add_tail(&my_chan->chan.device_node, &dma_dev->channels);
    }

    /* 5. 注册DMA设备 */
    ret = dma_async_device_register(dma_dev);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, dma_dev);
    return 0;
}

通过这三个核心数据结构的合理设计与使用,DMA引擎驱动能够向内核上层提供一个清晰、可扩展的硬件抽象层,使得DMA控制器消费者(如音频驱动、RAID加速模块、网络驱动等)无需关心底层硬件细节,仅通过标准API即可完成数据搬运任务。