首页 > 系统相关 >linux 设备驱动模型

linux 设备驱动模型

时间:2025-01-20 10:21:36浏览次数:1  
标签:struct bus 模型 dev kobj linux device 驱动 my

作者:baron
个人网站: baron-z.cn

    Linux设备模型的核心是使用Bus、Class、Device、Driver四个核心数据结构,将大量的、不同功能的硬件设备(以及驱动该硬件设备的方法),以树状结构的形式,进行归纳、抽象,从而方便Kernel的统一管理。学习平台mt8768,内核版本kernel-4.9

一、kobject

    kobject 它是构建设备模型的根基,它使设备模型下能在 /sys/ 下以目录层次的形式呈现动态管理着所属对象的生命周期,以及提供了与用户空间进行信息交互的属性文件(attribute). 文章将着重这几个方面进行描述.

温馨提示本文的目录文件是两个不同的概念,别搞混了。

  • 目录: 可以打开里面可以存放目录和文件
  • 文件: 不可以打开只能读写

1、相关数据结构

1) kobject

核心结构kobject,牢牢记住每一个注册到内核的 kobject 就是一个目录

/* Kobject: include/linux/kobject.h line 60 */
struct kobject {
const char *name; //该Kobject的名称,同时也是sys/中的目录名称。
struct list_head entry; //用于将该Kobject加入到Kset中的链表头
struct kobject *parent; //kobject以此形成层次目录结构
struct kset *kset; //该kobject所属的Kset,可以为NULL,如果存在且没有指定parent,则会把Kset.kobj做为parent。
struct kobj_type *ktype; //该Kobject属于的kobj_type,只有拥有ktye的kobj才能创建属性文件
struct sysfs_dirent *sd; //该Kobject在sysfs中的表示
struct kref kref; //用于原子操作的引用计数(在include/linux/kref.h中定义)
unsigned int state_initialized:1; //指示该Kobject是否已经初始化,在Kobject的Init,Put,Add等操作时进行异常校验。
unsigned int state_in_sysfs:1; //指示该Kobject是否已经在sysfs中呈现,以便在自动注销时在从sysfs中移除

/*
* 记录是否已经向用户空间发送add uevent,如果有,且没有发送remove uevent,
* 则在自动注销时,补发remove uevent,以便让用户空间正确处理。
*/
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1; //如果该字段为1,则表示忽略所有上报的uevent事件。
};

2) kobj_type

    用户空间和内核进行交互的窗口之一,通过这个结构中包含的属性链表指向的属性文件和属性操作函数进行交互。

/* include/linux/kobject.h, line 108 */
struct kobj_type {
void (*release)(struct kobject *kobj); //当引用计数为 0 时自动调用,将包含该种类型 kobject 的数据结构的内存空间释放掉
const struct sysfs_ops *sysfs_ops; //kobject 属性操作函数指针,上层在 open 属性文件时获取这个指针
struct attribute **default_attrs; //属性链表指针,指向 attribute * 数组
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};

3) attribute

    牢牢记住,每一个注册到内核的 attribute 都是它所属的 kobj 目录下的一个属性文件

--include/linux/sysfs.h
/* FIXME
* The *owner field is no longer used.
* x86 tree has been cleaned up. The owner
* attribute is still left for other arches.
*/
struct attribute {
const char *name; /* 属性的名字,即sys目录中的属性文件名称 */
struct module *owner; /* 属性的拥有者,已不再使用 */
mode_t mode; /* 属性的读写权限,定义在include/linux/stat.h*/
};

内核定义的读写权限

// include/uapi/linux/stat.h
/*
* "0" 表示没有权限
* "1" 表示可执行权限
* "2" 表示可写权限
* "4" 表示可读权限
*/
#define S_IRWXU 00700 //用户所有者拥有执行、写、读权限
#define S_IRUSR 00400 //用户所有者拥有读权限
#define S_IWUSR 00200 //用户所有者拥有写权限
#define S_IXUSR 00100 //用户所有者拥有执行权限

#define S_IRWXG 00070 //用户组拥有xxx
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010

#define S_IRWXO 00007 //其他人拥有xxx
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001

// include/linux/stat.h
#define S_IRWXUGO (S_IRWXU|S_IRWXG|S_IRWXO)
#define S_IALLUGO (S_ISUID|S_ISGID|S_ISVTX|S_IRWXUGO)
#define S_IRUGO (S_IRUSR|S_IRGRP|S_IROTH)
#define S_IWUGO (S_IWUSR|S_IWGRP|S_IWOTH)
#define S_IXUGO (S_IXUSR|S_IXGRP|S_IXOTH)

注:kobject是目录,attribute是文件。

4) sysfs_ops

    属性文件操作函数,当 cat 属性文件时,会调用 kobj->ktype->sysfs_ops->show,当 echo 属性文件时调用 kobj->ktype->sysfs_ops->store函数

--include/linux/sysfs.h

struct sysfs_ops { /* 对属性的操作函数 */
ssize_t (*show)(struct kobject *, struct attribute *,char *); /* 读属性操作函数 */
ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); /* 写属性操作函数 */
};

2、kobject 的创建与初始化

1) kobject_create

    该函数动态申请一个 kobject 结构,然后调用 kobject_init 对内部成员进行初始化,并且使用 dynamic_kobj_ktype 作为默认的 ktype


kernel-4.9/lib/kobject.c

struct kobject *kobject_create(void)
{
struct kobject *kobj;

kobj = kzalloc(sizeof(*kobj), GFP_KERNEL);
if (!kobj)
return NULL;

kobject_init(kobj, &dynamic_kobj_ktype);
return kobj;
}

2) kobject_init

    该函数初始化 kobj 的 ktype 以及内部成员

void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;

if (!kobj) { /* 检测kobj是否为NULL */
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) { /* 检测ktype是否为NULL */
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) { /* 判断kobject是否已经被初始化过,如果初始化过给出警告 */
/* do not error out as sometimes we can recover */
printk(KERN_ERR "kobject (%p): tried to init an initialized "
"object, something is seriously wrong.\n", kobj);
dump_stack();
}

kobject_init_internal(kobj); /* 初始化kobject内部成员 */
kobj->ktype = ktype; /* 设置ktype */
return;
error:

printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}

3) kobject_init_internal

    该函数初始化引用计数,entry 链表以及状态位。

static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj) /* 参数检测,确保kobj不为空 */
return;
kref_init(&kobj->kref); /* 引用计数初始化,初始化为1 */
INIT_LIST_HEAD(&kobj->entry); /* 初始化kobject链表 */
kobj->state_in_sysfs = 0; /* 状态位设置:未导出到sys中 */
kobj->state_add_uevent_sent = 0; /* 状态位设置:未添加uevent */
kobj->state_remove_uevent_sent = 0; /* 状态位设置:未移除uevent */
kobj->state_initialized = 1; /* 状态位设置:已完成初始化 */
}

kobject的创建与初始化基本也就反复用这三个接口了。

3、kobject 的注册

1) kobject_add

    设置 kobj 的 name 以及 parent 并将 kobject 注册进入内核

int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs)
{
int retval;

retval = kobject_set_name_vargs(kobj, fmt, vargs); //设置kobject的name
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent; //设置kobject的parent
return kobject_add_internal(kobj); //在sys/中添加kobject的信息
}

2) kobject_add_internal

    将 kobject 注册进入内核

static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;

if (!kobj)
return -ENOENT;

if (!kobj->name || !kobj->name[0]) {
WARN(1, "kobject: (%p): attempted to be registered with empty "
"name!\n", kobj);
return -EINVAL;
}

parent = kobject_get(kobj->parent); //如果父节点存在,则增加父节点引用计数

/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) { //判断是否存在 kset
if (!parent)
parent = kobject_get(&kobj->kset->kobj); //如果父节点不存在则使用Kset->kobj作为父节点,并增加引用计数
kobj_kset_kobj_kset_leavejoin(kobj); //将kobject中的entry链接进入kset中的list链表。
kobj->parent = parent;
}

pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "<NULL>",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");

error = create_dir(kobj); //使用kobj创建目录和属性文件
if (error) { //如果创建失败减少引用计数
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;

/* be noisy on error issues */
if (error == -EEXIST)
pr_err("%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
pr_err("%s failed for %s (error: %d parent: %s)\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "'none'");
} else
kobj->state_in_sysfs = 1; //如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了

return error;
}

    为了方便理解我在这里附上 create_dir 、 sysfs_create_dir_ns 以及 populate_dir 的源码

static int create_dir(struct kobject *kobj)
{
const struct kobj_ns_type_operations *ops;
int error;

error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj)); //创建kobj目录
if (error)
return error;

error = populate_dir(kobj); //创建kobj默认属性文件
if (error) {
sysfs_remove_dir(kobj);
return error;
}

/***省略部分代码***/

return 0;
}

int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn;

BUG_ON(!kobj);

if (kobj->parent) //判断parent是否存在,如果不存在则在sys/下创建目录
parent = kobj->parent->sd;
else
parent = sysfs_root_kn; // sys/ 所在目录

if (!parent)
return -ENOENT;

kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns); //创建目录
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
return PTR_ERR(kn);
}

kobj->sd = kn;
return 0;
}

static int populate_dir(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);
struct attribute *attr;
int error = 0;
int i;

if (t && t->default_attrs) {
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {
error = sysfs_create_file(kobj, attr); //遍历default_attrs,创建存在的属性文件
if (error)
break;
}
}
return error;
}

kobjcet的注册主要完成了下面三件事情

    1. 判断父节点是否存在,如果存在增加父节点引用计数,判断是否存在 kset 如果存在则链接进 kset 如果 kset 存在且父节点不存在则使用 Kset->kobj 作为父节点,增加 kset 点引用计数
    1. 调用 create_dir 为 kobj 创建目录和属性文件,在 create_dir 中调用 sysfs_create_dir_ns 为 kobject 创建目录,创建时会判断如果父节点为 NULL 则使用 sysfs_root_kn 作为父节点,即直接在 sys/ 目录下创建当前目录,在 create_dir 中调用 populate_dir 遍历属性文件链表创建默认属性文件
    1. 创建成功则设置 state_in_sysfs 为 1

内核也提供了一些组合API

//就是将kobject_creat 函数和 kobject_add 函数组合在一起的函数,创建并注册一个 kobject 到内核。
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)

//就是将 kobject_init 函数和 kobject_add 函数组合在一起的函数
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)

上面的 api 这么多,可以根据需要灵活选择来创建并注册 kobj,我也总结了两条很简单的规则:

  • 如果你的 kobject 不需要嵌入到更大的数据结构则使用kobject_create_and_add
  • 反之如果你的 kobject 需要嵌入到更大的数据结构则使用kobject_init_and_add

为什么这么选择呢这涉及到后文提到的对对象生命周期管理的内容,这里只需记住这两条规则就行了。

3) 编程实验 1

    实验很简单,我们只需要再内核中创建一个名为 my_kobject 的目录,并不需要将 kobject 嵌入到其他数据结构因此选择使用 kobject_create_and_add

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

static struct kobject *my_kobj;

static int my_kobject_init(void)
{
my_kobj = kobject_create_and_add("my_kobject", NULL);

return 0;
}

static void my_kobject_exit(void)
{
kobject_del(my_kobj);
kfree(my_kobj);
}

module_init(my_kobject_init);
module_exit(my_kobject_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL"); // 这个要放到后面, 这个申明前面的内容才会遵守 GPL 规则

验证结果

k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/cache #
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_kobject power //加载后生成 my_kobject 目录
k85v1_64:/cache #
k85v1_64:/cache # rmmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_kobject 目录

上面这种方式内核也用的挺多的,例如我们熟悉的 /sys/dev 、/sys/dev/char 、/sys/dev/block 等都是用这个方式创建的。

4) 编程实验 2

    在sys/下创建一个叫做 my_dir 的目录,这里我们将 kobject 嵌入到我们自己创建的结构中,于是选择 kobject_init_and_add 来创建目录。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
char* name;
int data;
struct kobject kobj;
};

/*
* 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
*/
struct my_dir* my_dir_create(char* name)
{
struct my_dir* my_dirp;

my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
if (!my_dirp)
return NULL;

my_dirp->name = name;

return my_dirp;
}

static struct my_dir* my_dirp;

static int my_dir_init(void)
{

my_dirp = my_dir_create("my_dir");

kobject_init_and_add(&my_dirp->kobj, NULL, NULL, "%s", my_dirp->name);

return 0;
}

static void my_dir_exit(void)
{
kobject_del(&my_dirp->kobj);
kfree(my_dirp);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/ # ls sys/
block/ bus/ dev/ firmware/ kernel/ mtk_rgu/
bootinfo/ class/ devices/ fs/ module/ power/
k85v1_64:/ # ls sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power
k85v1_64:/ #
k85v1_64:/ # cd cache/
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //加载后生成 my_dir 目录
k85v1_64:/cache #
2|k85v1_64:/cache # cd /sys/my_dir/
k85v1_64:/sys/my_dir # ls
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cd ..
k85v1_64:/sys # rmmod my_kobject.ko
k85v1_64:/sys # ls
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //卸载后移除 my_dir 目录

    看起来这种方式更加复杂,但实际上我们的 bus、 device、 device_driver 等都是使用这个方式,使用这个方式的优点见后文 "对象生命周期管理" 以及 "用户空间与内核信息交互"

4、在 sys/ 下组织出目录层次

    object 的核心功能之一,利用 kobject.parent 组织出文件的目录层次,前面 kobject 的注册已经分析的很清楚了这里就不再赘述了,内核还提供了链接文件的创建接口。

// 在kobj目录下创建指向target目录的软链接,name 为软链接文件名称
int __must_check sysfs_create_link(struct kobject *kobj, struct kobject *target, const char *name);

编程实验

    这个实验很简单,在/sys/目录下创建一个目录 father 然后在这个目录下创建两个子文件 son1 和 son2,再在 son1 下创建一个链接到 son2 的链接文件 link_to_son2。 只是单纯的展示层次目录关系,因此无需将kobject嵌入到更大的数据结构,采用 kobject_create_and_add 来注册

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

static struct kobject* father;
static struct kobject* son1;
static struct kobject* son2;

static int my_kobject_init(void)
{
//在 /sys/ 目录下创建一个目录 father
father = kobject_create_and_add("father", NULL);

//在 father 目录下创建两个子文件 son1 和 son2
son1 = kobject_create_and_add("son1", father);
son2 = kobject_create_and_add("son2", father);

//在 son1 下创建一个链接到son2的链接文件 link_to_son2
sysfs_create_link(son1, son2, "link_to_son2");

return 0;
}

static void my_kobject_exit(void)
{
kobject_del(father);
kfree(father);

kobject_del(son1);
kfree(son1);

kobject_del(son2);
kfree(son2);
}

module_init(my_kobject_init);
module_exit(my_kobject_exit);

验证结果

k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/
k85v1_64:/sys # ls
block bootinfo bus class dev devices father firmware fs kernel module mtk_rgu power //创建出的 father
k85v1_64:/sys # cd father/
k85v1_64:/sys/father # ls
son1 son2 //创建出的 son1 son2
k85v1_64:/sys/father # cd son1/
k85v1_64:/sys/father/son1 # ls
link_to_son2
k85v1_64:/sys/father/son1 # ls -la
total 0
drwxr-xr-x 2 root root 0 2021-01-11 06:40 .
drwxr-xr-x 4 root root 0 2021-01-11 06:40 ..
lrwxrwxrwx 1 root root 0 2021-01-11 06:41 link_to_son2 -> ../son2 //创建出的链接文件
k85v1_64:/sys/father/son1 #

5、kobj 对象生命周期管理

    kobject 还有一个非常强大的功能就是管理所嵌入的对象的生命周期,而<color=brown>引用计数 kref 则是它管理所嵌入对象生命周期的核心。对于kerf内核提供了两个下面函数来进行操作。

1) kobject_get

