linux内核设备的注册由device_register()函数完成,这个函数是linux设备驱动模型的核心函数,实现在/drivers/base/core.c中:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);
在device_register()函数中,分为两个步骤:
device_initialize分析
该函数接收一个struct device *dev
参数,在该函数中初始化struct device
结构中的几个重要成员:
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);
#ifdef CONFIG_PROVE_LOCKING
mutex_init(&dev->lockdep_mutex);
#endif
l ockdep_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);
INIT_LIST_HEAD(&dev->links.defer_sync);
dev->links.status = DL_DEV_NO_DRIVER;
}
EXPORT_SYMBOL_GPL(device_initialize);
在struct device
中的links表示链接到该设备的suppliers和consumers,由struct dev_links_info
表示:
-
设置device下的links.status值为DL_DEV_NO_DRIVER,表示此时还没有对应驱动attach到这个设备。
以上步骤则是device_initialize()初始化设备时完成的操作。
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) {
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) {
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);
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))
set_dev_node(dev, dev_to_node(parent));
/* 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;
}
/* notify platform of device entry */
error = device_platform_notify(dev, KOBJ_ADD);
if (error)
goto platform_error;
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);
}
/* 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_uevent(&dev->kobj, KOBJ_ADD);
/*
* Check if any of the other devices (consumers) have been waiting for
* this device (supplier) to be added so that they can create a device
* link to it.
*
* This needs to happen after device_pm_add() because device_link_add()
* requires the supplier be registered before it's called.
*
* But this also needs to happen before bus_probe_device() to make sure
* waiting consumers can link to it before the driver is bound to the
* device and the driver sync_state callback is called for this device.
*/
if (dev->fwnode && !dev->fwnode->dev) {
dev->fwnode->dev = dev;
fw_devlink_link_device(dev);
}
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->p->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:
device_platform_notify(dev, KOBJ_REMOVE);
platform_error:
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;
}
EXPORT_SYMBOL_GPL(device_add);
-
(4)寻找父设备和父设备对应的kobj,并调用kobject_add()将dev->kobj添加到dev->kobj.parent:
-
(5)使用device_create_file为device创建sysfs属性文件:
dev_attr_uevent
是一个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_add_class_symlinks()的功能是将设备添加到指定的设备类中,并在/sys/class目录下为设备创建符号链接,以便用户空间程序能够方便地访问和管理设备。
-
(7)调用device_add_attrs()为设备添加属性:
device_add_attrs()的功能是为设备添加属性,并在/sys/devices目录下创建相应的属性文件。这样,用户空间程序可以通过访问设备的属性文件来读取和修改设备的属性值。这个函数在设备驱动的初始化过程中常常被调用,以确保设备的属性能够正确地显示和访问。
-
(8)调用bus_add_device()添加设备到bus:
bus_add_device用于将设备添加到总线上。它的功能是将一个设备(struct device结构体)添加到指定总线(struct bus_type结构体)上,并进行相应的初始化和注册操作。
bus_add_device的执行逻辑:
-
(1)从dev->bus中取得bus_type*类型的指针bus,如果获取bus不成功,则函数直接返回;如果bus获取成功,则会继续后续的第(2)步操作。
-
(2)调用device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,这个操作会体现在sysfs文件系统中的/sys/devices/xxx/xxx_device/目录中。
-
(3)调用device_add_groups将bus_dev_groups添加到内核中。
-
(4)调用sysfs_create_link将该设备在sysfs中的目录,链接到该bus的devices目录下
-
(5)接着依然调用sysfs_create_link,在该设备的sysfs目录中,创建一个指向该设备所在bus目录的链接,命名为subsystem。
-
(6)前面几个操作实则是向sysfs文件系统注册关于设备的信息,向用户空间抛出接口。{BANNED}最佳后步骤则是调用klist_add_tail()将该设备指针保存到bus->p->klist_devices中。
-
(11)通过bus_notifier告知系统设备已经添加:
-
(12)调用bus_probe_device()为该设备probe一个驱动。该函数实现如下:
具体执行流程如下:
-
(1)从dev中解析出该dev所在而bus,如果bus不存在,则退出该函数。
-
(2)如果设置了driver_autoprobe,则调用device_initial_probe(dev)。该函数本质调用到device_attach(),尝试将设备连接到驱动程序。
-
(3)遍历bus上的子系统接口链表interfaces,如果add_dev函数指针存在,则调用对应的函数。(从源码来看有些驱动程序,会使用struct subsys_interface来实现,在此处实现对注册的subsys_interface下的add_dev的调用执行)
-
(13)如果父设备存在,则会将该设备添加到父设备的klist_children链表中(klist_children是包含此设备的所有子节点的链表):
-
(14)如果设备的class不为NULL,则会将class绑定到device:
在内核中,struct class_interface是用于表示设备类和设备驱动之间的接口的结构体。它定义了设备类与设备驱动之间的关联关系,允许设备驱动在注册时与相应的设备类进行关联,并提供了一组函数指针,用于设备类调用设备驱动中的操作。
struct class_interface结构体定义如下:
struct class_interface {
struct list_head node;
struct class *class;
int (*add_dev) (struct device *, struct class_interface *);
void (*remove_dev) (struct device *, struct class_interface *);
};
-
node: 用于将struct class_interface链接到设备类的接口链表中。
-
class: 指向与该接口相关联的设备类。
-
add: 指向设备类调用设备驱动的添加操作的函数指针。当设备添加到设备类时,会调用此函数。
-
remove: 指向设备类调用设备驱动的移除操作的函数指针。当设备从设备类中移除时,会调用此函数。
通过使用struct class_interface,设备驱动可以与设备类进行交互,以便在设备添加或移除时执行相应的操作。这种机制允许设备驱动与设备类解耦,使得设备驱动可以在设备类的上下文中执行一些操作,而无需直接操作设备类。
回到device_add()中,使用了list_for_each_entry()遍历interfaces链表,如果设置了class_intf->add_dev,则调用该回调函数指针指向的函数。
总结
结合本文内容和linux内核源码,得出以下结论:
-
(1)在设备驱动模型中,所有的设备注册操作{BANNED}最佳后都会调用device_register()函数实现。
-
(2)在笔者分析的linux版本下的device_register()中,存在两个数据结构:struct class_interface 和struct subsys_interface。从内核源码来看,这两个结构只在为数不多的几个特定驱动程序中使用,那么可证明这可能是历史发展遗留下来的代码,在device_register中仍然保留了对这部分代码的支持。
-
(3)在device_register()中调用了bus_probe_device(),从而证明在注册设备的时候发生了『设备与驱动匹配』的过程。