Linux设备模型
参考
- http://www.wowotech.net/device_model/kobject.html
- https://blog.csdn.net/TongxinV/article/details/54853122
- https://bbs.huaweicloud.com/blogs/186115
- https://www.cnblogs.com/schips/p/linux_device_model.html
❗首先,要理解设备驱动模型到底是什么,不能一上来就研究kobject这鬼东西。
它以kobject为底层,组织类class、总线bus、设备device、驱动driver等高级数据结构,
同时实现对象引用计数、维护对象链表、对象上锁、对用户空间的表示等服务,
设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。
驱动的开发,就简化为对内核所规定的数据结构的填充和实现。
原理
(设备多样性—>复杂度提升—>设备驱动模型,可以理解为此原理和设备树相当)
信息多了,不能每次都往里面塞,提取共性,减少空间,进行查找匹配,更好操作。
换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,
而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。
- Linux内核可以在各种体系结构和硬件平台上运行,因此需要最大限度地提高代码在平台之间的可重用性。
- 分层实现也实现了软件工程的高内聚-低耦合的设计思想。低耦合体现在对外提供统一的抽象访问接口,高内聚将相关度紧密的集中抽象实现。
- Linux内核驱动程序模型是先前在内核中使用的所有不同驱动程序模型的统一。
它旨在通过将一组数据和操作整合到全局可访问的数据结构中,来扩展基础总线并桥接设备驱动程序。
Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,
将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。
理解
看了几天,这个东西确实难搞,各层引用,层层循环,绕来绕去,实属无奈,下面几部分是我自己的理解,合理记录一下
- 上层容器----四大框架
- 内核空间和用户空间的交互----sysfs虚拟根文件系统
- 底层架构----三大基石
底层架构
这一部分确实是最难啃的了,先来看看kobject、ktype、kset三大基石;
kobject
如内核所说,它是组成设备模型的基本结构,是所有用来描述设备模型的数据结构的基类,
它是一个对象的抽象,它用于管理对象。(听着有面向对象那味了)
kobject就是一个工具人,它内嵌在一个大型的数据结构中,用于组织成拓扑关系,为这个数据结构提供一些底层的功能实现,
简单来说,上层结构通过它来获取底层的内容,另外,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。(这句话先看着)
ktype
- A ktype is the type of object that embeds a kobject. Every structure
that embeds a kobject needs a corresponding ktype. The ktype controls
what happens to the kobject when it is created and destroyed.
简单来说,它是kobject的属性操作集合,可以说是对象的属性,
同时,kobject的生命周期管理是由其关联的ktype来实现的,包括kobject创建和删除时所执行的一些操作。
kset
kset是一些kobject的集合,这些kobject可以有相同的ktype(属性),也可以不同。kset也可看为特殊的kobject,
同时,kset自己也包含一个kobject;因此在sysfs中,kset也是对应这一个目录,但是目录下面包含着其他的kojbect。
每个Kobject不一定出现在sys中,但Kset中的每个Kobject会出现在sys中。(这两句句话先看着)
kobject、ktype、kset数据结构
大佬的图,一目了然,它们的关系大概清晰了:
kobject
// include/linux/kobject.h
struct kobject {
const char *name; /* 对应sysfs的目录名 */
struct list_head entry; /*用于连接到所属kset的链表中,用于将kobj挂在kset->list中*/
struct kobject *parent; /*指向父对象,形成层次结构,在sysfs中表现为父子目录的关系*/
struct kset *kset; /*属于哪个kset,表征该kobj所属的kset。 kset可以作为parent的“候补”:当注册时,传入的parent为空时,可以让kset来担当。*/
struct kobj_type *ktype; /*类型属性,每个kobj或其嵌入的结构对象应该都对应一个kobj_type。 */
struct kernfs_node *sd; /*sysfs中与该对象对应的文件节点对象 在3.14以后的内核中,sysfs基于kernfs来实现。*/
struct kref kref; /*对象的引用计数*/
unsigned int state_initialized:1; /* 记录初始化与否。调用kobject_init()后,会置位。 */
unsigned int state_in_sysfs:1; /* 记录kobj是否注册到sysfs,在kobject_add_internal()中置位。 */
unsigned int state_add_uevent_sent:1; /* 当发送KOBJ_ADD消息时,置位。提示已经向用户空间发送ADD消息。*/
unsigned int state_remove_uevent_sent:1; /* 当发送KOBJ_REMOVE消息时,置位。提示已经向用户空间发送REMOVE消息。*/
unsigned int uevent_suppress:1; /* 如果该字段为1,则表示忽略所有上报的uevent事件。Uevent提供了“用户空间通知”的功能实现,通过该功能,当内核中有Kobject的增加、删除、修改等动作时,会通知用户空间 */
};
//include/linux/kref.h
struct kref {
atomic_t refcount;
};
硬件设备的数量、种类是非常多的,这就决定了Kernel中将会有大量的有关设备模型的数据结构。
这些数据结构一定有一些共同的功能,需要抽象出来统一实现,否则就会不可避免的产生冗余代码。于是kobject来了。
目前为止,Kobject主要提供如下功能:
- 构成层次结构:通过parent指针,可以将所有Kobject以层次结构的形式组合起来;
- 生命周期管理:使用引用计数(reference count),来记录Kobject被引用的次数,并在引用次数变为0时把它释放;
- 和sysfs虚拟文件系统配合,将每一个kobject及其特性,以文件的形式,开放到用户空间。
ktype
// include/linux/kobject.h
struct kobj_type {
void (*release)(struct kobject *kobj); /* 处理对象终结的回调函数。该接口应该由具体对象负责填充。 */
const struct sysfs_ops *sysfs_ops; /* 该类型kobj的sysfs操作接口。 */
struct attribute **default_attrs;/* 该类型kobj自带的缺省属性(文件),这些属性文件在注册kobj时,直接pop为该目录下的文件。 */
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj); /*child_ns_type/namespace 是 文件系统命名空间相关)略*/
const void *(*namespace)(struct kobject *kobj);
};
// linux/sysfs.h
struct sysfs_ops { /* kobject操作函数集 */
ssize_t (*show)(struct kobject *, struct attribute *, char *);
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
/* 所谓的attribute就是内核空间和用户空间进行信息交互的一种方法,
例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的行为,
那么可以将该变量以sysfs attribute的形式开放出来 */
struct attribute {
const char *name;
umode_t mode;
};
- 当kobject的引用计数为0时,通过release方法来释放相关的资源。
- attribute为属性,每个属性在sysfs中都有对应的属性文件。
- sysfs_op的两个方法用于实现读取和写入属性文件时应该采取的行为。
kset
struct kset {
struct list_head list; /*属于该kset的kobject链表,与kobj->entry对应,用来组织本kset管理的kobj*/
spinlock_t list_lock; /*用于该kset下所有的kobject的链表上锁*/
struct kobject kobj; /*该kset内嵌的kobj*/
/*
kset用于发送消息的操作函数集。
需要指出的是,kset能够发送它所包含的各种子kobj、孙kobj的消息;
即kobj或其父辈、爷爷辈,都可以发送消息;优先父辈,然后是爷爷辈,以此类推。
*/
const struct kset_uevent_ops *uevent_ops;
};
关系图
kobject在创建的时候,默认设置kobj_type的值为dynamic_kobj_ktype,通常kobject会嵌入在其他结构中来使用,
因此它的初始化跟特定的结构相关,典型的比如struct device和struct device_driver;
在/sys文件系统中,通过echo/cat的操作,最终会调用到show/store函数,而这两个函数的具体实现可以放置到驱动程序中
如果只看kset/kobject的数据结构组织,可能还是会迷惑,它怎么跟Linux的设备模型相关?
这时就不得不提到Linux内核中一个很精妙的存在container_of,它可以通过成员变量的地址来获取所在结构的地址信息。
前文提到过kobject/kset结构本身不会单独使用,通常都是会嵌套在其他结构中,既然kobjcet/kset能组织成拓扑结构,
那么包含它们的结构同样可以构建这个关系,因为可以通过container_of就可以找到结构体的首地址。
kobject API
kobject的分配和释放
/*kmalloc自行分配(一般是跟随上层数据结构分配),并在初始化后添加到kernel。*/
void kobject_init(struct kobject *kobj, struct kobj_type *ktype);
int kobject_add(struct kobject *kobj, struct kobject *parent,const char *fmt, ...);
int kobject_init_and_add(struct kobject *kobj,struct kobj_type *ktype, struct kobject *parent,const char *fmt, ...);
描述:
kobject_init,初始化通过kmalloc等内存分配函数获得的struct kobject指针;
kobject_add,将初始化完成的kobject添加到kernel中,参数包括需要添加的kobject、该kobject的parent(用于形成层次结构,可以为空)、用于提供kobject name的格式化字符串;
kobject_init_and_add,是上面两个接口的组合
/*Kobject模块可以使用kobject_create自行分配空间,并内置了一个ktype(dynamic_kobj_ktype),用于在计数为0是释放空间。*/
struct kobject * __must_check kobject_create(void);
truct kobject * __must_check kobject_create_and_add(const char *name,struct kobject *parent);
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}
static struct kobj_type dynamic_kobj_ktype = {
.release = dynamic_kobj_release,
.sysfs_ops = &kobj_sysfs_ops,
};
描述:
kobject_create,该接口为kobj分配内存空间,并以dynamic_kobj_ktype为参数,调用kobject_init接口,完成后续的初始化操作。
kobject_create_and_add,是kobject_create和kobject_add的组合,不再说明。
dynamic_kobj_release,直接调用kfree释放kobj的空间。
在sysfs中建立目录
int kobject_add_internal(struct kobject *kobj);
kobject_add_internal()会根据kobj.parent和kobj.kset来综合决定父对象(相当于/sysfs中的父级目录):
1、如果有parent则默认parent为父对象;
如果没有parent作为父对象,但是有当前的kobj位于kset中,则使用kset中的kobj作为父对象
如果没有parent也没有kset作为父对象,那该kobj属于顶层对象:在sysfs中,我们可以看到kobj位于/sys/下。
2、该 kobject 目录下的属性文件依赖于 kobject.ktype
详见<案例>
kobject引用计数的修改
/*通过kobject_get和kobject_put可以修改kobject的引用计数,并在计数为0时,调用ktype的release接口,释放占用空间。*/
struct kobject *kobject_get(struct kobject *kobj);
extern void kobject_put(struct kobject *kobj);
描述:
kobject_get,调用kref_get,增加引用计数。
kobject_put,以内部接口kobject_release为参数,调用kref_put。kref模块会在引用计数为零时,调用kobject_release。
内部接口:
kobject_release,通过kref结构,获取kobject指针,并调用kobject_cleanup接口继续。
kobject_cleanup,负责释放kobject占用的空间。
kset注册和初始化
/*Kset是一个特殊的kobject,因此其初始化、注册等操作也会调用kobject的相关接口,除此之外,会有它特有的部分。
另外,和Kobject一样,kset的内存分配,可以由上层软件通过kmalloc自行分配,也可以由Kobject模块负责分配*/
void kset_init(struct kset *kset);
int __must_check kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);
struct kset * __must_check kset_create_and_add(const char *name,const struct kset_uevent_ops *u,struct kobject *parent_kobj);
描述:
kset_init,该接口用于初始化已分配的kset,主要包括调用kobject_init_internal初始化其kobject,然后初始化kset的链表。
需要注意的时,如果使用此接口,上层软件必须提供该kset中的kobject的ktype。
kset_register,先调用kset_init,然后调用kobject_add_internal将其kobject添加到kernel。
kset_unregister,直接调用kobject_put释放其kobject。当其kobject的引用计数为0时,即调用ktype的release接口释放kset占用的空间。
kset_create_and_add,会调用内部接口kset_create动态创建一个kset,并调用kset_register将其注册到kernel。
目录创建流程分析
案例测试
kobj创建
//mykobj.c
/*如果没有parent也没有kset作为父对象,那该kobj属于顶层对象:在sysfs中,我们可以看到kobj位于/sys/下。*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>
struct kobject *g_mykobj = NULL;
struct kobject *g_mycurobj = NULL;
static int __init test_init(void)
{
g_mycurobj = kobject_create_and_add("test",NULL);
if(NULL == g_mycurobj)
{
printk("create kobj error\n");
return -1;
}
g_mykobj = kobject_create_and_add("curtest",g_mycurobj);
if(NULL == g_mykobj)
{
if(g_mycurobj)
{
kobject_del(g_mycurobj);
kobject_put(g_mycurobj);
}
}
return 0;
}
static void __exit test_exit(void)
{
if(g_mykobj)
{
kobject_del(g_mykobj);
kobject_put(g_mykobj);
}
if(g_mycurobj)
{
kobject_del(g_mycurobj);
kobject_put(g_mycurobj);
}
return;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("z");
测试:
//mykobj2.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kobject.h>
#include <linux/slab.h>
//自定义一个结构,包含了struct kobject子结构
struct test_kobj {
int value;
struct kobject kobj;
};
//自定义个属性结构体,包含了struct attribute结构
struct test_kobj_attribute {
struct attribute attr;
ssize_t (*show)(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buf);
ssize_t (*store)(struct test_kobj *obj, struct test_kobj_attribute *attr, const char *buf, size_t count);
};
//声明一个全局结构用于测试
struct test_kobj *obj;
//用于初始化sysfs_ops中的函数指针
static ssize_t test_kobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct test_kobj_attribute *test_kobj_attr;
ssize_t ret = -EIO;
test_kobj_attr = container_of(attr, struct test_kobj_attribute, attr);
//回调到具体的实现函数
if (test_kobj_attr->show)
ret = test_kobj_attr->show(container_of(kobj, struct test_kobj, kobj), test_kobj_attr, buf);
return ret;
}
//用于初始化sysfs_ops中的函数指针
static ssize_t test_kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count)
{
struct test_kobj_attribute *test_kobj_attr;
ssize_t ret = -EIO;
test_kobj_attr = container_of(attr, struct test_kobj_attribute, attr);
//回调到具体的实现函数
if (test_kobj_attr->store)
ret = test_kobj_attr->store(container_of(kobj, struct test_kobj, kobj), test_kobj_attr, buf, count);
return ret;
}
//用于初始化kobj_ktype
const struct sysfs_ops test_kobj_sysfs_ops = {
.show = test_kobj_attr_show,
.store = test_kobj_attr_store,
};
//用于初始化kobj_ktype,最终用于释放kobject
void obj_release(struct kobject *kobj)
{
struct test_kobj *obj = container_of(kobj, struct test_kobj, kobj);
printk(KERN_INFO "test kobject release %s\n", kobject_name(&obj->kobj));
kfree(obj);
}
//定义kobj_ktype,用于指定kobject的类型,初始化的时候使用
static struct kobj_type test_kobj_ktype = {
.release = obj_release,
.sysfs_ops = &test_kobj_sysfs_ops,
};
//show函数的具体实现
ssize_t name_show(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buffer)
{
return sprintf(buffer, "%s\n", kobject_name(&obj->kobj));
}
//show函数的具体实现
ssize_t value_show(struct test_kobj *obj, struct test_kobj_attribute *attr, char *buffer)
{
return sprintf(buffer, "%d\n", obj->value);
}
//store函数的具体实现
ssize_t value_store(struct test_kobj *obj, struct test_kobj_attribute *attr, const char *buffer, size_t size)
{
sscanf(buffer, "%d", &obj->value);
return size;
}
//定义属性,最终注册进sysfs系统
struct test_kobj_attribute name_attribute = __ATTR(name, 0664, name_show, NULL);
struct test_kobj_attribute value_attribute = __ATTR(value, 0664, value_show, value_store);
struct attribute *test_kobj_attrs[] = {
&name_attribute.attr,
&value_attribute.attr,
NULL,
};
//定义组
struct attribute_group test_kobj_group = {
.name = "test_kobj_group",
.attrs = test_kobj_attrs,
};
//模块初始化函数
static int __init test_kobj_init(void)
{
int retval;
printk(KERN_INFO "test_kobj_init\n");
obj = kmalloc(sizeof(struct test_kobj), GFP_KERNEL);
if (!obj) {
return -ENOMEM;
}
obj->value = 1;
memset(&obj->kobj, 0, sizeof(struct kobject));
//添加进sysfs系统
kobject_init_and_add(&obj->kobj, &test_kobj_ktype, NULL, "test_kobj");
//在sys文件夹下创建文件
retval = sysfs_create_files(&obj->kobj, (const struct attribute **)test_kobj_attrs);
if (retval) {
kobject_put(&obj->kobj);
return retval;
}
//在sys文件夹下创建group
retval = sysfs_create_group(&obj->kobj, &test_kobj_group);
if (retval) {
kobject_put(&obj->kobj);
return retval;
}
return 0;
}
//模块清理函数
static void __exit test_kobj_exit(void)
{
printk(KERN_INFO "test_kobj_exit\n");
kobject_del(&obj->kobj);
kobject_put(&obj->kobj);
return;
}
module_init(test_kobj_init);
module_exit(test_kobj_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("z");
测试:
//kernel案例
//samples/kobject/kobject-example.c
kset创建
//kernel案例,和kobj差不多步骤
//samples/kobject/kset-example.c
原理图
kobject使用流程
Kobject大多数情况下(有一种例外,下面会讲)会嵌在其它数据结构中使用,其使用流程如下:
- 定义一个struct kset类型的指针,并在初始化时为它分配空间,添加到内核中
- 根据实际情况,定义自己所需的数据结构原型,该数据结构中包含有Kobject
- 定义一个适合自己的ktype,并实现其中回调函数
- 在需要使用到包含Kobject的数据结构时,动态分配该数据结构,并分配Kobject空间,添加到内核中
- 每一次引用数据结构时,调用kobject_get接口增加引用计数;引用结束时,调用kobject_put接口,减少引用计数
- 当引用计数减少为0时,Kobject模块调用ktype所提供的release接口,释放上层数据结构以及Kobject的内存空间
上面有提过,有一种例外,Kobject不再嵌在其它数据结构中,可以单独使用,这个例外就是:开发者只需要在sysfs中创建一个目录,而不需要其它的kset、ktype的操作。这时可以直接调用kobject_create_and_add接口,分配一个kobject结构并把它添加到kernel中。
底层架构总结
至此,我了解了3k之间的关系,知道底层架构为上层创建目录提供基础,组织了sysfs目录层次结构,管理对象生命周期等作用,后面还有许多的关联未牵出。其次,上文多次提及sysfs,它究竟是啥,这个放最后记录吧。
上层容器
这里记录Linux设备模型中四个重要概念:Bus,Class,Device和Driver。
首先kobject使这它们之间建立联系,并且通过sysfs向用户反映他们之间的联系。
这部分首先简单说明它们四个的概念、然后是注册、挂接流程,相互关系,驱动和设备的匹配等
-
总线(bus)是linux发展过程中抽象出来的一种设备模型,为了统一管理所有的设备,内核中每个设备都会被挂载在总线上,这个bus可以是对应硬件的bus(i2c bus、spi bus)、可以是虚拟bus(platform bus),内核通过总线将设备与驱动分离,bus将所有挂在上面的具体设备抽象成两部分,device_driver和device。
-
device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备
-
Class是虚拟出来的,只是为了抽象设备的共性。设备模型中的Class所提供的功能也一样了,例如一些相似的device(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。所以,Class说了,我帮你们实现吧,你们会用就行了。
-
简单来说,bus 负责维护 注册进来的devcie 与 driver ,每注册进来一个device 或者 driver 都会调用 Bus->match 函数 将device 与driver 进行配对,并将它们加入链表。
如果配对成功,调用Bus->probe或者driver->probe函数, 调用kobject_uevent函数设置环境变量(通知用户空间),mdev进行创建设备节点等操作。
Bus
上数据结构
struct bus_type {
const char *name; //该bus的名称,会在sysfs中以目录的形式存在,如platform bus在sysfs中表现为"/sys/bus/platform
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups; //bus_attrs、dev_attrs、drv_attrs,一些默认的attribute,
const struct attribute_group **dev_groups; //可以在bus、device或者device_driver添加到内核时,自动为它们添加相应的attribute
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv); //match,一个由具体的bus driver实现的回调函数。当任何属于该Bus的device或者device_driver添加到内核时, 内核都会调用该接口
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);//一个由具体的bus driver实现的回调函数。当任何属于该Bus的device,发生添加、移除或者其它动作时, Bus模块的核心逻辑就会调用该接口,以便bus driver能够修改环境变量
/*
probe、remove,这两个回调函数,和device_driver中的非常类似,但它们的存在是非常有意义的。可以想象一下,如果需要probe(其实就是初始化)指定的device话,需要保证该device所在的bus是被初始化过、确保能正确工作的。这就要就在执行device_driver的probe前,先执行它的bus的probe。remove的过程相反。
注1:并不是所有的bus都需要probe和remove接口的,因为对有些bus来说(例如platform bus),
它本身就是一个虚拟的总线,无所谓初始化,直接就能使用,因此这些bus的driver就可以将这两个回调函数留空。
*/
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;
struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
功能:
- bus的注册和注销
- 本bus下有device或者device_driver注册到内核时的处理
- 本bus下有device或者device_driver从内核注销时的处理
- device_drivers的probe处理
- 管理bus下的所有device和device_driver
bus的注册
/* include/linux/device.h, line 118 */
extern int __must_check bus_register(struct bus_type *bus);
其执行流程为:
1.为bus_type中struct subsys_private类型的指针分配空间,并更新priv->bus和bus->p两个指针为正确的值
2.初始化priv->subsys.kobj的name、kset、ktype等字段,启动name就是该bus的name(它会体现在sysfs中),kset和ktype由bus模块实现,分别为bus_kset和bus_ktype
3.调用kset_register将priv->subsys注册到内核中,该接口同时会向sysfs中添加对应的目录(如/sys/bus/spi)
4.调用bus_create_file向bus目录下添加一个uevent attribute(如/sys/bus/spi/uevent)
5.调用kset_create_and_add分别向内核添加devices和device_drivers kset,同时会体现在sysfs中,对应创建 /sys/bus/$(bus->name)/devices&driver
6.初始化priv指针中的mutex、klist_devices和klist_drivers等变量
7.调用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
8.调用bus_add_attrs,添加由bus_attrs指针定义的bus的默认attribute,这些attributes最终会体现在/sys/bus/xxx目录下
device和device_driver的添加
后面记录的内核提供了device_register和driver_register两个接口,供各个driver模块使用。
而这两个接口的核心逻辑,是通过bus模块的bus_add_device和bus_add_driver实现的。
/* 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:system/virtual/platform
在Linux内核中,有三种比较特殊的bus(或者是子系统),分别是system bus、virtual bus和platform bus。
它们并不是一个实际存在的bus(像USB、I2C等),而是为了方便设备模型的抽象,而虚构的。
- system bus是旧版内核提出的概念,用于抽象系统设备(如CPU、Timer等等)。
- virtaul bus是一个比较新的bus,主要用来抽象那些虚拟设备,所谓的虚拟设备,是指不是真实的硬件设备,而是用软件模拟出来的设备,例如虚拟机中使用的虚拟的网络设备。
- platform bus就比较普通,它主要抽象集成在CPU(SOC)中的各种设备。这些设备直接和CPU连接,通过总线寻址和中断的方式,和CPU交互信息。
后面将单独记录platform总线驱动设备。
device和device driver
device设备–实物
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.*/
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
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_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;
};
parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。
p,一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。
kobj,该数据结构对应的struct kobject。
init_name,该设备的名称。
注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“bus name + device ID”的方式创造。
type,struct device_type结构是新版本内核新引入的一个结构,它和struct device关系,非常类似stuct kobj_type和struct kobject之间的关系,后续会再详细说明。
bus,该device属于哪个总线(后续会详细描述)。
driver,该device对应的device driver。
platform_data,一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。
power、pm_domain,电源管理相关的逻辑
devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录,如下:
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1\:
1:1/ 1:11/ 1:13/ 1:14/ 1:2/ 1:3/ 1:5/ 1:7/ 1:8/ 1:9/
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1
1:1/ 1:11/ 1:13/ 1:14/
1|root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1
/sys/dev/char/1:1
class,该设备属于哪个class。
groups,该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件。
device driver设备驱动–程序
//include/linux/device.h
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
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);
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;
};
name,该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。
bus,该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。
owner、mod_name,內核module相关的变量,暂不描述。
suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute,如下:root@android:/storage/sdcard0 # ls /sys/bus/platform/drivers/switch-gpio/
bind uevent unbind
在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。
probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。
shutdown、suspend、resume、pm,电源管理相关的内容,会在电源管理专题中详细说明。
groups,和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。
p,driver core的私有数据指针,其它模块不能访问。
注册并匹配
设备和驱动匹配probe时机
所谓的"probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式,我们先不关注了),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。
设备驱动prove的时机有如下几种(分为自动触发和手动触发):
- 将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)
- 将struct device_driver类型的变量注册到内核中时自动触发(driver_register)
- 手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
- 手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
- 自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_driver)
注:① probe动作实际是由bus模块实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪些device、哪些driver配对。
② 每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。
设备模型框架下驱动开发的基本步骤
在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:
步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。
步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。
这两步完成后,内核会在合适的时机,调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。
注:① 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用。
② 内核提供很多struct device结构的操作接口(include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。
③ device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。
④ device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。
⑤ driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。
案例
https://github.com/jinrx3/ldd_bdd.git
class
含义
class 是设备类,设备类是一个设备的高层视图,它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,而不用关心设备是如何连接和工作的。
设备类是用来抽象设备的共性而诞生的。
类成员通常由上层代码所控制,而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
数据结构描述
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
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;
};
其实struct class和struct bus很类似,解释如下:
name,class的名称,会在“/sys/class/”目录下体现。
owner是class所属的模块,虽然class是涉及一类设备,但也是由相应的模块注册的。比如usb类就是由usb模块注册的。
class_atrrs,该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件。
dev_attrs,该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件。
dev_bin_attrs,类似dev_attrs,只不过是二进制类型attribute。
dev_kobj,表示该class下的设备在/sys/dev/下的目录,现在一般有char和block两个,如果dev_kobj为NULL,则默认选择char。
dev_uevent,当该class下有设备发生变化时,会调用class的uevent回调函数。
class_release,用于release自身的回调函数。
dev_release,用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针。
p,私有数据,和struct bus结构一样,不再说明。
类接口
// include/linux/device.h
struct class_interface {
//
struct list_head node;
// 指向所属class的指针
struct class *class;
// 有设备添加到所属class时调用的函数
int (*add_dev) (struct device *, struct class_interface *);
// 设备删除时调用。
void (*remove_dev) (struct device *, struct class_interface *);
};
class_interface实际上做的是在class driver在class下有设备添加或移除的时候,调用预先设置好的回调函数(add_dev和remove_dev),关于add_dev()还有一点要说明,如果class_interface比设备更晚添加到class,也会在添加到class的时候执行。
类的sysfs属性
// include/linux/device.h
struct class_attribute {
struct attribute attr;
ssize_t (*show)(struct class *class, char *buf);
ssize_t (*store)(struct class *class, const char *buf, size_t count);
};
#define CLASS_ATTR(_name, _mode, _show, _store) \
struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
API
device_add_class_symlinks
// drivers/base/core.c
static int device_add_class_symlinks(struct device *dev)
{
int error;
if (!dev->class)
return 0;
error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem");
if (dev->parent && device_is_not_partition(dev)) {
error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
"device");
if (error)
goto out_subsys;
}
#ifdef CONFIG_BLOCK
/* /sys/block has directories and does not need symlinks */
if (sysfs_deprecated && dev->class == &block_class)
return 0;
#endif
/* link in the class directory pointing to the device */
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
return 0;
}
class_kset与初始化
static struct kset *class_kset;
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
if (!class_kset)
return -ENOMEM;
return 0;
}
class_kset代表了/sys/class对应的kset,在classes_init()中创建。
classes_init()的作用,和之前见到的buses_init()、devices_init()作用相似,都是构建/sys下的主要目录结构。
创建/删除class的属性
// include/linux/device.h
static inline int __must_check class_create_file(struct class *class,
const struct class_attribute *attr)
{
return class_create_file_ns(class, attr, NULL);
}
static inline void class_remove_file(struct class *class,
const struct class_attribute *attr)
{
return class_remove_file_ns(class, attr, NULL);
}
int class_create_file_ns(struct class *cls, const struct class_attribute *attr,
const void *ns)
{
int error;
if (cls)
error = sysfs_create_file_ns(&cls->p->subsys.kobj,
&attr->attr, ns);
else
error = -EINVAL;
return error;
}
void class_remove_file_ns(struct class *cls, const struct class_attribute *attr,
const void *ns)
{
if (cls)
sysfs_remove_file_ns(&cls->p->subsys.kobj, &attr->attr, ns);
}
class_create_file():创建class的属性文件。
class_remove_files():删除class的属性文件。
属性加入sysfs和删除
static int add_class_attrs(struct class *cls)
{
int i;
int error = 0;
if (cls->class_attrs) {
for (i = 0; cls->class_attrs[i].attr.name; i++) {
error = class_create_file(cls, &cls->class_attrs[i]);
if (error)
goto error;
}
}
done:
return error;
error:
while (--i >= 0)
class_remove_file(cls, &cls->class_attrs[i]);
goto done;
}
static void remove_class_attrs(struct class *cls)
{
int i;
if (cls->class_attrs) {
for (i = 0; cls->class_attrs[i].attr.name; i++)
class_remove_file(cls, &cls->class_attrs[i]);
}
}
class 注册
// include/linux/device.h
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_register(class) \
({ \
static struct lock_class_key __key; \
__class_register(class, &__key); \
})
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->interfaces);
kset_init(&cp->glue_dirs);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset;
#else
cp->subsys.kobj.kset = class_kset;
#endif
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
error = kset_register(&cp->subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);
__class_register()中进行实际的class注册工作:
先是分配和初始化class_private结构。
可以看到对cp->glue_dirs,只是调用kset_init()定义,并未实际注册到sysfs中。
调用kobject_set_name()创建kobj中实际的类名。
cls->dev_kobj如果未设置,这里会被设为sysfs_dev_char_kobj。
调用kset_register()将class注册到sysfs中,所属kset为class_kset,使用类型为class_ktype。因为没有设置parent,会以/sys/class为父目录。
最后调用add_class_attrs()添加相关的属性文件。
注册class_interface
int class_interface_register(struct class_interface *class_intf)
{
struct class *parent;
struct class_dev_iter iter;
struct device *dev;
if (!class_intf || !class_intf->class)
return -ENODEV;
parent = class_get(class_intf->class);
if (!parent)
return -EINVAL;
mutex_lock(&parent->p->mutex);
list_add_tail(&class_intf->node, &parent->p->interfaces);
if (class_intf->add_dev) {
class_dev_iter_init(&iter, parent, NULL, NULL);
while ((dev = class_dev_iter_next(&iter)))
class_intf->add_dev(dev, class_intf);
class_dev_iter_exit(&iter);
}
mutex_unlock(&parent->p->mutex);
return 0;
}
class_interface_register()把class_interface添加到指定的class上。
调用class_get()获取class的引用计数。
使用class->mutex进行保护。
将classs_intf添加到class的接口列表中。
对已经添加到class上的设备补上add_dev()操作。
这里使用的class->mutex是用来保护class的类接口链表。对于简单的list_head来说,这种mutex保护是应该的。但对于武装到牙齿的klist来说,就完全不必要了,因为klist内置了spinlock来完成互斥的操作。所以之前其它的klist链表操作都没有mutex保护。
比较spinlock和mutex的话,spinlock操作要比mutex快很多,因为对mutex的操作本身就需要spinlock来保护。但mutex的好处是它可以阻塞。使用spinlock时间太长的话,一是浪费cpu时间,二是禁止了任务抢占。klist是使用spinlock来保护的,这适合大部分情况,但在klist遍历时也可能调用一些未知的操作,它们可能很耗时,甚至可能阻塞,这时最好能使用mutex加以替换。
sysfs
含义原理
sysfs是一个基于RAM的文件系统,它和Kobject一起,可以将Kernel的数据结构导出到用户空间,以文件目录结构的形式,提供对这些数据结构(以及数据结构的属性)的访问支持。
sysfs把连接在系统上的设备和总线组织成为一个分级的文件,它们可以由用户空间存取,向用户空间展示内核数据结构以及他们的属性。它的一个目的就是展示设备驱动模型中各组件的层次关系。
- sysfs文件系统提供了一种用户与内核数据结构进行交互的方式,可以通过mount -t sysfs sysfs /sys来进行挂载;
- Linux设备模型中,设备、驱动、总线组织成拓扑结构,通过sysfs文件系统以目录结构进行展示与管理;
- Linux设备模型中,总线负责设备和驱动的匹配,设备与驱动都挂在某一个总线上,当它们进行注册时由总线负责去完成匹配,进而回调驱动的probe函数;
- SoC系统中有spi, i2c, pci等实体总线用于外设的连接,而针对集成在SoC中的外设控制器,Linux内核提供一种虚拟总线platform用于这些外设控制器的连接,此外platform总线也可用于没有实体总线的外设;
- 在/sys目录下,bus用于存放各类总线,其中总线中会存放挂载在该总线上的驱动和设备,比如serial8250,devices存放了系统中的设备信息,class是针对不同的设备进行分类。
目录和文件的创建
sysfs文件系统中提供了四类文件的创建与管理,分别是目录、普通文件、软链接文件、二进制文件。
- 目录层次往往代表着设备驱动模型的结构
- 软链接文件则代表着不同部分间的关系。比如某个设备的目录只出现在/sys/devices下,其它地方涉及到它时只好用软链接文件链接过去,保持了设备唯一的实例。
- 普通文件和二进制文件往往代表了设备的属性,读写这些文件需要调用相应的属性读写。
sysfs的根目录下包含了七个目录:block、bus、class、devices、firmware、module和power。
$ tree /sys | head -n 300
/sys
├── block
...
├── bus
│ ├── cpu
│ │ ├── devices
│ │ │ └── cpu0 -> ../../../devices/system/cpu/cpu0
│ │ ├── drivers
│ │ │ └── processor
│ │ │ ├── bind
│ │ │ ├── cpu0 -> ../../../../devices/system/cpu/cpu0
│ │ │ ├── uevent
│ │ │ └── unbind
│ │ ├── drivers_autoprobe
│ │ ├── drivers_probe
│ │ └── uevent
...
$ ls /sys
block bus class dev devices firmware fs kernel module power
- block目录下的每个子目录都对应着系统的一个块设备。
- bus目录提供了一个系统总线视图。
- class目录包含了以高层功能逻辑组织起来的系统设备视图。
- devices目录是系统中设备拓扑结构视图,它直接映射除了内核中设备结构体的组织层次。
- firmware目录包含了一些诸如ACPI、EDD、EFI等低层子系统的特殊树。
- power目录包含了系统范围的电源管理数据。
- 最重要的目录是devices,该目录将设备模型导出到用户空间。目录结构就是系统中实际的设备拓扑。
sysfs提供了四类文件的创建与管理,那么它就离不开kobject的支持,在sysfs文件系统中是以文件的形式提供的,即:kobject的所有属性,都在它对应的sysfs目录下以文件的形式呈现。内核要在用户空间体现出来,就需要attribute。
$ ls /sys/class/leds/input1::capslock
brightness device max_brightness power subsystem trigger uevent
其中,brightness、trigger这些文件就是属性,以文件的形式提供。
总结一下:所谓的attibute,就是内核空间和用户空间进行信息交互的一种方法。例如某个driver定义了一个变量,却希望用户空间程序可以修改该变量,以控制driver的运行行为,那么就可以将该变量以sysfs attribute的形式开放出来。
Linux内核中,attribute分为普通的attribute和二进制attribute,如下:
/* include/linux/sysfs.h, line 26 */
struct attribute {
const char *name;
umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
bool ignore_lockdep:1;
struct lock_class_key *key;
struct lock_class_key skey;
#endif
};
/* include/linux/sysfs.h, line 100 */
struct bin_attribute {
struct attribute attr;
size_t size;
void *private;
ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
ssize_t (*write)(struct file *,struct kobject *, struct bin_attribute *,
char *, loff_t, size_t);
int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
struct vm_area_struct *vma);
};
属性读写方法sysfs_ops
#include <linux/sysfs.h>
struct sysfs_ops {
/* 读取该 sysfs 文件时该方法被调用 */
ssize_t (*show)(struct kobject *, struct attribute *, char *);
/* 写入该 sysfs 文件时该方法被调用 */
ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);
};
struct sysfs_ops中包含show和store两个函数指针,它们分别在sysfs文件读和文件写时调用,描述如何使用这些属性。
API
创建/删除新属性
#include <linux/sysfs.h>
int sysfs_create_file(struct kobject *kobj, const struct attribute *attr);
int sysfs_create_files(struct kobject *kobj, const struct attribute **attr);
void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
sysfs_create_file()这个接口通过attr参数指向相应的attribute结构体,而参数kobj则指定了属性所在的kobject对象。
在该函数被调用之前,给定的属性将被赋值,如果成功,该函数返回零。否则返回负的错误码。
kobject中ktype.sysfs_ops操作将负责处理新属性。现有的show()和store()方法必须能够处理新属性。
删除一个属性通过函数sysfs_remove_file()完成:一旦调用sysfs_remove_file,给定的属性将不复存在于给定的kobject目录中。
创建/删除软链接
int sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);
int sysfs_create_link_nowarn(struct kobject *kobj, struct kobject *target, const char *name);
void sysfs_remove_link(struct kobject *kobj, const char *name);
// 一旦调用返回,给定的连接将不复存在于给定的kobject目录中。
该函数创建的符号连接名由name指定,连接则由kobj对应的目录映射到target指定的目录。如果成功,则返回零,如果失败,返回负的错误码。
而由sysfs_create_link()创建的符号连接可通过函数sysfs_remove_link()删除
platform平台总线设备
概括来说,Platform设备包括:基于端口的设备(已不推荐使用,保留下来只为兼容旧设备,legacy);连接物理总线的桥设备;集成在SOC平台上面的控制器;连接在其它bus上的设备等等。
这些设备有一个基本的特征:可以通过CPU bus直接寻址(例如在嵌入式系统常见的“寄存器”)。因此,由于这个共性,内核在设备模型的基础上(device和device_driver),对这些设备进行了更进一步的封装,抽象出paltform bus、platform device和platform driver,以便驱动开发人员可以方便的开发这类设备的驱动。
Platform设备在内核中的实现主要包括三个部分:
- Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
- Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
- Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备。
struct platform_device {
const char *name; //dev,真正的设备(Platform设备只是一个特殊的设备,因此其核心逻辑还是由底层的模块实现)
int id; //用于标识该设备的ID
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
//回调函数
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
基本上操作和之前的普通总线相同
设备资源管理
一个设备能工作,需要依赖很多的外部条件,如供电、时钟等等,这些外部条件称作设备资源(device resouce)。对于现代计算机的体系结构,可能的资源包括:
a)power,供电。
b)clock,时钟。
c)memory,内存,在kernel中一般使用kzalloc分配。
d)GPIO,用户和CPU交换简单控制、状态等信息。
e)IRQ,触发中断。
f)DMA,无CPU参与情况下进行数据传输。
g)虚拟地址空间,一般使用ioremap、request_region等分配。
h)等等
devres能做的(也是它的唯一功能),就是:
提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。
而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。
struct devres_node {
struct list_head entry;
dr_release_t release;
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};
struct devres {
struct devres_node node;
/* -- 3 pointers */
unsigned long long data[]; /* guarantee ull alignment */
};
一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。
node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源。
--有参考,侵删--