2. Linux DMA 子系统架构:dmaengine 框架、dma_device 结构体、dma_chan 通道模型

Linux DMA 子系统通过 dmaengine 框架为内核驱动和用户空间提供统一的 DMA 操作接口。该框架屏蔽了不同 DMA 控制器的硬件差异,使开发者无需关心底层寄存器细节即可完成高效的数据搬运。本章深入剖析 dmaengine 的三层核心模型:框架层、设备层(dma_device)和通道层(dma_chan)。

核心概念速览: dmaengine 框架采用“控制器 → 设备 → 通道”三级抽象。一个 DMA 控制器(硬件 IP)对应一个 dma_device 结构体;每个 dma_device 管理多个 dma_chan 通道;上层用户通过通道提交传输描述符(dma_async_tx_descriptor)并获取完成回调。

2.1 dmaengine 框架整体架构

dmaengine 框架位于 drivers/dma/ 目录,向上对内核其他子系统(如 SPI、I2C、音频、网络)提供标准 API,向下通过 dma_device 操作集与具体硬件交互。其核心设计思想是异步传输 + 回调通知,避免 CPU 在数据搬运过程中被阻塞。

dmaengine 框架三层架构图 dmaengine 框架三层架构 上层用户:SPI / I2C / Audio / Network / 用户空间 DMA 调用 dmaengine API:dma_request_channel() / dmaengine_prep_slave_sg() / dmaengine_submit() dmaengine 核心框架(drivers/dma/dmaengine.c) 通道管理 / 描述符生命周期 / 回调分发 / 虚拟通道支持 dma_device 硬件抽象层 device_alloc_chan_resources / device_prep_slave_sg / device_issue_pending / device_tx_status 具体 DMA 硬件:PL330 / EDMA / SDMA / XDMA
设计意图: 三层分离使得添加新 DMA 控制器只需实现 dma_device 中的回调函数,无需修改上层调用代码。这也是 Linux 内核“面向接口编程”的典型范例。

2.2 dma_device 结构体:控制器的软件抽象

dma_device 是 DMA 控制器在内核中的软件代表,定义在 include/linux/dmaengine.h。它包含了控制器的能力描述和操作函数指针。每个 DMA 控制器驱动在 probe 阶段分配并初始化一个 dma_device 实例,然后通过 dma_async_device_register() 注册到框架。

dma_device 核心字段与含义
字段 类型 说明
dev struct device * 指向硬件设备结构体,用于 DMA API 映射和电源管理
channels struct list_head 该控制器下所有 dma_chan 通道的链表头
cap_mask dma_cap_mask_t 位掩码,声明控制器支持的传输类型(如 MEMCPY、SLAVE、CYCLIC)
directions u32 位掩码,表示支持的 DMA 方向(DEV_TO_MEM / MEM_TO_DEV / MEM_TO_MEM)
src_addr_widths u32 支持的源端数据宽度(1/2/4/8 字节)
dst_addr_widths u32 支持的目的端数据宽度
max_burst u32 单次突发传输最大长度(字节),直接影响带宽
device_alloc_chan_resources 函数指针 分配通道资源(描述符池、中断等)
device_prep_slave_sg 函数指针 准备 SLAVE 模式的 SG 传输描述符
device_issue_pending 函数指针 将待处理描述符推送到硬件队列
device_tx_status 函数指针 查询传输状态(已完成字节数 / 错误码)
性能关键点: max_burst 和 src_addr_widths / dst_addr_widths 直接决定了单次 DMA 传输能搬运的数据量。在带宽测试中,若 burst 设置过小(如 1 字节),DMA 控制器将频繁仲裁总线,导致有效带宽急剧下降。通常建议将 burst 设为硬件支持的最大值(如 64 或 128 字节)。

2.3 dma_chan 通道模型:传输的执行单元

dma_chan 是 DMA 传输的逻辑执行单元。一个 DMA 控制器可能包含多个物理通道(如 8 通道 PL330),每个物理通道对应一个 dma_chan 实例。通道模型的核心职责包括:

  • 通道申请与释放: 上层驱动通过 dma_request_channel() 按能力掩码申请通道,使用完毕后通过 dma_release_channel() 归还。
  • 描述符管理: 每个通道维护一个 desc_allocated / desc_submitted / desc_issued / desc_completed 链表,管理传输描述符的完整生命周期。
  • 回调机制: 传输完成时,框架调用 dma_async_tx_descriptor 中的 callback 函数,通知上层数据已就绪。
  • 虚拟通道支持: 对于硬件通道数较少的控制器,框架提供 virt-dma 辅助层,用软件队列模拟更多逻辑通道。
