4. Platform驱动注册:platform_driver结构体、probe/remove回调函数、module_platform_driver宏
Platform驱动注册是驱动与内核建立绑定的核心步骤。本章深入剖析 platform_driver 结构体的组成、probe/remove 回调函数的生命周期,以及 module_platform_driver 宏的简化机制,帮助开发者掌握驱动注册的标准范式。
4.1 platform_driver结构体详解
platform_driver 是 Linux 内核中用于描述 Platform 设备驱动的核心结构体,定义在 <linux/platform_device.h> 中。它封装了驱动与设备匹配、初始化和移除所需的所有回调函数及标识信息。
关键点: platform_driver 是驱动开发者必须填充的“驱动身份证”,内核通过它来识别驱动能力并调用对应的处理函数。
| 成员 | 类型 | 说明 |
|---|---|---|
probe |
int (*probe)(struct platform_device *) |
设备匹配成功时调用,完成硬件初始化、资源申请 |
remove |
int (*remove)(struct platform_device *) |
设备移除或驱动卸载时调用,释放资源 |
shutdown |
void (*shutdown)(struct platform_device *) |
系统关机或重启时调用,执行优雅关闭 |
suspend |
int (*suspend)(struct platform_device *, pm_message_t) |
系统进入低功耗模式时调用(可选) |
resume |
int (*resume)(struct platform_device *) |
系统从低功耗模式恢复时调用(可选) |
driver |
struct device_driver |
内嵌通用驱动结构体,包含名称、owner、of_match_table 等 |
id_table |
const struct platform_device_id * |
传统设备 ID 匹配表(非设备树场景使用) |
// platform_driver 结构体定义(内核源码)
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
4.2 probe回调函数:驱动的初始化入口
probe 是 Platform 驱动中最重要的回调函数。当内核发现一个 Platform 设备与驱动匹配成功时,会立即调用驱动的 probe 函数。该函数负责完成以下关键任务:
- 获取硬件资源:通过
platform_get_resource()获取 I/O 内存、中断号、DMA 通道等资源 - 映射 I/O 内存:使用
devm_ioremap_resource()或ioremap()将物理地址映射到内核虚拟地址空间 - 注册中断处理:调用
devm_request_irq()注册中断服务例程 - 初始化硬件:配置寄存器、复位外设、启动时钟等
- 创建设备文件:注册字符设备、misc 设备或创建 sysfs 属性文件
- 初始化内核数据结构:分配私有数据、初始化锁、工作队列、定时器等
最佳实践: 优先使用
devm_ 系列资源管理函数(如 devm_kzalloc、devm_ioremap_resource),这些函数会在驱动卸载或 probe 失败时自动释放资源,避免内存泄漏。
// 典型的 probe 函数实现示例
static int my_platform_probe(struct platform_device *pdev)
{
struct resource *res;
struct my_device_data *data;
int irq;
int ret;
// 1. 分配私有数据结构
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
platform_set_drvdata(pdev, data);
// 2. 获取 I/O 内存资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
// 3. 映射 I/O 内存
data->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->regs))
return PTR_ERR(data->regs);
// 4. 获取中断号并注册中断
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_irq(&pdev->dev, irq, my_irq_handler,
0, pdev->name, data);
if (ret)
return ret;
// 5. 初始化硬件
my_hw_init(data);
// 6. 注册字符设备
ret = my_cdev_register(data);
if (ret)
return ret;
dev_info(&pdev->dev, "probe success\n");
return 0;
}
4.3 remove回调函数:资源的优雅释放
remove 回调函数是 probe 的逆操作,在设备从系统中移除或驱动卸载时被调用。它的核心职责是:
- 停止硬件操作:禁用中断、停止 DMA 传输、关闭时钟
- 注销设备文件:移除字符设备、misc 设备或 sysfs 属性
- 释放资源:虽然
devm_资源会自动释放,但非托管资源需要手动释放 - 清理内核数据结构:销毁工作队列、删除定时器、释放锁
注意: 如果 probe 中使用了
devm_ 系列函数,remove 回调中通常只需要执行硬件停止操作和注销设备文件,内存和 I/O 映射资源会自动释放。但中断和字符设备的注销仍需显式处理。
// 典型的 remove 函数实现示例
static int my_platform_remove(struct platform_device *pdev)
{
struct my_device_data *data = platform_get_drvdata(pdev);
// 1. 停止硬件操作
my_hw_stop(data);
// 2. 注销字符设备
my_cdev_unregister(data);
// 3. 非托管资源手动释放(如果使用了非 devm_ 函数)
// free_irq(data->irq, data);
// iounmap(data->regs);
// kfree(data);
// 4. 清空驱动私有数据
platform_set_drvdata(pdev, NULL);
dev_info(&pdev->dev, "remove success\n");
return 0;
}
4.4 probe/remove 生命周期与调用流程
理解 probe 和 remove 的调用时机对于编写健壮的驱动至关重要。下图展示了从驱动注册到设备匹配、probe 执行,再到 remove 的完整生命周期:
4.5 module_platform_driver 宏:简化驱动注册
module_platform_driver 是一个便捷宏,它将驱动注册、注销与模块的加载/卸载操作自动关联起来。使用该宏可以避免手动编写 module_init 和 module_exit 函数。
// module_platform_driver 宏定义(内核源码)
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
// 展开后等价于:
static int __init my_driver_init(void)
{
return platform_driver_register(&my_platform_driver);
}
module_init(my_driver_init);
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver);
}
module_exit(my_driver_exit);
使用建议: 对于大多数 Platform 驱动,推荐直接使用
module_platform_driver 宏。只有当驱动需要在 init 函数中执行额外的初始化操作(如注册多个驱动、解析模块参数等)时,才需要手动编写 module_init 和 module_exit。
4.6 完整驱动注册示例
以下是一个完整的 Platform 驱动注册代码框架,展示了从结构体定义到宏注册的完整流程:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/interrupt.h>
/* 私有数据结构 */
struct my_device_data {
void __iomem *regs;
int irq;
struct cdev cdev;
dev_t dev_num;
};
/* probe 回调 */
static int my_probe(struct platform_device *pdev)
{
// ... 资源获取与初始化代码 ...
return 0;
}
/* remove 回调 */
static int my_remove(struct platform_device *pdev)
{
// ... 资源释放与清理代码 ...
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
/* platform_driver 结构体定义 */
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device",
.of_match_table = my_of_match,
.owner = THIS_MODULE,
},
};
/* 使用宏简化注册 */
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Platform driver example");
4.7 驱动注册常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| probe 从未被调用 | 设备树 compatible 不匹配或驱动未注册 | 检查 of_match_table 与设备树节点是否一致 |
| probe 返回 -EPROBE_DEFER | 依赖的资源(如时钟、GPIO)尚未准备好 | 返回 -EPROBE_DEFER,内核会在依赖资源就绪后重试 |
| remove 后系统崩溃 | 资源释放顺序错误或存在并发访问 | 先停止硬件操作,再注销设备文件,最后释放资源 |
| 模块卸载失败 | 设备文件仍被打开或引用计数不为零 | 确保 remove 中正确注销设备文件,检查引用计数 |
| 多次 probe 导致资源泄漏 | 未使用 devm_ 系列函数管理资源 |
改用 devm_kzalloc、devm_ioremap_resource 等 |
调试技巧: 在 probe 和 remove 函数中添加
dev_dbg() 或 dev_info() 日志,通过 dmesg 观察驱动加载和卸载的完整流程。使用 ls /sys/bus/platform/drivers/<driver_name>/ 查看驱动绑定的设备列表。
4.8 本章小结
- platform_driver 结构体是驱动注册的核心,必须填充
probe、remove和driver成员 - probe 回调负责硬件初始化和资源获取,应优先使用
devm_系列函数 - remove 回调负责资源释放和硬件停止,是 probe 的逆操作
- module_platform_driver 宏简化了驱动注册流程,推荐在大多数场景下使用
- 理解 probe/remove 的生命周期和调用时机,是编写稳定可靠驱动的关键