RK3568驱动指南|第十篇 热插拔-第114章 内核发送事件到用户空间的方法

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


第114章 内核发送事件到用户空间的方法

   在前一章节中,我们成功地通过uevent机制将内核事件发送到用户空间。然而,本章节我们尝试在实验中去掉了创建kset的步骤,并发现用户空间无法收到事件。在分析这个问题之前,让我们回顾一下前一章节中的实验。

114.1 实验现象

在前一章节的代码示例中,我们演示了如何在内核中创建kset和kobject,并将其添加到sysfs文件系统中。这样做的目的是为了在sysfs中创建相应的目录结构,以便用户空间能够找到正确的sysfs路径来监听事件或访问设备属性。然而,当我们去掉了创建kset的步骤后,用户空间无法收到事件。这是因为没有正确的sysfs路径供用户空间监听事件。

上个章节的驱动代码修改如下图(图 114-1)所示:

RK3568驱动指南|第十篇 热插拔-第114章 内核发送事件到用户空间的方法_第1张图片

图 114-1

编译之后,加载驱动程序,如下图(图 114-2)所示:

RK3568驱动指南|第十篇 热插拔-第114章 内核发送事件到用户空间的方法_第2张图片

图 114-2

为了解决这个问题,我们需要在代码层面确保正确创建kset和kobject,并将其添加到sysfs文件系统中。这样,用户空间才能够通过sysfs路径找到相应的事件或属性文件。在接下来的部分,我们将通过代码分析来解释为什么没有创建kset会导致用户空间无法接收事件。

114.2 代码分析

在上一小节的实验中,为什么没有创建kset会导致用户空间无法接收事件呢?要搞清楚这个问题,我们可以追踪下kobject_uevent函数。如下所示:

/**
 * kobject_uevent - notify userspace by sending an uevent
 *
 * @kobj: struct kobject that the action is happening to
 * @action: action that is happening
 *
 * Returns 0 if kobject_uevent() is completed with success or the
 * corresponding error when it fails.
 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
	return kobject_uevent_env(kobj, action, NULL);
}
EXPORT_SYMBOL_GPL(kobject_uevent);

在上述代码中,可以看出kobject_uevent()函数是用于通知用户空间的,它通过发送uevent来实现。该函数接受俩个参数:kobject表示发生操作的struct kobject对象,action表示正在发生的操作。在这个函数中调用了kobject_uevent_env()函数,并将其返回值作为自己的返回值。这说明kobject_uevent()函数实际上是一个简化的接口,它将kobject_uevent_env()函数的调用参数设置为NULL,并返回相同的结果。

kobject_uevent_env函数代码比较多,接下来我们拆分为几个部分给大家讲解下。

第一部分,如下所示:


/**
 * kobject_uevent_env - send an uevent with environmental data  //发送带有环境变量数据的事件
 *
 * @kobj: struct kobject that the action is happening to //正在发生动作的对象
 * @action: action that is happening   //正在发生的动作
 * @envp_ext: pointer to environmental data  //环境变量的指针
 *
 * Returns 0 if kobject_uevent_env() is completed with success or the
 * corresponding error when it fails.
 */

第二部分如下所示:

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
		       char *envp_ext[])
{
	struct kobj_uevent_env *env;  //指向kobj_uevent_env结构体的指针,用于存储发送的事件和环境变量
	const char *action_string = kobject_actions[action]; //事件的类型
	const char *devpath = NULL;//存放kobject的路径
	const char *subsystem;    //存放所属子系统的名称
	struct kobject *top_kobj; //指向顶层top_kobj的kobject指针
	struct kset *kset;   //指向kset的指针,表示kobject所属的kset
	const struct kset_uevent_ops *uevent_ops; //指向struct kset_uevent_ops结构体的指针
	int i = 0;    //计数器i,用来编译环境变量数组
	int retval = 0  //表示函数的执行结果,也就是返回值

第三部分如下所示:

	if (action == KOBJ_REMOVE)
		kobj->state_remove_uevent_sent = 1;

	pr_debug("kobject: '%s' (%p): %s\n",
		 kobject_name(kobj), kobj, __func__);

	/* search the kset we belong to */
	top_kobj = kobj;
	while (!top_kobj->kset && top_kobj->parent)
		top_kobj = top_kobj->parent;

	if (!top_kobj->kset) {
		pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
			 "without kset!\n", kobject_name(kobj), kobj,
			 __func__);
		return -EINVAL;
	}

	kset = top_kobj->kset;
	uevent_ops = kset->uevent_ops;

