4. platform驱动与设备匹配

在 Linux 内核的驱动模型中,platform 总线是一种虚拟总线,用于管理那些不依附于 PCI、USB、I2C 等物理总线的设备(如 SoC 内部外设、内存映射设备)。驱动与设备的匹配是 platform 机制的核心环节,本章将深入分析 platform_driver_register 和 platform_device_register 的源码执行路径,揭示匹配流程的完整链路。

4.1 platform 总线匹配模型概述

platform 总线使用 id_table 优先匹配机制,当驱动注册时,内核会遍历已注册的设备列表,逐一检查驱动是否支持该设备。匹配顺序如下:

  1. id_table 匹配:驱动提供 struct platform_device_id 数组,通过 name 字段与设备名比较。
  2. OF(Device Tree)匹配:如果驱动定义了 of_match_table,则与设备树节点的 compatible 属性匹配。
  3. ACPI 匹配:在 ACPI 系统上,通过 acpi_match_table 进行匹配。
  4. name 回退匹配:如果以上均未匹配,最后尝试将驱动名与设备名直接比较。
实战要点:在嵌入式 Linux 开发中,id_table 是最常用的匹配方式。驱动开发者只需在 platform_driver 结构体中填充 id_table 字段,即可实现多设备兼容。

4.2 platform_driver_register 源码分析

platform_driver_register 是驱动注册的入口函数,其内部调用 __platform_driver_register,最终通过总线核心的 driver_register 完成注册。关键源码路径如下:

// drivers/base/platform.c
int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    // 如果驱动提供了 of_match_table,则将其赋值给 driver.of_match_table
    if (drv->of_match_table)
        drv->driver.of_match_table = drv->of_match_table;
    // 如果驱动提供了 id_table,则将其赋值给 driver.id_table
    if (drv->id_table)
        drv->driver.id_table = &drv->id_table[0];
    return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);

driver_register 会调用总线类型的 probe 方法,对于 platform 总线,该方法是 platform_probe。在 probe 之前,内核会执行匹配检查:

// drivers/base/dd.c
int driver_register(struct device_driver *drv)
{
    ...
    // 遍历已注册的设备,尝试匹配
    bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
    ...
}

static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;
    // 调用总线匹配函数
    if (!drv->bus->match || drv->bus->match(dev, drv))
        // 匹配成功,执行 probe
        driver_probe_device(drv, dev);
    return 0;
}

4.3 platform_device_register 源码分析

设备注册的入口是 platform_device_register,它负责将设备添加到 platform 总线,并触发匹配流程:

// drivers/base/platform.c
int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    arch_setup_pdev_archdata(pdev);
    return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);

int platform_device_add(struct platform_device *pdev)
{
    ...
    // 设置设备所属总线为 platform_bus_type
    pdev->dev.bus = &platform_bus_type;
    ...
    // 将设备添加到设备模型中
    device_add(&pdev->dev);
    ...
}

device_add 会触发总线核心的 bus_probe_device,进而调用 device_attach,遍历已注册的驱动进行匹配:

// drivers/base/dd.c
int device_attach(struct device *dev)
{
    ...
    // 遍历总线上的驱动,尝试匹配
    bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
    ...
}

4.4 匹配函数 platform_match 深度解析

platform 总线的匹配函数是 platform_match,它实现了上述四种匹配策略的优先级逻辑:

// drivers/base/platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    // 1. 尝试 OF 风格匹配(设备树)
    if (of_driver_match_device(dev, drv))
        return 1;

    // 2. 尝试 ACPI 匹配
    if (acpi_driver_match_device(dev, drv))
        return 1;

    // 3. 尝试 id_table 匹配
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    // 4. 回退:直接比较驱动名和设备名
    return (strcmp(pdev->name, drv->name) == 0);
}
关键发现:id_table 匹配的优先级高于 name 回退匹配,但低于 OF 和 ACPI 匹配。在纯非设备树系统中,id_table 是最高优先级的匹配方式。

4.5 id_table 匹配实现:platform_match_id