增加 kobj 引用计数

/**
* kobject_get - increment refcount for object.
* @kobj: object.
*/
struct kobject *kobject_get(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_get() is being "
"called.\n", kobject_name(kobj), kobj);
kref_get(&kobj->kref); //增加引用计数
}
return kobj;
}

2) kobject_put

void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.\n", kobject_name(kobj), kobj);
kref_put(&kobj->kref, kobject_release); //调用kref_put减少引用计数,同时传入回调函数
}
}

调用 kref_put 减少引用计数,同时传入回调函数 kobject_release,该回调函数在引用计数为0时调用。

int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
WARN_ON(release == (void (*)(struct kref *))kfree);

if (atomic_dec_and_test(&kref->refcount)) { /* 当引用计数为0时,调用 release 函数进行资源的释放 */
release(kref);
return 1;
}
return 0;
}

减少引用计数,当 kref 为 0 时调用传入的 release 回调函数,即前面的 kobject_release 函数

static void kobject_release(struct kref *kref)
{
kobject_cleanup(container_of(kref, struct kobject, kref));
}

kobject_put 传入的回调函数,使用 container_of 函数获取到包含 kref 的 kobjec 结构地址,并传入 kobject_cleanup

/*
* kobject_cleanup - free kobject resources.
* @kobj: object to cleanup
*/

static void kobject_cleanup(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);
const char *name = kobj->name;

pr_debug("kobject: '%s' (%p): %s, parent %p\n",
kobject_name(kobj), kobj, __func__, kobj->parent);

if (t && !t->release) // 判断 release 函数是否存在
pr_debug("kobject: '%s' (%p): does not have a release() "
"function, it is broken and must be fixed.\n",
kobject_name(kobj), kobj);

/* send "remove" if the caller did not do it but sent "add" */
if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) {
pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n",
kobject_name(kobj), kobj);
kobject_uevent(kobj, KOBJ_REMOVE); //发送 uevent 事件 KOBJ_REMOVE
}

/* remove from sysfs if the caller did not do it */
if (kobj->state_in_sysfs) { // 如果在sys中存在kobj则调用kobject_del删除kobj
pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n",
kobject_name(kobj), kobj);
kobject_del(kobj);
}

if (t && t->release) { //如果release存在则调用release函数
pr_debug("kobject: '%s' (%p): calling ktype release\n",
kobject_name(kobj), kobj);
t->release(kobj);
}

/* free name if we allocated it */
if (name) { // 释放name空间
pr_debug("kobject: '%s': free name\n", name);
kfree_const(name);
}
}

    判断 kobj 的 release 函数是否存在,如果在 sys 中存在 kobj 则调用 kobject_del 删除 kobj, 如果 release 存在则调用 release 函数,该函数需要自己实现,如果是用 kobject_create 创建的 kobj,则会使用默认 dynamic_kobj_ktype 中的 release。

/**
* kobject_del - unlink kobject from hierarchy.
* @kobj: object.
*/
void kobject_del(struct kobject *kobj)
{
struct kernfs_node *sd;

if (!kobj)
return;

sd = kobj->sd;
sysfs_remove_dir(kobj); //删除sys目录相关文件
sysfs_put(sd);

kobj->state_in_sysfs = 0;
kobj_kset_leave(kobj); //删除所属的kset链表中的kobj成员,减少该kset引用计数
kobject_put(kobj->parent); //减少parent计数
kobj->parent = NULL;
}

删除sys目录相关文件,减少parent引用计数,并调用 kobj_kset_leave函数从 kset list 中移除这个kobject

/* remove the kobject from its kset's list */
static void kobj_kset_leave(struct kobject *kobj)
{
if (!kobj->kset)
return;

spin_lock(&kobj->kset->list_lock);
list_del_init(&kobj->entry);
spin_unlock(&kobj->kset->list_lock);
kset_put(kobj->kset);
}

可以看到 kobject_put 的实现比较复杂,但总的来说它也就完成了下面几件事情

    1. 减少 kobject 引用计数,当 kobject 引用计数为 0 时调用 kobject->ktype->release函数
    1. 向用户空间发送 uevent 事件 KOBJ_REMOVE
    1. 调用 kobject_del 删除 sys 目录相关文件,从属于的 kset 链表中删除该 kobj 成员,减少 kset 引用计数,减少 parent 的引用计数(这里分别减少了paren t的和所属的 kset 的引用计数)

    其中我们需要实现的也就是这个回调函数 kobject->ktype->release ,但实际上内核提供的设备模型已经都实现好了,例如 bus 总线

static void bus_release(struct kobject *kobj)
{
struct subsys_private *priv = to_subsys_private(kobj);
struct bus_type *bus = priv->bus;

kfree(priv);
bus->p = NULL;
}

static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
.release = bus_release,
};

int bus_register(struct bus_type *bus)
{
...
priv->subsys.kobj.ktype = &bus_ktype;
...
}

    其他的 device,device_driver,等凡是内核提供的结构基本内核都帮我们设计好了它的 relase 函数

3) 优化 my_dir

    有了上面知识了之后我们也可以用其来优化我们前面创建的 my_dir, 给我们的 my_dir 增加自动释放自身数据结构的功能。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
char* name;
int data;
struct kobject kobj;
};

/*
* 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
*/
struct my_dir* my_dir_create(char* name)
{
struct my_dir* my_dirp;

my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
if (!my_dirp)
return NULL;

my_dirp->name = name;

return my_dirp;
}

static struct my_dir* my_dirp;

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构
void my_dir_release(struct kobject *kobj)
{
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

printk("my dir release\n");

kfree(my_dirp);
}

struct kobj_type my_dir_type = {
.release = my_dir_release,
};

static int my_dir_init(void)
{

my_dirp = my_dir_create("my_dir");

kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

return 0;
}

static void my_dir_exit(void)
{
kobject_put(&my_dirp->kobj);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //这里创建出 my_dir

k85v1_64:/cache # rmmod my_kobject.ko

此同时内核打印出下面 log

[ 52.897412] <7>.(7)[2526:rmmod] my dir release

在查看时发现目录已经被删除

k85v1_64:/cache # ls /sys/
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu power //mydir 被移除
k85v1_64:/cache #

    这里用了一个非常巧妙的方式实现了,利用 kobject 释放其所嵌入的更大的数据结构的功能,从而实现对 my_dir 生命周期的管理,于是可以得到下面这个结论: 凡是需要做对象生命周期管理的对象,都可以通过内嵌kobject来实现需求 该结论来自窝窝科技的文章

6、用户空间与内核信息交互

    kobject 的核心功能之一, 能实现用户空间与内核空间的信息交互,该功能非常, 非常, 非常重要, 是驱动开发中最常用的手段之一. 我们知道每一个注册的 kobject 都会在 /sys 中以目录的形式呈现,也就是 bus 等数据结构可以利用嵌入 kobject 可以使它显示在 /sys 中。而 attribute 又会以文件的形式出现在目录中, 通过这些属性文件, 我们就能够获取/修改内核中的变量,字符串等信息。在Linux内核中,attribute 分为普通的 attribute 和二进制 attribute,这里只记录普通的,二进制的没研究,后面有机会研究了再补充(希望渺茫).

1) 属性文件调用逻辑

在 fs/sysfs/file.c 文件下查看相关的逻辑代码

static const struct sysfs_ops *sysfs_file_ops(struct kernfs_node *kn)
{
struct kobject *kobj = kn->parent->priv;

if (kn->flags & KERNFS_LOCKDEP)
lockdep_assert_held(kn);
return kobj->ktype ? kobj->ktype->sysfs_ops : NULL;
}

/* kernfs read callback for regular sysfs files with pre-alloc */
static ssize_t sysfs_kf_read(struct kernfs_open_file *of, char *buf,
size_t count, loff_t pos)
{
const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
struct kobject *kobj = of->kn->parent->priv;
ssize_t len;
...
len = ops->show(kobj, of->kn->priv, buf);
...

return min_t(ssize_t, count, len);
}

static ssize_t sysfs_kf_write(struct kernfs_open_file *of, char *buf,
size_t count, loff_t pos)
{
const struct sysfs_ops *ops = sysfs_file_ops(of->kn);
struct kobject *kobj = of->kn->parent->priv;

if (!count)
return 0;

return ops->store(kobj, of->kn->priv, buf, count);
}

处理过程很简单在 cat/echo 属性文件时(读/写属性文件写数据时)

  • 1, 先调用 sysfs_file_ops 获取到kobj->ktype->sysfs_ops 指针
  • 2, 调用对应内核的 show/store 函数。

从这里可以得出两条结论:

    1. 对于用户空间来讲,只负责把数据丢给内核的 store 以及从内核的 show 函数获取数据,至于 store 的数据用来做什么和 show 获取到数据表示什么意思则由内核决定。
    1. 如果从属的 kobject(就是 attribute 文件所在的目录)没有 ktype,或者没有 ktype->sysfs_ops 指针,是不允许它注册任何 attribute 的

2) 属性文件的创建以及删除

内核也提供了创建属性文件的 api

include/linux/sysfs.h
int sysfs_create_file(struct kobject *kobj, struct attribute *attr); //在传入的 kobj下创建 attr 属性文件

static inline void sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) //在传入的 kobj 下移除 attr 属性文件

int sysfs_create_files(struct kobject *kobj, const struct attribute **ptr) //在 kobj 下创建传入的 prt 指向的一组属性文件

void sysfs_remove_files(struct kobject *kobj, const struct attribute **attr);//在 kobj 下移除传入的 prt 指向的一组属性文件

3) 在 my_dir 下创建属性文件

动手实践一下,在 my_dir 下创建两个属性文件

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
char* name;
int data;
struct kobject kobj;
};

/*
* 动态创建一个 struct my_dir, 并初始化 name 后返回该指针
*/
struct my_dir* my_dir_create(char* name)
{
struct my_dir* my_dirp;

my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
if (!my_dirp)
return NULL;

my_dirp->name = name;

return my_dirp;
}

static struct my_dir* my_dirp;

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构
void my_dir_release(struct kobject *kobj)
{
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

printk("my dir release\n");

kfree(my_dirp);
}

/* 读属性操作函数, cat 属性文件时调用这个函数 */
ssize_t my_dir_show(struct kobject *kobj, struct attribute *attr,char *buf)
{

printk("my dir show attr->name : %s\n", attr->name);

sprintf(buf, "%s\n", attr->name);
return strlen((char*)attr->name) +2;
}

/* 写属性操作文件, echo 属性文件时调用这个函数 */
ssize_t my_dir_store(struct kobject *kobj,struct attribute *attr,const char *buf, size_t count)
{
printk("my dir store : %s\n", buf);

return count;
}

//有 kobj_type 同时还要存在 my_sysfs_ops
struct sysfs_ops my_sysfs_ops = {
.show = my_dir_show,
.store = my_dir_store,
};

//要创建属性文件首先要有 kobj_type
struct kobj_type my_dir_type = {
.release = my_dir_release,
.sysfs_ops = &my_sysfs_ops,
};

/* 每一个注册的 attribute 结构都是一个属性文件,这里创建两个属性文件*/
struct attribute my_dir_attr1 = {
.name = "my_dir_attr1",
.mode = S_IRWXUGO,
};

struct attribute my_dir_attr2 = {
.name = "my_dir_attr2",
.mode = S_IRWXUGO,
};

static int my_dir_init(void)
{

my_dirp = my_dir_create("my_dir");

kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

//创建 my_dir_attr1 属性文件
sysfs_create_file(&my_dirp->kobj, &my_dir_attr1);

//创建 my_dir_attr2 属性文件
sysfs_create_file(&my_dirp->kobj, &my_dir_attr2);

return 0;
}

static void my_dir_exit(void)
{
kobject_put(&my_dirp->kobj);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

k85v1_64:/cache # insmod my_kobject.ko //加载模块
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/
k85v1_64:/sys # ls
block bootinfo bus class dev devices firmware fs kernel module mtk_rgu my_dir power //查看生成了 my_dir
k85v1_64:/sys # cd my_dir/ //进入 my_dir
k85v1_64:/sys/my_dir # ls
my_dir_attr1 my_dir_attr2 //查看生成了 my_dir_attr1 my_dir_attr2
k85v1_64:/sys/my_dir #

k85v1_64:/sys/my_dir # echo 123 > my_dir_attr1 //写入123

//于此同时内核打印下面信息
[ 848.043148] <0>.(0)[5617:sh]my dir store : 123

k85v1_64:/sys/my_dir # cat my_dir_attr1 //读取数据
my_dir_attr1
//于此同时内核打印下面信息
[ 1002.452204] <7>.(7)[8065:cat]my dir show attr->name : my_dir_attr1

k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # echo 222 > my_dir_attr2
//于此同时内核打印下面信息
[ 1028.696923] <1>.(1)[5617:sh]my dir store : 222

k85v1_64:/sys/my_dir # cat my_dir_attr2
my_dir_attr2
//于此同时内核打印下面信息
[ 1033.116307] <7>.(7)[8397:cat]my dir show attr->name : my_dir_attr2

4) 优化属性文件操作

    上面的例子我们虽然创建了属性文件,也能操作属性文件,但是两个属性文件最终都是调用的同一个 show/store 函数,即同一个 kobject 下的所有的属性文件使用同一个属性操作函数。而这样明显属性文件就失去了它的独立性。

怎么实现属性文件自己的 show/store ?

我们可以将 attribute 嵌入到更大的数据结构中,该数据结构包含真正的 show/store 函数然后使用 my_dir_type 中的 show/store 函数作为中转函数,利用 container_of 调用属性文件真正的 show/store 函数.