以上代码中,首先检查action是否为KOBJ_REMOVE如果是,则设置 kobj->state_remove_uevent_sent 为1,表示“remove”事件已发送。

接下来通过调用pr_debug()函数输出调试信息,显示相关的kobject名称和指针以及当前函数名称。

然后,通过循环查找kobj所属的kset,直到找到具体有效kset的顶层kobj,即kset的根节点。这是通过沿着kobj的父对象链向上遍历实现的,如果找不到有效的kset,则输出调试信息,并返回-EINVAL表示发送uevent失败。

这个过程的目的是为了确定当前kobj所属的kset,以便后面的uevent发送能够正确地通知用户空间的应用程序。因为uevent是通过netlink socket发送给用户空间的应用程序的,而netlink socket是基于kset的。

最后,将找到的kset赋值给变量kset,并将其uevent_ops字段赋值给变量uevent_ops,以便后续使用。

第四部分如下所示:

	/* skip the event, if uevent_suppress is set*/
	if (kobj->uevent_suppress) {
		pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
				 "caused the event to drop!\n",
				 kobject_name(kobj), kobj, __func__);
		return 0;
	}

   在上面的代码中,首先检查kobj->uevent_suppress是否为1,如果设置kobj->uevent_suppress,则输出调试信息表示该事件被跳过,并返回0表示成功。

第五部分如下所示:

	/* skip the event, if uevent_suppress is set*/
	if (kobj->uevent_suppress) {
		pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
				 "caused the event to drop!\n",
				 kobject_name(kobj), kobj, __func__);
		return 0;
	}
	/* skip the event, if the filter returns zero. */
	if (uevent_ops && uevent_ops->filter)
		if (!uevent_ops->filter(kset, kobj)) {
			pr_debug("kobject: '%s' (%p): %s: filter function "
				 "caused the event to drop!\n",
				 kobject_name(kobj), kobj, __func__);
			return 0;
		}

	/* originating subsystem */
	if (uevent_ops && uevent_ops->name)
		subsystem = uevent_ops->name(kset, kobj);
	else
		subsystem = kobject_name(&kset->kobj);
	if (!subsystem) {
		pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
			 "event to drop!\n", kobject_name(kobj), kobj,
			 __func__);
		return 0;
	}

在上面的代码中,检查uevent_ops是否存在,并且uevent_ops->filter是否存在。如果两者都存在,并且调用uevent_ops->filter(kset, kobj),返回值为0,则表示过滤器函数导致该事件被跳过。输出相应的调试信息,并返回0表示成功。

   然后,根据uevent_ops中的name字段获取原始子系统的名称。如果uevent_ops->name存在,则调用uevent_ops->name(kset, kobj)函数获取子系统名称,否则使用kset的名称作为子系统名称。如果无法获取子系统名称,输出调试信息,并返回0表示成功。

综上所述,这部分代码主要用于检查是否应该跳过发送uevent的条件,如uevent_suppress标志、过滤器函数的返回结果以及获取原始子系统名称。如果满足这些条件,则该事件将被跳过,并返回0表示成功。

