一、核心三角色定义
-
总线 bus_type
物理总线:I2C、SPI、PCI、CAN、Platform 平台总线;
内核抽象
struct bus_type,统一管理匹配规则、探测、卸载、sysfs 接口。作用:作为中介,负责设备和驱动匹配、管理挂载在这条总线上的所有设备/驱动。
-
设备 device(struct device)
描述一个真实硬件外设,保存硬件资源(地址、中断、GPIO、时钟、设备树compatible字符串)。
设备只描述「硬件有什么资源」,不含操作硬件的代码。
-
驱动 driver(struct device_driver / platform_driver)
包含操作硬件的代码(寄存器初始化、read/write、中断处理),提供
probe探测函数。驱动只描述「怎么操作硬件」,不绑定固定硬件。
二、核心协作逻辑:总线做媒人,设备、驱动自动配对
匹配核心规则
总线内部提供匹配回调函数,内核自动遍历:
- 新注册设备 → 遍历当前总线上所有已注册驱动,挨个匹配;
- 新注册驱动 → 遍历当前总线上所有已注册设备,挨个匹配;
匹配成功 → 执行驱动.probe()函数,完成硬件初始化;
匹配失败 → 二者保持独立,无任何交互。
不同总线匹配规则不一样:
- Platform总线:匹配设备树
compatible字符串; - I2C/SPI:匹配设备ID表;
- PCI:匹配厂商ID、设备ID。
三、完整工作流程分两种场景
场景1:先有设备,后加载驱动(设备树常用,RK3568主流)
- 内核解析设备树,在Platform总线下创建
struct device设备实例;
存入:总线维护的设备链表,此时只有设备,无驱动。 - 加载驱动
.ko,驱动调用platform_driver_register()向Platform总线注册platform_driver; - 总线触发匹配流程:拿驱动的id_table/compatible和总线上所有设备对比;
- 找到compatible完全一致的设备 → 匹配成功;
- 总线调用驱动的
.probe()函数:- probe内从device中提取硬件资源(寄存器基地址、中断、PWM引脚);
- 初始化时钟、GPIO、寄存器;
- 内部创建cdev、class、device_create,生成/dev/xxx字符设备节点;
- 用户层open /dev/xxx,调用file_operations操作硬件。
场景2:先注册驱动,后出现设备(热插拔硬件,USB/I2C)
- 开机先加载驱动,驱动注册到对应总线,挂在驱动链表;
- 硬件热插入总线,内核检测到硬件,生成struct device挂入总线设备链表;
- 总线自动拿新设备和所有驱动匹配;
- 匹配成功执行probe,初始化硬件。
四、三者数据结构关联关系
-
struct bus_type- 维护两条双向链表:设备链表、驱动链表;
- 核心回调:
match()(匹配)、probe()、remove()、shutdown(); - sysfs目录:
/sys/bus/xxx/,存放devices、drivers子目录。
-
struct device(硬件)- 挂载在对应bus的设备链表;
- 成员
bus指向所属总线; - 成员
driver:匹配成功后,指向绑定的驱动; - 存放硬件资源:
struct resource寄存器、irq、dma、设备树节点。
-
struct device_driver(驱动代码)- 挂载在对应bus的驱动链表;
- 成员
bus指向所属总线; - 内置匹配表(compatible字符串/ID列表);
- 提供
.probe初始化硬件,.remove卸载释放资源。
绑定关系一句话
device.bus = 所属总线
driver.bus = 所属总线
匹配成功后 device.driver = 对应驱动
五、以你之前的PWM字符驱动举完整实例(Platform总线)
- 设备(设备树)
dts
pwm_demo@10000000 {
compatible = "rockchip,pwm-demo";
reg = <0x10000000 0x1000>;
gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
};
内核启动自动生成platform device,挂载到platform总线。
- 驱动(platform_driver)
c
static const struct of_device_id pwm_of_match[] = {
{ .compatible = "rockchip,pwm-demo" },
{}
};
static struct platform_driver pwm_plat_drv = {
.probe = pwm_probe,
.remove = pwm_remove,
.driver = {
.name = "pwm_drv",
.of_match_table = pwm_of_match,
},
};
module_platform_driver(pwm_plat_drv);
- 加载驱动,注册到platform总线;
- 总线匹配compatible字符串,匹配成功调用
pwm_probe; - probe函数内部:
解析reg获取寄存器、解析gpio、使能时钟;
执行字符设备整套流程:alloc_chrdev_region、cdev_init、cdev_add、class_create、device_create; - 用户操作/dev/pwm_subsystem控制硬件;
- rmmod驱动,总线调用
.remove,逆序释放硬件、销毁字符设备。
六、如果自己自定义一套全新总线(拓展你提问的"创建自己的总线")
1. 自定义总线核心结构
定义 struct bus_type my_bus,实现match/probe/remove;
内核调用 bus_register(&my_bus) 注册全新总线。
2. 自定义设备、自定义驱动
- 自定义设备结构体内嵌
struct device dev; - 自定义驱动内嵌
struct device_driver drv; - 设备注册进自定义总线,驱动也注册到同一总线;
- 总线match函数实现自定义匹配规则(比如自定义硬件ID)。
3. 适用场景
极少需要自定义总线;现有Platform/I2C/SPI/PCI总线已经覆盖绝大多数外设,仅自研私有硬件协议才会新建总线。
七、总线模型核心优势(为什么Linux要这套架构)
- 设备、驱动完全解耦
驱动不写死寄存器地址,硬件信息全部放设备树(设备侧);更换硬件只需改设备树,驱动代码不用修改。 - 一份驱动适配多个同类型硬件
只要compatible匹配,同一个驱动可以驱动多个同型号外设。 - 自动管理生命周期
热插拔、卸载、电源管理由总线统一调度,不用驱动单独处理。 - sysfs统一可视化管理
/sys/bus/下可直观查看总线、挂载的设备、绑定的驱动,方便调试。
八、极简总结协作流程
- 总线作为统一管理容器,分别维护设备链表、驱动链表;
- 新增设备/驱动时,总线执行match匹配规则;
- 匹配成功,总线调用驱动probe完成硬件初始化;
- probe中创建字符设备等交互接口,供用户层使用;
- 卸载时总线调用remove,释放硬件与内核资源;
设备和驱动永远不直接互相查找,全部通过总线中转配对。