于是我们调整代码架构将通用的部分提取出来作为 my_kobject_core.c 这部分代码如下:

#include "my_kobject_core.h"

//当引用计数为 0 时会自动调用这个函数来释放包含 kobject 的更大的数据结构 struct my_dir
void my_dir_release(struct kobject *kobj)
{
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

printk("my dir release\n");

kfree(my_dirp);
}

/* 读属性文件操作函数中间层 */
ssize_t my_dir_show(struct kobject *kobj, struct attribute *attr,char *buf)
{
struct my_attribute *my_attr;
ssize_t ret = -EIO;

my_attr = container_of(attr, struct my_attribute, attr);
if (my_attr->show)
ret = my_attr->show(kobj, my_attr, buf);

return ret;
}

/* 写属性文件操作函数中间层 */
ssize_t my_dir_store(struct kobject *kobj,struct attribute *attr,const char *buf, size_t count)
{
struct my_attribute *my_attr;
ssize_t ret = -EIO;

my_attr = container_of(attr, struct my_attribute, attr);
if (my_attr->store)
ret = my_attr->store(kobj, my_attr, buf, count);

return ret;
}

//有 kobj_type 同时还要存在 my_sysfs_ops
struct sysfs_ops my_sysfs_ops = {
.show = my_dir_show,
.store = my_dir_store,
};

//要创建属性文件首先要有 kobj_type
struct kobj_type my_dir_type = {
.release = my_dir_release,
.sysfs_ops = &my_sysfs_ops,
};

/* 在 /sys/ 下创建一个名为 name dir */
struct my_dir* my_dir_regiseter(char* name)
{
struct my_dir* my_dirp;

my_dirp = kzalloc(sizeof(*my_dirp), GFP_KERNEL);
if (!my_dirp)
return NULL;

my_dirp->name = name;

kobject_init_and_add(&my_dirp->kobj, &my_dir_type, NULL, "%s", my_dirp->name);

return my_dirp;
}
EXPORT_SYMBOL_GPL(my_dir_regiseter);

/* 移除注册的 my_dir */
void my_dir_unregiseter(struct my_dir* my_dirp)
{
if(my_dirp)
kobject_put(&my_dirp->kobj);
}
EXPORT_SYMBOL_GPL(my_dir_unregiseter);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

我们将公共的部分放在头文件 my_kobject_core.h 中

#ifndef _KOBJECT_CORE_H_
#define _KOBJECT_CORE_H_

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/slab.h>
#include <linux/kobject.h>

struct my_dir
{
char* name;
int date;
struct kobject kobj;
};

struct my_attribute {
struct attribute attr; //将 attribute 嵌入到更大的数据结构中
//真正的 show/store函数
ssize_t (*show)(struct kobject *kobj, struct my_attribute *attr, char *buf);
ssize_t (*store)(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count);
};

extern struct my_dir* my_dir_regiseter(char* name);
extern void my_dir_unregiseter(struct my_dir* my_dirp);

#endif

    在我们想要创建目录的时候就调用 my_dir_regiseter来创建 my_dir,想要创建属性文件的时候调用 sysfs_create_file来创建。 于是在我们真正在 my_kobject.c 中创建 my_dir 以及其属性文件如下。

#include "my_kobject_core.h"

static struct my_dir* my_dirp;

//属于 my_dir_attr1 自己的 show 函数
ssize_t my_dir_attr1_show(struct kobject *kobj, struct my_attribute *attr, char *buf)
{
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

printk("%s\n",attr->attr.name);
sprintf(buf, "%s : %d\n", attr->attr.name, my_dirp->date);
return strlen(buf);
}

//属于 my_dir_attr1 自己的 store 函数
ssize_t my_dir_attr1_store(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count)
{
int i = 0;
int tmp = 0;

struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

//由于输入的是字符串这里做一个简单的转换,并不严谨仅用于展示功能
for(i=0; i<count; i++)
{
if(buf[i] > '0' && buf[i] < '9'){
tmp = 10 *tmp + (buf[i] - '0');
}
}

my_dirp->date = tmp;

printk("%s store : my_dirp->date = %d, buf = %s\n",attr->attr.name, my_dirp->date, buf);

return count;
}

//属于 my_dir_attr2 自己的 show 函数
ssize_t my_dir_attr2_show(struct kobject *kobj, struct my_attribute *attr, char *buf)
{
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

printk("%s\n",attr->attr.name);
sprintf(buf, "%s : %d\n", attr->attr.name, my_dirp->date);
return strlen(buf);
}

//属于 my_dir_attr2 自己的 store 函数
ssize_t my_dir_attr2_store(struct kobject *kobj, struct my_attribute *attr, const char *buf, size_t count)
{
int i = 0;
int tmp = 0;
struct my_dir* my_dirp = container_of(kobj, struct my_dir, kobj);

//由于输入的是字符串这里做一个简单的转换,并不严谨仅用于展示功能
for(i=0; i<count; i++)
{
if(buf[i] > '0' && buf[i] < '9')
tmp = 10 *tmp + (buf[i] - '0');
}

my_dirp->date = tmp;

printk("%s store : my_dirp->date = %d, buf = %s\n",attr->attr.name, my_dirp->date, buf);

return count;
}

/* 这里创建两个属性文件 my_dir_attr1、my_dir_attr2 */
struct my_attribute my_dir_attr1 = {
.attr ={
.name = "my_dir_attr1",
.mode = S_IRWXUGO,
},

.show = my_dir_attr1_show,
.store = my_dir_attr1_store,

};

struct my_attribute my_dir_attr2 = {
.attr ={
.name = "my_dir_attr2",
.mode = S_IRWXUGO,
},

.show = my_dir_attr2_show,
.store = my_dir_attr2_store,

};

static int my_dir_init(void)
{
my_dirp = my_dir_regiseter("my_dir");

//创建 my_dir_attr1 属性文件
sysfs_create_file(&my_dirp->kobj, &my_dir_attr1.attr);

//创建 my_dir_attr2 属性文件
sysfs_create_file(&my_dirp->kobj, &my_dir_attr2.attr);

return 0;
}

static void my_dir_exit(void)
{
my_dir_unregiseter(my_dirp);
}

module_init(my_dir_init);
module_exit(my_dir_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果:

//加载模块
k85v1_64:/cache # insmod my_kobject_core.ko
k85v1_64:/cache # insmod my_kobject.ko
k85v1_64:/cache #
k85v1_64:/cache #
k85v1_64:/cache # cd /sys/my_dir/
k85v1_64:/sys/my_dir # ls
my_dir_attr1 my_dir_attr2
k85v1_64:/sys/my_dir # echo 11 > my_dir_attr1
//同时内核打印出
[ 5223.566440] <2>.(2)[23263:sh]my_dir_attr1 store : my_dirp->date = 11, buf = 11
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cat my_dir_attr1
my_dir_attr1 : 11
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # echo 22 > my_dir_attr2
//同时内核打印出
[ 5248.493173] <3>.(3)[23263:sh]my_dir_attr2 store : my_dirp->date = 22, buf = 22
k85v1_64:/sys/my_dir #
k85v1_64:/sys/my_dir # cat my_dir_attr2
my_dir_attr2 : 22
k85v1_64:/sys/my_dir #

    上面的逻辑实现比前面的代码要复杂一点点,可以花点时间看一下,这个方式的牛逼之处在于通过 kobject 我们将我们自己创建的数据结构 struct my_dir 开放到用户空间,以目录的形式呈现出来,同时通过属性文件用户空间能够获取和修改 my_dir.date 这个属于my_dir的成员变量。内核的 bus、device、device_driver 等设备模型不过是在这个基础之上增加了一些其他功能,如设备和驱动的匹配等。

二 、kset

     kset 本身包含 kobject 结构,因此拥有上述 kobject 的所有特性,除此之外, kset 具有管理 kobject 的功能以及提供热插拔功能. kset 的数据结构如下

/* include/linux/kobject.h, line 159 */
struct kset {

struct list_head list; // 该Kset下所有的Kobject都被链接进入该节点
spinlock_t list_lock;

struct kobject kobj; // kset也是kobj的一种,因为c语言无法向面向对象一样继承,因此只能使用这种方式。

/*
* 该kset的uevent操作函数集。
* 当任何Kobject需要上报uevent时,都要调用它所从属的kset的uevent_ops,
* 添加环境变量,或者过滤event(kset可以决定哪些event可以上报)。
* 因此,如果一个kobject不属于任何kset时,是不允许发送uevent的。
*/
const struct kset_uevent_ops *uevent_ops;
}

可以看出主要多出了两个数据结构 list 和 kset_uevent_ops,在前面的 kobject_add 分析中可以知道,只要是 kobject 属于某个 kset 那么都会被链接到所属的 kset 中的 list 链表。 也就是 kset 具有管理 kobject 的功能,举个栗子,例如: 当 kernel 关机时, 会在 device_shutdown 中利用 devices_kset->list 找到该链表上的所有设备,并做相应的操作

void device_shutdown(void)
{
struct device *dev, *parent;

spin_lock(&devices_kset->list_lock);

//通过 kset->list 链表找到链表上的所有设备
while (!list_empty(&devices_kset->list)) {
dev = list_entry(devices_kset->list.prev, struct device, kobj.entry);

...... //对所有的设备做相关操作

}
spin_unlock(&devices_kset->list_lock);
}

    我们所熟知的热插拔机制的功能也是由 kset 提供的,热插拔指的是当一个设备加入系统,内核如何通知用户空间。感兴趣可以看看这篇文章:http://bbs.chinaunix.net/thread-3678367-1-1.html

    热插拔大致的可以分为两个部分组成,内核部分和用户空间部分,而 kset则负责将事件发送到用户空间,而发送的方式则是 uevent。 大致的流程如下

外设接入设备,设备产生中断
内核响应中断,调用 device_add 添加新设备
在device_add中会调用 kobject_uevent 将事件通知给用户空间

    由于热插拔的内容相对复杂可以单独成文,而且也不影响对设备模型的理解,因此这里只提一下它的功能,关于热插拔更加详细的内容后面认真学习之后再整理出一篇文章。

1、 kset的创建与注册

1) kset_create

    动态获取一个 kset 内存空间,初始化 uevent_ops ,初始化 parent_kobj ,初始化一个内核默认的 kset_ktype ,初始化 kset 中的 kset 为NULL

static struct kset *kset_create(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int retval;

kset = kzalloc(sizeof(*kset), GFP_KERNEL); //动态获得一个 kset 内存空间
if (!kset)
return NULL;

retval = kobject_set_name(&kset->kobj, "%s", name); //初始化 name
if (retval) {
kfree(kset);
return NULL;
}

kset->uevent_ops = uevent_ops; //初始化 uenent_ops
kset->kobj.parent = parent_kobj; //初始化 parent_kobj

/*
* The kobject of this kset will have a type of kset_ktype and belong to
* no kset itself. That way we can properly free it when it is
* finished being used.
*/
kset->kobj.ktype = &kset_ktype; //初始化该 kset 的 ktype
kset->kobj.kset = NULL; //kset 的 kset 为空

return kset;
}

2) kset_init

初始化kset的kobj成员,初始化list链表

/**
* kset_init - initialize a kset for use
* @k: kset
*/
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj); //初始化kobj成员
INIT_LIST_HEAD(&k->list); //初始化list链表
spin_lock_init(&k->list_lock);
}

3) kset_register

将 kset 注册进入内核

/**
* kset_register - initialize and add a kset.
* @k: kset.
*/
int kset_register(struct kset *k)
{
int err;

if (!k)
return -EINVAL;

kset_init(k); //调用kset_init初始化kobj成员,初始化list链表
err = kobject_add_internal(&k->kobj); //注册kobj即生成kset对应的目录
if (err)
return err;
kobject_uevent(&k->kobj, KOBJ_ADD); //发送uevent事件,KOBJ_ADD
return 0;
}

3) kset_create_and_add

动态创建一个kset结构,并将其注册,其实就是 kset_create 和 kset_register 的组合

struct kset *kset_create_and_add(const char *name,
const struct kset_uevent_ops *uevent_ops,
struct kobject *parent_kobj)
{
struct kset *kset;
int error;

kset = kset_create(name, uevent_ops, parent_kobj); //动态创建 kset
if (!kset)
return NULL;
error = kset_register(kset); //注册kset
if (error) {
kfree(kset);
return NULL;
}
return kset;
}

kset 的创建与注册的函数的选择和前面的kobject一样:

当 kset 需要嵌入到更大的数据结构时使用 kset_register
当 kset 不需嵌入到更大的数据结构时使用 kset_create_and_add

2、卸载kset

1) kset_unregister

从卸载一个keset

/**
* kset_unregister - remove a kset.
* @k: kset.
*/
void kset_unregister(struct kset *k)
{
if (!k)
return;
kobject_del(&k->kobj);
kobject_put(&k->kobj);
}

2) kset_put

减少kset的kobj成员引用计数

static inline void kset_put(struct kset *k)
{
kobject_put(&k->kobj);
}

三、bus 总线

    Linux 认为总线是CPU和一个或多个设备之间信息交互的通道。就设备模型而言,所有设备都是通过总线连接的,这样的总线有两类

  • 第一类是 i2c、spi、usb 等这类总线,这类总线有实际的物理总线,它本身就有总线这个概念,我们直接根据功能将其抽象出来成一个个单独的总线。
  • 第二类就是虚拟总线,如i2c控制器、GPIO控制器、UART控制器等,cpu 直接通过寄存器或地址与之进行通信。但是呢我们又想将他们统一到设备模型,因此 platform 这类虚拟总线就诞生了。

1、数据结构

1) bus_type

每一个 bus_type 都代表一个 bus

struct bus_type {
const char *name; //总线类型的名称
const char *dev_name; //当 dev->init_name 为空时,使用"bus->dev_name+device ID”的形式,为的设备生成一个名称。
struct device *dev_root; //bus总线上的设备默认的根节点
struct device_attribute *dev_attrs; //总线所属设备的默认的属性文件
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 的 device 和 device_driver 注册到bus时,调用该接口
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); //用于 bus 的 device 对环境变量的添加
int (*probe)(struct device *dev); //match成功之后调用这个函数
int (*remove)(struct device *dev); //和probe相反
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);

int (*num_vf)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p; //描述 bus 在 /sys 中的层次结构,也管理着该总线上的dev和driver以及总线的一些属性
struct lock_class_key lock_key;
};

2) subsys_private

这个结构被用在两个地方 class 和 bus,在不同的位置有不同的含义,当它出现在 bus下用它描述 bus 在 /sys 中的层次结构

