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 的完整生命周期:

Platform 驱动 probe/remove 生命周期流程图 module_platform_driver() 内核总线匹配设备 匹配? 调用 probe() 回调 获取资源 → 映射内存 → 注册中断 设备移除/驱动卸载 调用 remove() 回调 生命周期说明 1. 驱动注册后等待设备匹配 2. 匹配成功 → probe 执行 3. 设备移除 → remove 执行 4. probe 失败不会调用 remove probe 返回值含义 0:成功,驱动绑定设备 -ENOMEM:内存不足 -ENXIO:资源获取失败 -EINVAL:参数无效

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 的生命周期和调用时机,是编写稳定可靠驱动的关键