高级部分(Bus、Class、Device、Driver)
深入,并且广泛
-沉默犀牛
这篇文章只分析Bus、Class的作用,和表示它们的结构体。不分析接口函数
Bus
Bus是处理器与一个或者多个device之间的通道。在设备模型中,所有的device都通过bus相连,这意味着,系统中的每一个device都要连接在一个Bus上,这个Bus可以是内部Bus,虚拟Bus,或者platform Bus。Bus之间可以相互穿插,比如一个USB控制器通常是一个PCI设备。以下分析代表Bus的结构体:bus_type
struct bus_type {
const char *name; //该bus的名称,会在sysfs中以目录的形式存在,
//如platform bus在sysfs中表现为"/sys/bus/platform”
const char *dev_name; //对有些设备而言(例如批量化的USB设备),设计者根本就懒得
//为它起名字的,而内核也支持这种懒惰,允许将设备的名字留空。
//这样当设备注册到内核后,设备模型的核心逻辑
//就会用"bus->dev_name+device ID”的形式,
//为这样的设备生成一个名称。
struct device *dev_root; //bus的默认父设备
struct device_attribute *dev_attrs; //以下是默认的attribute
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); //一个由具体的bus driver实现的回调函数。
//当任何属于该Bus的device或者device_driver
//添加到内核时,内核都会调用该接口,如果
//新加的device或device_driver匹配上了自己
//的另一半的话,该接口要返回非零值,此时
//Bus模块的核心逻辑就会执行后续的处理。
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev); //probe和remove这两个函数,和device_driver中
//的非常类似,但它们的存在是非常有意义的。可
//以想象一下,如果需要probe(其实就是初始化)
//指定的device话,需要保证该device所在的
//bus是被初始化过、确保能正确工作的。这就要
//就在执行device_driver的probe前,先执行
//它的bus的probe。remove的过程相反。
//注1:并不是所有的bus都需要probe和remove接口的,因为对有些bus来说
//(例如platform bus),它本身就是一个虚拟的总线,无所谓初始化,直接
//就能使用,因此这些bus的driver就可以将这两个回调函数留空。
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev); //shutdown、suspend、resume是电源管理相关的
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;
};
通过以上的注释,我们可以理解Bus的作用,比较重要的就是match函数。之前说过,所有的Device都要连接到Bus上,那这些Device要怎么用,是其对应的Driver来实现的。Driver可以看成每一个Device的用法,Bus上会有很多个Device,也相应的会有很多个Driver(虽然二者可能数目不相等,因为存在热拔插),那为了让每一个Device找到自己对应的Driver,match函数就在这里起作用,帮助Device找到对应的Driver。
我们在sysfs中可以看到sys/bus目录下有i2c、usb、platform等,而且每一个bus下又有devices、drivers目录。上一篇文章讲到了,如果要在sysfs中有目录,那么就必须有Kobject结构体才行,可是我们现在没有在bus_type中看到kobject结构体啊?答案就在p指向的subsys_private结构体中:
struct subsys_private {
struct kset subsys; //本bus(kset是同类kobject的集合,用来作为表示bus非常合适
// kset中有kobject,所以在sysfs中有目录)
struct kset *devices_kset; //本bus下所有的device
struct list_head interfaces; //用于保存该bus下所有的interface,下做介绍
struct mutex mutex;
struct kset *drivers_kset; //本bus下所有的driver
struct klist klist_devices; //这是两个链表,用于保存本bus下所有的device和device_driver的指针
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1; //用于控制该bus下的drivers或者device是否自动probe
struct bus_type *bus; //用于保存上层的bus
struct kset glue_dirs;
struct class *class; //用于保存上层的Class
};
以上就能完全看出Bus的用途了。此外在对interface做个介绍:
struct subsys_interface {
const char *name; //interface的名称
struct bus_type *subsys; //interface所属的bus
struct list_head node; //用于将interface挂到bus中
int (*add_dev)(struct device *dev, struct subsys_interface *sif);
int (*remove_dev)(struct device *dev, struct subsys_interface *sif);
//两个回调函数,subsys interface的核心功能。当bus下有设备增加或者删
除的时候,bus core会调用它下面所有subsys interface
的add_dev或者remove_dev回调。设计者可以在这两个回调函数
中实现所需功能,例如绑定该“specific functionality”所
对应的driver,等等。
};
Class
在设备模型中,Bus、Device、Device driver等等,都比较好理解,因为它们对应了实实在在的东西,所有的逻辑都是围绕着这些实体展开的。但是Class就有些不同了,因为它是虚拟出来的,只是为了抽象设备的共性。
举个例子,一些年龄相仿、需要获取的知识相似的人,聚在一起学习,就构成了一个班级(Class)。这个班级可以有自己的名称(如295),但如果离开构成它的学生(device),它就没有任何存在意义。另外,班级存在的最大意义是什么呢?是由老师讲授的每一个课程!因为老师只需要讲一遍,一个班的学生都可以听到。不然的话(例如每个学生都在家学习),就要为每人请一个老师,讲授一遍。而讲的内容,大多是一样的,这就是极大的浪费。
设备模型中的Class所提供的功能也一样了,例如一些相似的device(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。所以,Class说了,我帮你们实现吧,你们会用就行了。
接下来看一下代表Class的结构体class:
struct class {
const char *name; //class的名称,会在“/sys/class/”目录下体现
struct module *owner;
struct class_attribute *class_attrs; //该class的默认attribute,会在class注册到
//内核时,自动在“/sys/class/xxx_class”下创建
//对应的attribute文件
const struct attribute_group **dev_groups; //该class下每个设备的attribute,会在设备注册
//到内核时,自动在该设备的sysfs目录下创建对应
//的attribute文件
struct kobject *dev_kobj; //表示该class下的设备在/sys/dev/下的目录
//现在一般有char和block两个,如果dev_kobj
//为NULL,则默认选择char
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
//当该class下有设备发生变化时,会调用class
//的uevent回调函数。
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class); //用于release自身的回调函数。
void (*dev_release)(struct device *dev); //用于release class内设备的回调函数。
//在device_release接口中,会依次检查Device、
//Device Type以及Device所在的class,
//是否注册release接口,如果有则调用相应
//的release接口release设备指针。
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p; //与之前bus中的一样
};
我们了解struct device和struct device_driver这两个数据结构,其中struct device结构会包含一个struct class指针(这从侧面说明了class是device的集合,甚至,class可以是device的driver)
从蜗窝科技中原文作者有一个对于bus和class的看法,我觉得很好:
对于bus和class,我的理解是:
同一个bus下的设备,是一种“空间上(或物理上)”聚集,之所以加引号,可能是虚拟的;
同一个class下的设备,是一种“文化上”的聚集,例如我们有共同的特征、共同的兴趣爱好等等。
那么,一个设备是否可能既从属于某一个bus,又从属于某一个class?是可以的。通常的做法是:
该设备的device指针(由设备模型管理),和bus打交道,如某一个platform设备下的device指针;
如果需要加入某一个class,则新添一个子设备,让这个设备加入到class。
Device
device结构体代表了每一个设备,看过结构体后就知道它是什么了:
struct device {
struct device *parent; //该设备的父设备,一般是该设备所从属的bus、controller等设备。
struct device_private *p; //一个用于struct device的私有数据结构指针
//该指针中会保存子设备链表、用于添加到bus/driver/prent等设备
//中的链表头等等
struct kobject kobj; //该数据结构对应的struct kobject。
const char *init_name; //该设备的名称
//在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备
//都必须有一个合法的名称,可以在初始化时给出,也可以由内核根
//据“bus name + device ID”的方式创造
const struct device_type *type; //device_type与device的关系,非常像ktype与kobject的关系
struct mutex mutex;
struct bus_type *bus; //该device属于哪个总线
struct device_driver *driver; //该device对应的device driver
void *platform_data; //一个指针,用于保存具体的平台相关的数据
void *driver_data;
struct dev_pm_info power; //电源管理相关的逻辑
struct dev_pm_domain *pm_domain; //电源管理相关的逻辑
#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
struct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins; //"PINCTRL”功能
#endif
#ifdef CONFIG_NUMA
int numa_node; //"NUMA”功能
#endif
u64 *dma_mask;
u64 coherent_dma_mask;
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools;
struct dma_coherent_mem *dma_mem;
#ifdef CONFIG_DMA_CMA
struct cma *cma_area;
#endif
struct removed_region *removed_mem;
struct dev_archdata archdata;
struct device_node *of_node;
struct acpi_dev_node acpi_node;
dev_t devt; //dev_t是一个32位的整数,它由两个部分(Major和Minor)组成
//在需要以设备节点的形式(字符设备和块设备)向用户空间提供
//接口的设备中,当作设备号使用
u32 id;
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class; //该设备属于哪个class
const struct attribute_group **groups; //该设备的默认attribute集合。
//将会在设备注册时自动在sysfs中创建对应的文件。
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
Device_driver
struct device_driver {
const char *name; //该driver的名称。和device结构一样,该名称非常重要3
struct bus_type *bus; //该driver所驱动设备的总线设备
struct module *owner; //內核module相关的变量
const char *mod_name; //內核module相关的变量
bool suppress_bind_attrs; //是不在sysfs中启用bind和unbind attribute
//在kernel中,bind/unbind是从用户空间手动的为driver
//绑定/解绑定指定的设备的机制。
enum probe_type probe_type;
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
//probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。
//Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像
//PC程序,会有一个main函数,main函数的开始就是开始,return的地方
//就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有
//driver和device同时存在时,才需要开始执行driver的代码逻辑。这
//也是probe和remove两个接口名称的由来:检测到了设备和移除了设备
//(就是为热拔插起的!)
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; //默认属性
const struct dev_pm_ops *pm;
struct driver_private *p;
};
这两篇设备结构模型_低级/高级部分,解释了为什么bus、class、device会出现在sysfs中(因为内嵌了kobject),也解释清楚了它们之间的关系。这样就从逻辑上把握住了设备模型的整体框架,至于涉及到的API,可以想像,也无非是对这些结构体中的成员的操作。
本文参考了蜗窝科技-设备驱动模型-(一)~(八)