第六部分,如下所示:

	if (IS_ENABLED(CONFIG_ARCH_ROCKCHIP) &&
	    IS_ENABLED(CONFIG_FREEZER) &&
	    IS_ENABLED(CONFIG_ANDROID)) {
		/*
		 * Android healthd try to listen power_supply subsystem uevent,
		 * but which will block system from suspend on big.LITTLE system
		 * because thermal_cooling_device_unregister will called when
		 * cpufreq_exit. So ignore this uevent when suspend.
		 */
		extern bool pm_freezing;

		if (pm_freezing && !strcmp(subsystem, "thermal"))
			return 0;
	}

上面的代码是一个条件判断,用于特定条件下跳过发送uevent的操作,它首先使用IS_ENABLED()宏进行编译时配置的条件判断,检查是否启用了CONFIG_ARCH_ROCKCHIP、CONFIG_FREEZER和CONFIG_ANDROID配置选项。如果这三个选项都启用了,那么代码将进入条件块部分。内部的注释解释了为什么需要跳过发送uevent的操作。在big.LITTLE系统上,当cpufreq_exit函数被调用时,thermal_cooling_device_unregister函数也会被调用,这可能会导致系统挂起失败,为了避免这种情况,该函数忽略了thermal子系统的uevent事件。

代码中使用了一个外部变量pm_freezing,如果pm_freezing为真(即系统正在进入挂起状态),并且子系统名称与"thermal"相同时,将返回0表示跳过发送uevent的操作。

第七部分,如下所示:

	/* environment buffer */
	env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
	if (!env)
		return -ENOMEM;

	/* complete object path */
	devpath = kobject_get_path(kobj, GFP_KERNEL);
	if (!devpath) {
		retval = -ENOENT;
		goto exit;
	}

	/* default keys */
	retval = add_uevent_var(env, "ACTION=%s", action_string);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "DEVPATH=%s", devpath);
	if (retval)
		goto exit;
	retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
	if (retval)
		goto exit;

	/* keys passed in from the caller */
	if (envp_ext) {
		for (i = 0; envp_ext[i]; i++) {
			retval = add_uevent_var(env, "%s", envp_ext[i]);
			if (retval)
				goto exit;
		}
	}
	/* let the kset specific function add its stuff */
	if (uevent_ops && uevent_ops->uevent) {
		retval = uevent_ops->uevent(kset, kobj, env);
		if (retval) {
			pr_debug("kobject: '%s' (%p): %s: uevent() returned "
				 "%d\n", kobject_name(kobj), kobj,
				 __func__, retval);
			goto exit;
		}
	}

上面代码中,首先,它分配了一个 kobj_uevent_env 结构体的内存空间,并检查是否成功分配。然后它获取了 kobj 对象所在的路径,并检查是否成功获取。接下来,它添加了一些默认的键值对到环境变量中,包括 ACTIONDEVPATH 和 SUBSYSTEM。最后,如果有额外的键值对传入,则将其也添加到环境变量中。循环的终止条件是 envp_ext[i] 为空指针。

   接下来它首先检查uevent_opsuevent_ops->uevent是否存在。如果存在,则调用uevent_ops->uevent函数,并传递ksetkobjenv作为参数。如果uevent_ops->uevent返回非零值(表示出错),则会打印一条带有错误信息的调试消息,然后跳转到标签exit

第八部分,如下所示:

	switch (action) {
	case KOBJ_ADD:
		/*
		 * Mark "add" event so we can make sure we deliver "remove"
		 * event to userspace during automatic cleanup. If
		 * the object did send an "add" event, "remove" will
		 * automatically generated by the core, if not already done
		 * by the caller.
		 */
		kobj->state_add_uevent_sent = 1;
		break;

	case KOBJ_UNBIND:
		zap_modalias_env(env);
		break;

	default:
		break;
	}

在上面的代码中,当action为KOBJ_ADD时,将kobject对象的state_add_uevent_sent成员设置为1,表示已经发送了“add”事件,以便在自动清理期间将“remove”事件传递给用户空间。如果该对象已经发送了“add”事件,内核会自动发送“remove”事件,否则需要调用者手动发送“remove”事件。

