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框架注册通道能力 |
下图展示了这三层注册之间的调用关系与数据流:
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设备就绪的完整调用链:
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框架注册设备能力。每一层的注册顺序必须与注销顺序严格对称,否则会导致资源泄漏或内核崩溃。掌握这一基础框架,是后续实现高效数据搬运的基石。