struct subsys_private {
struct kset subsys; // 该 bus 在 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 的设备链表,该 bus 下的所有 device 挂接在该节点
struct klist klist_drivers; //bus 的驱动链表,该 bus下 的所有 drivers 挂接在该节点
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1; //用于控制该bus下的 drivers 或者 device 是否自动 probe
struct bus_type *bus; //保存该 subsys_private 结构所属 bus

struct kset glue_dirs; // 在 device_add 中使用
struct class *class; // 当用在 class 时,保存该 subsys_private 结构所属的 class
};

2、bus 根目录的初始化

buses_init 在 driver_init 中被调用,用于初始化 bus 的根目录,以后所有的 bus 总线均为该目录的子目录。

int __init buses_init(void)
{
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
if (!bus_kset)
return -ENOMEM;

system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
if (!system_kset)
return -ENOMEM;

return 0;
}

buses_init 会创建以下目录

sys/bus
sys/system

3、bus 总线的注册

该函数向内核注册一个 bus 总线

int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
struct lock_class_key *key = &bus->lock_key;

//动态创建一个subsys_private结构priv
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
if (!priv)
return -ENOMEM;

priv->bus = bus; //初始化priv->bus
bus->p = priv; //初始化bus->p

BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

//设置 priv->subsys.kobj->name = bus->name
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;

priv->subsys.kobj.kset = bus_kset; //初始化kset
priv->subsys.kobj.ktype = &bus_ktype; //初始化ktype
priv->drivers_autoprobe = 1; //设置默认可以自动匹配probe

//到这里我们发现并没有设置parent,因此使用bus_kset作为parent,创建sys/bus/bus->name文件夹
retval = kset_register(&priv->subsys);
if (retval)
goto out;

//在总线所在目录创建 uevent 属性文件
retval = bus_create_file(bus, &bus_attr_uevent);
if (retval)
goto bus_uevent_fail;

//在总线所在目录创建 devices 目录
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}

//在总线所在目录创建 drivers 目录
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_devices,该总线上的所有设备将被链入该链表
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
//初始化驱动链表 klist_drivers,该总线上的所有驱动将被链入该链表
klist_init(&priv->klist_drivers, NULL, NULL);

/*
* 在当前总线所在目录创建 drivers_probe 属性文件
* 在当前总线所在目录创建 drivers_autoprobe 属性文件
*/
retval = add_probe_files(bus);
if (retval)
goto bus_probe_files_fail;

//在当前总线所在目录创建 bus_groups 属性文件(一组)
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;
}

bus_register 会创建以下节点:

/sys/bus/xxx
/sys/bus/xxx/uevent
/sys/bus/xxx/devices ----- bus下注册的dev将出现在这个目录
/sys/bus/xxx/drivers ----- bus下注册的drv将出现在这个目录
/sys/bus/xxx/drivers_probe
/sys/bus/xxx/drivers_autoprobe

    bus_register 创建并初始化 priv->klist_devices 和 priv->klist_drivers 这两条由该总线维护的非常重要的两条链表,该总线下的 dev 和 drv 会分别链入这两条链表。

4、编程实验创建自己的总线

在内核中创建一个名为 my_bus 的总线

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

int my_bus_probe(struct device *dev)
{
printk("my_bus_probe\n");
return 0;
}

int my_bus_match(struct device *dev, struct device_driver *drv)
{
printk("my_bus_match\n");
return 0;
}

int my_bus_remove(struct device *dev)
{
printk("my_bus_remove\n");
return 0;
}

struct bus_type my_bus = {
.name = "my_bus",
.probe = my_bus_probe,
.match = my_bus_match,
.remove = my_bus_remove,
};

static int my_bus_init(void)
{
bus_register(&my_bus); //注册bus总线
return 0;
}

static void my_bus_exit(void)
{
bus_unregister(&my_bus); //卸载bus总线
}

module_init(my_bus_init);
module_exit(my_bus_exit);

模块验证

XF-E97:/sys/bus # ls
amba container gpio iio scsi spi
clockevents cpu hid mmc sdio usb
clocksource event_source i2c platform snd_seq workqueue
XF-E97:/sys/bus #
//加载模块
XF-E97:/sys/bus # insmod /cache/my_bus.ko
XF-E97:/sys/bus #
XF-E97:/sys/bus # ls //查看生成了新的总线 my_bus
amba container gpio iio platform snd_seq workqueue
clockevents cpu hid mmc scsi spi
clocksource event_source i2c my_bus sdio usb
XF-E97:/sys/bus # cd my_bus/
XF-E97:/sys/bus/my_bus # ls //查看 my_bus 下生成节点
devices drivers drivers_autoprobe drivers_probe uevent
XF-E97:/sys/bus/my_bus #

该模块创建下面节点

/sys/bus/my_bus
/sys/bus/my_bus/uevent
/sys/bus/my_bus/devices
/sys/bus/my_bus/drivers
/sys/bus/my_bus/drivers_probe
/sys/bus/my_bus/drivers_autoprobe

4、创建属性文件

内核也提供了在 bus 总线下创建属性文件的接口

//用于在bus所在目录创建属性文件
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
//删除在bus所在目录创建的属性文件
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr)

四、device 设备

device 用于抽象系统中所有的硬件设备,描述它的名字、属性、从属的 Bus、从属的 Class 等信息。

1、数据结构

1) device

struct device {
struct device *parent; //父设备

struct device_private *p; //保存设备链表

struct kobject kobj; //对应的kobj用于创建dev目录
const char *init_name; //设备的名称,非常重要,如果不存在则使用"bus->name + device ID" 如果都不存在则不允许创建设备
const struct device_type *type; //设备类型

struct mutex mutex; /* mutex to synchronize calls toits driver. */

struct bus_type *bus; /* 设备挂接的总线 */
struct device_driver *driver; /* 匹配成功后,链接匹配到的driver */
void *platform_data; /* 用于保存私有数据。 */
void *driver_data; /* Driver data, set and get with dev_set get_drvdata */
struct dev_links_info links;
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_GENERIC_MSI_IRQ
struct list_head msi_list;
#endif

#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
const struct dma_map_ops *dma_ops;
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */
unsigned long dma_pfn_offset;

struct device_dma_parameters *dma_parms;

struct list_head dma_pools; /* dma pools (if dma'ble) */

struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma allocations */
#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; /* 设备号 */
u32 id; /* device instance */

spinlock_t devres_lock;
struct list_head devres_head;

struct klist_node knode_class; //链接到dev->class->p->klist_devices节点
struct class *class; //所属的class
const struct attribute_group **groups; /* optional groups */

void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct iommu_fwspec *iommu_fwspec;

bool offline_disabled:1;
bool offline:1;
bool of_node_reused:1;
};

2) device_private

struct device_private {
struct klist klist_children; //用于挂接子设备的 knode_parent
struct klist_node knode_parent; //挂接到父设备的 klist_children
struct klist_node knode_driver; //链入连接的 driver 的 klist_devices 链表
struct klist_node knode_bus; //链接进入 bus->p->klist_devices 链表
struct list_head deferred_probe;
struct device *device; //指向所属的 dev
};

2、device 根目录的初始化

device文件目录初始化函数,在 driver_init 中被调用,内核初始化时被调用。

int __init devices_init(void)
{
/*
* 在 sys/ 下创建一个名为 devices 目录,deices_kset
* 在 sys/ 下创建一个名为 dev 的目录,dev_kobj
* 在 sys/dev/ 下创建一个名为 block 的目录,sysfs_dev_block_kobj
* 在 sys/dev/ 下创建一个名为 char 的目录,sysfs_dev_char_kobj
*/
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;

return 0;

char_kobj_err:
kobject_put(sysfs_dev_block_kobj);
block_kobj_err:
kobject_put(dev_kobj);
dev_kobj_err:
kset_unregister(devices_kset);
return -ENOMEM;
}

devices_init 初始化了 device 的基本根目录,如下

/sys/devices
/sys/dev
/sys/dev/block
/sys/dev/char

bus总线管理着该总线下的所有设备和驱动,他们分别位于/sys/bus/xxx/device /sys/bus/xxx/driver

3、device的注册

1) device_register

使用 device_regster 注册 device ,这里以源码的形式分析。

int device_register(struct device *dev)
{
device_initialize(dev); //对dev进行一些初始化
return device_add(dev); //将dev注册进bus
}

2) device_initialize

void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset; //初始化kset
kobject_init(&dev->kobj, &device_ktype); //初始化ktypoe

// 对一些链表进行初始化
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1); //设置该设备节点为-1,一般未注册前默认为-1
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif
}

从它的初始化可以看出,所有通过device_register注册的dev的dev->kobj->list,都将挂接在 devices_kset->list上(在kobj_add注册时链接) ,如果没有父设备则设备将使用 devices_kset 作为父设备目录节点,因此可以得出下面结论:

  • 所有通过 device_regster 创建的设备都应该是/sys/devices/的子目录

3) device_add

这个函数将 device 注册进 bus,同时它也做了很多事情非常多的事情,具体做了什么事情我们跟着源码来看一下。

int device_add(struct device *dev)
{
struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;

dev = get_device(dev); //增加dev引用计数
if (!dev)
goto done;

if (!dev->p) {
/* 动态分配一个 device_private 并且初始化 dev成员*/
error = device_private_init(dev);
if (error)
goto done;
}

/*
* for statically allocated devices, which should all be converted
* some day, we need to initialize the name. We prevent reading back
* the name, and force the use of dev_name()
*/
if (dev->init_name) { // 如果有init_name则设置,设置dev->kobj->name 为 init_name
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL; //这里需要注意的是,这个 init_name 被设置为空
}

/* subsystems can specify simple device enumeration */
//如果 dev->kobj->name 为空且对应的bus设置了dev_name 则使用这个名字+dev->id作为设备名
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

//如果这没有设置dev->kobj-name 则直接返回error
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}

pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

//增加 dev-parent 的kobj引用计数
parent = get_device(dev->parent);

//返回parent->kobj
kobj = get_device_parent(dev, parent);
if (IS_ERR(kobj)) {
error = PTR_ERR(kobj);
goto parent_error;
}
if (kobj)
dev->kobj.parent = kobj;

/* use parent numa_node */
if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) // 一般未注册前默认为-1
set_dev_node(dev, dev_to_node(parent));

/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
// 注册 dev->kobj,在 sys/ 下创建相关目录
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}

/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);

//在dev目录下创建属性文件uevent
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;

/* 在dev所在目录创建三个链接文件
* of_node ---> 设备树生成的节点
* subsystem ---> dev->class
* device ---> 父设备目录
* 同时在所属的class目录下创建指向 dev 目录的链接文件,dev->init_name
*/
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;

//该函数用于在 dev下创建所属的 class->dev_groups, 所属的 type->groups 和 dev->groups 指向的属性文件以及属性文件 online
error = device_add_attrs(dev);
if (error)
goto AttrsError;

//将设备注册进bus,详细分析见后文
error = bus_add_device(dev);
if (error)
goto BusError;

//电源相关暂时忽略
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;

//电源相关暂时忽略
device_pm_add(dev);

if (MAJOR(dev->devt)) {
//在设备所在目录下创建属性文件 dev
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;
//如果存在class则在class所在目录下创建指向dev->kobj的链接文件"major+minor",否则在 /sys/char 下创建
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;

//创建设备文件节点 /dev/dev->init_name
devtmpfs_create_node(dev);
}

/* Notify clients of device addition. This call must come
* after dpm_sysfs_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
//向用户空间发送 uevent 事件 KOBJ_ADD
kobject_uevent(&dev->kobj, KOBJ_ADD);

//遍历所挂接的bus上的所有drv,对所有的drv进行匹配,匹配成功则调用相对应的probe函数,详细分析见后文
bus_probe_device(dev);

if (parent) //将 dev->p->knode_parent 节点加入 parent->p->klist_children
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);

if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
// 绑定device和class,将dev->knode_class 节点链接到dev->class->p->klist_devices
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);

/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
mutex_unlock(&dev->class->p->mutex);
}
done:
put_device(dev);
return error;
SysEntryError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
DevAttrError:
device_pm_remove(dev);
dpm_sysfs_remove(dev);
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
glue_dir = get_glue_dir(dev);
kobject_del(&dev->kobj);
Error:
cleanup_glue_dir(dev, glue_dir);
parent_error:
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}

4) get_device_parent

这个函数用于获取 dev->kobj->parent

static struct kobject *get_device_parent(struct device *dev, struct device *parent)
{
if (dev->class) { //是否设置class
struct kobject *kobj = NULL;
struct kobject *parent_kobj;
struct kobject *k;

#ifdef CONFIG_BLOCK
/* block disks show up in /sys/block */
if (sysfs_deprecated && dev->class == &block_class) {
if (parent && parent->class == &block_class)
return &parent->kobj;
return &block_class.p->subsys.kobj;
}
#endif

/*
* If we have no parent, we live in "virtual".
* Class-devices with a non class-device as parent, live
* in a "glue" directory to prevent namespace collisions.
*/
if (parent == NULL) //parent为空
parent_kobj = virtual_device_parent(dev); //在/sys/devices/目录下创建 virtual 目录
else if (parent->class && !dev->class->ns_type) //dev->class->ns_type
return &parent->kobj;
else
parent_kobj = &parent->kobj; //设置parent_kobj

mutex_lock(&gdp_mutex);

/* find our class-directory at the parent and reference it */
//如果已经在dev->class->p->glue_dirs下注册了 parent_kobj 则增加它的引用计数,并直接返回
spin_lock(&dev->class->p->glue_dirs.list_lock);
list_for_each_entry(k, &dev->class->p->glue_dirs.list, entry)
if (k->parent == parent_kobj) {
kobj = kobject_get(k);
break;
}
spin_unlock(&dev->class->p->glue_dirs.list_lock);
if (kobj) { //直接返回
mutex_unlock(&gdp_mutex);
return kobj;
}

/* or create a new class-directory at the parent device */
/* 在 parent_kobj 并没有在 dev->class->p->glue_dirs 中,
* 则在父目录下创建一个名为 dev->class->name 的目录,
* 并将 class->p->glue_dirs 作为其kset。
*/
k = class_dir_create_and_add(dev->class, parent_kobj);
/* do not emit an uevent for this simple "glue" directory */
mutex_unlock(&gdp_mutex);
return k; //返回dir->kobj
}

/* subsystems can specify a default root directory for their devices */
//如果parent为空则且dev->bus->dev_root不为空则使用dev->bus->dev_root->kobj作为父节点
if (!parent && dev->bus && dev->bus->dev_root)
return &dev->bus->dev_root->kobj;

if (parent)
return &parent->kobj; //返回父节点
return NULL;
}

class_dir_create_and_add

