4. DMA引擎驱动注册与注销:module_init/module_exit、platform_driver_register、dma_async_device_register

DMA引擎驱动的生命周期管理是驱动开发的基础。本章聚焦于DMA引擎驱动的注册与注销流程,涵盖从模块入口/出口函数、平台驱动注册到DMA异步设备注册的完整链路。理解这一流程,是后续实现数据搬运优化的前提。

4.1 驱动注册与注销的整体架构

一个典型的DMA引擎驱动在Linux内核中遵循“模块化 → 平台驱动 → DMA设备”三层注册模型。每一层都有对应的注销函数,确保资源有序释放。

核心注册/注销函数对照表
层次 注册函数 注销函数 作用域
模块层 module_init() module_exit() 内核模块加载/卸载入口
平台驱动层 platform_driver_register() platform_driver_unregister() 与设备树/平台设备匹配
DMA设备层 dma_async_device_register() dma_async_device_unregister() 向DMA框架注册通道能力

下图展示了这三层注册之间的调用关系与数据流:

DMA引擎驱动三层注册架构 module_init() 模块入口函数 platform_driver_register() 平台驱动注册 dma_async_device_register() DMA设备注册 调用 调用 module_exit() 模块退出函数 platform_driver_unregister() 平台驱动注销 dma_async_device_unregister() DMA设备注销 调用 调用 ⚠ 注册与注销顺序必须严格对称 注册:module_init → platform_driver_register → dma_async_device_register 注销:module_exit → platform_driver_unregister → dma_async_device_unregister

4.2 module_init / module_exit:驱动入口与出口

每个内核模块都必须定义入口和出口函数。对于DMA引擎驱动,module_init 通常调用平台驱动注册,module_exit 则调用平台驱动注销。

4.2.1 标准模板

static int __init my_dma_init(void)
{
    int ret;

    pr_info("My DMA driver: initializing\n");

    ret = platform_driver_register(&my_dma_platform_driver);
    if (ret)
        pr_err("Failed to register platform driver: %d\n", ret);

    return ret;
}

static void __exit my_dma_exit(void)
{
    platform_driver_unregister(&my_dma_platform_driver);
    pr_info("My DMA driver: exited\n");
}

