瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第九期_设备模型_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第101章 总线注册流程理论分析实验
在上个章节中,我们成功地在总线目录下创建了属性文件,为总线添加了额外的信息和控制选项。现在,在本章节中,我们将深入分析总线注册的流程,从代码的层面来理解总线的实现过程。通过这样的分析,我们将更好地理解总线注册的内部机制,并能够应用这些知识来实现更复杂的总线功能。让我们深入代码,一起学习总线注册的流程吧!
101.1 bus_register函数解析
开发板上电,我们进入到开发板的/sys/bus/mybus目录下。
图101-1
如上图所示,为什么在sys/bus目录下会生成mybus目录以及对应的devices,drivers,drivers_autoprobe,drivers_probe,uevent目录和属性呢?
在开发板/sys目录下的目录都对应一个kobject,所以我们猜测mybus目录和devices,drivers目录和kobject有关系的。而kobject一般要嵌入到其他结构体中去使用。如下图所示,kobject嵌入到device结构体中。
图101-2
在struct device结构体中包含了kobject结构体,而struct bus_type结构体又包含了struct device结构体。如下图所示: 图101-3
所以我们猜测这些/sys/bus/下的目录是和struct device结构体中的kobj有关系。接下来我们追踪bus_register函数,函数原型如下所示:
/*
bus_register - 注册一个驱动核心子系统
@bus: 要注册的总线
注册总线时,首先将总线与 kobject 基础设施关联起来,然后注册属于该总线的子系统:
归属于该子系统的设备和驱动程序。
*/
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;
// 分配并初始化一个 subsys_private 结构体,用于保存子系统的相关信息
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
// 初始化一个阻塞通知链表 bus_notifier
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
// 设置子系统的名称
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
// 设置子系统的 kset 和 ktype
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
// 注册子系统的 kset
retval = kset_register(&priv->subsys);
if (retval)
goto out;
// 在总线上创建一个属性文件
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;
// 创建并添加 "devices" 子目录的 kset
priv->devices_kset = kset_create_and_add("devices", NULL, &priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
// 创建并添加 "drivers" 子目录的 kset
priv->drivers_kset = kset_create_and_add("drivers", NULL, &priv->subsys.kobj);
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
// 初始化接口链表、互斥锁和设备/驱动的 klist
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_groups_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
EXPORT_SYMBOL_GPL(bus_register);
*/
以下是该函数的主要步骤:
1 分配并初始化一个 subsys_private 结构体,该结构体用于保存子系统的相关信息。
2 将 bus 结构体与 subsys_private 关联起来。
3 初始化一个阻塞通知链表 bus_notifier。
4 通过 kobject_set_name() 设置子系统的名称。
5 设置子系统的相关属性,如 kset 和 ktype。
6 注册子系统的 kset。
7 创建并添加 "devices" 和 "drivers" 两个子目录的 kset。
8 初始化子系统的接口链表、互斥锁和设备/驱动的 klist。
9 添加驱动探测文件。
10 添加总线的属性组。
11 打印调试信息,表示总线注册成功。
接下来我们对上述步骤进行补充讲解。
在步骤1 中,出现了新的结构体:struct subsys_private,如下所示:
struct subsys_private {
struct kset subsys;
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
struct subsys_private 是一个结构体,用于保存驱动核心子系统(bus)的私有信息。每个子系统都可以有私有数据,这些私有数据存储在struct subsys_private结构体中。那么什么是子系统呢?
在Linux中,子系统是一种机制,用于将特定功能的实现抽象为一个独立的实体。它提供了一种方便的方式,将相关的代码和数据结构组织在一起,以实现特定的功能。子系统可以被视为一个功能模块,它封装了相关的功能和操作,使得用户和应用程序可以通过统一的接口与其交互。
在Linux中,存在许多常见的子系统,每个子系统都负责实现特定的功能。以下是一些常见的子系统示例。
- 虚拟文件系统(VFS)子系统:VFS子系统提供了对不同文件系统的统一访问接口,使得应用程序可以透明地访问各种文件系统(如ext4、NTFS、FAT等),而无需关心底层文件系统的具体实现。
- 设备驱动子系统:设备驱动子系统管理和控制硬件设备的驱动程序。它提供了与硬件设备交互的接口,使得应用程序可以通过驱动程序与设备进行通信和控制。
- 网络子系统:网络子系统负责管理和控制网络相关的功能。它包括网络协议栈、套接字接口、网络设备驱动程序等,用于实现网络通信和网络协议的处理。
- 内存管理子系统:内存管理子系统负责管理系统的物理内存和虚拟内存。它包括内存分配、页面置换、内存映射等功能,用于有效地分配和管理系统的内存资源。
- 进程管理子系统:进程管理子系统负责管理和控制系统中的进程。它包括进程的创建、调度、终止等功能,以及进程间通信的机制,如信号、管道、共享内存等。
- 电源管理子系统:电源管理子系统负责管理和控制系统的电源管理功能。它可以用于控制电源的开关、电源模式的切换、节能功能的实现等。
- 文件系统子系统:文件系统子系统负责管理和控制文件系统的创建、格式化、挂载、数据存取等操作。它支持各种文件系统类型,如ext4、FAT、NTFS等。
- 图形子系统:图形子系统负责管理和控制图形显示功能,包括显示驱动程序、窗口管理、图形渲染等。它提供了图形界面的支持,使得用户可以通过图形方式与计算机交互。
在步骤2中,priv->bus = bus;将priv结构体中的bus成员设置为当前注册的总线。这样做的目的是将bus成员与当前总线建立关联。通过将bus成员设置为当前总线,priv结构体可以获取并访问与该总线相关的信息和功能。这种关联可以使priv结构体在操作当前总线时更加方便和高效。
在步骤2 中,bus->p = priv;将priv结构体指针存储在当前注册的总线结构体的成员p中,目的是让当前注册的总线结构体能够快速地找到并访问与之关联的priv结构体。通过将priv结构体指针存储在总线结构体的成员中,总线可以轻松地获取与之相关的私有数据结构。这种关联使得总线能够直接访问和操作与特定总线相关的数据和功能,而无需通过其他方式来查找或传递指针。
在步骤8中,klist_init函数用于初始化两个内核链表(klist),分别是priv->klist_devices和priv->klist_drivers。
- klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);这行代码初始化了名为priv->klist_devices的内核链表。klist_devices_get和klist_devices_put是两个回调函数,用于在向链表添加或移除元素时执行相应的操作。通常,这些回调函数用于在链表中的每个元素被引用或释放时执行额外的操作。例如,当设备被添加到链表时,klist_devices_get函数可能会增加设备的引用计数;当设备从链表中移除时,klist_devices_put函数可能会减少设备的引用计数。
- klist_init(&priv->klist_drivers, NULL, NULL);这行代码初始化了名为priv->klist_drivers的内核链表,但与第一个初始化不同,这里没有提供回调函数。因此,这个链表在添加或移除元素时不会执行额外的操作。这种情况下,链表主要用于存储驱动程序对象,而不需要附加的处理逻辑。
通过分析bus_register函数,我们对设备模型有了更深层次的感悟,如下所示:
1.kobject和kset是设备模型的基本框架,它们可以嵌入到其他结构体中以提供设备模型的功能。kobject代表设备模型中的一个对象,而kset则是一组相关的kobject的集合。
2. 属性文件在设备模型中具有重要作用,它们用于在内核空间和用户空间之间进行数据交换。属性文件可以通过sysfs虚拟文件系统在用户空间中表示为文件,用户可以读取或写入这些文件来与设备模型进行交互。属性文件允许用户访问设备的状态、配置和控制信息,从而实现了设备模型的管理和配置。
3. sysfs虚拟文件系统在设备模型中扮演关键角色,它可以将设备模型的组织层次展现出来。通过sysfs,设备模型中的对象、属性和关系可以以目录和文件的形式在用户空间中表示。这种组织形式使用户能够以层次结构的方式浏览和管理设备模型,从而方便地获取设备的信息、配置和状态。sysfs提供了一种统一的接口,使用户能够通过文件系统操作来与设备模型进行交互,提供了设备模型的可视化和可操作性。