static struct kobject* class_dir_create_and_add(struct class *class, struct kobject *parent_kobj)
{
struct class_dir *dir;
int retval;

dir = kzalloc(sizeof(*dir), GFP_KERNEL);
if (!dir)
return ERR_PTR(-ENOMEM);

dir->class = class;
kobject_init(&dir->kobj, &class_dir_ktype);

dir->kobj.kset = &class->p->glue_dirs;

retval = kobject_add(&dir->kobj, parent_kobj, "%s", class->name);
if (retval < 0) {
kobject_put(&dir->kobj);
return ERR_PTR(retval);
}
return &dir->kobj;
}

get_device_parent函数需要分为情况讨论
设备属于某个class且parent为空

  1. 创建 /sys/devices/virtual目录,并将其作为父目录parent_kobj
  2. 遍历 dev->class->p->glue_dirs 下的所有kobj, 如果存在 parent_kobj,则增加其引用计数并直接返回 parent_kobj
  3. 否则在parent_kobj下创建一个dir->kobj (/sys/dev/vitual/dir->kobj->name),使用传入的class->name 作为其dir->kobj->name, 并将class->p->glue_dirs作为其kset。并返回dir->kobj作为parent_kobj,这种情况会曾加如下目录
/sys/devices/virtual
/sys/devices/virtual/class->name //dev->class->p->glue_dirs 下不存在 parent_kobj 则创建这个节点

设备属于某个class且parent不为空

  1. 如果存在dev->class->ns_type则直接返回 parent_kobj (这种情况不增加目录)
  2. 如果上述不存在,则遍历 dev->class->p->glue_dirs 下的所有 kobj, 如果存在parent_kobj,则增加其引用计数并直接返回 parent_kobj
  3. 否则在 parent_kobj 下创建一个dir->kobj,使用传入的 class->name 作为其 dir->kobj->name, 并将 class->p->glue_dirs 作为其 kset。并返回 dir->kobj作为parent_kobj,这种情况会增加如下目录
..../parent_kobj->name/class->name

设备属不属于某个class
如果 parent 为空则且 dev->bus->dev_root 不为空则使用 dev->bus->dev_root->kobj 作为父节点,否则直接返回 parent->kobj

该函数会创建 class 与 dev 之间的连接,首先在传入的 dev 所在目录创建三个链接文件,of_node、subsystem、device,同时在所属的 class 目录下创建指向 dev 目录的链接文件,dev->init_name

static int device_add_class_symlinks(struct device *dev)
{
struct device_node *of_node = dev_of_node(dev);
int error;

if (of_node) { //设备树生成的节点
//在 dev->kobj 下创建指向of_node->kobj的链接文件of_node
error = sysfs_create_link(&dev->kobj, &of_node->kobj,"of_node");
if (error)
dev_warn(dev, "Error %d creating of_node link\n",error);
/* An error here doesn't warrant bringing down the device */
}

if (!dev->class)
return 0;
//在 dev->kobj 下创建指向 dev->class->p->subsys.kobj 的链接文件"subsystem"
error = sysfs_create_link(&dev->kobj,
&dev->class->p->subsys.kobj,
"subsystem");
if (error)
goto out_devnode;

//在 dev->kobj 下创建指向 dev->parent->kobj 的链接文件 device
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 */
//在 dev->class->p->subsys.kobj 下创建指向 dev->kobj 链接文件 "dev->init_name"
error = sysfs_create_link(&dev->class->p->subsys.kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_device;

return 0;

out_device:
sysfs_remove_link(&dev->kobj, "device");

out_subsys:
sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
sysfs_remove_link(&dev->kobj, "of_node");
return error;
}

6) device_add_attrs

该函数用于在 dev 下创建所属的 class->dev_groups, 所属的 type->groups dev->groups指向的属性文件以及属性文件 online

static int device_add_attrs(struct device *dev)
{
struct class *class = dev->class;
const struct device_type *type = dev->type;
int error;

if (class) {
//创建 class->dev_groups 属性文件
error = device_add_groups(dev, class->dev_groups);
if (error)
return error;
}

if (type) {
//创建 type->groups 属性文件
error = device_add_groups(dev, type->groups);
if (error)
goto err_remove_class_groups;
}

//创建 dev->groups 属性文件
error = device_add_groups(dev, dev->groups);
if (error)
goto err_remove_type_groups;

//创建属性文件 online
if (device_supports_offline(dev) && !dev->offline_disabled) {
error = device_create_file(dev, &dev_attr_online);
if (error)
goto err_remove_dev_groups;
}

return 0;

err_remove_dev_groups:
device_remove_groups(dev, dev->groups);
err_remove_type_groups:
if (type)
device_remove_groups(dev, type->groups);
err_remove_class_groups:
if (class)
device_remove_groups(dev, class->dev_groups);

return error;
}

7) bus_add_device

这个函数将 device 注册进入bus

int bus_add_device(struct device *dev)
{
struct bus_type *bus = bus_get(dev->bus);
int error = 0;

if (bus) {
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
/* 在当前设备所在目录下创建 dev->dev_attrs 默认属性文件(有就创建没有就不创建) */
error = device_add_attrs(bus, dev);
if (error)
goto out_put;
/* 在当前设备所在目录下创建属性文件 bus->dev_groups */
error = device_add_groups(dev, bus->dev_groups);
if (error)
goto out_id;
/* 在设备所在bus的devices目录下创建指向设备所在目录的软链接,名字为dev->name */
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_groups;
/* 在设备所在目录下创建指向设备所在总线的的软链接名为 subsystem */
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
if (error)
goto out_subsys;
/* 将设备链入 bus->p->klist_devices 链表 */
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;

out_subsys:
sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
device_remove_groups(dev, bus->dev_groups);
out_id:
device_remove_attrs(bus, dev);
out_put:
bus_put(dev->bus);
return error;
}

bus_add_device 会创建以下节点

/sys/bus/xxx/devices/dev->name ----> ..../dev->name
..../dev->name/subsystem ----> /sys/bus/xxx

8) bus_probe_device

如果drivers_autoprobe为1,即可以自动匹配则调用 device_initial_probe(dev)

void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;

if (!bus)
return;

//如果设置了 bus->p->drivers_autoprobe 则进行匹配
if (bus->p->drivers_autoprobe)
device_initial_probe(dev);

mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}

device_initial_probe

void device_initial_probe(struct device *dev)
{
__device_attach(dev, true);
}

__device_attach

static int __device_attach(struct device *dev, bool allow_async)
{
int ret = 0;

device_lock(dev);
if (dev->driver) { //在初始化dev的时候手动指定了driver,则在这里进行绑定
if (device_is_bound(dev)) { //判断设备和驱动是否已经绑定
ret = 1;
goto out_unlock;
}

// 手动绑定设备和驱动
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
struct device_attach_data data = {
.dev = dev,
.check_async = allow_async,
.want_async = false,
};

if (dev->parent)
pm_runtime_get_sync(dev->parent);

//遍历 dev->bus 上所有的 drv ,对每一个drv 调用 __device_attach_driver 函数
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver);
if (!ret && allow_async && data.have_async) {
/*
* If we could not find appropriate driver
* synchronously and we are allowed to do
* async probes and there are drivers that
* want to probe asynchronously, we'll
* try them.
*/
dev_dbg(dev, "scheduling asynchronous probe\n");
get_device(dev);
async_schedule(__device_attach_async_helper, dev);
} else {
pm_request_idle(dev);
}

if (dev->parent)
pm_runtime_put(dev->parent);
}
out_unlock:
device_unlock(dev);
return ret;
}

device_bind_driver

int device_bind_driver(struct device *dev)
{
int ret;

/*
* 创建链接文件 .../dev->driver->p->kobj/dev->kobj->name
* 创建链接文件 .../dev->kobj/driver
*/
ret = driver_sysfs_add(dev);
if (!ret)
driver_bound(dev); //手动绑定
else if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
return ret;
}

__device_attach_driver

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
struct device_attach_data *data = _data;
struct device *dev = data->dev;
bool async_allowed;
int ret;

/*
* Check if device has already been claimed. This may
* happen with driver loading, device discovery/registration,
* and deferred probe processing happens all at once with
* multiple threads.
*/
if (dev->driver)
return -EBUSY;
/*
* 调用 drv 所属的 bus 的 match 函数,这里可以看出来如果不初始化 drv->bus->match 函数则默认匹配成功。
static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
*/
ret = driver_match_device(drv, dev);
if (ret == 0) {
/* no match */
return 0;
} else if (ret == -EPROBE_DEFER) {
dev_dbg(dev, "Device match requests probe deferral\n");
driver_deferred_probe_add(dev);
} else if (ret < 0) {
dev_dbg(dev, "Bus failed to match device: %d", ret);
return ret;
} /* ret > 0 means positive match */

//获取驱动加载方式是同步加载还是异步加载
async_allowed = driver_allows_async_probing(drv);

if (async_allowed)
data->have_async = true;

if (data->check_async && async_allowed != data->want_async)
return 0;

//调用 driver_probe_device
return driver_probe_device(drv, dev);
}

driver_probe_device

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
int ret = 0;

if (!device_is_registered(dev))
return -ENODEV;

pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);

if (dev->parent)
pm_runtime_get_sync(dev->parent);

pm_runtime_barrier(dev);

//调用 really_probe 函数
ret = really_probe(dev, drv);
pm_request_idle(dev);

if (dev->parent)
pm_runtime_put(dev->parent);

return ret;
}

really_probe

static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = -EPROBE_DEFER;
int local_trigger_count = atomic_read(&deferred_trigger_count);
bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
!drv->suppress_bind_attrs;
#ifdef CONFIG_MTPROF
unsigned long long ts = 0;
#endif
if (defer_all_probes) {
/*
* Value of defer_all_probes can be set only by
* device_defer_all_probes_enable() which, in turn, will call
* wait_for_device_probe() right after that to avoid any races.
*/
dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
return ret;
}

atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));

re_probe:
dev->driver = drv;

/* If using pinctrl, bind pins now before probing */
// 如果使用了 pinctrl 则会在这里先加载一次,如果 dts 写错了但能编译过了,就是不probe
// 当移植代码的时候,当我们注册的驱动无法 prob e到设备的时候记得检查一下是否是 dts 的问题,
// 因为不同个平台的 dts 可能由微小的差别,我就踩过这个坑 =_=
ret = pinctrl_bind_pins(dev);
if (ret)
goto pinctrl_bind_failed;
/*
* 在drv所在目录创建指向dev目录的链接文件,名字使用 dev->kobj->name
* 在dev所在目录创建指向drv目录的连接文件,名字使用 "driver"
*/
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}

if (dev->pm_domain && dev->pm_domain->activate) {
ret = dev->pm_domain->activate(dev);
if (ret)
goto probe_failed;
}

//调用 dev->bus->probe
if (dev->bus->probe) {
TIME_LOG_START();
ret = dev->bus->probe(dev);
TIME_LOG_END();
bootprof_probe(ts, dev, drv, (unsigned long)dev->bus->probe);
if (ret)
goto probe_failed;
} else if (drv->probe) { //如果 dev->bus->probe 不存在则调用 drv->probe
TIME_LOG_START();
ret = drv->probe(dev);
TIME_LOG_END();
bootprof_probe(ts, dev, drv, (unsigned long)drv->probe);
if (ret)
goto probe_failed;
}

if (test_remove) {
test_remove = false;

if (dev->bus->remove)
dev->bus->remove(dev);
else if (drv->remove)
drv->remove(dev);

devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
if (dev->pm_domain && dev->pm_domain->dismiss)
dev->pm_domain->dismiss(dev);
pm_runtime_reinit(dev);

goto re_probe;
}

pinctrl_init_done(dev);

if (dev->pm_domain && dev->pm_domain->sync)
dev->pm_domain->sync(dev);

//建立设备与驱动的连接
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;

probe_failed:
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
if (dev->pm_domain && dev->pm_domain->dismiss)
dev->pm_domain->dismiss(dev);
pm_runtime_reinit(dev);

switch (ret) {
case -EPROBE_DEFER:
/* Driver requested deferred probing */
dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
break;
case -ENODEV:
case -ENXIO:
pr_debug("%s: probe of %s rejects match %d\n",
drv->name, dev_name(dev), ret);
break;
default:
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}

4、device_register总结

1) 检查设备名的合法性

从代码可以看出对于 dev 来说名字是一个非常重要的参数,首先使用 init_name 作为dev->kobj 的名字同时将 init_name 设置为空,如果 init_name 初始为空则使用 "bus->dev_nam + dev->id" 作为dev->kobj 的名字,如果设备没有设置名字则直接返回错误。

2)在sys/创建文件目录的层次关系的创建

下面列出所有可能出现的情况
设备的 bus 为空,class 为空,parent 为空

/sys/devices/xxx
/sys/devices/xxx/power
/sys/devices/xxx/uevent
/sys/devices/xxx/of_node //如果存在设备树节点则创建

/sys/devices/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 为空,class 为空,parent 不为空

/sys/devices/.../xxx->parent/xxx
/sys/devices/.../xxx->parent/xxx/power
/sys/devices/.../xxx->parent/xxx/uevent
/sys/devices/.../xxx->parent/xxx/of_node //如果存在设备树节点则创建

/sys/devices/.../xxx->parent/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/.../xxx->parent/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 不为空,class 为空,parent 为空

/sys/devices/xxx
/sys/devices/xxx/power
/sys/devices/xxx/uevent
/sys/devices/xxx/of_node //如果存在设备树节点则创建

/* bus_add_device 创建下面目录 */
/sys/bus/xxx->bus/devices/xxx --->/sys/devices/xxx //指向设备
/sys/devices/xxx/subsystem ---> /sys/bus/xxx->bus //指向所挂接的bus总线

/sys/devices/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

//和drv匹配成功创建下面目录
/sys/devices/xxx/driver ---> /sys/bus/drivers/xxx->driver/
/sys/bus/drivers/xxx->driver/xxx ---> /sys/devices/xxx

设备的bus不为空,class为空,parent不为空

/sys/devices/.../xxx->parent/xxx
/sys/devices/.../xxx->parent/xxx/power
/sys/devices/.../xxx->parent/xxx/uevent
/sys/devices/.../xxx->parent/xxx/of_node //如果存在设备树节点则创建

/* bus_add_device 创建下面目录 */
/sys/bus/xxx->bus/devices/xxx --->/sys/devices/.../xxx->parent/xxx //指向设备
/sys/devices/.../xxx->parent/xxx/subsystem ---> /sys/bus/xxx->bus //指向所挂接的bus总线

/sys/devices/.../xxx->parent/xxx/dev //有设备号的设备会创建这个节点
/sys/dev/char/"major+minor" ---> /sys/devices/.../xxx->parent/xxx //有设备号的设备会创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