platform_match_id 函数遍历驱动提供的 id_table 数组,将每个 entry 的 name 字段与设备名比较:

// drivers/base/platform.c
static const struct platform_device_id *platform_match_id(
        const struct platform_device_id *id,
        struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

匹配成功后,pdev->id_entry 被设置为匹配到的 id 条目,驱动 probe 函数可以通过 platform_get_device_id 获取该条目,从而根据不同的设备型号执行不同的初始化逻辑。

4.6 匹配流程全景图

下图展示了从驱动/设备注册到匹配成功的完整流程:

platform 驱动与设备匹配流程全景图 platform_driver_register driver_register bus_for_each_dev __driver_attach platform_device_register platform_device_add device_add __device_attach platform_match 匹配策略优先级 ① OF匹配 → ② ACPI匹配 ③ id_table匹配 → ④ name回退 driver_probe_device

4.7 匹配流程步骤总结

步骤 触发点 核心函数 说明
1 驱动注册 platform_driver_register 设置 bus 为 platform_bus_type,调用 driver_register
2 设备注册 platform_device_register 设置 bus 为 platform_bus_type,调用 device_add
3 遍历设备/驱动 bus_for_each_dev / bus_for_each_drv 遍历总线上的设备或驱动列表
4 匹配检查 platform_match 按优先级执行四种匹配策略
5 匹配成功 driver_probe_device 调用驱动的 probe 函数,完成绑定

4.8 实战案例:使用 id_table 实现多设备兼容

以下示例展示如何通过 platform_device_id 表让一个驱动支持两个不同的 UART 设备:

// 定义 id_table
static const struct platform_device_id my_uart_ids[] = {
    { .name = "uart_a", .driver_data = (kernel_ulong_t)&uart_a_data },
    { .name = "uart_b", .driver_data = (kernel_ulong_t)&uart_b_data },
    { }
};

MODULE_DEVICE_TABLE(platform, my_uart_ids);

// 定义 platform_driver
static struct platform_driver my_uart_driver = {
    .probe  = my_uart_probe,
    .remove = my_uart_remove,
    .driver = {
        .name = "my_uart",
        .of_match_table = of_match_ptr(my_uart_of_match),
    },
    .id_table = my_uart_ids,
};

module_platform_driver(my_uart_driver);

// probe 函数中获取匹配的 id_entry
static int my_uart_probe(struct platform_device *pdev)
{
    const struct platform_device_id *id = platform_get_device_id(pdev);
    if (id) {
        dev_info(&pdev->dev, "匹配到设备: %s\n", id->name);
        // 根据 id->driver_data 执行不同初始化
    }
    return 0;
}
常见陷阱:如果驱动同时定义了 of_match_table 和 id_table,在设备树系统中 OF 匹配优先级更高,id_table 可能不会被使用。此时 platform_get_device_id 返回 NULL,驱动应优先使用 of_match_node 获取匹配数据。

4.9 匹配流程中的关键数据结构

数据结构 所在文件 关键字段 作用
platform_driver include/linux/platform_device.h id_table, driver 驱动核心结构,包含匹配表和通用驱动属性
platform_device include/linux/platform_device.h name, id, dev 设备核心结构,name 用于匹配
platform_device_id include/linux/mod_devicetable.h name, driver_data id_table 条目,name 与设备名匹配
bus_type include/linux/device.h match, probe 总线类型,platform_match 被赋值给 match 回调

4.10 匹配流程的时序关系

无论驱动先注册还是设备先注册,最终都能完成匹配:

  • 驱动先注册:platform_driver_register 遍历已注册的设备列表,对每个设备调用 platform_match。
  • 设备先注册:platform_device_register 遍历已注册的驱动列表,对每个驱动调用 platform_match。
  • 热插拔场景:设备或驱动在运行时动态注册,同样会触发匹配流程。
内核设计哲学:platform 总线的匹配机制保证了驱动与设备之间的松耦合关系。驱动开发者只需关注 id_table 的定义,无需关心设备的注册顺序或存在时机,内核会自动完成匹配与绑定。