当action为KOBJ_UNBIND时,调用 zap_modalias_env函数清除环境变量中MODALIAS变量。MODALIAS是一个特殊的环境变量,用于描述设备的模块别名。在设备驱动程序加载时,内核会根据MODALIAS环境变量自动加载相应的驱动程序模块。在设备卸载时,需要清除MODALIAS环境变量,以便下次重新加载设备驱动程序时能够正确地识别设备。 

第九部分,如下所示:

	retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
	if (retval) {
		mutex_unlock(&uevent_sock_mutex);
		goto exit;
	}
	retval = kobject_uevent_net_broadcast(kobj, env, action_string,
					      devpath);

上述代码将一个名为 "SEQNUM" 的环境变量添加到 uevent 环境变量列表中,并将其值设置为 uevent_seqnum 的值加 1。其中,add_uevent_var 是一个内部函数,用于将一个键值对添加到 uevent 环境变量列表中。如果添加失败,函数将返回一个非零值,同时会释放 uevent_sock_mutex 互斥锁并跳转到 exit 标签处进行清理操作。这个函数的主要作用是为 uevent 事件添加一个唯一的序列号,以便在处理 uevent 事件时可以识别它们的顺序。通俗的话讲就是每次发送一个事件,都要有它的事件号,该事件号不能重复,也会被加到环境变量里面。

kobject_uevent_net_broadcast 是一个内核函数,用于将一个 uevent 事件发送到系统中所有的网络命名空间中。它的参数包括 kobj,env,action_string 和 devpath。其中,kobj 是与 uevent 事件相关的内核对象,env 是一个包含 uevent 环境变量的列表,action_string 是一个字符串,表示 uevent 事件的类型,devpath 是一个字符串,表示与 uevent 事件相关的设备路径。该函数会遍历系统中所有的网络命名空间,并将 uevent 事件发送到每个网络命名空间中。这个函数的主要作用是在内核中广播一个 uevent 事件,以便用户空间的应用程序可以接收并处理这些事件。

第十部分,如下所示:

#ifdef CONFIG_UEVENT_HELPER
	/* call uevent_helper, usually only enabled during early boot */
	if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
		struct subprocess_info *info;

		retval = add_uevent_var(env, "HOME=/");
		if (retval)
			goto exit;
		retval = add_uevent_var(env,
					"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
		if (retval)
			goto exit;
		retval = init_uevent_argv(env, subsystem);
		if (retval)
			goto exit;

		retval = -ENOMEM;
		info = call_usermodehelper_setup(env->argv[0], env->argv,
						 env->envp, GFP_KERNEL,
						 NULL, cleanup_uevent_env, env);
		if (info) {
			retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
			env = NULL;	/* freed by cleanup_uevent_env */
		}
	}
#endif

在内核中调用用户空间的 uevent_helper 程序来处理 uevent 事件。uevent_helper 是一个用户空间程序,它可以在内核空间生成 uevent 事件时被调用。如果 CONFIG_UEVENT_HELPER 宏被定义,那么内核会在生成 uevent 事件时调用 uevent_helper 程序,以便在用户空间中处理这些事件。在上述代码中,如果 uevent_helper 变量不为空且 kobj_usermode_filter 函数返回 false,那么就会调用 call_usermodehelper_setup 函数来启动一个用户空间进程,并将 env 中的参数传递给该进程。在这个过程中,env 中的参数将会被转换成环境变量,并被传递给用户空间进程。

    以上通过分析kobject_uevent_env()函数,了解到此函数用于在内核中生成并发送uevent事件到用户空间。如果没有创建kset,会导致用户空间无法接收事件,可以详细见代码分析的第三部分。


RK3568驱动指南|第十篇 热插拔-第114章 内核发送事件到用户空间的方法_第3张图片

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