//和drv匹配成功创建下面目录
/sys/devices/.../xxx->parent/xxx/driver ---> /sys/bus/drivers/xxx->driver/
/sys/bus/drivers/xxx->driver/xxx ---> /sys/devices/.../xxx->parent/xxx

设备的 bus 为空,class 不为空,parent 为空

//dir->kobj使用xxx->class->name做名字

/sys/devices/virtual
/sys/devices/virtual/dir->kobj
/sys/devices/virtual/dir->kobj/xxx
/sys/devices/virtual/dir->kobj/xxx/uevent
/sys/devices/virtual/dir->kobj/xxx/of_node //如果存在设备树节点则创建

/* device_add_class_symlinks 创建下面目录 */
/sys/devices/virtual/dir->kobj/xxx/subsystem ---> /sys/class/xxx->class/xxx
/sys/class/dev->class/xxx ---> /sys/devices/virtual/dir->kobj/xxx

/sys/devices/xxx/dev //有设备号的设备会创建这个节点

/*
* .../dev->class->dev_kobj/"major+minor" ---> /sys/devices/virtual/dir->kobj/xxx 有dev->class->dev_kobj和设备号的设备会创建这个节点
* dev->class->dev_kobj 这个属性在class被注册的时候一般被默认设置为 sysfs_dev_char_kobj,即 /sys/dev/char节点
*/
/sys/dev/char/"major+minor" ---> /sys/devices/virtual/dir->kobj/xxx //有设备号会默认创建这个节点
/dev/xxx //有设备号的设备会创建这个节点

设备的 bus 为空,class 不为空,parent 不为空

//dir->kobj使用xxx->class->name做名字

/sys/devices/.../xxx->parent/dir->kobj/
/sys/devices/.../xxx->parent/dir->kobj/xxx
/sys/devices/.../xxx->parent/dir->kobj/xxx/uevent
/sys/devices/.../xxx->parent/dir->kobj/xxx/of_node //如果存在设备树节点则创建

/* device_add_class_symlinks 创建下面目录 */
/sys/devices/.../xxx->parent/dir->kobj/xxx/subsystem ---> /sys/class/xxx->class
/sys/devices/xxx->parent/dir->kobj/xxx/device ---> dev->parent->kobj
/sys/class/dev->class/xxx ---> /sys/devices/xxx->parent/dir->kobj/xxx

/sys/devices/xxx/dev //有设备号的设备会创建这个节点

/*
* .../dev->class->dev_kobj/"major+minor" ---> /sys/devices/xxx->parent/dir->kobj/xxx 有dev->class->dev_kobj和设备号的设备会创建这个节点
* dev->class->dev_kobj 这个属性在class被注册的时候一般被默认设置为 sysfs_dev_char_kobj,即 /sys/dev/char节点
*/
/sys/dev/char/"major+minor" ---> /sys/devices/xxx->parent/dir->kobj/xxx //有设备号会默认创建这个节点

/dev/xxx //有设备号的设备会创建这个节点

为什么没有同时出现一个设备同时属于 bus 和 class 的情况,通过整理 device_add 创建的目录层次可以发现,bus(class)下的设备都会在注册的的时候在设备目录创建 subsystem ,这个链接文件指向 bus(class),同时也会在 bus(class) 目录创建指向设备的链接文件。在 device_add_class_symlinks 函数和 bus_add_device 函数中都会在当前设备下创建 "subsystem" 这个属性文件,很明显存在着冲突,这得出一个结论:内核在向 bus 注册设备的时候,如果设备同属于 class 和 bus 时,设备是无法注册的

3)和挂接的bus上的所有drv进行匹配

匹配流程如下

bus_probe_device--->
device_initial_probe(dev) ---> //如果bus->p->drivers_autoprobe被置位则调用这个函数
---> __device_attach(dev, true);
---> bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); //对于该bus上的每一个drv都调用__device_attach_driver函数
---> __device_attach_driver(drv, data)
---> driver_match_device(drv, dev);
---> drv->bus->match(dev, drv)
---> driver_probe_device(drv, dev) //如果匹配成功则调用这个函数
---> really_probe(dev, drv);
---> dev->driver = drv; //将匹配成功的驱动赋值给对应的设备
---> dev->bus->probe(dev) //默认调用这个
---> drv->probe(dev) //如果没有设置 dev->bus->probe 函数,则调用这

dev 在注册的时候会和所挂接 bus上 的所有 drv 进行匹配,即调用 drv->bus->match(dev, drv) 函数,如果匹配成功则调用所在总线上的 probe 函数,即 dev->bus->probe(dev) 函数 , 从这里也可以看出匹配的规则是灵活的,它由总线决定,由总线来决定设备和驱动的匹配规则, 比如 platform 总线就规定了5种匹配规则,这里只是提一下,后面的platform设备详述。

4)建立与字符设备的联系

常用的字符设备就是通过设备号与设备模型建立联系的,当我们在设备模型中注册一个 dev 时,如果存在设备号则会在/dev下创建对应的设备文件,我们可以通过这个文件的设备号,在已经注册的字符设备链表中查询到我们已经注册的字符设备。

5、创建我们自己的设备

我们之前已经创建了一个 my_bus 总线,现在在这个基础之上再创建一个 my_dev

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

extern struct bus_type my_bus;

struct device my_dev = {
.init_name = "my_dev",
.bus = &my_bus,
};

static int my_device_init(void)
{
device_register(&my_dev);
return 0;
}

static void my_device_exit(void)
{
device_unregister(&my_dev);
}

module_init(my_device_init);
module_exit(my_device_exit);

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

XF-X2:/sys/bus/my_bus/devices # ls
XF-X2:/sys/bus/my_bus/devices #
XF-X2:/sys/bus/my_bus/devices # insmod /cache/my_device.ko
XF-X2:/sys/bus/my_bus/devices #
XF-X2:/sys/bus/my_bus/devices # ls
my_dev
XF-X2:/sys/bus/my_bus/devices # cd my_dev/
XF-X2:/sys/bus/my_bus/devices/my_dev # ls
power subsystem uevent

6、在dev下创建属性文件

属性文件作为用户空间和内核空间交互的常用手段之一,它的重要性不言而喻。 device_register 在 device_initialize 中会将 ktype 初始化为 device_ktype

static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
};

static const struct sysfs_ops dev_sysfs_ops = {
.show = dev_attr_show,
.store = dev_attr_store,
};

可以看到内核已经实现了 device_release 函数当 dev 引用计数为 0 时会自动释放掉自己。同样的内核也实现了属性文件中间层的函数 dev_attr_show 和 dev_attr_store

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct device_attribute *dev_attr = to_dev_attr(attr); //获取到更大的结构 device_attribute
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO;

if (dev_attr->show)
ret = dev_attr->show(dev, dev_attr, buf); //调用 device_attribute 的成员 show 函数
if (ret >= (ssize_t)PAGE_SIZE) {
print_symbol("dev_attr_show: %s returned bad count\n",
(unsigned long)dev_attr->show);
}
return ret;
}

static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,
const char *buf, size_t count)
{
struct device_attribute *dev_attr = to_dev_attr(attr); //获取到更大的结构 device_attribute
struct device *dev = kobj_to_dev(kobj);
ssize_t ret = -EIO;

if (dev_attr->store)
ret = dev_attr->store(dev, dev_attr, buf, count);//调用 device_attribute 的成员 store 函数
return ret;
}

struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};

这个框架看起来是不是很熟悉,其实前面我们自己在my_dir就已经实现了这个框架了,这里只是换了个壳而已,像前面的 bus,以及后面的 driver 等模型,内核已经帮我们实现了相关框架,我们只需要直接用就行了。创建属性结构的方法太麻烦了,没关系内核也为我们封装了快速创建并初始化 device_attribute 结构的宏了

/*
* 快速创建一个的 device_attribute 属性结构 dev_attr_name
*/
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

/*
* 快速创建一个可读可写的 device_attribute 属性结构 dev_attr_name
* 属性操作函数为, name_show、name_store
*/
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)


/*
* 快速创建一个只读的 device_attribute 属性结构 dev_attr_name
* 属性操作函数为, name_show
*/
#define DEVICE_ATTR_RO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RO(_name)

/*
* 快速创建一个只写的 device_attribute 属性结构 dev_attr_name
* 属性操作函数为, name_store
*/
#define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name)

#define DEVICE_ULONG_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }

#define DEVICE_INT_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }

#define DEVICE_BOOL_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \
{ __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }

#define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = \
__ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)

创建出了 device_attribute 结构,再调用 device_attribute 函数就可以在当前设备下快速创建属性文件。

int device_create_file(struct device *dev,
const struct device_attribute *attr)
{
int error = 0;

if (dev) {
WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
"Attribute %s: write permission without 'store'\n",
attr->attr.name);
WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
"Attribute %s: read permission without 'show'\n",
attr->attr.name);
error = sysfs_create_file(&dev->kobj, &attr->attr); //注册属性文件
}

return error;
}
EXPORT_SYMBOL_GPL(device_create_file);

编程实现

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

extern struct bus_type my_bus;
extern struct class *my_class;

struct device my_dev = {
.init_name = "my_dev",
.bus = &my_bus,
};

ssize_t my_attr_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk("%s\n",attr->attr.name);
sprintf(buf, "%s\n", attr->attr.name);
return strlen((char*)attr->attr.name) +2;
}

ssize_t my_attr_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
printk("%s store : %s\n",attr->attr.name, buf);
return count;
}

DEVICE_ATTR(my_attr_test, 0664, my_attr_show, my_attr_store);

static int my_device_init(void)
{
device_register(&my_dev);
device_create_file(&my_dev,&dev_attr_my_attr_test); //注意这里需要添加自己的前缀 dev_attr_
return 0;
}

static void my_device_exit(void)
{
device_unregister(&my_dev);
}

module_init(my_device_init);
module_exit(my_device_exit);
MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

验证结果

//cat自己创建出的设备节点
XF-X2:/sys/bus/my_bus/devices/my_dev # cat my_attr_test
my_attr_test
[ 501.905519] <6>.(6)[2845:cat]my_attr_test //内核打印

//ehco自己创建的设备节点
XF-X2:/sys/bus/my_bus/devices/my_dev # echo 123>my_attr_test
[ 545.227562] <4>.(4)[2821:sh]my_attr_test store : 123 //内核打印

五、device_driver 驱动

Linux设备模型用 Driver 抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。

1、数据结构

1) device_driver

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; //保存相关链表,也保存了kobj
};

2) driver_private

struct driver_private {
struct kobject kobj; // 用于创建 driver 所在目录
struct klist klist_devices; // 用于链接匹配到的设备
struct klist_node knode_bus; // 链接到bus->p->klist_drivers
struct module_kobject *mkobj;
struct device_driver *driver; // 指向该结构的拥有者
};

2、driver的注册

相较于device的注册,driver的注册就比较简单

  1. 在总线上查找drv,判断drv是否已经注册进bus,防止重复注册
  2. 调用bus_add_driver将drv注册进入bus
  3. 创建属性文件 drv->groups
  4. 向上层发送uevent事件,KOBJ_ADD
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;

BUG_ON(!drv->bus->p);

if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);

//在总线上查找drv,判断drv是否已经注册进bus,防止重复注册
other = driver_find(drv->name, drv->bus);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}

//调用bus_add_driver将drv注册进入bus
ret = bus_add_driver(drv);
if (ret)
return ret;

//创建属性文件 drv->groups
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}

//向上层发送uevent事件,KOBJ_ADD
kobject_uevent(&drv->p->kobj, KOBJ_ADD);

return ret;
}

bus_add_driver

int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;

bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;

pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
//初始化klist_devices
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;

priv->kobj.kset = bus->p->drivers_kset; //初始化kest

// 由于没有设置 parent 因此使用 bus->p->drivers_kset 做父节点,并在这里创建drv目录,
// 从这里可以知道drv的根目录为 /sys/bus/drv->bus/drivers/
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;

//将priv->knode_bus 链接进入priv->bus->p->klist_drivers
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

//判断是否可以自动probe,如果可以,则遍历klist_devices,对其中的每一个dev都调用drv->bus->match(dev, drv)函数
if (drv->bus->p->drivers_autoprobe) {
if (driver_allows_async_probing(drv)) {
pr_debug("bus: '%s': probing driver %s asynchronously\n",
drv->bus->name, drv->name);
async_schedule(driver_attach_async, drv);
} else
error = driver_attach(drv);
if (error)
goto out_unregister;
}
}
module_add_driver(drv->owner, drv);

//在drv所在目录创建属性文件 uevent
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}

//在drv所在目录创建属性文件 drv_groups
error = driver_add_groups(drv, bus->drv_groups);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
__func__, drv->name);
}

//如果支持 bind 则在 drv 所在目录创建属性文件 bind 和 unbind
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}

return 0;

out_unregister:
kobject_put(&priv->kobj);
/* drv->p is freed in driver_release() */
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}

3、driver_register 总结

1) 在sys/创建对应节点

bus_add_driver 会创建下面节点

/sys/bus/xxx/drivers/drv->name
/sys/bus/xxx/drivers/drv->name/uevent

/* 支持 suppress_bind_attrs */
/sys/bus/xxx/drivers/drv->name/unbind
/sys/bus/xxx/drivers/drv->name/bind

2) 匹配 bus 总线上的设备

除了创建节点之外,如果可以自动 probe ,如果可以,则遍历 klist_devices,对其中的每一个 dev 都调用 drv->bus->match(dev, drv)函数,如果匹配成功则调用 really_probe(drv) ,在这个函数中默认先调用函数 dev->bus->probe(dev),如果没有设置 dev->bus->probe函数,则调用该函数,调用流程如下

bus_add_driver---->
driver_attach----> 无论如何最终都会调用这个函数
bus_for_each_dev---->
__driver_attach---->
driver_match_device---->
drv->bus->match(dev, drv) //如果匹配成功则调用 really_probe
really_probe---->
dev->bus->probe(dev) //默认调用这个
drv->probe(dev) //如果没有设置 dev->bus->probe 函数,则调用这个

4、注册我们自己的驱动

在我们创建的 bus 上注册我们自己的驱动

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

extern struct bus_type my_bus;

int my_drv_probe(struct device *dev)
{
printk("my_drv_probe\n");
return 0;
}

int my_drv_remove(struct device *dev)
{
printk("my_drv_remove\n");
return 0;
}

struct device_driver my_drv = {
.name = "my_drv",
.bus = &my_bus,
.probe = my_drv_probe,
.remove = my_drv_remove,
};

static int my_drv_init(void)
{
driver_register(&my_drv);
return 0;
}

static void my_drv_exit(void)
{
driver_unregister(&my_drv);
}

module_init(my_drv_init);
module_exit(my_drv_exit);

验证结果