dma_chan 通道状态机与描述符生命周期 dma_chan 通道状态机与描述符生命周期 空闲 (IDLE) 已分配 (ALLOC) 已提交 (SUBMIT) 已下发 (ISSUE) 已完成 (DONE) dma_request_channel() dmaengine_submit() dma_async_issue_pending() 硬件完成 + 回调 dmaengine_desc_free() / 通道释放

2.4 关键数据结构关系总结

以下表格总结了 dmaengine 框架中三个核心数据结构的分工与协作关系:

dma_device / dma_chan / dma_async_tx_descriptor 对比
结构体 层级 职责 生命周期 性能影响
dma_device 控制器 硬件能力描述、操作函数集、通道链表 驱动加载 → 卸载 决定最大 burst、地址宽度、传输方向
dma_chan 通道 传输执行单元、描述符队列、回调上下文 申请 → 释放 通道数影响并发度;虚拟通道增加调度开销
dma_async_tx_descriptor 描述符 单次传输的配置(源/目的地址、长度、回调) 准备 → 完成 → 释放 描述符复用减少分配开销;回调延迟影响响应

2.5 典型驱动注册流程(代码片段)

以下代码展示了一个典型 DMA 控制器驱动在 probe 函数中初始化 dma_device 并注册到框架的过程:

static int my_dma_probe(struct platform_device *pdev)
{
    struct my_dma_dev *dma_dev;
    struct dma_device *dd;
    int ret;

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

    dd = &dma_dev->dma_slave;
    INIT_LIST_HEAD(&dd->channels);

    /* 填充能力掩码:支持 SLAVE 和 MEMCPY */
    dma_cap_set(DMA_SLAVE, dd->cap_mask);
    dma_cap_set(DMA_MEMCPY, dd->cap_mask);

    /* 设置硬件参数 */
    dd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);
    dd->src_addr_widths = BIT(1) | BIT(2) | BIT(4);  /* 1/2/4 字节 */
    dd->dst_addr_widths = BIT(1) | BIT(2) | BIT(4);
    dd->max_burst = 128;  /* 最大突发 128 字节 */

    /* 绑定操作函数 */
    dd->device_alloc_chan_resources = my_dma_alloc_chan_resources;
    dd->device_prep_slave_sg = my_dma_prep_slave_sg;
    dd->device_issue_pending = my_dma_issue_pending;
    dd->device_tx_status = my_dma_tx_status;
    dd->dev = &pdev->dev;

    /* 注册到 dmaengine 框架 */
    ret = dma_async_device_register(dd);
    if (ret) {
        dev_err(&pdev->dev, "Failed to register DMA device: %d\n", ret);
        return ret;
    }

    platform_set_drvdata(pdev, dma_dev);
    dev_info(&pdev->dev, "DMA controller registered, max_burst=%d\n", dd->max_burst);
    return 0;
}
调试技巧: 在 /sys/class/dma/ 目录下可以查看系统已注册的 DMA 通道信息。执行 ls /sys/class/dma/ 列出所有通道,cat /sys/class/dma/dma0chan0/device/max_burst 可读取该通道的最大突发长度。这是验证驱动配置是否正确的快速方法。

2.6 带宽与延迟测试中的架构映射

理解 dmaengine 架构对于后续章节的带宽和延迟测试至关重要。下表总结了架构各层与测试指标的对应关系:

架构层与性能测试指标映射
架构层 对应测试指标 典型瓶颈
dma_device 能力 最大理论带宽(max_burst × 总线频率) burst 设置过小、地址宽度受限
dma_chan 调度 多通道并发带宽、通道切换延迟 虚拟通道软件开销、中断共享冲突
dma_async_tx_descriptor 单次传输延迟、描述符分配/释放开销 频繁 alloc/free、回调函数执行时间过长
硬件 FIFO / 总线仲裁 实际有效带宽、传输延迟抖动 FIFO 深度不足、总线竞争(如 AXI 共享)
常见误区: 很多开发者认为只要硬件支持高带宽,软件就能自动获得高性能。实际上,dma_device 中 max_burst 的配置、device_issue_pending 的实现效率、描述符池的预分配策略,都会显著影响最终测得的带宽和延迟。后续章节将逐一通过实验数据验证这些因素的影响。