导读 | 对于内核总线来说,大家应该会对Platform总线比较熟悉,知道在Platform总线上会有两个链表,一条链表用来把该总线上的所有设备链接起来,一条链表用来把该总线上的驱动链接起来。 |
当设备或者驱动添加到链表时,会触发总线的match函数。那么,您有没有深入去研究过内核总线呢?在本文中,我们来深入探讨一下内核中的总线,主要涉及到以下问题:
1.内核中是如何部署总线的。
2.设备和驱动是如何挂载到总线上的。
3.设备和其对应的驱动是如何通过总线进行匹配的。
我们从函数start_kernel来分析总线的部署,实际上在函数start_kernel调用之前,会有汇编代码来处理启动参数,启动模式,创建内核空间页表,准备好堆栈等。由于这些同总线部署关系不大,暂且就认为start_kernel就是内核的main函数。start_kernel内部会调用rest_init,rest_init函数内部创建内核线程kernel_init,而kernel_init中有如下的函数调用过程:
kernel_init-->do_basic_setup->driver_init—>buses_init和platform_bus_init
此处的buses_init和platform_bus_init就是总线的部署函数,也是本小节的重点,且buses_init必须在platform_bus_init前面调用。因为Platform总线是挂载在bus总线下的,接下来我们详细分析下这两个过程。
内核中所有的对象如bus,都是一个kobject,而把相同类型的kobject集合到一起就组成了一个kset,而函数buses_init内部就是通过bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)来注册bus总线对应的kset,其最后bus_kset如下图1所示:
图 1 bus_kset结构
至此算是准备好了bus_kset,我们继续往下看一下其他类型的总线是如何和bus进行关联的。
该函数主要完成两个功能,其函数如下:
struct bus_type platform_bus_type = { .name = "platform", .dev_attrs = platform_dev_attrs, .match = platform_match, .uevent = platform_uevent, .pm = &platform_dev_pm_ops, }; EXPORT_SYMBOL_GPL(platform_bus_type); int __init platform_bus_init(void) { int error; early_platform_cleanup(); error = device_register(&platform_bus); if (error) return error; error = bus_register(&platform_bus_type); if (error) device_unregister(&platform_bus); return error; }
device_register是用来注册一个device,并添加到系统中,最后会在/sys/devices/目录下建立 platform目录对应的设备对象,其路径是/sys/devices/platform/。
bus_register是将Platform bus总线注册进系统,其实内部就是创建了对应的kset和kobject等,且主要完成以下三项工作:
初始化必须的结构体,struct subsys_private 和对应的kobkect。
同bus总线建立关系,kobject.parent 设置为上一步已经创建好的bus_kset.kobj, kobject.kset设置为bus_kset,把对应的kobject.ktype设置为bus_ktype。
把对应的kobjet添加到对应的kset的链表中,对总线来说,就是添加到bus_kset中的链表中。
下面是bus_register函数的实现(删除了创建失败退出时free内存等的操作),且我在代码中增加了注释,方便大家查阅:
/** * bus_register - register a bus with the system. * @bus: bus. * * Once we have that, we registered the bus with the kobject * infrastructure, then register the children subsystems it has: * the devices and drivers that belong to the bus. */ int bus_register(struct bus_type *bus) { int retval; //step:创建并分配,初始化struct subsys_private结构体指针 struct subsys_private *priv; 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; //step2:同上面创建的bus_kset进行关联 //kset_register时,会设置对应priv->subsys。Kobject->parent = bus_kset.kobj priv->subsys.kobj.kset = bus_kset; priv->subsys.kobj.ktype = &bus_ktype; priv->drivers_autoprobe = 1; retval = kset_register(&priv->subsys); //一会添加到bus_kset链表中 if (retval) goto out; //step3:创建对应的属性文件 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; } //step4:初始化两个比较重要的链表,后面内容中会提到这两个链表 klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); //step5:添加探针文件 retval = add_probe_files(bus); if (retval) goto bus_probe_files_fail; retval = bus_add_attrs(bus); if (retval) goto bus_attrs_fail; pr_debug("bus: '%s': registered\n", bus->name); return 0; …… return retval; } EXPORT_SYMBOL_GPL(bus_register);
对于其他总线(如IIC等),也是通过bus_register进行注册的,比如bus_register(&i2c_bus_type)和bus_register(&mmc_bus_type)等,其原理同上面一样,在此就不挨个介绍了。
通过上面的分析,我们知道了bus总线,且其他总线挂载在bus总线下,等总线部署完成后,不同设备会挂载在对应的总线下面。对于SPI,IIC等设备,他们都可以挂载在对应的总线下同CPU进行数据交互。但在嵌入式系统中,有些设备是不属于这些常见的总线,因此引入了虚拟的Platform总线,本小节正是通过虚拟的Platform总线来说明总线部署的。
我们依然采用Platform总线来说明设备和驱动的挂载问题。
对于Platform总线来说,可以通过函数platform_device_register来挂载(有的地方称之为注册)设备,也可以通过设备树来挂载,在内核启动时,会进行设备树的解析,本文中不涉及设备树,主要介绍platform_device_register的方式。
该函数原型如下:
int platform_device_register(struct platform_device *pdev) { device_initialize(&pdev->dev); return platform_device_add(pdev); }
函数在执行的过程中,有如下调用关系:
platform_device_add---->设置struct platform_device中的总线类型及其他参数--->device_add--->bus_add_device---->klist_add_tail
这个调用过程省略了一些属性和节点等的处理,我关注的重点在函数klist_add_tail,该函数是把当前设备添加到platform_bus中的一个链表中,这个链表在Platform总线部署时就初始化完成了,其初始化函数就是函数bus_register中的step4,可以翻阅上一个小节来查看。
对于Platform总线来说,可以通过函数platform_driver_register来挂载(有的地方称之为注册)设备,其函数原型如下:
int platform_driver_register(struct platform_driver *drv) { drv->driver.bus = &platform_bus_type; if (drv->probe) drv->driver.probe = platform_drv_probe; if (drv->remove) drv->driver.remove = platform_drv_remove; if (drv->shutdown) drv->driver.shutdown = platform_drv_shutdown; return driver_register(&drv->driver); } EXPORT_SYMBOL_GPL(platform_driver_register);
函数在执行的过程中,有如下调用关系:
driver_register---> 设置对应的参数等---->driver_find---> bus_add_driver----> klist_add_tail
相对于设备挂载,多了一个函数driver_find的调用,该函数主要目的就是判断驱动是否已经挂载上了,其余处理方式同设备挂载相同。最为重要的依然是klist_add_tail,把该驱动添加到了platform_bus中的一个链表中。
其他类型的总线设备和驱动相同,也会存在两个链表,设备和驱动均挂载到相应的链表中。
从第2小节中,我们知道Platform总线下有两个链表,我采用下面的图来具体化这两个链表,图左边的设备链表,图中仅呈现3个设备,实际上会有很多,图右边为驱动链表。不管是左边的设备还是右边的驱动,均有name字段(通常情况下是compatible),这是个非常重要的字段,后面我们会用到它。
图 2Platform总线的两个链表
针对匹配问题,我依然采用Platform总线来阐述,我们已经知道在进行驱动挂载时,会调用函数bus_add_driver,该函数内核实际上还会调用一个函数driver_attach(针对设置drivers_autoprobe的情况),下面是函数driver_attach的调用情况:
driver_attach --->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach) ---> klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL)); while ((dev = next_device(&i)) && !error) error = fn(dev, data); --->driver_match_device ---> drv->bus->match ---> platform_match
从上面代码过程可以看出,当挂载驱动时,会遍历图2中左边的链表,最后调用Platform总线的match函数platform_match (match函数是在struct bus_type platform_bus_type中设置的,在总线部署时阶段调用platform_bus_init就设置好了)来进行设备和驱动的匹配。每个总线都会有自己的match函数,且match函数里面会通过多种方式匹配,如常见的compitable,name或者id_table,只要有一个能匹配上,则认为驱动和设备匹配成功。
本文主要采用Platform来说明了内核中总线部署,设备和驱动挂载,及设备和驱动的匹配问题,实际上其他总线也是采用相同的方式,在我的描述过程中,重点在于总线,忽略了一些sysfs节点,引用计数,kobject,kset等,但这些在内核架构中也是比较重要的环节,希望大家在了解总线架构后,也能有时间去深入查看内核总线的各个处理细节。
特别说明:不同的内核,可能使用到的函数,或者函数的实现同文章中介绍的存在出入,但其原理及架构相同,可以作为参考。
原文来自:
本文地址://q13zd.cn/kernel-bus-architecture.html编辑:J+1,审核员:逄增宝
Linux大全:
Linux系统大全: