瑞芯微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主板
在前面的章节中,我们介绍了设备模型四个重要的组成部分:总线,设备,驱动,类。然而,要更深入地理解设备模型,我们需要进一步探究其代码层面的实现。在第86章节实验中,我们创建了kobject。本章节我们从代码的层面分析下——为什么当创建kobj的时候,父节点为NULL,会在系统根目录/sys目录下创建呢。
sysfs文件系统是Linux内核提供的一种虚拟文件系统,用于向用户空间提供内核中设备,驱动程序和其他内核对象的信息。它以一种层次结构的方式组织数据,并将这些数据表示为文件和目录,使得用户空间可以通过文件系统接口访问和操作内核对象的属性。
sysfs 提供了一种统一的接口,用于浏览和管理内核中的设备、总线、驱动程序和其他内核对象。它在 /sys 目录下挂载,用户可以通过查看和修改 /sys 目录下的文件和目录来获取和配置内核对象的信息。
为什么说kobject和kset是设备模型的基本框架呢?本小节来进行详细阐述。
当使用kobject时,通常不会单独使用它,而是将其嵌入到一个数据结构中。这样做的目的是将高级对象接入到设备模型中。比如cdev结构体和platform_device结构体,如下所示:
cdev 结构体如下所示,其成员有kobject
struct cdev {
struct kobject kobj;//内嵌到cdev中的kobject
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
}
platform_device结构体如下所示,其成员有device结构体,在device结构体中包含了kobject 结构体。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
device结构体如下所示:
struct device {
//设备的父设备
struct device *parent;
//私有指针
struct device_private *p;
//对应的kobj
struct kobject kobj;
//设备初始化的名字
const char *init_name;
//设备类型
const struct device_type *type;
//设备所属的总线
struct bus_type *bus;
struct device_driver *driver;
...........
//设备所属的类
struct class *class;
//设备的属性组
const struct attribute_group **groups; /* optional groups */
...........
};
所以我们也可以把总线,设备,驱动看作是kobject的派生类。因为他们都是设备模型中的实体,通过继承或扩展kobject来实现与设备模型的集成。
在Linux内核中,kobject是一个通用的基础结构,用于构建设备模型。每个kobject实例对应于sys目录下的一个目录,这个目录包含了该kobject相关的属性,操作和状态信息。如下图所示:
图 89-1
因此,可以说kobject是设备模型的基石,通过创建对应的目录结构和属性文件, 它提供了一个统一的接口和框架,用于管理和操作设备模型中的各个实体。
在系统启动的时候会在/sys目录下创建以下目录,如下图所示:
图 89-2
让我们从代码层面一层层解释一下为什么当使用kobject_create_and_add()函数创建kobject时,父节点为NULL会在系统根目录/sys下创建。
逐步追踪路径如下所示:
kobject_create_and_add->kobject_add->kobject_add_varg->kobject_add_internal->create_dir->sysfs_create_dir_ns(fs/sysfs/dir.c)
接下来我们看一下kobject_create_and_add函数实现,如下所示:
struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
{
struct kobject *kobj;
int retval;
kobj = kobject_create();
if (!kobj)
return NULL;
retval = kobject_add(kobj, parent, "%s", name);
if (retval) {
pr_warn("%s: kobject_add error: %d\n", __func__, retval);
kobject_put(kobj);
kobj = NULL;
}
return kobj;
}
EXPORT_SYMBOL_GPL(kobject_create_and_add);
在上述代码中,首先调用了kobject_create()函数创建看一个新的kobject。该函数分配了一个新的kobject结构体,并对其进行初始化,包括将kobject的name字段设置为传入的name参数,将kobject的parent字段设置为传入的parent参数。
接下来,函数调用kobject_add()将新创建的kobject添加到设备模型中,我们进一步追究下kobject_add()实现,如下所示:
int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval;
if (!kobj)
return -EINVAL;
if (!kobj->state_initialized) {
pr_err("kobject '%s' (%p): tried to add an uninitialized object, something is seriously wrong.\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
在上述函数中,函数调用kobject_add_varg(),该函数会根据传入的参数将kobj添加到设备模型中。kobject_add_varg()函数的实现可能会根据具体情况进行一些额外的处理,例如创建对应的目录并设置父节点等。我们进一步探究下kobject_add_varg()函数的实现,如下所示:
static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
struct kobject *parent,
const char *fmt, va_list vargs)
{
int retval;
retval = kobject_set_name_vargs(kobj, fmt, vargs);
if (retval) {
pr_err("kobject: can not set name properly!\n");
return retval;
}
kobj->parent = parent;
return kobject_add_internal(kobj);
}
在上述函数中,kobject_add_varg()函数用于将指定的kobject添加到设备模型中。它通过调用kobject_set_name_vargs()设置kobject的名称,并将父节点赋值给kobject的parent字段。然后,它调用kobject_add_internal()函数执行实际的添加操作。接下来我们进一步探究下kobject_add_internal()函数的实现,如下所示:
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
if (!kobj->name || !kobj->name[0]) {
WARN(1,
"kobject: (%p): attempted to be registered with empty name!\n",
kobj);
return -EINVAL;
}
parent = kobject_get(kobj->parent);
/* join kset if set, use it as parent if we do not already have one */
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);
kobj_kset_join(kobj);
kobj->parent = parent;
}
pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
kobject_name(kobj), kobj, __func__,
parent ? kobject_name(parent) : "",
kobj->kset ? kobject_name(&kobj->kset->kobj) : "");
error = create_dir(kobj);
if (error) {
kobj_kset_leave(kobj);
kobject_put(parent);
kobj->parent = NULL;
/* be noisy on error issues */
if (error == -EEXIST)
pr_err("%s failed for %s with -EEXIST, don't try to register things with the same name in the same directory.\n",
__func__, kobject_name(kobj));
else
pr_err("%s failed for %s (error: %d parent: %s)\n",
__func__, kobject_name(kobj), error,
parent ? kobject_name(parent) : "'none'");
} else
kobj->state_in_sysfs = 1;
return error;
}
kobject_add_internal()函数用于在设备模型中添加指定的kobject。它会检查kobject的有效性和名称是否为空,并处理kobject所属的kset相关的操作。然后,它会创建kobject在sysfs中的目录,并处理创建失败的情况。最后,它会设置kobject的相关状态,并返回相应的错误。我们继续追究create_dir()函数的实现,如下所示:
static int create_dir(struct kobject *kobj)
{
const struct kobj_ns_type_operations *ops;
int error;
error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
if (error)
return error;
error = populate_dir(kobj);
if (error) {
sysfs_remove_dir(kobj);
return error;
}
/*
* @kobj->sd may be deleted by an ancestor going away. Hold an
* extra reference so that it stays until @kobj is gone.
*/
sysfs_get(kobj->sd);
/*
* If @kobj has ns_ops, its children need to be filtered based on
* their namespace tags. Enable namespace support on @kobj->sd.
*/
ops = kobj_child_ns_ops(kobj);
if (ops) {
BUG_ON(ops->type <= KOBJ_NS_TYPE_NONE);
BUG_ON(ops->type >= KOBJ_NS_TYPES);
BUG_ON(!kobj_ns_type_registered(ops->type));
sysfs_enable_ns(kobj->sd);
}
return 0;
}
create_dir()函数用于创建与给定kobject相关联的目录,并填充该目录。它还处理了引用计数、命名空间操作等相关的逻辑。如果创建目录或填充目录时发生错误,函数会相应地处理并返回错误码。函数调用sysfs_create_dir_ns()函数来创建与kobj相关联的目录。sysfs_create_dir_ns实现如下所示:
/**
* sysfs_create_dir_ns - create a directory for an object with a namespace tag
* @kobj: object we're creating directory for
* @ns: the namespace tag to use
*/
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
struct kernfs_node *parent, *kn;
kuid_t uid;
kgid_t gid;
BUG_ON(!kobj);
if (kobj->parent)
parent = kobj->parent->sd;
else
parent = sysfs_root_kn;
if (!parent)
return -ENOENT;
kobject_get_ownership(kobj, &uid, &gid);
kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
S_IRWXU | S_IRUGO | S_IXUGO, uid, gid,
kobj, ns);
if (IS_ERR(kn)) {
if (PTR_ERR(kn) == -EEXIST)
sysfs_warn_dup(parent, kobject_name(kobj));
return PTR_ERR(kn);
}
kobj->sd = kn;
return 0;
}
在上面的函数中,当没有父节点的时候,父节点被赋值成了sysfs_root_kn,即/sys目录根目录的节点。如果有parent,则它的父节点为 kobj->parent->sd,然后调用kernfs_create_dir_ns创建目录。那么sysfs_root_kn是在什么时候创建的呢?我们找到fs/sysfs/mount.c文件,如下所示:
int __init sysfs_init(void)
{
int err;
sysfs_root = kernfs_create_root(NULL, KERNFS_ROOT_EXTRA_OPEN_PERM_CHECK,
NULL);
if (IS_ERR(sysfs_root))
return PTR_ERR(sysfs_root);
sysfs_root_kn = sysfs_root->kn;
err = register_filesystem(&sysfs_fs_type);
if (err) {
kernfs_destroy_root(sysfs_root);
return err;
}
return 0;
}
通过上述对API函数的分析,我们可以总结出创建目录的规律,如下所示: