4. platform总线机制:platform_driver与platform_device的匹配流程,probe/remove函数

platform总线是Linux内核中用于管理非可枚举设备(如SoC内部集成外设、传统ISA设备等)的虚拟总线。它绕过了PCI/USB等物理总线的硬件枚举能力,通过软件方式将设备与驱动绑定。面试中,platform总线的匹配流程、probe/remove生命周期是高频考点。

核心认知: platform总线不是物理总线,而是内核为“无总线设备”提供的一套软件抽象层。它通过name字段进行匹配,匹配成功后调用驱动的probe函数完成设备初始化。

4.1 platform总线模型架构

platform总线模型由三部分组成:platform_device(设备端)、platform_driver(驱动端)和platform_bus_type(总线类型)。内核通过platform_bus_type中的match函数完成设备与驱动的配对。

platform总线模型架构 platform_bus_type .match = platform_match platform_device 链表 device_1 (name="serial") device_2 (name="gpio") device_3 (name="i2c") platform_driver 链表 driver_1 (name="serial") driver_2 (name="spi") driver_3 (name="gpio") 匹配规则:优先比较name字段,其次比较id_table(设备树兼容性)

4.2 platform_driver与platform_device数据结构

理解两个核心结构体的字段是掌握匹配流程的基础。

结构体 关键字段 作用说明
platform_device name 设备名称,匹配时的首要依据
id 设备实例编号,-1表示单实例
dev 内嵌的通用device结构体
num_resources 资源数量(IRQ、I/O内存等)
resource 资源数组指针
platform_driver driver.name 驱动名称,与device.name匹配
id_table 设备树兼容性匹配表(可选)
probe 匹配成功后调用的初始化函数
remove 设备移除时调用的清理函数
shutdown 系统关机时的回调

4.3 匹配流程详解

platform总线的匹配由platform_match函数实现,该函数是platform_bus_type的.match回调。匹配顺序遵循以下优先级:

  1. 设备树OF匹配:如果驱动提供了of_match_table,则优先与设备树节点的compatible属性进行匹配。
  2. ACPI匹配:在支持ACPI的平台上,尝试ACPI表匹配。
  3. id_table匹配:检查驱动id_table中的name字段是否与设备name一致。
  4. name直接匹配:比较driver.name与device.name是否相等。
面试陷阱: 很多开发者误以为platform匹配只比较name字段。实际上,设备树(DT)匹配的优先级最高。在设备树普及的今天,驱动通常通过of_match_table实现匹配,name匹配仅作为传统方式的兜底。
platform_match 匹配流程 platform_match 入口 步骤1:of_driver_match_device() 检查设备树compatible属性 匹配? 是 匹配成功 → probe 否 步骤2:acpi_driver_match_device() ACPI表匹配(x86/ARM64) 匹配? 是 否 步骤3:platform_match_id() 遍历驱动id_table匹配name 匹配? 是 否 步骤4:strcmp(drv->name, dev->name) 直接比较name字符串 匹配结果 任一匹配成功 → probe 全部匹配失败 → 返回-ENODEV 等待下次注册事件

4.4 probe函数:驱动的初始化入口

当匹配成功后,内核调用驱动的probe函数。这是驱动开发者最常接触的接口,负责完成设备初始化、资源申请、硬件配置等核心工作。

static int my_serial_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;
    int irq, ret;

    // 1. 获取I/O内存资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res)
        return -ENXIO;

    // 2. 请求并映射I/O内存
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    // 3. 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    // 4. 注册中断处理函数
    ret = devm_request_irq(&pdev->dev, irq, my_serial_isr,
                           0, pdev->name, pdev);
    if (ret)
        return ret;

    // 5. 初始化硬件寄存器
    writel(CTRL_ENABLE, base + UART_CTRL);

    // 6. 创建字符设备或注册其他框架
    // ...

    dev_info(&pdev->dev, "serial device initialized\n");
    return 0;
}
最佳实践: 优先使用devm_系列API(managed device resource),如devm_ioremap_resource、devm_request_irq。这些API会在设备移除或驱动卸载时自动释放资源,避免内存泄漏和资源泄露。

4.5 remove函数:优雅的清理出口

remove函数在设备从系统中移除或驱动卸载时被调用。它的职责是probe的逆操作:释放资源、注销设备、恢复硬件到安全状态。

static int my_serial_remove(struct platform_device *pdev)
{
    struct my_serial_data *data = platform_get_drvdata(pdev);

    // 1. 禁用硬件(停止中断、关闭外设时钟)
    writel(0, data->base + UART_CTRL);

    // 2. 注销设备节点(如字符设备)
    device_destroy(my_serial_class, data->devno);
    cdev_del(&data->cdev);

    // 3. 释放私有数据(devm_系列自动释放,无需手动)
    // devm_* 资源会自动释放

    // 4. 清除drvdata
    platform_set_drvdata(pdev, NULL);

    dev_info(&pdev->dev, "serial device removed\n");
    return 0;
}
关键注意: 如果使用了devm_系列API,remove函数中不需要手动调用iounmap、free_irq、kfree等操作。内核会在remove返回后自动释放这些托管资源。但非托管资源(如cdev_add、device_create)必须手动清理。

4.6 驱动注册与注销流程

驱动模块的加载和卸载通过以下宏实现:

操作 宏/函数 说明
驱动注册 module_platform_driver(my_driver) 封装了module_init/exit,自动注册platform_driver
手动注册 platform_driver_register(&my_driver) 在module_init中调用,注册到platform总线
驱动注销 platform_driver_unregister(&my_driver) 在module_exit中调用,从platform总线移除
设备注册 platform_device_register(&my_device) 注册platform设备到总线
设备注销 platform_device_unregister(&my_device) 从总线移除platform设备
// 推荐方式:使用module_platform_driver宏
static struct platform_driver my_serial_driver = {
    .probe  = my_serial_probe,
    .remove = my_serial_remove,
    .driver = {
        .name = "my_serial",
        .of_match_table = my_serial_of_match,
    },
};

module_platform_driver(my_serial_driver);

// 等价于:
// module_init(my_serial_init);  → platform_driver_register()
// module_exit(my_serial_exit);  → platform_driver_unregister()

4.7 面试高频问题与避坑指南

面试官常问:
  • “platform总线的匹配顺序是什么?设备树匹配和name匹配哪个优先级高?”
  • “probe函数返回负数代表什么?内核会如何处理?”
  • “如果probe失败,之前申请的devm资源会泄露吗?”
  • “remove函数中,devm_资源还需要手动释放吗?”
  • “platform_driver和platform_device的注册顺序有要求吗?”
问题 答案要点
匹配顺序 OF匹配 → ACPI匹配 → id_table匹配 → name直接匹配
probe返回负数 表示初始化失败,内核将设备标记为未绑定,释放已申请的devm资源
devm资源泄露 不会。devm资源绑定在device生命周期上,probe失败时自动释放
remove中devm处理 不需要手动释放,内核在remove返回后自动清理devm资源
注册顺序 无严格要求。先注册设备或先注册驱动均可,内核会遍历已有链表进行匹配
深入理解: platform总线的设计体现了Linux设备模型的核心思想——延迟绑定。设备和驱动可以独立注册,总线负责在两者之间建立连接。这种解耦设计使得驱动模块可以动态加载,设备可以热插拔,系统具有极高的灵活性。