瑞芯微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主板
在上个章节中,我们成功在自定义总线上注册了设备,使得系统能够正确地管理和操作该设备。本章节我们将深入分析设备注册的流程,从代码的层面来理解设备的注册过程。
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函数会将设备添加到设备总线的设备列表中,并执行与设备添加相关的操作,例如分配设备号、创建设备节点等。最后,函数返回设备添加的结果,通常是一个整数值表示成功或失败的状态码。
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,表示设备当前没有驱动程序。
接下来我们分析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函数分析结束。