LV205-注册设备到总线
前面已经可以注册自定义总线了,现在来看看怎么在总线上注册一个设备?
一、设备
其实下面这些大部分在《LV005-设备模型简介.md》这一节中都已经大概了解过了,这里简单回顾一下吧。
1. 设备简介
驱动开发的过程中,我们最关心的莫过于设备以及对应的驱动了。我们编写驱动的目的,最终就是为了使设备可以正常工作。在 Linux 中,一切都是以文件的形式存在, 设备也不例外。/sys/devices 目录记录了系统中所有设备,实际上在 sys 目录下所有设备文件最终都会指向该目录对应的设备文件;此外还有另一个目录/sys/dev 记录所有的设备节点, 但实际上都是些链接文件,同样指向了 devices 目录下的文件。

1.1 struct device
在内核使用 device 结构体来描述我们的物理设备,这个结构体定义在 device.h - include/linux/device.h - struct device:
struct device {
struct device *parent;
//......
const char *init_name; /* initial name of the device */
//......
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *platform_data; /* Platform specific data, device core doesn't touch it */
void *driver_data; /* Driver data, set and get with dev_set/get_drvdata */
//......
struct device_node *of_node; /* associated device tree node */
//......
dev_t devt; /* dev_t, creates the sysfs "dev" */
//......
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
//......
};- parent :表示该设备的父对象,前面提到过,旧版本的设备之间没有任何关联,引入 Linux 设备模型之后,设备之间呈树状结构,便于管理各种设备;
- init_name :指定该设备的名称,总线匹配时,一般会根据比较名字,来进行配对;
- bus :表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册到对应的总线。
- platform_data :特定设备的私有数据,通常定义在板级文件中;
- driver_data :同上,驱动层可通过 dev_set/get_drvdata 函数来获取该成员;
- of_node :存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的 of_match_table 以及设备树的 compatible 属性进行比较之后,将匹配的节点保存到该变量。
- devt :dev_t 类型变量,字符设备章节提及过,它是用于标识设备的设备号,该变量主要用于向/sys 目录中导出对应的设备。
- class :指向了该设备对应类,开篇我们提到的触摸,鼠标以及键盘等设备,对于计算机而言,他们都具有相同的功能,都归属于输入设备。我们可以在/sys/class 目录下对应的类找到该设备,如 input、leds、pwm 等目录;
- group :指向 struct attribute_group 类型的指针,指定该设备的属性;
- release :回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”的错误。
1.2 设备的注册与注销
1.2.1 device_register()
device_register() 定义如下:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);【参数说明】
- dev : struct device 结构体类型指针。
【返回值】
- 成功: 0
- 失败: 负数
1.2.2 device_unregister()
device_unregister() 定义如下:
void device_unregister(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
device_del(dev);
put_device(dev);
}
EXPORT_SYMBOL_GPL(device_unregister);【参数说明】
- dev :struct device 结构体类型指针
【返回值】 无
1.3 总结
当成功注册总线时,会在/sys/bus 目录下创建对应总线的目录,该目录下有两个子目录,分别是 drivers 和 devices, 我们使用 device_register 注册的设备从属于某个总线时,该总线的 devices 目录下便会存在该设备文件。
2. 设备属性文件
在开发单片机的时候,如果想要读取某个寄存器的值,我们可能需要加入一些新的代码,并重新编译。但对于 Linux 内核来讲,每次都需要编译一遍源码, 实在太浪费时间和精力了。为此,Linux 提供以下接口,来注册和注销一个设备属性文件。我们可以通过这些接口直接在用户层进行查询/修改,避免了重新编译内核的麻烦。
2.1 DEVICE_ATTR()
DEVICE_ATTR()是一个宏,它定义在 device.h - include/linux/device.h - DEVICE_ATTR:
#define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)struct device_attribute 定义在 device.h - include/linux/device.h - struct device_attribute
/* interface for exporting device attributes */
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);
};- DEVICE_ATTR 宏:定义用于定义一个 device_attribute 类型的变量,##表示将##左右两边的标签拼接在一起,因此, 我们得到变量的名称应该是带有 dev_attr_前缀的。该宏定义需要传入四个参数_name,_mode,_show,_store,分别代表了文件名, 文件权限,show 回调函数,store 回调函数。show 回调函数以及 store 回调函数分别对应着用户层的 cat 和 echo 命令, 当我们使用 cat 命令,来获取/sys 目录下某个文件时,最终会执行 show 回调函数;使用 echo 命令,则会执行 store 回调函数。 参数_mode 的值,可以使用 S_IRUSR、S_IWUSR、S_IXUSR 等宏定义。
2.2 device_create_file()
device_create_file()函数,声明在 device.h - include/linux/device.h - device_create_file
extern int device_create_file(struct device *device,
const struct device_attribute *entry);- device_create_file() 函数用于创建文件,它有两个参数成员,第一个参数表示的是设备,前面学习 device 结构体时,其成员中有个 bus_type 变量, 用于指定设备挂载在某个总线上,并且会在总线的 devices 子目录创建一个属于该设备的目录,device 参数可以理解为在哪个设备目录下,创建设备文件。 第二个参数则是我们自己定义的 device_attribute 类型变量。
2.3 device_remove_file()
device_remove_file()函数,声明在 device.h - include/linux/device.h - device_remove_file:
extern void device_remove_file(struct device *dev,
const struct device_attribute *attr);- device_remove_file() 函数用于删除文件,当我们的驱动注销时,对应目录以及文件都需要被移除。 其参数和 device_create_file 函数的参数是一样。
2.4 使用实例
在调用 device_create_file() 函数之前,需要先定义好属性结构体 struct device_attribute ,并将其相关字段填充好。有两种方式,一种是直接定义:
struct device_attribute sdev_attr_data_var = {
.attr = {
.name = "sdev_attr_data", // 属性的名称, 将会显示在 下,并可以使用 cat/echo 进行操作
.mode = 0664, // 属性的访问权限
},
.show = sdev_attr_data_show, // 属性的 show 回调函数
.store = sdev_attr_data_store, // 属性的 show 回调函数
};
ret = device_create_file(&sdev, &sdev_attr_data_var);另一种是通过宏来定义:
// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 dev_attr_,后面要再拼接 name 才行
// #define DEVICE_ATTR(_name, _mode, _show, _store)
// struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
DEVICE_ATTR(name_var, S_IRUSR, sdev_attr_data_show, sdev_attr_data_store);
ret = bus_create_file(&sdev, &dev_attr_name_var); // dev_attr_ 是固定的,由宏决定show 和 store 函数实现如下:
typedef struct __SDEV_ATTR_VAR_{
char name_attr[32];
int data;
}sdev_attr_var_t;
sdev_attr_var_t sdev_attr = {0}; // sdevice 的属性
static ssize_t sdev_attr_data_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%d\n", sdev_attr.data);
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
return count;
}
static ssize_t sdev_attr_data_store(struct device * dev, struct device_attribute * attr, const char *buf, size_t count)
{
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
sscanf(buf, "%d\n", &sdev_attr.data);
return count;
}3. 设备注册到总线 demo
这里会涉及到两个内核模块的编译,一个是设备的内核模块,一个是总线的内核模块。
3.1 demo 源码
源码可以看这里,这里有两个,一个是只创建设备,另一个还为设备添加了属性文件操作。
这里直接已加了属性的 demo 为例 05_device_model/14_device_register_with_attr/sdevice_demo.c:
#include <linux/init.h> /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */
#include <linux/device.h>
#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"
// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif
#define SDEV_NAME "sdev" // 和驱动中的匹配名称相同时就可以匹配对应的驱动
extern struct bus_type sbus; // sbus 操作函数集的那个全局变量,包含了 match 函数
typedef struct __SDEV_ATTR_VAR_{
char name_attr[32];
int data;
}sdev_attr_var_t;
sdev_attr_var_t sdev_attr = {0}; // sdevice 的属性
/**
* @brief sdev_release()
* @note
* @param [in]
* @param [out]
* @retval
*/
void sdev_release(struct device *dev)
{
int len = 0;
const char *device_name = dev_name(dev);
len = strlen(device_name);
PRT("dev name is %s, len = %d\n", device_name, len);
}
static struct device sdev = {
.init_name = SDEV_NAME, // 设备的初始化名称 "sdev"
.bus = &sbus, // 所属总线
.release = sdev_release, // 设备的释放回调函数
};
/**
* @brief sdev_show()
* @note 提供 show 回调函数,这样用户便可以通过 cat 命令, 来查询相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_data_show(struct device *dev, struct device_attribute *attr, char *buf)
{
ssize_t count = 0;
count = sprintf(buf, "%d\n", sdev_attr.data);
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
return count;
}
/**
* @brief sdev_store()
* @note 提供 store 回调函数,这样用户便可以通过 echo 命令, 来修改相关属性参数
* @param [in]
* @param [out]
* @retval
*/
static ssize_t sdev_attr_data_store(struct device * dev, struct device_attribute * attr, const char *buf, size_t count)
{
PRT("attr->name=%s count=%d\n", attr->attr.name, count);
sscanf(buf, "%d\n", &sdev_attr.data);
return count;
}
// 下面这个宏可以简化属性变量的定义,从定义可以看出,这里定义的属性的变量前缀是 dev_attr_,后面要再拼接 name 才行
// #define DEVICE_ATTR(_name, _mode, _show, _store)
// struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
struct device_attribute sdev_attr_data_var = {
.attr = {
.name = "sdev_attr_data", // 属性的名称, 将会显示在 下,并可以使用 cat/echo 进行操作
.mode = 0664, // 属性的访问权限
},
.show = sdev_attr_data_show, // 属性的 show 回调函数
.store = sdev_attr_data_store, // 属性的 show 回调函数
};
/**
* @brief sdev_demo_init()
* @note 设备结构体以及属性文件结构体注册
* @param [in]
* @param [out]
* @retval
*/
static __init int sdev_demo_init(void)
{
int ret = 0;
printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n", __FUNCTION__,
__LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION);
PRT("sdev_demo module init!\n");
ret = device_register(&sdev);
if(ret < 0)
{
PRTE("device_register fail! ret = %d\n", ret);
goto err_device_register;
}
ret = device_create_file(&sdev, &sdev_attr_data_var);
if(ret < 0)
{
PRTE("device_create_file fail!\n");
goto err_device_create_file;
}
return 0;
err_device_create_file:
device_unregister(&sdev);
err_device_register:
return ret;
}
/**
* @brief sdev_demo_exit
* @note 设备结构体以及属性文件结构体注销。
* @param [in]
* @param [out]
* @retval
*/
static __exit void sdev_demo_exit(void)
{
device_remove_file(&sdev, &sdev_attr_data_var);
device_unregister(&sdev); // 取消注册设备
PRT("sdev_demo module exit!\n");
}
module_init(sdev_demo_init); // 将__init 定义的函数指定为驱动的入口函数
module_exit(sdev_demo_exit); // 将__exit 定义的函数指定为驱动的出口函数
/* 模块信息(通过 modinfo chrdev_led_demo 查看) */
MODULE_LICENSE("GPL v2"); /* 源码的许可证协议 */
MODULE_AUTHOR("sumu"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */3.2 开发板测试
我们编译完,把对对应的驱动拷贝到开发板中,按以下步骤测试效果。
- (1)一开始的 /sys/bus 目录 和 /sys/devices 目录

- (2)加载模块

加载模块后,会发现,在/sys/bus 目录生成了 sbus 目录,并且/sys/bus/sbus/devices 目录下也生成了指向设备的软链接,我们注册的设备可以在 /sys/devices 中看到。
- (3)查看/修改 sdev_attr_data 的值
总线的属性,之前已经修改和验证过了,这里只看一下设备的属性文件,其实操作都是一样的:

- (5)卸载模块

可以看到,卸载模块后,资源释放,sbus 目录被删除,相关的属性文件也都删除了,/sys/devices 中的 sdev 目录也删除了。
二、设备怎么注册的?
那肯定是从 device_register() 函数开始了。
1. device_register()
device_register() 函数定义如下:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);该函数用于注册设备到内核中。函数接受 一个指向 struct device 类型的设备对象指针作为参数。首先,代码调用 device_initialize() 函数对 设备对象进行初始化。接下来,代码调用 device_add() 函数将设备添加到内核中。
device_add() 函数会将设备添加到设备总线的设备列表中,并执行与设备添加相关的操作,例如分配设备号、 创建设备节点等。最后,函数返回设备添加的结果,通常是一个整数值表示成功或失败的状态码。
2. device_initialize()
device_initialize() 函数定义如下:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
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);
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);
dev->links.status = DL_DEV_NO_DRIVER;
}
EXPORT_SYMBOL_GPL(device_initialize);该函数用于对设备对象进行初始化。函数 接收一个指向 struct device 类型的设备对象指针作为参数。
dev->kobj.kset = devices_kset;代码将设备对象的 kobj.kset 成员设置为 devices_kset,表示该设备对象所属的 kset 为 devices_kset,即设备对象属于 devices 子系统。
kobject_init(&dev->kobj, &device_ktype);代码调用 kobject_init() 函数初始化设备对象的 kobj 成员,使用 device_ktype 作为 ktype。通过这个函数调用,设备对象的 kobject 被正确地初始化和设置。
INIT_LIST_HEAD(&dev->dma_pools);代码使用 INIT_LIST_HEAD 宏初始化设备对象的 dma_pools 链表头,以确保它为空链表。
mutex_init(&dev->mutex);代码接着调用 mutex_init() 函数初始化设备对象的 mutex 互斥锁,用于对设备进行互斥操作。
lockdep_set_novalidate_class(&dev->mutex);通过 lockdep_set_novalidate_class() 函数,设置 dev-> mutex 的验证类别为无效,以避免死锁分析 器对该互斥锁的验证。
spin_lock_init(&dev->devres_lock);调用 spin_lock_init() 函数初始化设备对象的 devres_lock 自旋锁,用于对设备 资源进行保护。通
INIT_LIST_HEAD(&dev->devres_head);过 INIT_LIST_HEAD 宏初始化设备对象的 devres_head 链表头,以确保它为空链表。
device_pm_init(dev);调用 device_pm_init() 函数初始化设备对象的电源管理相关信息。
set_dev_node(dev, -1);代码使用 set_dev_node() 函数将设备对象的设备节点设置为 -1,表示没有指定设备节点。
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif在#ifdef CONFIG_GENERIC_MSI_IRQ 条件编译块内,代码使用 INIT_LIST_HEAD 宏初始化设备对象的 msi_list 链表头,用于管理设备的 MSI(消息信号中断)信息。
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);代码使用 INIT_LIST_HEAD 宏初始化设备对象的 consumers、suppliers 等链表头,用于管理设备间的连接关系。
dev->links.status = DL_DEV_NO_DRIVER;代码将设备对象的 status 成员设置为 DL_DEV_NO_DRIVER ,表示设备当前没有驱动程序。
3. device_add()
device_add() 函数定义如下:
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);
if (!dev)
goto done;
if (!dev->p) {
// 如果设备的私有数据(private data)未初始化,则进行初始化
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()
*/
/* 对于静态分配的设备(应该都会被转换),需要初始化设备名称。我们禁止读回名称,并强制使用 dev_name()函数。*/
if (dev->init_name) {
// 初始化设备的名称
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
/* subsystems can specify simple device enumeration */
/* 子系统可以指定简单的设备枚举 */
// 如果设备的名称为空,并且设备所属总线的名称不为空,则设置设备名称
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
// 获取设备的父设备引用
parent = get_device(dev->parent);
// 获取设备的父 kobject
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 */
// 使用父设备的 NUMA 节点
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
/* first, register with generic layer. 首先,向通用层注册设备 */
/* we require the name to be set before, and pass NULL 我们需要在此之前设置设备的名称,并将 parent 设置为 NULL */
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);
// 创建设备的 uevent 属性文件
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
// 添加设备类的符号链接
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
// 添加设备的属性
error = device_add_attrs(dev);
if (error)
goto AttrsError;
// 将设备添加到总线
error = bus_add_device(dev);
if (error)
goto BusError;
// 在设备电源管理目录中添加设备
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev); // 添加设备到电源管理
// 如果设备的 devt 存在主设备号
if (MAJOR(dev->devt)) {
// 创建设备的 dev 属性文件
error = device_create_file(dev, &dev_attr_dev);
if (error)
goto DevAttrError;
// 创建设备的 sys 设备节点
error = device_create_sys_dev_entry(dev);
if (error)
goto SysEntryError;
// 在 devtmpfs 上创建设备节点
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);
// 为设备的 kobject 发送 KOBJ_ADD 事件
kobject_uevent(&dev->kobj, KOBJ_ADD);
// 对总线中的设备进行探测
bus_probe_device(dev);
if (parent) // 如果存在父设备,则将当前设备添加到父设备的子设备列表中
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 */
// 将设备添加到类别的设备列表中
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)) // 如果存在主设备号,则移除设备的 dev 属性文件
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); // 移除设备的 uevent 属性文件
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE); // 为设备的 kobject 发送 KOBJ_REMOVE 事件
glue_dir = get_glue_dir(dev); // 获取设备的粘合目录
kobject_del(&dev->kobj); // 删除设备的 kobject
Error:
cleanup_glue_dir(dev, glue_dir); // 清理设备的粘合目录
parent_error:
put_device(parent); // 释放父设备的引用
name_error:
kfree(dev->p); // 释放设备的私有数据
dev->p = NULL;
goto done;
} struct device *parent;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;初始化了函数中使用的一些局部变量。
- 第 1839 行
dev = get_device(dev);
if (!dev)
goto done;调用 get_device() 函数以获取设备的引用。
- 第 1843 行
if (!dev->p) {
error = device_private_init(dev);
if (error)
goto done;
}如果设备结构体的 p 成员为空,那么调用 device_private_init() 函数进行设备的私有初始化。
- 第 1854 行
/*
* 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) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}如果设备的 init_name 成员非空,那么使用 dev_set_name() 函数将其作为设备的名称,并将 init_name 设置为空。
/* subsystems can specify simple device enumeration */
if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
if (!dev_name(dev)) {
error = -EINVAL;
goto name_error;
}如果设备的名称为空且设备的总线(bus)和总线的名称(dev_name)非空,那么使用总线名称 和设备 ID 设置设备的名称。接着,检查一下设备名称,如果设备的名称为空,那么设置错误码为-EINVAL,并跳转到 name_error 标签处。
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
if (IS_ERR(kobj)) {
error = PTR_ERR(kobj);
goto parent_error;
}
if (kobj)
dev->kobj.parent = kobj;打印调试信息,包括设备的名称和函数名。获取设备的父设备,并设置设备的父对象。如果获取父对象的过程中发生错误,那么将错误码设为获取父对象的返回值,并跳转到 parent_error 标签处。
- 第 1879 行
/* use parent numa_node */
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));如果设备有父设备且设备的节点号为 NUMA_NO_NODE,那么将设备的节点号设为父设备 的节点号。
- 第 1885 行
/* first, register with generic layer. */
/* we require the name to be set before, and pass NULL */
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
if (error) {
glue_dir = get_glue_dir(dev);
goto Error;
}使用 kobject_add() 函数将设备的内核对象添加到内核对象层次结构中。如果添加过程中发 生错误,那么获取设备的 "粘合目录"(glue_dir)并跳转到 Error 标签处。
- 第 1892 行
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);如果存在平台通知函数(platform_notify),则调用该函数通知平台设备已添加。
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
if (error)
goto BusError;
error = dpm_sysfs_add(dev);
if (error)
goto DPMError;
device_pm_add(dev);创建设备的 uevent 属性文件。添加设备的类符号链接。添加设备的属性。添加设备到总 线。添加设备电源管理相关的 sysfs 接口。启动设备接下来的代码主要是处理设备的设备号 (devt)相关操作,以及通知相关组件设备添加的过程。
后面就不再详细分析了。
4. bus_add_device()
bus_add_device() 函数定义如下:
/**
* bus_add_device - add device to bus
* @dev: device being added
*
* - Add device's bus attributes.
* - Create links to device's bus.
* - Add the device to its bus's list of devices.
*/
int bus_add_device(struct device *dev)
{
// 获取设备所属的总线类型(bus_type)的指针
struct bus_type *bus = bus_get(dev->bus);
int error = 0; // 错误码初始化为 0
if (bus) { // 如果成功获取总线类型指针
// 打印调试信息,包括总线名称和设备名称
pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
// 将设备添加到总线类型的设备组(dev_groups)中
error = device_add_groups(dev, bus->dev_groups);
if (error) // 如果添加过程中发生错误
goto out_put;// 跳转到 out_put 标签处,执行错误处理代码
// 在总线类型的设备集(kset)的内核对象(kobj)下创建设备的符号链接
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
if (error)
goto out_groups;
// 在设备的内核对象(kobj)下创建指向总线类型子系统(subsystem)的符号链接
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
if (error)
goto out_subsys;
// 将设备的节点添加到总线类型的设备列表中
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0; // 返回 0 表示成功添加设备
out_subsys:
// 移除设备和总线类型子系统之间的符号链接
sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
// 从总线类型的设备组中移除设备
device_remove_groups(dev, bus->dev_groups);
out_put:
bus_put(dev->bus);// 减少设备的总线引用计数
return error;
}通过设备的 bus 字段获取设备所属的总线类型(bus_type)的指针。 这个函数会增加总线的引用计数,确保总线在设备添加过程中不会被释放。
检查总线类型指针是否有效。
打印调试信息,包括 总线名称和设备名称,以便跟踪设备添加的过程。
将设备添加到总线类型的设备组(dev_groups) 中。设备组是一组属性文件,用于在设备的 sysfs 目录中显示和设置设备的属性。
在总线类型 的设备集(devices_kset)的内核对象(kobj)下创建设备的符号链接。这个符号链接将设备的 sysfs 目录链接到总线类型的设备集目录中。
在设备的内核对 象(kobj)下创建指向总线类型子系统(subsystem)的符号链接。这个符号链接将设备的 sysfs 目录链接到总线类型子系统的目录中。
将设备的节点添加到总线类 型的设备列表中。这个步骤用于维护总线类型下的设备列表。