总线(bus)是linux发展过程当中抽象出来的一种设备模型,为了统一管理全部的设备,内核中每一个设备都会被挂载在总线上,这个bus能够是对应硬件的bus(i2c bus、spi bus)、能够是虚拟bus(platform bus)。html
bus将全部挂在上面的具体设备抽象成两部分,driver和device。node
driver实现了同类型设备的驱动程序实现,而device则向系统注册具体的设备须要的资源,每当添加一个新的driver(device)到bus中时,都将调用bus的match函数,试图寻找匹配的device(driver)。linux
总线大概是这样的:框架
若是匹配成功,就调用probe函数,在probe函数中实现设备的初始化、各类配置以及生成用户空间的文件接口。函数
举个例子,针对AT24CXX(一种经常使用的存储设备)这种同系列产品,他们的操做方式都是很是类似的,不一样的无非是容量大小。post
那么咱们就没有必要为AT24C0一、AT24C02去分别写一份驱动程序,而是统一为其写一份兼容全部AT24CXX的驱动程序,而后再传入不一样的参数以对应具体的型号。debug
在linux驱动管理模型中的体现就是:驱动程序对应driver、须要的具体型号的硬件资源对应device,将其挂在bus上。rest
将driver注册到bus上,当用户须要使用AT24C01时,以AT24C01的参数构建一个对应device,注册到bus中,bus的match函数匹配上以后,调用probe函数,便可完成AT24C01的初始化,完成在用户空间的文件接口注册。code
以此类推,添加全部的AT24CXX设备均可以以这样的形式实现,只须要构建一份device,而不用为每一个设备重写一份驱动,提升了复用性,节省了内存空间。orm
linux将设备挂在总线上,对应设备的注册和匹配流程由总线进行管理,在linux内核中,每个bus,都由struct bus_type来描述:
struct bus_type { const char *name; const char *dev_name; struct device *dev_root; ... int (*match)(struct device *dev, struct device_driver *drv); int (*uevent)(struct device *dev, struct kobj_uevent_env *env); int (*probe)(struct device *dev); int (*remove)(struct device *dev); void (*shutdown)(struct device *dev); struct subsys_private *p; ...
};
为了突出重点,省去了一些暂时不须要深刻了解的成员,咱们来看看其中最主要的几个成员:
name : 该bus的名字,这个名字是这个bus在sysfs文件系统中的体现,对应/sys/bus/$name.
dev_name : 这个dev_name并不对应bus的名称,而是对应bus所包含的struct device的名字,即对应dev_root。
dev_root:bus对应的device结构,每一个设备都须要对应一个相应的struct device.
match:bus的device链表和driver链表进行匹配的实际执行回调函数,每当有device或者driver添加到bus中时,调用match函数,为device(driver)寻找匹配的driver(device)。
uevent:bus时间回调函数,当属于这个bus的设备发生添加、删除、修改等行为时,都将出发uvent事件。
probe:当device和driver经由match匹配成功时,将会调用总线的probe函数实现具体driver的初始化。事实上每一个driver也会提供相应的probe函数,先调用总线的probe函数,在总线probe函数中调用driver的probe函数。
remove:移除挂载在设备上的driver,bus上的driver部分也会提供remove函数,在执行移除时,先调用driver的remove,而后再调用bus的remove以清除资源。
struct subsys_private *p:见下文
struct subsys_private { struct kset subsys; struct kset *devices_kset; struct kset *drivers_kset; struct klist klist_devices; struct klist klist_drivers; ... };
其中struct kset subsys、struct kset devices_kset、struct kset drivers_kset都是在sysfs文件系统中建立对应的目录。
struct klist klist_devices、struct klist klist_drivers是两个主要的数据部分,klist_devices是存储全部注册到bus的device的链表,而klist_drivers是存储全部注册到bus的driver的链表。
了解了bus的结构,那么,bus是怎么注册的呢?
spi bus的注册过程在KERNEL/drivers/spi/spi.c中:
static int __init spi_init(void) { ... status = bus_register(&spi_bus_type); ... return 0; } postcore_initcall(spi_init);
i2c bus的注册过程在KERNEL/drivers/i2c/i2c-core-base.c中:
static int __init i2c_init(void) { ... bus_register(&i2c_bus_type); ... } postcore_initcall(i2c_init);
而platform bus的注册过程在KERNEL/drivers/base/platform.c中:
int __init platform_bus_init(void) { ... bus_register(&platform_bus_type); ... }
i2c和spi为物理总线,这两种总线经过postcore_initcall()将各自的init函数注册到系统中,postcore_initcall的详解能够参考另外一篇博客:linux init机制。
而platform做为虚拟总线,platform_bus_init被系统初始化时直接调用,调用流程为:
start_kernel -> rest_init(); -> kernel_thread(kernel_init, NULL, CLONE_FS); -> kernel_init() -> kernel_init_freeable(); -> do_basic_setup(); -> driver_init(); ->platform_bus_init();
spi_bus_type、i2c_bus_type、platform_bus_type分别为对应的struct bus_type描述结构体。
对应的spi_bus_type、i2c_bus_type、platform_bus_type实现我将分别在spi、i2c、platform具体框架解析中介绍。
能够看到,这三种总线都是由bus_register()接口注册的,那么这个接口到底作了什么呢?
int bus_register(struct bus_type *bus) { int retval; struct subsys_private *priv; struct lock_class_key *key = &bus->lock_key; priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); if (!priv) return -ENOMEM; priv->bus = bus; bus->p = priv; BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier); retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); if (retval) goto out; priv->subsys.kobj.kset = bus_kset; priv->subsys.kobj.ktype = &bus_ktype; priv->drivers_autoprobe = 1; retval = kset_register(&priv->subsys); if (retval) goto out; retval = bus_create_file(bus, &bus_attr_uevent); if (retval) goto bus_uevent_fail; priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj); if (!priv->devices_kset) { retval = -ENOMEM; goto bus_devices_fail; } priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj); if (!priv->drivers_kset) { retval = -ENOMEM; goto bus_drivers_fail; } INIT_LIST_HEAD(&priv->interfaces); __mutex_init(&priv->mutex, "subsys mutex", key); klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); retval = add_probe_files(bus); if (retval) goto bus_probe_files_fail; retval = bus_add_groups(bus, bus->bus_groups); if (retval) goto bus_groups_fail; pr_debug("bus: '%s': registered\n", bus->name); return 0; }
在上面贴出的代码中,能够看出,bus_register()其实也没作什么特别的事,主要是两个:
既然bus_register只是初始化了相应的资源,在/sys下导出接口文件,那整个bus是如何工做的呢?
以i2c为例,咱们来看看这整个过程:
首先使用i2c_new_device接口来添加一个device:
struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { struct i2c_client *client; client = kzalloc(sizeof *client, GFP_KERNEL); client->adapter = adap; client->dev.platform_data = info->platform_data; if (info->archdata) client->dev.archdata = *info->archdata; client->flags = info->flags; client->addr = info->addr; client->irq = info->irq; client->dev.parent = &client->adapter->dev; client->dev.bus = &i2c_bus_type; client->dev.type = &i2c_client_type; client->dev.of_node = info->of_node; client->dev.fwnode = info->fwnode; ... device_register(&client->dev); ... }
申请一个i2c_client并对其赋值,而后以这个为参数调用device_register(&client->dev),将dangqiandevice添加到bus中。
int device_register(struct device *dev) { device_initialize(dev); return device_add(dev); } int device_add(struct device *dev) { ... bus_add_device(dev); ... } int bus_add_device(struct device *dev){ ... klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); ... bus_probe_device(dev); ... }
在device_register中,调用了device_add,紧接着调用bus_add_device,根据函数名称能够看出是将这个device添加到对应的bus中。
果真,根据bus_add_device()的源代码,能够看到,将当前device连接到其对应bus的devices链表,而后在下面调用bus_probe_device();这个函数的做用就是轮询对应bus的drivers连接,查看新添加的device是否存在匹配的driver。
对应的,i2c_driver_register()将i2c driver部分添加到bus中,再轮询检查bus的devices链表是否有对应的device能匹配上,有兴趣的能够从i2c_driver_register()开始研究源代码。
上文中提到当bus中有新的device和driver添加时,会调用bus的match函数进行匹配,那么究竟是怎么匹配的呢?
简单来讲,在静态定义的device中,通常会有.name属性,与driver的.id_table属性相匹配。
device部分还有可能从设备树转换而来,就有设备树中相应的.compatible属性和driver的of_match_table.compatible属性相匹配。
事实上对于匹配这一部分,能够直接参考每一个bus的match函数实现。
这一章节只是对linux中的总线作一个概念性的说明,在以后的博客中会详细介绍到相应bus的框架,同时也会详解对应的match()函数实现。
敬请期待!
好了,关于linux的bus讨论就到此为止啦,若是朋友们对于这个有什么疑问或者发现有文章中有什么错误,欢迎留言
原创博客,转载请注明出处!
祝各位早日实现项目丛中过,bug不沾身.