RK3568驱动指南|第九篇 设备模型-第104章 设备注册流程分析实验

发布时间:2024年01月04日

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。

?
【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第五期_中断_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第104章?设备注册流程分析实验

在上个章节中,我们成功在自定义总线上注册了设备,使得系统能够正确地管理和操作该设备。本章节我们将深入分析设备注册的流程,从代码的层面来理解设备的注册过程。

104.1 device_register函数分析

device_register函数原型定义在drivers/base/core.c文件中,如下所示:

int device_register(struct device *dev)
{
	   device_initialize(dev);
	   return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);

上述代码展示了一个名为device_register的函数实现,用于注册设备到内核中。函数接受一个指向struct device类型的设备对象指针作为参数。首先,代码调用device_initialize函数对设备对象进行初始化。接下来,代码调用device_add函数将设备添加到内核中。device_add函数会将设备添加到设备总线的设备列表中,并执行与设备添加相关的操作,例如分配设备号、创建设备节点等。最后,函数返回设备添加的结果,通常是一个整数值表示成功或失败的状态码。

104.1.1 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);
	INIT_LIST_HEAD(&dev->links.needs_suppliers);
	INIT_LIST_HEAD(&dev->links.defer_hook);
	dev->links.status = DL_DEV_NO_DRIVER;
}
EXPORT_SYMBOL_GPL(device_initialize);

上述代码展示了一个名为device_initialize的函数实现,用于对设备对象进行初始化。函数接收一个指向struct device类型的设备对象指针作为参数。

首先,代码将设备对象的kobj.kset成员设置为devices_kset,表示该设备对象所属的kset为devices_kset,即设备对象属于devices子系统。

接下来,代码调用kobject_init函数初始化设备对象的kobj成员,使用device_ktype作为ktype。通过这个函数调用,设备对象的kobject被正确地初始化和设置。

然后,代码使用INIT_LIST_HEAD宏初始化设备对象的dma_pools、msi_list、consumers、suppliers、needs_suppliers和defer_hook等链表头,以确保它们为空链表。

代码接着调用mutex_init函数初始化设备对象的mutex互斥锁,用于对设备进行互斥操作。通过lockdep_set_novalidate_class函数,设置dev->mutex的验证类别为无效,以避免死锁分析器对该互斥锁的验证。

接下来,代码调用spin_lock_init函数初始化设备对象的devres_lock自旋锁,用于对设备资源进行保护。通过INIT_LIST_HEAD宏初始化设备对象的devres_head链表头,以确保它为空链表。

代码接着调用device_pm_init函数初始化设备对象的电源管理相关信息。然后,代码使用`set_dev_node`函数将设备对象的设备节点设置为-1,表示没有指定设备节点。在#ifdef CONFIG_GENERIC_MSI_IRQ条件编译块内,代码使用INIT_LIST_HEAD宏初始化设备对象的msi_list链表头,用于管理设备的MSI(消息信号中断)信息。

