大家好,我是王鸽,大家都知道Linux设备驱动非常重视软件的可重用和跨平台能力,如果没有总线设备驱动模型,驱动代码里写死寄存器地址、中断号,换一块芯片,驱动全改,内核也不知道有多少设备,谁能用得上,这个时候需要一个驱动适配多个设备,让驱动标准化不用修改,在设备和驱动之间添加一层总线,总线则负责匹配设备和驱动, 而驱动则以标准途径拿到板级信息, 这样子就引出Linux 内核中总线 - 设备 - 驱动(Bus-Device-Driver)模型的核心概念。主机驱动和设备驱动分隔开来,主机控制器驱动已经由半导体厂家编写好了,而设备驱动一般也由设备器件的厂家编写好了。一、模型核心定义(通俗理解)
Linux 的总线 - 设备 - 驱动模型是内核为了解耦设备和驱动设计的核心框架,各个层级负责的内容:总线(Bus)
设备和驱动的 “中介 / 匹配器”,比如 I2C、SPI、USB、PCI、platform(虚拟总线)等,负责管理挂在总线上的设备和驱动,并完成二者的匹配;
设备(Device)
硬件设备在内核中的抽象(比如一个温度传感器、一个 U 盘),包含设备的硬件信息(地址、中断号、名称等);
驱动(Driver)
操作硬件的代码集合,包含设备的初始化、读写、中断处理等逻辑,驱动不直接绑定设备,而是由总线负责匹配。
核心目标:让驱动和设备 “分离开发、动态匹配”—— 新增设备时无需修改驱动,新增驱动时无需修改设备代码,由总线自动完成匹配。二、核心结构体(关键基础)
Linux 内核通过以下核心结构体描述总线、设备、驱动,新手先掌握核心字段即可:1. 总线(bus_type)
#include<linux/device.h>struct bus_type { const char *name; // 总线名称(如"i2c"、"platform") // 设备和驱动的匹配函数(核心!总线通过这个函数判断驱动是否支持设备) int (*match)(struct device *dev, struct device_driver *drv); // 匹配成功后调用的probe函数(驱动的初始化入口) int (*probe)(struct device *dev); // 设备/驱动添加/移除时的回调 int (*add_device)(struct device *dev); int (*remove_device)(struct device *dev); // 其他字段(暂无需深入) struct subsys_private *p; struct lock_class_key lock_key; };
核心函数:match是总线的 “匹配规则”,比如按设备名称、兼容属性(compatible)匹配。总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。 match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。2. 设备(device)
struct device { struct kobject kobj; // 内核对象(用于sysfs) const char *init_name; // 设备名称 struct bus_type *bus; // 设备所属的总线 struct device_driver *driver; // 匹配成功的驱动 void *platform_data; // 设备私有数据(硬件参数) // 其他字段(地址、中断号等) dev_t devt; // 设备号 struct device_node *of_node; // 设备树节点(DTB)};
每个硬件设备都会被封装成device结构体,挂到对应的总线上。3. 驱动(device_driver)
struct device_driver { const char *name; // 驱动名称(用于匹配) struct bus_type *bus; // 驱动所属的总线 // 驱动的probe函数(匹配成功后执行,初始化硬件) int (*probe)(struct device *dev); // 驱动移除时的回调 int (*remove)(struct device *dev); // 匹配表(比如platform驱动的of_match_table) const struct of_device_id *of_match_table;};
驱动的核心逻辑在probe函数中实现(比如初始化寄存器、申请中断、创建字符设备等)。总线 - 设备 - 驱动模型,就是为了三件事:
驱动与硬件分离(驱动可复用)内核统一管理所有设备(结构不乱)自动匹配 + 热插拔(即插即用)
没有总线,就没有热插拔。
有了这个模型后,驱动可以无限复用,同一个驱动:可以接在不同的控制器,可以接在不同总线,可以用在不同芯片,可以对应多个设备。例如:一个 I2C 传感器驱动,可以在 NXP、STM、Rockchip 上跑,总线把 “平台差异” 全屏蔽了。三、核心工作流程(可视化)