XF-X2:/sys/bus/my_bus/drivers # ls
XF-X2:/sys/bus/my_bus/drivers #
XF-X2:/sys/bus/my_bus/drivers # insmod /cache/my
my_bus.ko my_device.ko my_driver.ko
XF-X2:/sys/bus/my_bus/drivers # insmod /cache/my_driver.ko
XF-X2:/sys/bus/my_bus/drivers # ls
my_drv
XF-X2:/sys/bus/my_bus/drivers #
XF-X2:/sys/bus/my_bus/drivers # cd my_drv/
XF-X2:/sys/bus/my_bus/drivers/my_drv # ls
bind uevent unbind

5、在driver目录下创建属性文件

和前面的 device 下创建属性文件相同,这里只给接口不再赘述

struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *driver, char *buf);
ssize_t (*store)(struct device_driver *driver, const char *buf,
size_t count);
};

#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DRIVER_ATTR_RW(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)

//用于在当前 driver 下创建属性文件
extern int __must_check driver_create_file(struct device_driver *driver, const struct driver_attribute *attr);

//删除属性文件
extern void driver_remove_file(struct device_driver *driver, const struct driver_attribute *attr);

六、class 类

class 用于管理同类的设备,常常被我们用来给上层开辟一个属性节点,多用于查看,修改对应的设备信息。

1、数据结构

1) class

struct class {
const char *name; // class名称,用来初始化 subsys_private->susbus.kobj
struct module *owner;

struct class_attribute *class_attrs; // 默然属性文件指针

// 默认的设备属性文件,当注册设备到该 class 上时,会自动在改设备下创建,即该 class 下的所有设备都会注册这个属性文件。
const struct attribute_group **dev_groups;

// 表示class下的设备在 /sys/dev 下的哪个目录,现在有两个目录 char 和 block 默认选择 char。
struct kobject *dev_kobj;

// 当class下的设备发生变化时,会调用class的uevent函数
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);
int (*shutdown)(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;
};

2) subsys_private

struct subsys_private {
struct kset subsys; // 该 class 在sysfs中的目录
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; // 保存上层的class
};

2、函数接口

1) classes_init

int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL); //创建 /sys/class 节点
if (!class_kset)
return -ENOMEM;
return 0;
}

2)class_create

用于在内核中创建一个 class

#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})

struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;

//动态创建 class 结构
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}

cls->name = name; //初始化 name
cls->owner = owner; //初始化 owner
cls->class_release = class_create_release; //初始化 默认release函数

retval = __class_register(cls, key); //注册class
if (retval)
goto error;

return cls;

error:
kfree(cls);
return ERR_PTR(retval);
}
EXPORT_SYMBOL_GPL(__class_create);

a. __class_register

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);
//动态创建一个 subsys_private 结构
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); //初始化 class 目录名
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; //设置子设备的默认类型为char

#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; //设置kset为class_kset 即出现在 /sys/class/目录下 (一般是不会设置class的父kobj的,因此默认使用kset作为父kobj)
#endif
cp->subsys.kobj.ktype = &class_ktype; //初始化class的默认ktype
cp->class = cls;
cls->p = cp;

error = kset_register(&cp->subsys); //注册kset创建对应的class节点,/sys/class/xxx
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls)); //增加引用计数,创建默认属性文件
class_put(cls);
return error;
}
EXPORT_SYMBOL_GPL(__class_register);

b. device_create

在 class 下创建对应的设备,返回创建的设备结构。

struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
{
va_list vargs;
struct device *dev;

va_start(vargs, fmt);
dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
va_end(vargs);
return dev;
}
EXPORT_SYMBOL_GPL(device_create);

struct device *device_create_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt,
va_list args)
{
return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
fmt, args);
}
EXPORT_SYMBOL_GPL(device_create_vargs);


static struct device *
device_create_groups_vargs(struct class *class, struct device *parent,
dev_t devt, void *drvdata,
const struct attribute_group **groups,
const char *fmt, va_list args)
{
struct device *dev = NULL;
int retval = -ENODEV;

if (class == NULL || IS_ERR(class))
goto error;

dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
retval = -ENOMEM;
goto error;
}

device_initialize(dev);
dev->devt = devt; //设置设备号
dev->class = class; //设置设备所属的类
dev->parent = parent; //设备的父节点
dev->groups = groups; //设置设备的默认
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata); //设置设备的私有数据

retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;

retval = device_add(dev); //注册设备
if (retval)
goto error;

return dev;

error:
put_device(dev);
return ERR_PTR(retval);
}

device_add 函数在前面已经有很详细的分析,这里不赘述,不过这里再补充说明一点,device_create 会在 /dev/ 目录下创建设备节点是因为设备有设备号,只要是调用 device_add 注册的设备,同时有设备号就会在 /dev/ 下创建设备节点

3) 属性操作接口

属性文件是 class 重点内容,我们多用 class 来给上层提供接口,属性文件创建接口如下。

//快速创建 class_attribute

#define CLASS_ATTR(_name, _mode, _show, _store) \
struct class_attribute class_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define CLASS_ATTR_RW(_name) \
struct class_attribute class_attr_##_name = __ATTR_RW(_name)
#define CLASS_ATTR_RO(_name) \
struct class_attribute class_attr_##_name = __ATTR_RO(_name)
static inline int __must_check class_create_file(struct class *class, const struct class_attribute *attr)

七、platform 设备

在计算机中有这样一类设备,它们通过各自的设备控制器,直接和 CPU 连接,CPU 可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此 Linux 就虚构了一条 Platform Bus ,供这些设备挂靠。

1、数据结构

1) platform_device

struct platform_device {
const char *name; //设备名称
int id;
bool id_auto;
struct device dev; // 真正的设备,嵌入在platform_device中
u32 num_resources; // 设备资源数量
struct resource *resource; //

const struct platform_device_id *id_entry;
char *driver_override; // 如果设置了这个名字,这用这个名字匹配驱动,它的优先级最高

/* MFD cell pointer */
struct mfd_cell *mfd_cell;

/* arch specific additions */
struct pdev_archdata archdata;
};

2) platform_driver

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;
};

3) resource

struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
};

4) of_dev_auxdata

struct of_dev_auxdata {
char *compatible;
resource_size_t phys_addr;
char *name;
void *platform_data;
};

2、platform 总线的构建

platform 总线是内核提供的虚拟总线,它个构建依赖于前面的,bus,device,driver设备模型。首先内核提供了一个名字叫 "platform" 的默认总线,它是一个全局结构并且被 EXPORT_SYMBOL_GPL 导出,如下

struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

除此之外内核也提供了该总线下的一个设备,名字叫做 "platform_bus"

struct device platform_bus = {
.init_name = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

这是一个设备结构,并不是bus,虽然他的名字叫做 platform_bus, 我也不知道为啥叫这个名字,第一次读我就以为这是个bus。它作为基本设备,它注册之后内核将会创建出如下节点

/sys/devices/platform

platform_bus 的结构只初始化了一个名字,为什么要注册一个只有名字的设备在这里,我想是为了方便管理,将它作为以后 platform 设备的父设备,以后只要是 platform 设备,都将出现在 /sys/devices/platform,一眼就能找出那些是 platform 设备。上述的 platform_bus_type 和 platform_bus 是在platform_bus_init 中被注册的,它被driver_init调用,即在内核启动的时候被创建。

int __init platform_bus_init(void)
{
int error;

early_platform_cleanup();

error = device_register(&platform_bus); //创建platform总线
if (error)
return error;
error = bus_register(&platform_bus_type); //创建一个名为"platform"的设备
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}

以后但凡是注册在 platform 总线上的设备都叫做 platform 设备,注册在该总线上的驱动叫做 platform 驱动

3、platform 设备接口

内核在开机时创建了 platform 总线,同时也提供了该总线相关操作函数

1) platform_device_register

使用这个函数注册一个 platform 设备

int platform_device_register(struct platform_device *pdev)
{
int ret;
#ifdef CONFIG_MTPROF
unsigned long long ts = 0;
#endif
TIME_LOG_START();
//对pdev->dev做一些初始化
device_initialize(&pdev->dev);
// 空函数,啥也没干
arch_setup_pdev_archdata(pdev);
//真正的注册函数
ret = platform_device_add(pdev);
TIME_LOG_END();
bootprof_pdev_register(ts, pdev);
return ret;
}

真正的注册函数是 platform_device_add

int platform_device_add(struct platform_device *pdev)
{
int i, ret;

if (!pdev)
return -EINVAL;

if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; //设置设备的父设备为platform_bus

pdev->dev.bus = &platform_bus_type; //设置bus为platform_bus_type

switch (pdev->id) { 设置 pdev->dev->init_name
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE: // -1
dev_set_name(&pdev->dev, "%s", pdev->name);
break;
case PLATFORM_DEVID_AUTO: // -2
/*
* Automatically allocated device ID. We mark it as such so
* that we remember it must be freed, and we append a suffix
* to avoid namespace collision with explicit IDs.
*/
ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
if (ret < 0)
goto err_out;
pdev->id = ret;
pdev->id_auto = true;
dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
break;
}

for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];

if (r->name == NULL)
r->name = dev_name(&pdev->dev);

p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}

if (p && insert_resource(p, r)) {
dev_err(&pdev->dev, "failed to claim resource %d\n", i);
ret = -EBUSY;
goto failed;
}
}

pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));

//向内核注册这个设备
ret = device_add(&pdev->dev);
if (ret == 0)
return ret;

failed:
if (pdev->id_auto) {
ida_simple_remove(&platform_devid_ida, pdev->id);
pdev->id = PLATFORM_DEVID_AUTO;
}

while (--i >= 0) {
struct resource *r = &pdev->resource[i];
if (r->parent)
release_resource(r);
}

err_out:
return ret;
}

该函数设置设备的父设备为platform_bus,以后凡是挂接在 platform 总线上的设备都将使用 platform_bus 作为它的父设备。即所有的设备都将在下面目录生成

/sys/devices/platform/xxx

2) platform_driver_register

同样内核也提供了 platform 驱动的注册函数

#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;

//将驱动注册进总线
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);
  • 1, 可以看出 drv 的,probe,rmove 函数被分别初始化为 platform_drv_probe,platform_drv_remove。最后调用 driver_register 将驱动注册进总线

  • 2, 对于 platform 设备的注册最后会调用,device_add,它最终会遍历platform_bus_type上所有的 drv,并对每一个 drv 调用 platform_match函数。

  • 3, 而对于platform_driver 的注册会调用 driver_register,它最终会遍历 platform_bus_type 上左右的d ev,对每一个dev都调用platform_match 函数。

像这种交叉遍历的方式在内核中很常见,input子系统中也使用这样的方式。也就是无论如何只要总线上有设备,驱动注册的时候就会去与之匹配,同理总线上有驱动,设备注册时就会去与之匹配。

static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* when driver_override is set, only bind to the matching driver */
/* 如果设置了driver_override,则匹配和driver_override相同名字的驱动 */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);

/* attempt an of style match first,使用设备树方式匹配 */
if (of_driver_match_device(dev, drv))
return 1;

/* then try acpi style match */
/* 电源相关,跳过 */
if (acpi_driver_match_device(dev, drv))
return 1;

/* then try to match against the id table */
/* 如果设置了id_table, 则与id_table中的名字进行匹配 */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != null;

/* fall-back to driver name match */
/* 比较驱动和设备的名称 */
return (strcmp(pdev->name, drv->name) == 0);
}

从这个函数可以看出 platform 设备的匹配方式有 5 种,按照优先级如下:

  1. 如果设置了driver_override,则匹配和driver_override相同名字的设备,它的优先级最高
  2. 使用设备树方式匹配,这是目前比较常用的方式之一
  3. 电源相关方式匹配
  4. 如果设置了id_table, 则与id_table中的名字进行匹配
  5. 最后比较驱动和设备的名称,也是比较常用的方式之一

其中比较常用的是设备树和设备驱动名称进行匹配,下面详细分析一下设备树匹配流程。

static inline int of_driver_match_device(struct device *dev, const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
static const struct of_device_id *__of_match_node(const struct of_device_id *matches,
const struct device_node *node)
{
const struct of_device_id *best_match = NULL;
int score, best_score = 0;

if (!matches)
return NULL;

for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
score = __of_device_is_compatible(node, matches->compatible,
matches->type, matches->name);
if (score > best_score) {
best_match = matches;
best_score = score;
}
}

return best_match;
}
static int __of_device_is_compatible(const struct device_node *device,
const char *compat, const char *type, const char *name)
{
struct property *prop;
const char *cp;
int index = 0, score = 0;

/* Compatible match has highest priority */
if (compat && compat[0]) {
prop = __of_find_property(device, "compatible", NULL); /* 查找 compatible 节点 */
for (cp = of_prop_next_string(prop, NULL); cp;
//获取该节点的字符串
cp = of_prop_next_string(prop, cp), index++)
{
//获得的字符串和compat进行比较
if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
score = INT_MAX/2 - (index << 2);
break;
}
}
if (!score)
return 0;
}

/* Matching type is better than matching name */
if (type && type[0]) {
if (!device->type || of_node_cmp(type, device->type))
return 0;
score += 2;
}

/* Matching name is a bit better than not */
if (name && name[0]) {
if (!device->name || of_node_cmp(name, device->name))
return 0;
score++;
}

return score;
}

再来看看 of_device_id 这个结构

struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};

我们用到的是 compatible 作为匹配的对象,从结构看最对大支持的长度为128个字节。从上述代码可以看出匹配的过程就是匹配 drv.id->compatible 和 dts中的 compatible 节点比较,例如: hall 的 dts 的节点如下

hall: hall{
compatible = "mediatek,hall-gpio-int";
};

在驱动中如下配置

#ifdef CONFIG_OF
//创建一个 of_device_id 数组并初始化内部成员。
static const struct of_device_id hall_switch_of_match[] = {
{.compatible = "mediatek,hall-gpio-int"},
{},
};
#endif

//创建一个platform_driver结构并对里面的driver结构进行初始化
static struct platform_driver hall_driver = {
.probe = hall_probe,
.suspend = hall_suspend,
.resume = hall_resume,
.remove = ln4913_remove,
.driver = {
.name = "ln4913_Driver",
.of_match_table = hall_switch_of_match,
},
};

从这里可以看出匹配其实是会遍历 hall_switch_of_match 数组中的 compatible 描述,也就是说一个驱动可以尝试匹配多个设备,直到匹配到为止。由前面的分析可知一旦匹配成功,就会调用 really_probe 函数

---> really_probe
---> dev->bus->probe(dev) //默认点调用这个,明显platform_bus_type,没有设置prob函数
---> drv->probe(dev) //如果没有设置 dev->bus->probe 函数,则调用这函数,platform 驱动在注册的时候呢,将其初始化为platform_drv_probe

