核心匹配机制:platform_device 和 platform_driver(也叫平台总线,它是一种虚拟的总线而非物理的)
对于老版内核没有设备树的情况下设备驱动匹配过程
在设备树时代,需要为每一个单板(Board)编写一个专属的C文件(如 arch/arm/mach-xxx/board-yyy.c),在里面用代码定义所有的硬件设备和资源。这种方式导致内核代码与特定硬件高度耦合,可移植性极差,匹配依赖的是 name (名字) 字符串。设备和驱动之间唯一的“握手暗号”就是一个纯粹的C语言字符串。
用实例说明 (描述一个UART设备):
1. "设备"的注册 (在 Device File 中)
在内核源码的 arch/arm/ 目录下,曾经有大量 mach-xxx(代表SoC系列,如 mach-s3c2410)和 plat-yyy 目录。在这些目录里,有针对每一块特定开发板的C文件,例如 board-smdk2410.c。
在这个 board-smdk2410.c 文件中,开发者会定义一个 platform_device 结构体数组,来描述这块板子上的所有设备。
一个简化的例子 (描述一个UART设备):
/* arch/arm/mach-s3c2410/board-smdk2410.c */
/* 1. 定义UART1的硬件资源 (物理地址、中断号) */
static struct resource s3c2410_uart1_resource[] = {
[0] = {
.start = 0x50004000, // UART1的寄存器基地址
.end = 0x50007FFF,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 15, // UART1的中断号
.end = 15,
.flags = IORESOURCE_IRQ,
},
};
/* 2. 定义UART1这个 "平台设备" */
static struct platform_device s3c2410_uart1_device = {
.name = "s3c2410-uart", // <-- 关键的匹配名字
.id = 1, // 设备ID (用于区分同名设备)
.resource = s3c2410_uart1_resource, // 挂接硬件资源
.num_resources = ARRAY_SIZE(s3c2410_uart1_resource),
};
/* 3. 在板子初始化时,把这个设备"注册"给内核 */
static void __init smdk2410_init(void)
{
// ... 其他初始化 ...
/* 向内核的"平台总线"注册这个UART1设备 */
platform_device_register(&s3c2410_uart1_device);
// ... 注册I2C设备、SPI设备等 ...
}
2. "驱动"的注册 (在 Driver File 中)
在内核的 drivers/ 目录下,对应的驱动程序(例如 drivers/tty/serial/s3c2410.c)会定义一个 platform_driver 结构体。
简化的驱动例子:
/* drivers/tty/serial/s3c2410.c */
static struct platform_driver s3c2410_uart_driver = {
.driver = {
.name = "s3c2410-uart", // <-- 关键的匹配名字
.owner = THIS_MODULE,
},
.probe = s3c2410_uart_probe, // 匹配成功时调用的函数
.remove = s3c2410_uart_remove,
};
/* 驱动加载时,向内核注册这个驱动 */
static int __init s3c2410_uart_init(void)
{
return platform_driver_register(&s3c2410_uart_driver);
}
匹配过程总结
-
内核启动。
-
驱动
s3c2410_uart_init被调用,它向平台总线(Platform Bus)注册了一个驱动,说:“我能处理名字叫s3c2410-uart的设备”。 -
内核继续启动,执行特定于板子的
smdk2410_init函数。 -
这个函数调用
platform_device_register,向平台总线注册了一个设备,说:“我这里有一个名字叫s3c2410-uart的设备”。 -
平台总线发现,“哎,刚注册的这个设备的名字,和我这里登记的一个驱动的名字完全一样!”
-
匹配成功。总线立刻调用该驱动的
.probe函数 (s3c2410_uart_probe),并将s3c2410_uart1_device结构体的指针(包括它携带的硬件资源)传递给probe函数。 -
驱动的
probe函数开始工作,ioremap内存地址,request_irq中断号,UART设备开始工作。
用设备树描述硬件的方式下设备驱动匹配过程
第一步:内核启动后,OF(Open Firmware)子系统会率先被初始化。
-
解析DTB:它会读取Bootloader传入内存中的设备树二进制文件(DTB)。
-
创建
platform_device:当它遍历到一个代表SoC片上外设的节点时(比如一个UART控制器),它会在内核中动态创建一个struct platform_device实例。这可以看作是硬件设备在内核中的“数字孪生”。 -
信息填充:这个
platform_device结构体会被填充从设备树节点中解析出的信息,例如寄存器地址(reg属性)、中断号(interrupts属性)等资源。 -
注册到总线:最后,内核调用
platform_device_register(),将这个代表具体硬件的platform_device注册到一条名为“平台总线”的虚拟总线上,静静地等待着能驾驭它的驱动出现。
设备树示例
i2c0: i2c@fe7d0000 {
compatible = "rockchip,rk3588-i2c"; // 关键的“身份标识”
reg = <0x0 0xfe7d0000 0x0 0x1000>;
interrupts = <GIC_SPI 133 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru PCLK_I2C0>, <&cru SCLK_I2C0>;
status = "disabled";
};
第二步:驱动“报到” - 驱动程序的注册
驱动程序可以被静态编译进内核,也可以作为模块(.ko)动态加载。
-
驱动初始化:当驱动被加载时,它的
module_init()函数会被调用。 -
定义
platform_driver:在驱动代码中,开发者会定义一个struct platform_driver实例,它代表了整个驱动的功能集合。 -
提供“认领清单”:在这个结构体中,一个名为
.driver.of_match_table的成员是关键。它指向一个of_device_id数组,这个数组里清晰地列出了本驱动支持的所有设备的compatible字符串。 -
注册到总线:驱动调用
platform_driver_register(),将自己注册到平台总线上,表明“我已经准备就绪,可以开始工作了”。
驱动代码示例 (.c)
// 定义本驱动能处理的设备列表(认领清单)
static const struct of_device_id rockchip_i2c_of_match[] = {
{ .compatible = "rockchip,rk3588-i2c" }, // 声明可以驱动这个设备
{ .compatible = "rockchip,rk3399-i2c" },
{ /* 数组结束符 */ }
};
static struct platform_driver rockchip_i2c_driver = {
.probe = rockchip_i2c_probe, // 匹配成功后调用的函数
.remove = rockchip_i2c_remove,
.driver = {
.name = "i2c-rockchip",
.of_match_table = rockchip_i2c_of_match, // 关联“认领清单”
},
};
// 通过 module_init 宏在加载时注册这个 platform_driver
module_init(platform_driver_register(&rockchip_i2c_driver));
第三步:内核“牵线” - 匹配逻辑核心
当一个新的驱动被注册到平台总线时,总线会自动为它触发一次匹配扫描。
-
遍历设备:平台总线会遍历总线上所有尚未绑定驱动的
platform_device。 -
比较
compatible:对于每一个设备,总线会取出其compatible属性字符串,然后与新注册驱动的of_match_table列表中的每一项进行对比。 -
匹配成功:一旦发现完全相同的字符串,匹配即告成功!内核的驱动核心会将设备与驱动进行“绑定”,在彼此的结构体中记录对方的指针。
第四步:大功告成 - 调用 probe 函数
绑定成功是结果,但驱动的真正工作才刚刚开始。
-
调用
probe:匹配成功后,内核会立刻调用platform_driver中定义的.probe函数。 -
传递设备信息:调用时,会将匹配上的
platform_device指针作为参数传给probe函数。 -
硬件初始化:
probe函数通过这个参数,就能获取到设备的所有硬件资源(寄存器地址、中断号等),然后执行内存映射(ioremap)、请求中断(request_irq)等一系列硬件初始化操作。
当 probe 函数成功返回后,一个设备就真正地“活”了起来,准备好为上层提供服务。
匹配过程总结
整个流程清晰而高效,形成了一个完美的闭环:
设备树通过
compatible属性提供硬件的“身份ID” -> 内核在启动过程中会解析并为之创建platform_device-> 驱动通过of_match_table声明自己能处理的“身份ID” -> 平台总线对比两者ID,若匹配成功 -> 调用驱动的probe函数完成最终的初始化。
399

被折叠的 条评论
为什么被折叠?



