前面已经可以注册自定义总线了,现在来看看怎么在总线上注册一个设备?若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.2的64位版本 VMware® Workstation 17 Pro 17.6.0 build-24238078 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
一、设备 其实下面这些大部分在《LV06-04-linux设备模型-01-设备模型简介.md》这一节中都已经大概了解过了,这里简单回顾一下吧。
1. 设备简介 驱动开发的过程中,我们最关心的莫过于设备以及对应的驱动了。我们编写驱动的目的,最终就是为了使设备可以正常工作。在Linux中,一切都是以文件的形式存在, 设备也不例外。/sys/devices目录记录了系统中所有设备,实际上在sys目录下所有设备文件最终都会指向该目录对应的设备文件;此外还有另一个目录/sys/dev记录所有的设备节点, 但实际上都是些链接文件,同样指向了devices目录下的文件。
1.1 struct device 在内核使用device结构体来描述我们的物理设备,这个结构体定义在 device.h - include/linux/device.h - struct device :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct device { struct device *parent ; const char *init_name; struct bus_type *bus ; struct device_driver *driver ; void *platform_data; void *driver_data; struct device_node *of_node ; dev_t devt; struct class *class ; const struct attribute_group **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() 定义如下:
1 2 3 4 5 6 int device_register (struct device *dev) { device_initialize(dev); return device_add(dev); } EXPORT_SYMBOL_GPL(device_register);
参数:
返回值:
1.2.2 device_unregister() device_unregister() 定义如下:
1 2 3 4 5 6 7 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);
参数:
返回值: 无
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 :
1 2 #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
1 2 3 4 5 6 7 8 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
1 2 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 :
1 2 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 ,并将其相关字段填充好。有两种方式,一种是直接定义:
1 2 3 4 5 6 7 8 9 10 struct device_attribute sdev_attr_data_var = { .attr = { .name = "sdev_attr_data" , .mode = 0664 , }, .show = sdev_attr_data_show, .store = sdev_attr_data_store, }; ret = device_create_file(&sdev, &sdev_attr_data_var);
另一种是通过宏来定义:
1 2 3 4 5 DEVICE_ATTR(name_var, S_IRUSR, sdev_attr_data_show, sdev_attr_data_store); ret = bus_create_file(&sdev, &dev_attr_name_var);
show和store函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 typedef struct __SDEV_ATTR_VAR_ { char name_attr[32 ]; int data; }sdev_attr_var_t ; sdev_attr_var_t sdev_attr = {0 }; 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源码 源码可以看这里,这里有两个,一个是只创建设备,另一个还为设备添加了属性文件操作。
05_device_model/13_device_register · 苏木/imx6ull-driver-demo - 码云 - 开源中国
05_device_model/14_device_register_with_attr · 苏木/imx6ull-driver-demo - 码云 - 开源中国
点击查看详情
这里直接已加了属性的demo为例 05_device_model/14_device_register_with_attr/sdevice_demo.c · 苏木/imx6ull-driver-demo - 码云 - 开源中国 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/device.h> #include "./timestamp_autogenerated.h" #include "./version_autogenerated.h" #include "./sdrv_common.h" #ifndef PRT #define PRT printk #endif #ifndef PRTE #define PRTE printk #endif #define SDEV_NAME "sdev" extern struct bus_type sbus ; typedef struct __SDEV_ATTR_VAR_ { char name_attr[32 ]; int data; }sdev_attr_var_t ; sdev_attr_var_t sdev_attr = {0 }; 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, .bus = &sbus, .release = sdev_release, }; 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; } struct device_attribute sdev_attr_data_var = { .attr = { .name = "sdev_attr_data" , .mode = 0664 , }, .show = sdev_attr_data_show, .store = sdev_attr_data_store, }; 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; } 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); module_exit(sdev_demo_exit); MODULE_LICENSE("GPL v2" ); MODULE_AUTHOR("sumu" ); MODULE_DESCRIPTION("Description" ); MODULE_ALIAS("module's other name" );
3.2 开发板测试 我们编译完,把对对应的驱动拷贝到开发板中,按以下步骤测试效果。
(1)一开始的 /sys/bus 目录 和 /sys/devices 目录
加载模块后,会发现,在/sys/bus目录生成了 sbus目录,并且/sys/bus/sbus/devices 目录下也生成了指向设备的软链接,我们注册的设备可以在 /sys/devices 中看到。
总线的属性,之前已经修改和验证过了,这里只看一下设备的属性文件,其实操作都是一样的:
可以看到,卸载模块后,资源释放,sbus目录被删除,相关的属性文件也都删除了,/sys/devices 中的sdev目录也删除了。
二、设备怎么注册的? 那肯定是从 device_register() 函数开始了。
device_register() 函数定义如下:
1 2 3 4 5 6 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() 函数会将设备添加到设备总线的设备列表中,并执行与设备添加相关的操作,例如分配设备号、 创建设备节点等。最后,函数返回设备添加的结果,通常是一个整数值表示成功或失败的状态码。
device_initialize() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 类型的设备对象指针作为参数。
1 dev->kobj.kset = devices_kset;
代码将设备对象的 kobj.kset 成员设置为 devices_kset,表示该设备对象所属的 kset 为 devices_kset,即设备对象属于 devices 子系统。
1 kobject_init(&dev->kobj, &device_ktype);
代码调用 kobject_init() 函数初始化设备对象的 kobj 成员,使用 device_ktype 作为 ktype。通过这个函数调用,设备对象的 kobject 被正确地初始化和设置。
1 INIT_LIST_HEAD(&dev->dma_pools);
代码使用 INIT_LIST_HEAD 宏初始化设备对象的 dma_pools链表头,以确保它为空链表。
1 mutex_init(&dev->mutex);
代码接着调用 mutex_init() 函数初始化设备对象的 mutex 互斥锁,用于对设备进行互斥操作。
1 lockdep_set_novalidate_class(&dev->mutex);
通过 lockdep_set_novalidate_class() 函数,设置 dev->mutex 的验证类别为无效,以避免死锁分析 器对该互斥锁的验证。
1 spin_lock_init(&dev->devres_lock);
调用 spin_lock_init() 函数初始化设备对象的 devres_lock 自旋锁,用于对设备 资源进行保护。通
1 INIT_LIST_HEAD(&dev->devres_head);
过 INIT_LIST_HEAD 宏初始化设备对象的 devres_head 链表头,以确保它为空链表。
调用 device_pm_init() 函数初始化设备对象的电源管理相关信息。
代码使用 set_dev_node() 函数将设备对象的设备节点设置为 -1,表示没有指定设备节点。
1 2 3 #ifdef CONFIG_GENERIC_MSI_IRQ INIT_LIST_HEAD(&dev->msi_list); #endif
在#ifdef CONFIG_GENERIC_MSI_IRQ 条件编译块内,代码使用 INIT_LIST_HEAD 宏初始化设备对象的 msi_list 链表头,用于管理设备的 MSI(消息信号中断)信息。
1 2 INIT_LIST_HEAD(&dev->links.consumers); INIT_LIST_HEAD(&dev->links.suppliers);
代码使用 INIT_LIST_HEAD 宏初始化设备对象的 consumers、suppliers等链表头,用于管理设备间的连接关系。
1 dev->links.status = DL_DEV_NO_DRIVER;
代码将设备对象的 status 成员设置为 DL_DEV_NO_DRIVER ,表示设备当前没有驱动程序。
device_add() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 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) { error = device_private_init(dev); if (error) goto done; } if (dev->init_name) { dev_set_name(dev, "%s" , dev->init_name); dev->init_name = NULL ; } 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); kobj = get_device_parent(dev, parent); if (IS_ERR(kobj)) { error = PTR_ERR(kobj); goto parent_error; } if (kobj) dev->kobj.parent = kobj; if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) set_dev_node(dev, dev_to_node(parent)); error = kobject_add(&dev->kobj, dev->kobj.parent, NULL ); if (error) { glue_dir = get_glue_dir(dev); goto Error; } if (platform_notify) platform_notify(dev); 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); if (MAJOR(dev->devt)) { error = device_create_file(dev, &dev_attr_dev); if (error) goto DevAttrError; error = device_create_sys_dev_entry(dev); if (error) goto SysEntryError; devtmpfs_create_node(dev); } if (dev->bus) blocking_notifier_call_chain(&dev->bus->p->bus_notifier, BUS_NOTIFY_ADD_DEVICE, dev); 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); klist_add_tail(&dev->knode_class, &dev->class->p->klist_devices); 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; }
1 2 3 4 5 struct device *parent ;struct kobject *kobj ;struct class_interface *class_intf ;int error = -EINVAL;struct kobject *glue_dir = NULL ;
初始化了函数中使用的一些局部变量。
1 2 3 dev = get_device(dev); if (!dev) goto done;
调用 get_device() 函数以获取设备的引用。
1 2 3 4 5 if (!dev->p) { error = device_private_init(dev); if (error) goto done; }
如果设备结构体的 p 成员为空,那么调用 device_private_init() 函数进行设备的私有初始化。
1 2 3 4 5 6 7 8 9 if (dev->init_name) { dev_set_name(dev, "%s" , dev->init_name); dev->init_name = NULL ; }
如果设备的 init_name 成员非空,那么使用 dev_set_name() 函数将其作为设备的名称,并将 init_name 设置为空。
1 2 3 4 5 6 7 8 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 标签处。
1 2 3 4 5 6 7 8 9 10 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 标签处。
1 2 3 if (parent && (dev_to_node(dev) == NUMA_NO_NODE)) set_dev_node(dev, dev_to_node(parent));
如果设备有父设备且设备的节点号为 NUMA_NO_NODE ,那么将设备的节点号设为父设备 的节点号。
1 2 3 4 5 6 7 error = kobject_add(&dev->kobj, dev->kobj.parent, NULL ); if (error) { glue_dir = get_glue_dir(dev); goto Error; }
使用 kobject_add() 函数将设备的内核对象添加到内核对象层次结构中。如果添加过程中发 生错误,那么获取设备的”粘合目录”(glue_dir)并跳转到 Error 标签处。
1 2 3 if (platform_notify) platform_notify(dev);
如果存在平台通知函数(platform_notify),则调用该函数通知平台设备已添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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)相关操作,以及通知相关组件设备添加的过程。
后面就不再详细分析了。
bus_add_device() 函数定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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)); error = device_add_groups(dev, bus->dev_groups); if (error) goto out_put; error = sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev_name(dev)); if (error) goto out_groups; 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 ; 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 目录链接到总线类型子系统的目录中。
将设备的节点添加到总线类 型的设备列表中。这个步骤用于维护总线类型下的设备列表。