CSDN链接:
Linux设备模型剖析系列一(基本概念、kobject、kset、kobj_type)
Linux设备模型剖析系列之二(uevent、sysfs)
Linux设备模型剖析系列之三(device和device driver)
Linux设备模型剖析系列之四(BUS)
Linux设备模型剖析系列之五(class)
Linux设备模型系列文章之六(设备资源管理)
Linux设备模型剖析系列文章之七(kobj、kset)
在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者platform Bus。
内核通过struct bus_type
结构抽象Bus,它是在include/linux/device.h中定义的。下文会围绕该结构,描述Linux内核中Bus的功能,以及相关的实现逻辑。最后,会简单的介绍一些标准的Bus(如platform),介绍它们的用途、它们的使用场景。
按照老传统,描述功能前,先介绍一下该模块的一些核心数据结构,对bus模块而言,核心数据结构就是struct bus_type
,另外,还有一个subsystem相关的结构,会一并说明。
/* inlcude/linux/device.h, line 93 */
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct bus_attribute *bus_attrs;
struct device_attribute *dev_attrs;
struct driver_attribute *drv_attrs;
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 (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
该结构和device_driver中的struct driver_private类似。先看定义:
/* drivers/base/base.h, line 28 */
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;
};
/sys/bus/spi/devices
和/sys/bus/spi/drivers
),分别包括本bus下所有的device和device_driver。根据上面的核心数据结构,可以总结出bus模块的功能包括:
bus的注册是由bus_register接口实现的,该接口的原型是在include/linux/device.h中声明的,并在drivers/base/bus.c中实现,bus_register:工作就是完成bus_type_private的初始化,创建注册的这条总线需要的目录文件(在这条总线目录下创建/device/driver 目录;初始化这条总线上的设备链表:struct klist klist_devices;初始化这条总线上的驱动链表:struct klist klist_drivers;)其原型如下:
/* include/linux/device.h, line 118 */
extern int __must_check bus_register(struct bus_type *bus);
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name,_mode,_show,_store) { \
.attr = {.name = __stringify(_name), .mode = _mode }, \
.show = _show, \
.store = _store, \
}
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
struct klist {
spinlock_t k_lock;
struct list_head k_list;
void (*get)(struct klist_node *);
void (*put)(struct klist_node *);
} __attribute__ ((aligned (sizeof(void *))));
kset_register
将priv->subsys注册到内核中,该接口同时会向sysfs中添加对应的目录(如/sys/bus/spi);bus_create_file
向bus目录下添加一个uevent attribute(如/sys/bus/spi/uevent);kset_create_and_add
分别向内核添加devices和device_drivers kset,同时会体现在sysfs中;add_probe_files
接口,在bus下添加drivers_probe和drivers_autoprobe两个attribute (如/sys/bus/spi/drivers_probe
和/sys/bus/spi/drivers_autoprobe
),其中drivers_probe允许用户空间程序主动出发指定bus下的device_driver的probe动作,而drivers_autoprobe控制是否在device或device_driver添加到内核时,自动执行probe;bus_add_attrs
,添加由bus_attrs指针定义的bus的默认attribute,这些attributes最终会体现在/sys/bus/xxx目录下内核提供了device_register和driver_register两个接口,供各个driver模块使用。而这两个接口的核心逻辑,是通过bus模块的bus_add_device和bus_add_driver实现的,下面我们看看这两个接口的处理逻辑。
这两个接口都是在drivers/base/base.h中声明,在drivers/base/bus.c中实现,其原型为:
/* drivers/base/base.h, line 106 */
extern int bus_add_device(struct device *dev);
/* drivers/base/base.h, line 110 */
extern int bus_add_driver(struct device_driver *drv);
bus_add_device的处理逻辑:
调用内部的device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,它们会体现在/sys/devices/xxx/xxx_device/目录中;
调用sysfs_create_link接口,将该device在sysfs中的目录,链接到该bus的devices目录下,例如:
# ls /sys/bus/spi/devices/spi1.0 -l
lrwxrwxrwx root root 2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi\_master/spi1/spi1.0
调用sysfs_create_link接口,在该设备的sysfs目录中(如/sys/devices/platform/alarm/)中,创建一个指向该设备所在bus目录的链接,取名为subsystem,例如:
# ls /sys/devices/platform/alarm/subsystem -l
lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform
最后,毫无疑问,要把该设备指针保存在bus->priv->klist_devices中
bus_add_driver的处理逻辑:
我们已经介绍过driver的probe时机及过程,其中大部分的逻辑会依赖bus模块的实现,主要为bus_probe_device
和driver_attach
接口。同样,这两个接口都是在drivers/base/base.h中声明,在drivers/base/bus.c中实现。
这两个结构的行为类似,即搜索所在的bus,比对是否有同名的device_driver(或device),如果有并且该设备没有绑定driver(注:这一点很重要,通过它可以使同一个driver,驱动相同名称的多个设备,后续在platform设备的描述中会提及)则调用device_driver的probe接口。
在旧的Linux内核版本中(linux2.6.23版本),sysfs下所有的顶层目录(包括一些二级目录)都是以调用subsystem_register
接口,以sub-system的形式注册到内核的,如:
# ls /sys
/sys/bus/ /sys/devices/ /sys/devices/system/
/sys/block /sys/kernel/ /sys/slab/
…
那时的subsystem_register的实现很简单,就是调用kset_register,创建一个kset。我们知道,kset就是一堆kobject的集合,并会在sysfs中以目录的形式呈现出来。
在新版本的内核中(linux3.10.29),subsystem的实现有了很大变化,例如:去掉了subsystem_register接口(但为了兼容/sys/device/system子系统,在drivers/base/bus.c中,增加了一个subsys_register的内部接口,用于实现相应的功能)。根据这些变化,现在注册subsystem有两种方式:
方式一:在各自的初始化函数中,调用kset_create_and_add接口,创建对应的子系统,包括:
其中bus子系统就是本文所讲的Bus模块,而其它的,我们会在后续的文章中陆续讲述。这种方式和旧版本内核使用kset_register接口的方式基本一样。
方式二:在bus模块中,利用subsys_register接口,封装出两个API:subsys_system_register
和subsys_virtual_register
,分别用于注册system设备(/sys/devices/system/*)和virtual设备(/sys/devices/virtual/*)。 而该方式和方式一的区别是:它不仅仅创建了sysfs中的目录,同时会注册同名的bus和device。
在Linux内核中,有三种比较特殊的bus(或者是子系统),分别是system bus、virtual bus和platform bus。它们并不是一个实际存在的bus(像USB、I2C等),而是为了方便设备模型的抽象,而虚构的。
system bus是旧版内核提出的概念,用于抽象系统设备(如CPU、Timer等等)。而新版内核认为它是个坏点子,因为任何设备都应归属于一个普通的子系统,所以就把它抛弃了(不建议再使用,它的存在只为兼容旧有的实现)。
virtual bus是一个比较新的bus,主要用来抽象那些虚拟设备。所谓的虚拟设备,是指不是真实的硬件设备,而是用软件模拟出来的设备,例如虚拟机中使用的虚拟的网络设备(有关virtula bus的描述,请参考LWN.net])。
platform bus就比较普通,它主要抽象集成在CPU(SOC)中的各种设备。这些设备直接和CPU连接,通过总线寻址和中断的方式,和CPU交互信息。
我们会在后续的文章中,进一步分析这些特殊的bus,这里就暂时不详细描述了。
subsys interface抽象了bus下所有设备的一些特定功能。代码中是这样注释的:
Interfaces usually represent a specific functionality of a subsystem/class of devices.
kernel使用struct subsys_interface
结构抽象subsys interface,并提供了subsys_interface_register/subsys_interface_unregister用于注册/注销subsys interface,**bus下所有的interface都挂载在struct subsys_private变量的interface链表上。struct subsys_interface的定义如下:
/**
* struct subsys_interface - interfaces to device functions
* @name: name of the device function
* @subsys: subsytem of the devices to attach to
* @node: the list of functions registered at the subsystem
* @add_dev: device hookup to device function handler
* @remove_dev: device hookup to device function handler
*
* Simple interfaces attached to a subsystem. Multiple interfaces can
* attach to a subsystem and its devices. Unlike drivers, they do not
* exclusively claim or control devices. Interfaces usually represent
* a specific functionality of a subsystem/class of devices.
*/
struct subsys_interface {
const char *name;
struct bus_type *subsys;
struct list_head node;
int (*add_dev)(struct device *dev, struct subsys_interface *sif);
int (*remove_dev)(struct device *dev, struct subsys_interface *sif);
};