来看看 platform 总线提供的 probe 函数

static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
int ret;

ret = of_clk_set_defaults(_dev->of_node, false);
if (ret < 0)
return ret;

ret = dev_pm_domain_attach(_dev, true);
if (ret != -EPROBE_DEFER) {
if (drv->probe) {
ret = drv->probe(dev); //最后调用platform_driver结构中的probe函数
if (ret)
dev_pm_domain_detach(_dev, true);
} else {
/* don't fail if just dev_pm_domain_attach failed */
ret = 0;
}
}

if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
dev_warn(_dev, "probe deferral not supported\n");
ret = -ENXIO;
}

return ret;
}

可以看到其实最终调用了platform_driver结构中的probe函数。除此之外匹配id也是常用的方式,匹配代码如下

static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}

4、创建自己的 platform 设备

1) 用名字进行匹配

注册platform_device

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

struct platform_device my_platform_dev = {
.name = "my_platform", //名字要和驱动的名字一样
};

static int my_platform_dev_init(void)
{
platform_device_register(&my_platform_dev);
return 0;
}

static void my_platform_dev_exit(void)
{
platform_device_register(&my_platform_dev);
}

module_init(my_platform_dev_init);
module_exit(my_platform_dev_exit);

注册platform_driver

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

int my_platform_probe(struct platform_device *pdev)
{
printk("my_platform_probe\n");
return 0;
}


int my_platform_remove(struct platform_device *pdev)
{
printk("my_platform_remove\n");
return 0;
}

struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my_platform", //名字要和device一样
},
};

static int my_platform_drv_init(void)
{
platform_driver_register(&my_platform_driver);
return 0;
}

static void my_platform_drv_exit(void)
{
platform_driver_unregister(&my_platform_driver);
}

module_init(my_platform_drv_init);
module_exit(my_platform_drv_exit);

验证结果

tb8768p1_64_bsp:/cache # insmod my_platform_drive.ko
tb8768p1_64_bsp:/cache # insmod my_platform_device.ko

//内核打出my_platform_probe说明匹配成功
[ 2111.422598] <6>.(5)[3265:insmod]my_platform_probe

2) 用设备树方式匹配

dts增加配置

my_platform_dts: my_platform_dts {
compatible = "mediatek,my_platform";
};

注册platform_driver

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

int my_platform_probe(struct platform_device *pdev)
{
printk("my_platform_probe\n");
return 0;
}


int my_platform_remove(struct platform_device *pdev)
{
printk("my_platform_remove\n");
return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id my_platform_match[] = {
{.compatible = "mediatek,my_platform"},
{},
};
#endif

struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my_platform",
#ifdef CONFIG_OF
.of_match_table = my_platform_match,
#endif
},
};

static int my_platform_drv_init(void)
{
{
platform_driver_register(&my_platform_driver);
return 0;
}

static void my_platform_drv_exit(void)
{
platform_driver_unregister(&my_platform_driver);
}

module_init(my_platform_drv_init);
module_exit(my_platform_drv_exit);

验证结果

XF-X2:/cache # insmod my_platform_drive.ko

//加载模块时内核打印出probe
[ 1018.455974] <4>.(7)[3165:insmod]my_platform_probe

5、dts如何生成 platform设备

对于当前的内核,我们一般不会主动去创建一个 platform 设备,我们往往通过设备树的方式添加 platform 设备。 例如 i2c 设备的 dts 如下

/ {
i2c0: i2c@11007000 {
compatible = "mediatek,i2c";
id = <0>;
reg = <0 0x11007000 0 0x1000>,
<0 0x11000080 0 0x80>;
interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
clocks = <&infracfg_ao INFRACFG_AO_I2C0_CG>,
<&infracfg_ao INFRACFG_AO_AP_DMA_CG>;
clock-names = "main", "dma";
clock-div = <5>;
mediatek,hs_only;
mediatek,skip_scp_sema;
};
};

该 dts 将会在内核中被解析为一个设备名为 11007000.i2c 的 platform 设备, 解析的规则是什么,跟着源码看一下

1) of_platform_default_populate_init

static int __init of_platform_default_populate_init(void)
{
struct device_node *node;

......
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL); //解析设备树并创建对应的 platform 设备
......

return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

2) of_platform_default_populate

//这里对传入的参数进行注释
//root = NULL
//of_default_bus_match_table
/*
* const struct of_device_id of_default_bus_match_table[] = {
* { .compatible = "simple-bus", },
* { .compatible = "simple-mfd", },
* { .compatible = "isa", },
* #ifdef CONFIG_ARM_AMBA
* { .compatible = "arm,amba-bus", },
* #endif /* CONFIG_ARM_AMBA */
* {} /* Empty terminated list */
* };
*
*/
//lookup = NULL
//parent = NULL
int of_platform_default_populate(struct device_node *root,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
return of_platform_populate(root, of_default_bus_match_table, lookup,
parent);
}

3) of_platform_populate

int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;

// root为空返回根节点
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;

pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);

for_each_child_of_node(root, child) { //对根节点下的每一个二级节点调用 of_platform_bus_create
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);

of_node_put(root);
return rc;
}

4) of_platform_bus_create

static int of_platform_bus_create(struct device_node *bus,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent, bool strict)
{
const struct of_dev_auxdata *auxdata;
struct device_node *child;
struct platform_device *dev;
const char *bus_id = NULL;
void *platform_data = NULL;
int rc = 0;

/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) { //检测当前节点是否有 compatible 属性
pr_debug("%s() - skipping %pOF, no compatible prop\n",
__func__, bus);
return 0;
}

if (of_node_check_flag(bus, OF_POPULATED_BUS)) { // 检测标志位防止重复注册
pr_debug("%s() - skipping %pOF, already populated\n",
__func__, bus);
return 0;
}

auxdata = of_dev_lookup(lookup, bus); // lookup 为 NULL 这里返回 NULL
if (auxdata) {
bus_id = auxdata->name;
platform_data = auxdata->platform_data;
}

if (of_device_is_compatible(bus, "arm,primecell")) { //从注释可以看出这里只是为了兼容老的设备树文件
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
of_amba_device_create(bus, bus_id, platform_data, parent);
return 0;
}

//真正的 platform 设备创建函数
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
////注意这里会和 dts节点进行匹配,如果匹配不上则会直接返回,因此一般情况下,是不会注册三级节点为设备节点
if (!dev || !of_match_node(matches, bus))
return 0;

for_each_child_of_node(bus, child) { //遍历bus的子节点回调 of_platform_bus_create 创建对应的 platform 设备
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}

5) of_platform_device_create_pdata

static struct platform_device *of_platform_device_create_pdata(
struct device_node *np,
const char *bus_id,
void *platform_data,
struct device *parent)
{
struct platform_device *dev;

//检测 dts 中的 status 属性是否为ture,默认为ture
//检测 OF_POPULATED 防止重复注册
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED))
return NULL;

// 动态创建一个 platform_device 并做简单初始化
// 检测该设备的 resources 并做相应的初始化
// 初始化设备的设备节点,如果该设备节点不存在 reg 属性则使用 np->parent 作为其父设备
// 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字,否则使用 "node->full_name" 作为设备名
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;

dev->dev.bus = &platform_bus_type; //初始化总线类型
dev->dev.platform_data = platform_data; //初始化私有数据这里为NULL
of_msi_configure(&dev->dev, dev->dev.of_node);

if (of_device_add(dev) != 0) { //注册 platform 设备
platform_device_put(dev);
goto err_clear_flag;
}

return dev;

err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}

6) of_device_alloc

// 动态创建一个 platform_device 并做简单初始化
// 检测该设备的 resources 并做相应的初始化
// 初始化设备的设备节点,如果该设备节点不存在 reg 属性则使用 np->parent 作为其父设备
// 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字,否则使用 "node->full_name" 作为设备名
struct platform_device *of_device_alloc(struct device_node *np,
const char *bus_id,
struct device *parent)
{
struct platform_device *dev;
int rc, i, num_reg = 0, num_irq;
struct resource *res, temp_res;

//动态创建一个 platform_device 并做简单初始化
dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
if (!dev)
return NULL;

/* count the io and irq resources */
//检测该设备的 resources 并做相应的初始化
while (of_address_to_resource(np, num_reg, &temp_res) == 0)
num_reg++;
num_irq = of_irq_count(np);

/* Populate the resource table */
if (num_irq || num_reg) {
res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
if (!res) {
platform_device_put(dev);
return NULL;
}

dev->num_resources = num_reg + num_irq;
dev->resource = res;
for (i = 0; i < num_reg; i++, res++) {
rc = of_address_to_resource(np, i, res);
WARN_ON(rc);
}
if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
pr_debug("not all legacy IRQ resources mapped for %s\n",
np->name);
}

dev->dev.of_node = of_node_get(np); // 初始化并增加设备节点引用计数
dev->dev.fwnode = &np->fwnode;

// 如果没有父设备则使用 platform_bus 作为父设备,前面整个过程都没有设置父设备
// 因此 dts 解析出来的设备将全部位于 /sys/devices/platform/ 下
dev->dev.parent = parent ? : &platform_bus;

if (bus_id) //bus_id = NULL 因此调用 of_device_make_bus_id 设置设备名
dev_set_name(&dev->dev, "%s", bus_id);
else
of_device_make_bus_id(&dev->dev);

return dev;
}
EXPORT_SYMBOL(of_device_alloc);

7) of_device_make_bus_id

// 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字
// 如果设备节点没有 reg 属性则使用 node->full_name 作为设备名,同时设置 node = node->parent
static void of_device_make_bus_id(struct device *dev)
{
struct device_node *node = dev->of_node;
const __be32 *reg;
u64 addr;

/* Construct the name, using parent nodes if necessary to ensure uniqueness */
while (node->parent) {
/*
* If the address can be translated, then that is as much
* uniqueness as we need. Make it the first component and return
*/
reg = of_get_property(node, "reg", NULL); //获取 reg 属性
if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) { //将 reg 表示的物理地址解析出来赋值给 addr
//如果解析成功则使用 "addr.node->name" 作为该设备的名字
dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
(unsigned long long)addr, node->name,
dev_name(dev));
return;
}

/* format arguments only used if dev_name() resolves to NULL */
// 如果没有 reg 属性则使用 node->full_name 作为设备名
dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
kbasename(node->full_name), dev_name(dev));
node = node->parent;
}
}

8) 总结

  • 只要dts中的节点有 compatible 属性,将会在内核中将该节点转换为 platform 设备,该设备将出现在/sys/devices/platform/
  • 如果有设备节点有 reg 属性则使用addr.node->name作为该设备的名字
  • 如果设备节点没有 reg 属性则使用 node->full_name作为设备名,同时设置node = node->parent

标签:struct,bus,模型,dev,kobj,linux,device,驱动,my
From: https://www.cnblogs.com/sheng520/p/18680832

相关文章

  • Linux常用命令-LVM逻辑卷管理
    Extend扩展vgextendlvextendReduce减少vgreducelvreduce1、PV物理卷管理1.1)、pvscan命令pvscan用于扫描系统中的所有物理卷,并输出相关信息。  1.2)、pvcreate命令pvcreate用于将分区或整个硬盘转换成物理卷,主要是添加LVM属性信息并划分PE存储单位。该命令需要使用硬盘......
  • Linux 常用操作命令大全
    目录一、基础知识1.1Linux系统的文件二、基础操作2.1重启系统2.2关闭系统切换用户(su)三、目录/文件操作3.1切换目录(cd)3.2查看目录(ls)3.3创建目录(mkdir)3.4删除/文件(rm)3.5修改(重命名)目录/文件(mv)3.6拷贝目录/文件(cp)3.7搜索/文件(find)3.8查看当前目录(pwd)四、文件操作4.1新......
  • apifoxCli在linux部署运行教程
    转载于 https://apifox.com/help/automated-testing/executing-test/apifox-cli/ #1、pull镜像dockerpullnode:22-alpine#2、创建目录mkdir-p/home/testTool/apifox/script#3、运行nodejs+并使用npm安装apifoxdockerrun\--nameapifox\-eTZ=Asia/Shanghai\--......
  • 计算机毕业设计Springboot智慧展馆系统 基于Springboot框架的智能展览馆管理系统开发
    计算机毕业设计Springboot智慧展馆系统x2k8li46(配套有源码程序mysql数据库论文)本套源码可以先看具体功能演示视频领取,文末有联xi可分享随着科技的飞速发展,传统的展览馆已经无法满足现代观众对互动性和信息获取的需求。在数字化时代,人们渴望更加智能化、个性化的参观体验......
  • AIGC视频生成明星——Emu Video模型
    大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Meta的视频生成模型EmuVideo,作为Meta发布的第二款视频生成模型,在视频生成领域发挥关键作用。......
  • Linux 下如何修改密码有效期?
    chage命令chage命令用于查看以及修改用户密码的有效期信息,它是changeage的缩写。当需要用户在指定时间内登录或者需要及时修改密码的时候可以使用此命令,通过此命令,我们可以查看帐号的各种信息,比如:最新一次修改密码日期、设置密码修改时间、在指定时间后锁定帐号。我们可......
  • Linux top命令详解
    查看多核CPU命令mpstat -P ALL  和  sar -P ALL 说明:sar -P ALL > aaa.txt   重定向输出内容到文件 aaa.txt1top 命令,然后按数字“1”可监控每个逻辑CPU的状况:观察上图,服务器有8个逻辑CPU,实际上是1个物理CPU。如果不按1,则在top视图里面显示的是所有cpu......
  • Linux查询端口是否被占用的四种方法
    一个面试题,使用三种不同的方法查看8080被哪个进程占用了。通常比较熟悉的方法是netstat和lsof两种,但还有什么方法呢。1.netstat或ss命令netstat-anlp|grep802.lsof命令这个命令是查看进程占用哪些文件的lsof-i:803.fuser命令fuser命令和lsof正好相反,是查看某个文......
  • 用Python实现SVM搭建金融反诈模型(含调试运行)
    1.概述信用卡盗刷一般发生在持卡人信息被不法分子窃取后,复制卡片进行消费或信用卡被他人冒领后激活并消费等情况下。一旦发生信用卡盗刷,持卡人和银行都会遭受一定的经济损失。本节要运用支持向量机分类算法搭建一个金融反欺诈模型。2.数据集使用的数据集共有1000条客户信用......
  • 基于AutoDL 进行 Llama_Factory+LoRA大模型微调
       其实这个环境的搭建比较容易,但是其中出现在AutoDL上访问WebUI界面是无法访问的,对该问题查阅了一些资料并记录.1.环境的配置及其校验Step1.使用Conda创建LLaMA-Factory的python虚拟环境condacreate-nllama_factorypython==3.11创建完成后,通过如下命令进入该......