前言
device和device driver结构体介绍
在上一篇章中讲了关于驱动中的deivce和device driver结构体,它们代表着一个设备以及其对应的驱动程序。那么,如果有很多设备,那就得靠一种特殊的方式将它们管理起来------Bus。
在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者Platform Bus。一个bus下挂载这多个device和device_driver,彼此之间又相互匹配,这就实现的一种便捷的管理。
注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。
1. struct bus_type
/**
* struct bus_type - The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @dev_root: Default device to use as the parent.
* @dev_attrs: Default attributes of the devices on the bus.
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a positive value if the
* given device can be handled by the given driver and zero
* otherwise. It may also return error code if determining that
* the driver supports the device is not possible. In case of
* -EPROBE_DEFER it will queue the device for deferred probing.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver's probe to initial the matched device.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
*
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* @resume: Called to bring a device on this bus out of sleep mode.
* @pm: Power management operations of this bus, callback the specific
* device driver's pm-ops.
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* @p: The private data of the driver core, only the driver core can
* touch this.
* @lock_key: Lock class key for use by the lock validator
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
*/
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
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);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
struct bus_type
是 Linux 内核中用于描述设备总线(Bus)的基本结构体。在 Linux 设备模型中,总线表示处理器与设备之间的连接通道,所有设备都通过某种总线与处理器相连(即使是内部的、虚拟的“平台”总线)。总线负责管理设备与驱动程序之间的匹配、驱动程序绑定、设备的挂起与恢复等操作。该结构体包含总线的名称、默认属性以及总线操作的回调函数。
主要成员变量
const char *name
- 总线的名称,用于标识总线类型,如
pci
、usb
、platform
等。会在sysfs中以目录的形式存在,如platform bus在sysfs中表现为"/sys/bus/platform”。
- 总线的名称,用于标识总线类型,如
const char *dev_name
- 用于子系统为设备枚举命名时使用的格式字符串,例如
foo%u
(foo
是前缀,%u
是设备 ID)。它是为设备生成默认名称的模板。该名称和struct device结构中的init_name有关。对有些设备而言(例如批量化的USB设备),设计者根本就懒得为它起名字的,而内核也支持这种懒惰,允许将设备的名字留空。这样当设备注册到内核后,设备模型的核心逻辑就会用"bus->dev_name+device ID”的形式,为这样的设备生成一个名称。
- 用于子系统为设备枚举命名时使用的格式字符串,例如
struct device *dev_root
- 作为默认父设备的根设备,所有在此总线上创建的设备都会以
dev_root
作为其父设备。如果总线有层次结构,这个字段将定义该层次的根节点。
- 作为默认父设备的根设备,所有在此总线上创建的设备都会以
struct device_attribute *dev_attrs
- 总线设备的默认属性。这些属性可以通过
/sys
文件系统导出给用户空间,允许用户查看和修改设备的某些状态或行为(该成员建议使用dev_groups
替代)。
- 总线设备的默认属性。这些属性可以通过
const struct attribute_group **bus_groups
- 总线的默认属性组,允许总线导出一组属性到
/sys
文件系统。
- 总线的默认属性组,允许总线导出一组属性到
const struct attribute_group **dev_groups
- 总线上设备的默认属性组,允许设备导出一组属性到
/sys
文件系统。
- 总线上设备的默认属性组,允许设备导出一组属性到
const struct attribute_group **drv_groups
- 总线驱动程序的默认属性组,允许驱动程序导出一组属性到
/sys
文件系统。
- 总线驱动程序的默认属性组,允许驱动程序导出一组属性到
核心操作函数
int (*match)(struct device *dev, struct device_driver *drv)
- 当一个新设备或驱动程序添加到总线时调用,用于匹配设备与驱动程序。返回值为正数表示匹配成功,
0
表示不匹配,负值表示错误。例如,在 PCI 总线上,match
函数可以检查设备和驱动的 PCI ID 是否匹配。
- 当一个新设备或驱动程序添加到总线时调用,用于匹配设备与驱动程序。返回值为正数表示匹配成功,
int (*uevent)(struct device *dev, struct kobj_uevent_env *env)
- 当设备添加、移除或发生其他重要事件时调用,用于生成用户空间事件(uevent)。该函数可以向环境变量中添加信息,以便 udev 或其他用户空间程序对设备进行相应操作。
int (*probe)(struct device *dev)
- 当一个设备与驱动程序匹配成功后调用,用于初始化设备。
probe
函数是驱动与设备绑定的核心,它为设备分配资源,并使设备能够正常工作。和device_driver中的非常类似,如果device_driver需要probe,该device所在的bus的probe肯定需要先执行初始化过才行。 - 并不是所有的bus都需要probe和remove接口的,因为对有些bus来说(例如platform bus),它本身就是一个虚拟的总线,无所谓初始化,直接就能使用,因此这些bus的driver就可以将这两个回调函数留空。
- 当一个设备与驱动程序匹配成功后调用,用于初始化设备。
int (*remove)(struct device *dev)
- 当设备从系统中移除时调用,负责释放设备资源并解除与驱动程序的绑定。
void (*shutdown)(struct device *dev)
- 系统关机或重启时调用,用于让设备进入静默或安全状态。通常用于保存设备状态并关闭设备,确保数据不丢失。
int (*online)(struct device *dev)
- 将设备从离线状态恢复到在线状态时调用,通常在设备热插拔场景中使用。
int (*offline)(struct device *dev)
- 将设备从在线状态切换到离线状态时调用,允许设备在热插拔时进行安全移除。
电源管理操作
int (*suspend)(struct device *dev, pm_message_t state)
- 当设备需要进入睡眠模式时调用,将设备挂起并进入低功耗状态。
state
参数决定设备进入的具体电源状态(例如待机或休眠)。
- 当设备需要进入睡眠模式时调用,将设备挂起并进入低功耗状态。
int (*resume)(struct device *dev)
- 将设备从睡眠模式恢复到正常运行状态时调用,通常与
suspend
函数配对使用。
- 将设备从睡眠模式恢复到正常运行状态时调用,通常与
const struct dev_pm_ops *pm
- 电源管理操作的指针,允许总线定义设备的电源管理操作,如挂起、恢复、系统睡眠等。总线上的设备可以通过该结构体中的操作与系统电源管理进行交互。
IOMMU(输入输出内存管理单元)操作
const struct iommu_ops *iommu_ops
- IOMMU 的总线操作,用于在总线上设置 IOMMU 驱动。IOMMU 是一种硬件功能,用于管理设备的 DMA 地址空间映射。
私有数据与锁
struct subsys_private *p
- 驱动核心的私有数据字段。这个字段只由驱动核心使用,通常不需要开发者直接操作。下面会提到
struct lock_class_key lock_key
- 锁验证器使用的锁类键,用于对总线上的锁进行分类和管理。
struct bus_type
是 Linux 内核设备模型中的核心结构体之一,定义了处理器与设备之间的通信通道。它包括总线的属性、设备与驱动程序之间的匹配规则、设备事件处理(如 probe
、remove
、shutdown
等)、电源管理操作和 IOMMU 设置等。通过该结构体,总线可以实现对设备和驱动程序的管理、匹配和控制。
2. struct subsys_private
/**
* struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
* @subsys - the struct kset that defines this subsystem
* @devices_kset - the subsystem's 'devices' directory
* @interfaces - list of subsystem interfaces associated
* @mutex - protect the devices, and interfaces lists.
*
* @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
* on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
* with.
*
* @glue_dirs - "glue" directory to put in-between the parent device to
* avoid namespace conflicts
* @class - pointer back to the struct class that this structure is associated
* with.
*
* This structure is the one that is the actual kobject allowing struct
* bus_type/class to be statically allocated safely. Nothing outside of the
* driver core should ever touch these fields.
*/
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
是 Linux 内核中用于保存 bus_type
和 class
结构体私有数据的结构体,它是设备模型核心框架中实现总线或类(class)的实际数据结构。它管理着设备和驱动程序的相关列表和操作,确保设备和驱动的注册、通知等过程能够顺利进行。
主要成员变量:
struct kset subsys
- 这个字段定义了该子系统的
kset
。kset
是内核对象(kobject
)的集合,用于表示系统中一组相关的对象。subsys
在设备模型中代表一个总线或类的集合。 - 它是将
bus_type
和class
结构体转化为 kobject 的基础,允许内核使用标准的kobject
操作与其交互。
- 这个字段定义了该子系统的
struct kset *devices_kset
- 这是一个
kset
,表示子系统下的devices
目录。该目录包含了该总线或类下的所有设备。当设备注册时,它会被添加到这个devices_kset
中。
- 这是一个
struct list_head interfaces
- 用于存储与该子系统关联的接口(subsystem interfaces)的链表。子系统接口提供了一种在子系统上操作设备的抽象方法。
interfaces
列表会包含所有与该总线或类相关的接口。
- 用于存储与该子系统关联的接口(subsystem interfaces)的链表。子系统接口提供了一种在子系统上操作设备的抽象方法。
struct mutex mutex
- 这是一个互斥锁,用于保护
devices
和interfaces
列表。在访问或修改这些列表时,确保不会出现并发问题。
- 这是一个互斥锁,用于保护
驱动程序相关字段:
struct kset *drivers_kset
- 这是总线上所有驱动程序的
kset
。它管理总线或类下所有驱动程序的集合。当一个驱动注册到总线时,它会被添加到drivers_kset
中。保存了本bus下所有的device_driver
- 这是总线上所有驱动程序的
struct klist klist_devices
- 用于迭代
devices_kset
的klist
。klist
是一个内核链表,支持在链表中高效地插入、删除和遍历设备。保存了本bus下所有的device
- 用于迭代
struct klist klist_drivers
- 用于迭代
drivers_kset
的klist
,同样是一个用于驱动程序集合的链表,支持高效的遍历操作。用于控制该bus下的drivers或者device是否自动probe
- 用于迭代
事件通知相关字段:
struct blocking_notifier_head bus_notifier
- 这是一个阻塞通知链表,允许其他内核模块监听总线上设备或驱动程序的事件(例如设备添加、移除)。总线通知器可以将这些事件广播给其他感兴趣的模块或子系统。
回指到 bus_type
和 class
结构体:
struct bus_type *bus
- 指向与该结构体关联的
bus_type
结构体,表示该私有结构体属于哪个总线。
- 指向与该结构体关联的
struct class *class
- 指向与该结构体关联的
class
结构体,表示该私有结构体属于哪个类。
- 指向与该结构体关联的
其他字段:
unsigned int drivers_autoprobe:1
- 这是一个标志位,用于指示是否启用了驱动程序的自动探测(autoprobe)。当设备添加到总线时,若启用了
autoprobe
,内核会自动为设备寻找合适的驱动并进行绑定。
- 这是一个标志位,用于指示是否启用了驱动程序的自动探测(autoprobe)。当设备添加到总线时,若启用了
struct kset glue_dirs
- 这是一个用于创建 “glue” 目录的
kset
,用于避免命名空间冲突。glue
目录是内核中的一种机制,用于在父设备与其子设备之间插入目录层,以避免名称冲突。
- 这是一个用于创建 “glue” 目录的
宏定义
to_subsys_private(obj)
- 这个宏用于将
kobject
转换为struct subsys_private
结构体实例。它使用container_of()
来获取struct subsys_private
结构体的地址,subsys.kobj
是该结构体中kobject
的具体成员。
- 这个宏用于将
struct subsys_private
是 Linux 设备模型中的核心结构之一,用于管理 bus_type
或 class
的私有数据。它通过一系列链表、kset
和 klist
管理设备和驱动程序的注册、遍历和事件通知。同时,它确保通过互斥锁的方式保证并发安全。这个结构体的存在使得 bus_type
和 class
可以在设备模型中进行静态分配,并且被安全地管理。
3. 核心功能
bus
模块的核心功能如下:
1. 总线的注册和注销
bus_type
结构体管理了总线的生命周期,包括总线的注册(bus_register
)和注销(bus_unregister
)。这两个函数负责将总线类型插入内核设备模型中,并提供接口与设备和驱动程序交互。
2. 设备(device)和驱动程序(device_driver)的注册处理
- 当一个设备或驱动程序注册到总线时,
bus_type
结构体中的回调函数会被调用。具体来说,当设备或驱动程序注册时,bus->match()
函数用于判断某个设备是否与该驱动程序匹配。匹配成功后,驱动程序的probe()
函数被调用,初始化设备。
3. 设备和驱动程序的注销处理
- 设备或驱动程序从总线中注销时,
bus_type
也负责清理资源。remove()
回调函数处理设备的卸载流程,确保设备正确解绑驱动,并进行清理。
4. 驱动程序的探测(probe)处理
bus_type
的probe()
函数用于执行设备和驱动程序的自动匹配。设备注册时,总线会遍历其下所有的驱动程序,调用match()
判断设备和驱动是否匹配,匹配成功后调用probe()
进行设备的初始化和驱动绑定。
5. 管理总线下的设备和驱动程序
bus_type
通过subsys_private
中的devices_kset
和drivers_kset
维护设备和驱动的集合。总线模块通过klist
和kset
结构来高效管理、遍历和操作所有挂载到该总线下的设备和驱动程序。- 还可以通过
bus_notifier
实现设备和驱动相关事件的通知机制,通知其他内核模块设备或驱动的注册、注销等动态变化。
这些功能使得总线模块能够在设备模型中起到承上启下的作用,管理着设备和驱动之间的关系,并提供设备自动探测、驱动绑定等功能。
4. bus的注册逻辑
bus_register()
是 Linux 内核中用于注册总线类型的接口,其作用是将一个新的 bus_type
注册到内核中,使得设备模型能够识别该总线并管理挂载到该总线下的设备和驱动程序。该接口的原型是在include/linux/device.h中声明的,并在drivers/base/bus.c中实现,其原型如下:
\Linux-4.9.88\drivers\base\bus.c:
/**
* bus_register - register a driver-core subsystem
* @bus: bus to register
*
* Once we have that, we register the bus with the kobject
* infrastructure, then register the children subsystems it has:
* the devices and drivers that belong to the subsystem.
*/
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); //------(1)
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); //------(2)
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);//------(3)
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);//------(4)
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);//------(5)
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);//------(6)
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus);//------(7)
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;
}
其注册逻辑包括多个步骤,详细如下:
1. 分配并初始化 struct subsys_private
结构
bus_register()
首先为bus_type
结构中的struct subsys_private
类型的指针分配内存,确保为总线的私有数据(priv
)分配空间。这一步通过动态内存分配来确保该总线的私有数据可以正确存储。- 同时更新:
priv->bus
指向bus_type
本身,形成关联。bus->p
指向分配好的priv
,完成相互关联。
2. 初始化 subsys_private
中的 kobject
相关字段
- 接下来,初始化
priv->subsys.kobj
中的相关字段,包括:name
: 使用bus_type
中的name
字段,作为总线的名称(例如spi
)。kset
和ktype
: 这些字段由bus_kset
和bus_ktype
提供。kset
表示内核中的一个对象集合,ktype
定义了对象的类型。
3. 将 subsys_private
注册到内核
- 使用
kset_register()
将priv->subsys
注册到内核对象模型中。注册的同时,内核会在/sys/bus
目录下创建对应总线的目录。例如,如果总线名称为spi
,会创建/sys/bus/spi
目录。
4. 向总线目录下添加 uevent
属性
- 调用
bus_create_file()
,为该总线在/sys/bus/<bus_name>/
目录下添加uevent
属性文件(例如/sys/bus/spi/uevent
),用于处理总线相关的用户空间事件。
5. 创建并添加 devices
和 drivers
kset
- 使用
kset_create_and_add()
,分别为总线下的设备(devices
)和驱动程序(drivers
)创建并添加 kset。这会在/sys/bus/<bus_name>/
下创建两个子目录:/sys/bus/<bus_name>/devices
:存放该总线下注册的设备。/sys/bus/<bus_name>/drivers
:存放该总线下注册的驱动程序。
6. 初始化 priv
中的 mutex
、 klist_devices
和 klist_drivers
等字段
- 通过初始化
subsys_private
结构中的mutex
、klist_devices
和klist_drivers
,确保总线可以有效管理设备和驱动的注册和注销。klist
是一个可以安全并发操作的双向链表,mutex
用于保护该链表。
7. 添加 drivers_probe
和 drivers_autoprobe
属性
- 调用
add_probe_files()
,为总线添加drivers_probe
和drivers_autoprobe
两个属性文件,路径分别为:/sys/bus/<bus_name>/drivers_probe
:允许用户空间程序主动触发总线下设备驱动的probe()
操作。/sys/bus/<bus_name>/drivers_autoprobe
:用于控制是否在设备或驱动程序添加到内核时,自动执行probe()
操作。
8. 添加总线的默认属性
bus_add_groups()
用于将 bus_type 结构中的属性组 bus->bus_groups(bus_groups中有attribute **attrs) 添加到 sys/bus/<bus_name> 目录下。这个属性组可能包括特定于该总线的自定义属性,供用户空间访问或修改。
bus_register()
的功能是将一个新的总线类型注册到 Linux 内核中,使设备模型能够识别、管理该总线下的设备和驱动程序。在此过程中,它会创建必要的内核数据结构,确保总线、设备、驱动的管理链条完整无误,并通过 sysfs 向用户空间暴露接口,方便用户和应用程序与内核交互。
这个注册逻辑确保了:
- 内核设备模型能够处理该总线上的设备和驱动程序。
- 设备和驱动程序可以通过
probe()
和remove()
函数实现自动匹配和解绑。 - 在 sysfs 中为总线、设备、驱动提供了用户空间访问入口。