4. platform驱动与设备匹配
在 Linux 内核的驱动模型中,platform 总线是一种虚拟总线,用于管理那些不依附于 PCI、USB、I2C 等物理总线的设备(如 SoC 内部外设、内存映射设备)。驱动与设备的匹配是 platform 机制的核心环节,本章将深入分析 platform_driver_register 和 platform_device_register 的源码执行路径,揭示匹配流程的完整链路。
4.1 platform 总线匹配模型概述
platform 总线使用 id_table 优先匹配机制,当驱动注册时,内核会遍历已注册的设备列表,逐一检查驱动是否支持该设备。匹配顺序如下:
- id_table 匹配:驱动提供
struct platform_device_id数组,通过name字段与设备名比较。 - OF(Device Tree)匹配:如果驱动定义了
of_match_table,则与设备树节点的compatible属性匹配。 - ACPI 匹配:在 ACPI 系统上,通过
acpi_match_table进行匹配。 - name 回退匹配:如果以上均未匹配,最后尝试将驱动名与设备名直接比较。
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);
}
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 匹配流程全景图
下图展示了从驱动/设备注册到匹配成功的完整流程:
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。 - 热插拔场景:设备或驱动在运行时动态注册,同样会触发匹配流程。