RK3568驱动指南|第九篇 设备模型-第89章 进一步探究设备模型

瑞芯微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主板


第89章 进一步探究设备模型

在前面的章节中,我们介绍了设备模型四个重要的组成部分:总线,设备,驱动,类。然而,要更深入地理解设备模型,我们需要进一步探究其代码层面的实现。在第86章节实验中,我们创建了kobject。本章节我们从代码的层面分析下——为什么当创建kobj的时候,父节点为NULL,会在系统根目录/sys目录下创建呢。

89.1 什么是sysfs文件系统

sysfs文件系统是Linux内核提供的一种虚拟文件系统,用于向用户空间提供内核中设备,驱动程序和其他内核对象的信息。它以一种层次结构的方式组织数据,并将这些数据表示为文件和目录,使得用户空间可以通过文件系统接口访问和操作内核对象的属性。

sysfs 提供了一种统一的接口,用于浏览和管理内核中的设备、总线、驱动程序和其他内核对象。它在 /sys 目录下挂载,用户可以通过查看和修改 /sys 目录下的文件和目录来获取和配置内核对象的信息。

89.2设备模型的基本框架

为什么说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相关的属性,操作和状态信息。如下图所示:

RK3568驱动指南|第九篇 设备模型-第89章 进一步探究设备模型_第1张图片

图 89-1

因此,可以说kobject是设备模型的基石,通过创建对应的目录结构和属性文件, 它提供了一个统一的接口和框架,用于管理和操作设备模型中的各个实体。

89.3 代码层面分析

在系统启动的时候会在/sys目录下创建以下目录,如下图所示:

RK3568驱动指南|第九篇 设备模型-第89章 进一步探究设备模型_第2张图片

图 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函数的分析,我们可以总结出创建目录的规律,如下所示:

  1. 1、无父目录、无kset,则将在sysfs的根目录(即/sys/)下创建目录。
  2. 2、无父目录、有kset,则将在kset下创建目录,并将kobj加入kset.list。
  3. 3、有父目录、无kset,则将在parent下创建目录。
  4. 4、有父目录、有kset,则将在parent下创建目录,并将kobj加入kset.list。

  5. RK3568驱动指南|第九篇 设备模型-第89章 进一步探究设备模型_第3张图片

你可能感兴趣的:(RK3568,RK3568驱动开发指南,#,RK3568驱动指南,第九期,驱动开发,linux)