概述
在 Linux 驱动模型里,经常会看到三类对象:
bus:总线,负责管理一类设备和驱动,并定义二者如何匹配;device:设备,表示系统里已经存在的某个硬件或虚拟硬件对象;driver:驱动,表示内核里能够驱动某类设备的软件实现。
它们之间最重要的一句话是:
device 和 driver 并不是直接互相寻找对方,而是都挂到同一个 bus 上,由 bus 的
match()规则负责撮合。
所以 Linux 驱动模型不是简单的"加载一个驱动,然后驱动自己去扫硬件"。更准确地说,内核维护了一套统一的设备模型:设备注册进来,驱动也注册进来;只要二者属于同一个 bus,bus 就会尝试匹配,匹配成功后进入 probe()。
本文先从通用 driver core 讲起,然后用 platform device / platform driver 展开,最后结合 dw-apb-uart 串口驱动说明:串口 driver 注册在哪里,串口 device 又是谁创建的。源码示例以本地 Linux 6.5.3 为参考。
1. 三个核心对象
1.1 bus_type:总线负责定义匹配规则
Linux 中的总线由 struct bus_type 描述,典型成员如下:
c
struct bus_type {
const char *name;
const char *dev_name;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(const struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
void (*remove)(struct device *dev);
...
};
这里最关键的是 match() 和 probe()。
match() 决定一个 device 和一个 driver 是否匹配。不同 bus 有不同的匹配规则,比如:
- PCI bus 根据 vendor/device id 匹配;
- USB bus 根据 USB id table 匹配;
- platform bus 可以根据 Device Tree compatible、ACPI id、platform id table、名字匹配;
probe() 则是匹配成功后的入口。很多 bus 的 probe() 只是一个中转函数,最终会调用到具体 driver 提供的 probe 函数。
1.2 device:设备是系统里已经存在的对象
通用设备对象由 struct device 描述:
c
struct device {
struct kobject kobj;
struct device *parent;
struct bus_type *bus;
struct device_driver *driver;
void *platform_data;
...
};
重点是:
dev->bus表示这个设备挂在哪个 bus 上;dev->driver表示匹配成功后绑定到哪个 driver;dev->parent表示设备层级关系;platform_data或of_node/fwnode等字段用于携带固件、板级或平台描述信息。
换句话说,device 不是凭空来的。它一定是某个内核子系统根据硬件枚举结果、固件描述、板级代码或虚拟化设备描述创建出来的。
1.3 device_driver:driver 是可以服务某类设备的实现
通用驱动对象由 struct device_driver 描述:
c
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe)(struct device *dev);
void (*sync_state)(struct device *dev);
int (*remove)(struct device *dev);
...
};
驱动注册时,需要说明自己属于哪个 bus。比如 platform driver 属于 platform_bus_type,PCI driver 属于 pci_bus_type,I2C driver 属于 i2c_bus_type。
driver core 只处理通用的注册、链表、sysfs、绑定和 probe 流程;至于怎么判断这个 driver 支不支持这个 device,则交给具体 bus 的 match()。
2. driver 和 device 谁先注册都可以
Linux 的设计里,device 和 driver 没有严格的先后要求。
2.1 device 先注册
如果设备先出现,比如启动阶段 Device Tree 被解析后创建了一个 platform device,那么大致流程是:
text
device_register()
-> device_add()
-> 把 device 加入所属 bus
-> 在 sysfs 中创建设备节点
-> 遍历 bus 上已有 driver
-> 调用 bus->match(dev, drv)
-> 匹配成功后调用 probe
也就是说,设备注册进来时,内核会拿它去找当前已经注册过的 driver。
2.2 driver 先注册
如果驱动先加载,比如模块被 modprobe 加载进内核,那么大致流程是:
text
driver_register()
-> bus_add_driver()
-> 把 driver 加入所属 bus
-> 遍历 bus 上已有 device
-> 调用 bus->match(dev, drv)
-> 匹配成功后调用 probe
也就是说,驱动注册进来时,内核会拿它去匹配当前已经存在的 device。
所以,无论是:
text
先有 device,后有 driver
还是:
text
先有 driver,后有 device
最终都会收敛到同一件事:
text
device 和 driver 位于同一个 bus
-> bus->match(dev, drv)
-> 匹配成功
-> really_probe()
-> driver probe
3. platform bus 是什么
platform bus 是 Linux driver core 中的一种软件总线,用来承载 platform_device 和 platform_driver。
它不是电气意义上的硬件总线,而是给这类设备准备的统一管理入口:
text
无法通过硬件总线自动枚举,
但又需要注册成 Linux device,
并参与 driver core 匹配和 probe 流程的板级/SoC 设备。
这类设备通常直接集成在 SoC 或板级硬件中,有固定的寄存器地址、中断号、clock、reset 等资源。硬件本身不会像 PCI/USB 设备那样主动报告"我是谁、我在哪里、我支持什么能力",因此内核需要通过 Device Tree、ACPI、board file,或者父设备驱动创建的子设备来知道它们的存在。
典型的 platform device 包括:
- UART 控制器;
- GPIO 控制器;
- I2C / SPI 控制器;
- SATA/AHCI 控制器;
- timer;
- watchdog;
- pinctrl;
- clock controller。
从 Linux 设备模型看,platform bus 的角色很明确:维护 platform 设备和 platform 驱动,并定义二者如何匹配、匹配后如何进入 probe。Linux 6.5.3 中的定义大致如下:
c
/* drivers/base/platform.c */
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.probe = platform_probe,
.remove = platform_remove,
...
};
其中:
platform_match():判断platform_device和platform_driver是否匹配;platform_probe():匹配成功后,调用具体platform_driver的 probe 函数;platform_remove():设备和驱动解绑时执行清理。
需要注意的是,platform bus 不是 PCI、USB、I2C、SPI、MDIO 这些 bus 的替代品。它通常承载的是"控制器本体",而不是控制器后面挂着的外设。
例如一个 SoC 内部的 I2C 控制器可能是 platform_device,因为这个控制器本身是固定 MMIO 设备;但 I2C 控制器下面的 EEPROM、PMIC、sensor 通常会注册成 i2c_client,挂到 I2C bus 上。SATA 也是类似:SoC 内部的 AHCI 控制器可以是 platform_device,而 AHCI 控制器后面发现的硬盘则进入 libata / SCSI / block 层。
4. platform device 从哪里来
platform_device 是 platform bus 上的设备对象:
c
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;
struct resource *resource;
...
};
注意这里内嵌了一个通用的 struct device dev。也就是说,platform_device 是对通用 device 的一层封装。
它通常携带几类信息:
- MMIO 寄存器地址,对应
reg或struct resource; - IRQ 中断号;
- DMA 信息;
- clock、reset、pinctrl 等资源;
- Device Tree node 或 ACPI companion;
- 设备名字和 id。
4.1 Device Tree 创建 platform device
在使用 Device Tree 的平台上,Device Tree 只描述硬件;是否创建成 platform_device,由内核的 OF/platform 扫描路径决定。这里的 OF 来自 Open Firmware,Linux 内核中很多 Device Tree 相关接口仍沿用 of_ 前缀。
例如一个 DesignWare APB UART 节点:
dts
uart0: serial@ff180000 {
compatible = "snps,dw-apb-uart";
reg = <0xff180000 0x100>;
interrupts = <...>;
clocks = <...>;
reg-shift = <2>;
reg-io-width = <4>;
status = "okay";
};
启动阶段,Device Tree / OF platform 代码会扫描合适的节点,并将其注册到 platform bus:
text
of_platform_populate()
-> of_platform_bus_create()
-> of_platform_device_create_pdata()
-> of_device_alloc()
-> of_device_add()
-> device_add(&dev->dev)
关键代码在 drivers/of/platform.c 和 drivers/of/device.c。
c
/* Linux 6.5.3: drivers/of/platform.c,关键路径摘录 */
static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
if (!dev->dev.dma_mask)
dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
of_device_alloc() 分配 platform_device,并把 DT node 关联到 dev->dev.of_node。dev->dev.bus = &platform_bus_type 决定该设备进入 platform bus。随后 of_device_add() 调用通用设备注册入口:
c
/* Linux 6.5.3: drivers/of/device.c,关键路径摘录 */
int of_device_add(struct platform_device *ofdev)
{
BUG_ON(ofdev->dev.of_node == NULL);
/* name and id have to be set before the device enters platform bus */
ofdev->name = dev_name(&ofdev->dev);
ofdev->id = PLATFORM_DEVID_NONE;
...
/*
* 因为前面已经设置 ofdev->dev.bus = &platform_bus_type,
* 这里会把设备加入 platform bus。
*/
return device_add(&ofdev->dev);
}
of_device_add() 本身不是重新选择 bus;它是在 ofdev->dev.bus 已经指向 platform_bus_type 的前提下,补齐 platform device 的 name/id,再调用 device_add() 完成设备注册。device_add(&ofdev->dev) 是通用 driver core 入口,会把设备加入 ofdev->dev.bus 指向的 bus。至于加入 bus 后如何匹配 driver,放到第 6 节再展开。
因此,Device Tree 创建 platform device 的核心动作是:
text
DT node
-> platform_device
-> dev->dev.bus = &platform_bus_type
-> device_add(&dev->dev)
-> 设备进入 platform bus
并不是所有 DT node 都会创建成 platform_device。例如 ARM PrimeCell 节点会走 AMBA 设备创建路径:
c
/* Linux 6.5.3: drivers/of/platform.c,关键路径摘录 */
if (of_device_is_compatible(bus, "arm,primecell")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0; /* 已按 AMBA device 处理,不再创建 platform_device */
}
/* 非 arm,primecell 节点走默认 OF/platform 路径 */
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
if (!dev || !of_match_node(matches, bus))
return 0;
这里的 return 0 很关键:PrimeCell 节点会提前走 AMBA 创建设备路径;只有没有命中这个特殊分支的节点,才继续调用 of_platform_device_create_pdata() 创建 platform_device。
这也说明:DT node 本身不声明 Linux bus 类型;扫描它的内核代码决定最终创建哪种 struct device。
text
Device Tree / OF platform 扫描到的节点 -> platform_device
Device Tree / OF AMBA 特殊处理的节点 -> amba_device
I2C core 扫描的子节点 -> i2c_client
SPI core 扫描的子节点 -> spi_device
4.2 ACPI 创建 platform device
在 x86、服务器平台或部分 ARM64 平台上,设备可能来自 ACPI。
ACPI 表里会描述设备的 _HID、_CID、资源窗口、中断等信息。内核 ACPI 子系统解析这些表后,也可以创建相应的 platform device。
对于使用 ACPI 匹配的 platform driver,常见写法是:
c
.acpi_match_table = xxx_acpi_match,
这个表的匹配规则放到第 6 节统一说明。
4.3 board file 手动注册 platform device
更老的内核或某些没有使用 Device Tree / ACPI 的平台,可能在板级代码里手动注册:
c
static struct resource xxx_uart_resources[] = {
DEFINE_RES_MEM(...),
DEFINE_RES_IRQ(...),
};
static struct platform_device xxx_uart_device = {
.name = "dw-apb-uart",
.id = 0,
.resource = xxx_uart_resources,
.num_resources = ARRAY_SIZE(xxx_uart_resources),
};
platform_device_register(&xxx_uart_device);
这种方式现在在新平台上少很多,但它最能说明本质:platform device 就是平台代码告诉内核"这里有一个设备"。
4.4 父设备驱动动态创建子设备
还有一种情况是,某个父设备 probe 后,发现自己下面还有子设备,于是动态创建 platform device。
例如一些 MFD、SoC glue driver、特殊总线控制器驱动,可能会在自己的 probe 过程中注册子 platform device。
这说明 platform device 不一定只来自启动阶段,也可以在运行期由其他驱动创建。
5. platform driver 是怎么注册的
platform_driver 是 platform bus 上的驱动对象:
c
struct platform_driver {
int (*probe)(struct platform_device *);
void (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
...
};
同样,它也内嵌了一个通用的 struct device_driver driver。
注册 platform driver 常见写法是(以 dw8250 串口驱动为例):
c
static struct platform_driver dw8250_platform_driver = {
.driver = {
.name = "dw-apb-uart",
.pm = pm_ptr(&dw8250_pm_ops),
.of_match_table = dw8250_of_match,
.acpi_match_table = dw8250_acpi_match,
},
.probe = dw8250_probe,
.remove = dw8250_remove,
};
module_platform_driver(dw8250_platform_driver);
module_platform_driver() 是一个辅助宏,大致展开后就是:
c
static int __init xxx_init(void)
{
return platform_driver_register(&xxx_platform_driver);
}
static void __exit xxx_exit(void)
{
platform_driver_unregister(&xxx_platform_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
而 platform_driver_register() 最终会把这个 driver 注册到 platform_bus_type 上:
text
platform_driver_register()
-> __platform_driver_register()
-> drv->driver.bus = &platform_bus_type
-> driver_register(&drv->driver)
之后 driver core 会遍历 platform bus 上已有的 platform device,尝试匹配。
6. platform device 和 platform driver 怎么匹配
platform bus 的匹配函数是 platform_match()。它不是只比较名字,而是按多个来源依次尝试:
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);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
6.1 Device Tree compatible 匹配
如果 device 来自 Device Tree,那么 platform_device.dev.of_node 会指向对应的设备树节点。
以 dw_8250 串口驱动为例:
c
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
{ .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data },
{ .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data },
{ .compatible = "starfive,jh7100-uart", .data = &dw8250_starfive_jh7100_data },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dw8250_of_match);
然后:
c
static struct platform_driver dw8250_platform_driver = {
.driver = {
.name = "dw-apb-uart",
.pm = pm_ptr(&dw8250_pm_ops),
.of_match_table = dw8250_of_match,
.acpi_match_table = dw8250_acpi_match,
},
.probe = dw8250_probe,
.remove = dw8250_remove,
};
如果 DTS 里的 compatible 属性和 driver 的 of_match_table 中的 compatible 属性对上,就匹配成功。
6.2 ACPI 匹配
ACPI 平台则通过 ACPI id 表匹配,还是以 dw_8250 串口驱动为例:
c
static const struct acpi_device_id dw8250_acpi_match[] = {
{ "80860F0A", (kernel_ulong_t)&dw8250_dw_apb },
{ "8086228A", (kernel_ulong_t)&dw8250_dw_apb },
{ "AMD0020", (kernel_ulong_t)&dw8250_dw_apb },
{ "AMDI0020", (kernel_ulong_t)&dw8250_dw_apb },
{ "AMDI0022", (kernel_ulong_t)&dw8250_dw_apb },
{ "APMC0D08", (kernel_ulong_t)&dw8250_dw_apb},
{ "BRCM2032", (kernel_ulong_t)&dw8250_dw_apb },
{ "HISI0031", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT33C4", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT33C5", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3434", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3435", (kernel_ulong_t)&dw8250_dw_apb },
{ },
};
MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);
然后:
c
static struct platform_driver dw8250_platform_driver = {
.driver = {
.name = "dw-apb-uart",
.pm = pm_ptr(&dw8250_pm_ops),
.of_match_table = dw8250_of_match,
.acpi_match_table = dw8250_acpi_match,
},
.probe = dw8250_probe,
.remove = dw8250_remove,
};
固件里的 ACPI 表大概会描述类似这样的设备对象(_HID、_CID 命中任意一个即可):
c
Device (UART)
{
Name (_HID, "80860F0A")
Name (_CID, "INT3434")
Name (_CRS, ResourceTemplate () {
Memory32Fixed (...)
Interrupt (...)
})
}
6.3 platform_device_id 匹配
id_table 是 platform_driver 提供的一组传统 platform 设备名。它不匹配 DT 的 compatible,也不匹配 ACPI _HID,而是匹配 platform_device.name。
Linux 6.5.3 中的关键逻辑如下:
c
/* Linux 6.5.3: 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;
}
典型写法是:
c
static const struct platform_device_id xxx_ids[] = {
{ "xxx-device", 0 },
{ }
};
MODULE_DEVICE_TABLE(platform, xxx_ids);
static struct platform_driver xxx_driver = {
.probe = xxx_probe,
.id_table = xxx_ids,
.driver = {
.name = "xxx-device",
},
};
这里真正参与比较的是:
text
platform_device.name <-> platform_device_id.name
这类匹配主要服务于非 DT/ACPI 或兼容旧 board file 的 platform device。对于 DT 创建的 device,如果 driver 提供了 of_match_table 且 compatible 能匹配,通常会先在 Device Tree compatible 匹配阶段成功,不会走到 id_table。
6.4 name 匹配
如果前面的方式都没有匹配上,最后还可以比较:
text
platform_device.name
platform_driver.driver.name
比如 device 的名字是 "dw-apb-uart",driver 的名字也是 "dw-apb-uart",也可以匹配。
不过在现代 DT/ACPI 平台上,更推荐通过 compatible 或 ACPI id 匹配,因为同一个 driver 可能支持多个兼容硬件变体。
7. probe 是怎么被调用到具体驱动的
当 platform_match() 返回成功后,driver core 会进入通用 probe 流程:
text
device_add()
-> bus_probe_device
-> __device_attach
-> driver_probe_device()
-> really_probe()
-> dev->driver = drv
-> call_driver_probe()
-> bus->probe(dev)
对于 platform bus 来说:
text
bus->probe(dev)
-> platform_probe(dev)
-> platform_driver->probe(platform_device)
c
static int platform_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;
......
if (drv->probe) {
ret = drv->probe(dev);
if (ret)
dev_pm_domain_detach(_dev, true);
}
......
}
也就是最终会调用到驱动里写的:
c
static int xxx_probe(struct platform_device *pdev)
{
...
}
9. 小结
bus_type 定义一类设备和驱动的管理方式,尤其是 match() 和 probe()。
device 表示系统中已经存在的硬件或虚拟硬件对象,它可能来自硬件枚举、Device Tree、ACPI、board file,或者其他驱动动态创建。
driver 表示可以驱动某类设备的软件实现。driver 注册时会挂到某个 bus 上。
device 和 driver 的匹配不是由二者私下完成的,而是由 bus 统一完成:
text
device_register()
driver_register()
-> 都会触发 bus 上的匹配流程
-> bus->match(dev, drv)
-> 匹配成功
-> bus->probe(dev)
-> driver->probe(...)
对于 platform 设备来说:
text
DTS / ACPI / board file / 父设备驱动
-> 创建 platform_device
-> 注册到 platform_bus_type
platform_driver_register()
-> 注册 platform_driver 到 platform_bus_type
platform_match()
-> compatible / ACPI id / id_table / name 匹配
platform_probe()
-> 调用具体 platform_driver->probe()
对于 dw-apb-uart 串口驱动来说:
text
dw8250_platform_driver 只是注册串口控制器 driver;
真正的 UART platform_device 通常来自 DTS 或 ACPI;
匹配成功后 driver core 调用 dw8250_probe(pdev)。
所以看驱动代码时,可以先问三个问题:
text
1. 这个 driver 注册到了哪个 bus?
2. 对应的 device 是谁创建的?
3. bus 的 match 规则用什么字段把它们匹配起来?
这三个问题想清楚,Linux 里大多数 driver probe 链路就能顺下来了。