module_init(my_dma_init);
module_exit(my_dma_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux DMA Expert");
MODULE_DESCRIPTION("Sample DMA engine platform driver");
最佳实践: 使用 __init 和 __exit 宏标记函数,内核会在初始化/退出后释放这些代码段占用的内存。对于DMA驱动,这可以节省约4-8KB的内核映像空间。

4.3 platform_driver_register:平台驱动注册

平台驱动是DMA引擎驱动与设备树或ACPI表之间的桥梁。注册时需要填充 struct platform_driver 结构体,核心成员包括:

成员 类型 说明
probe 函数指针 设备匹配成功时调用,在此完成DMA设备初始化
remove 函数指针 设备移除时调用,释放DMA资源
driver.name 字符串 驱动名称,用于和设备树 compatible 属性匹配
driver.of_match_table 结构体指针 设备树匹配表,定义支持的设备列表

4.3.1 平台驱动结构体定义示例

static const struct of_device_id my_dma_of_match[] = {
    { .compatible = "vendor,my-dma-controller", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_dma_of_match);

static int my_dma_probe(struct platform_device *pdev)
{
    struct my_dma_device *dma_dev;
    int ret;

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

    /* 初始化DMA引擎硬件和通道 */
    ret = my_dma_hw_init(dma_dev, pdev);
    if (ret)
        return ret;

    /* 填充 dma_async_device 结构体 */
    ret = my_dma_async_device_init(dma_dev);
    if (ret)
        return ret;

    /* 注册DMA设备 */
    ret = dma_async_device_register(&dma_dev->dma_dev);
    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\n");

    return 0;
}

static int my_dma_remove(struct platform_device *pdev)
{
    struct my_dma_device *dma_dev = platform_get_drvdata(pdev);

    dma_async_device_unregister(&dma_dev->dma_dev);
    my_dma_hw_deinit(dma_dev);

    return 0;
}

static struct platform_driver my_dma_platform_driver = {
    .probe  = my_dma_probe,
    .remove = my_dma_remove,
    .driver = {
        .name = "my-dma",
        .of_match_table = my_dma_of_match,
    },
};
常见错误: 在 probe 函数中,必须先完成所有硬件初始化,再调用 dma_async_device_register。如果注册后硬件初始化失败,DMA框架可能已经将设备暴露给用户空间,导致访问未就绪的硬件。

4.4 dma_async_device_register:DMA设备注册

这是DMA引擎驱动的核心注册步骤。调用前需要填充 struct dma_device 结构体,向内核声明该DMA控制器支持的通道数量、传输类型、能力标志等。

4.4.1 关键字段填充

字段 说明 典型值
chancnt 硬件支持的DMA通道数 4 / 8 / 16
copy_align 内存拷贝对齐要求(2的幂) 4(16字节对齐)
device_alloc_chan_resources 通道资源分配回调 函数指针
device_free_chan_resources 通道资源释放回调 函数指针
device_prep_dma_memcpy 准备内存拷贝传输描述符 函数指针
device_issue_pending 触发等待中的传输 函数指针
cap_mask DMA能力位图 DMA_MEMCPY 等

4.4.2 注册前结构体填充示例

static int my_dma_async_device_init(struct my_dma_device *my_dev)
{
    struct dma_device *dma_dev = &my_dev->dma_dev;

    /* 初始化DMA设备结构体 */
    INIT_LIST_HEAD(&dma_dev->channels);

    /* 设置能力标志 */
    dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask);
    dma_cap_set(DMA_INTERRUPT, dma_dev->cap_mask);

    /* 设置对齐要求 */
    dma_dev->copy_align = DMAENGINE_ALIGN_16_BYTES;

    /* 绑定回调函数 */
    dma_dev->device_alloc_chan_resources = my_dma_alloc_chan_resources;
    dma_dev->device_free_chan_resources  = my_dma_free_chan_resources;
    dma_dev->device_prep_dma_memcpy      = my_dma_prep_memcpy;
    dma_dev->device_issue_pending        = my_dma_issue_pending;
    dma_dev->device_tx_status            = my_dma_tx_status;

    /* 关联设备 */
    dma_dev->dev = &my_dev->pdev->dev;

    /* 初始化每个DMA通道 */
    for (int i = 0; i < my_dev->num_channels; i++) {
        struct dma_chan *chan = &my_dev->channels[i];
        chan->device = dma_dev;
        list_add_tail(&chan->device_node, &dma_dev->channels);
    }

    return 0;
}

4.5 注册与注销的完整流程

以下流程图展示了从模块加载到DMA设备就绪的完整调用链:

DMA引擎驱动注册完整流程 1. insmod / modprobe 加载模块 2. module_init() 被内核调用 3. platform_driver_register() 4. 设备树匹配 → probe() 被调用 5. dma_async_device_register() 6. DMA设备就绪,通道可用 注销流程为反向顺序:rmmod → module_exit → platform_driver_unregister → remove() → dma_async_device_unregister

4.6 常见问题与调试技巧

注册失败排查清单
  • platform_driver_register 返回 -ENODEV:设备树中 compatible 属性与驱动不匹配,检查 of_match_table。
  • dma_async_device_register 返回 -EINVAL:dma_device 结构体未正确填充,常见原因:chancnt 为0、device_alloc_chan_resources 为NULL、dev 指针未设置。
  • probe 函数未调用:确认设备树节点状态为 status = "okay",且驱动已编译进内核或已加载。
  • 注销时内核崩溃:dma_async_device_unregister 必须在所有通道释放后调用,确保没有正在进行的传输。
调试建议: 在 probe 和 remove 函数中添加 dev_dbg() 或 dev_info() 日志,通过 dmesg 观察注册/注销流程是否完整。使用 ls /sys/class/dma/ 确认DMA设备是否成功注册。

4.7 本章小结

DMA引擎驱动的注册与注销遵循严格的三层模型:module_init/module_exit 作为模块入口/出口,platform_driver_register/unregister 负责与设备树匹配,dma_async_device_register/unregister 最终向内核DMA框架注册设备能力。每一层的注册顺序必须与注销顺序严格对称,否则会导致资源泄漏或内核崩溃。掌握这一基础框架,是后续实现高效数据搬运的基石。