最后,代码使用INIT_LIST_HEAD宏初始化设备对象的consumers、suppliers、needs_suppliers和defer_hook等链表头,用于管理设备间的连接关系。代码将设备对象的status成员设置为DL_DEV_NO_DRIVER,表示设备当前没有驱动程序。

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

	/*
	 * 对于静态分配的设备(应该都会被转换),需要初始化设备名称。
	 * 我们禁止读回名称,并强制使用dev_name()函数。
	 */
	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);
	// 获取设备的父kobject
	kobj = get_device_parent(dev, parent);
	if (IS_ERR(kobj)) {
		error = PTR_ERR(kobj);
		goto parent_error;
	}
	if (kobj)
		dev->kobj.parent = kobj;

	// 使用父设备的NUMA节点
	if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
		set_dev_node(dev, dev_to_node(parent));

	// 首先,向通用层注册设备
	// 需要在此之前设置设备的名称,并将parent设置为NULL
	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);

	// 创建设备的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);
	}

	// 通知设备添加的事件链
	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);

	/*
	 * 检查其他设备(消费者)是否一直在等待该设备(供应者)的添加,
	 * 以便可以创建设备链接。
	 *
	 * 这需要在device_pm_add()之后进行,因为device_link_add()
	 * 要求在调用之前注册供应者。
	 *
	 * 但是,这也需要在bus_probe_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);
		// 将设备添加到类别的设备列表中
		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:
	// 如果存在主设备号,则移除设备的dev属性文件
	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:
	// 移除设备的uevent属性文件
	device_remove_file(dev, &dev_attr_uevent);

attrError:
	// 为设备的kobject发送KOBJ_REMOVE事件
	kobject_uevent(&dev->kobj, KOBJ_REMOVE);

	// 获取设备的粘合目录
	glue_dir = get_glue_dir(dev);

	// 删除设备的kobject
	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;
}

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

该函数以指向`struct device`的指针作为参数。它初始化了函数中使用的一些局部变量。

????dev = get_device(dev);

????if (!dev)

????????goto done;

调用get_device函数以获取设备的引用。

????if (!dev->p) {

????????error = device_private_init(dev);

????????if (error)

????????????goto done;

????}

如果设备结构体的p成员为空,那么调用device_private_init函数进行设备的私有初始化。

????if (dev->init_name) {

????????dev_set_name(dev, "%s", dev->init_name);

????????dev->init_name = NULL;

????}

如果设备的init_name成员非空,那么使用dev_set_name函数将其作为设备的名称,并将init_name设置为空。

????if (!dev_name(dev) && dev->bus && dev->bus->dev_name)

????????dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);

如果设备的名称为空且设备的总线(bus)和总线的名称(dev_name)非空,那么使用总线名称和设备ID设置设备的名称。

????if (!dev_name(dev)) {

????????error = -EINVAL;

????????goto name_error;

????}

如果设备的名称为空,那么设置错误码为-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标签处。

??if (parent && (dev_to_node(dev) == NUMA_NO_NODE))

????????set_dev_node(dev, dev_to_node(parent));

如果设备有父设备且设备的节点号为NUMA_NO_NODE,那么将设备的节点号设为父设备的节点号。

????error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);

????if (error) {

????????glue_dir = get_glue_dir(dev);

????????goto Error;

????}

使用kobject_add函数将设备的内核对象添加到内核对象层次结构中。如果添加过程中发生错误,那么获取设备的"粘合目录"(glue_dir)并跳转到`Error`标签处。

?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)相关操作,以及通知相关组件设备添加的过程。

在上述代码中,使用bus_add_device函数添加设备到总线中,我们再来追踪下这个函数。

int bus_add_device(struct device *dev)
{
    struct bus_type *bus = bus_get(dev->bus);  // 获取设备所属的总线类型(bus_type)的指针
    int error = 0;  // 错误码初始化为0

    if (bus) {  // 如果成功获取总线类型指针
        pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));  // 打印调试信息,包括总线名称和设备名称

        error = device_add_groups(dev, bus->dev_groups);  // 将设备添加到总线类型的设备组(dev_groups)中
        if (error)  // 如果添加过程中发生错误
            goto out_put;  // 跳转到 out_put 标签处,执行错误处理代码

        error = sysfs_create_link(&bus->p->devices_kset->kobj,
                                  &dev->kobj, dev_name(dev));  // 在总线类型的设备集(kset)的内核对象(kobj)下创建设备的符号链接
        if (error)  // 如果创建过程中发生错误
            goto out_groups;  // 跳转到 out_groups 标签处,执行错误处理代码

        error = sysfs_create_link(&dev->kobj,
                                  &dev->bus->p->subsys.kobj, "subsystem");  // 在设备的内核对象(kobj)下创建指向总线类型子系统(subsystem)的符号链接
        if (error)  // 如果创建过程中发生错误
            goto out_subsys;  // 跳转到 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_add_device()。下面是对该函数的分析:

1. bus_get(dev->bus):通过设备的 bus字段获取设备所属的总线类型(bus_type)的指针。这个函数会增加总线的引用计数,确保总线在设备添加过程中不会被释放。

2. if (bus):检查总线类型指针是否有效。

3. pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev)):打印调试信息,包括总线名称和设备名称,以便跟踪设备添加的过程。

4. device_add_groups(dev, bus->dev_groups):将设备添加到总线类型的设备组(dev_groups)中。设备组是一组属性文件,用于在设备的 sysfs 目录中显示和设置设备的属性。

5. sysfs_create_link(&bus->p->devices_kset->kobj, &dev->kobj, dev_name(dev)):在总线类型的设备集(devices_kset)的内核对象(kobj)下创建设备的符号链接。这个符号链接将设备的 sysfs 目录链接到总线类型的设备集目录中。

6. sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "subsystem"):在设备的内核对象(kobj)下创建指向总线类型子系统(subsystem)的符号链接。这个符号链接将设备的 sysfs 目录链接到总线类型子系统的目录中。

7. klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices):将设备的节点添加到总线类型的设备列表中。这个步骤用于维护总线类型下的设备列表。

至此,device_register函数分析结束。


文章来源:https://blog.csdn.net/BeiJingXunWei/article/details/135385402
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。