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)所示:

图 114-1

编译之后,加载驱动程序,如下图(图 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,会导致用户空间无法接收事件,可以详细见代码分析的第三部分。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/308727.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java解析json复杂数据的第三种思路

文章目录 一、概述二、数据预览1. 接口json数据2. json转xml数据 三、代码实现1. pom.xml2. 核心代码3. 运行结果 四、源码传送 一、概述 接上篇 java解析json复杂数据的两种思路 我们已经通过解析返回json字符串得到数据,现在改变思路, 按照如下流程获取数据: #mermaid-svg-k…

【数据库原理】(11)SQL数据查询功能

基本格式 SELECT [ALL|DISTINCT]<目标列表达式>[,目标列表达式>]... FROM <表名或视图名>[,<表名或视图名>] ... [ WHERE <条件表达式>] [GROUP BY<列名 1>[HAVING <条件表达式>]] [ORDER BY <列名 2>[ASC DESC]];SELECT: 指定要…

springboot医院信管系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

FastDFS之快速入门、上手

知识概念 分布式文件系统 通过计算机网络将各个物理存储资源连接起来。通过分布式文件系统&#xff0c;将网络上任意资源以逻辑上的树形结构展现&#xff0c;让用户访问网络上的共享文件更见简便。 文件存储的变迁&#xff1a; 直连存储&#xff1a;直接连接与存储&#xf…

Oracle regexp_substr

select regexp_substr(123|456|789, [^|], 1, 2) from dual;

暴雨信息发布算力网络应用平台打造零感知算网服务新模式

为进一步优化算力网络应用服务能力和降低算力网络使用难度&#xff0c;暴雨信息突破基于算力网络的实例跨域协同与迁移、基于测试评估的应用度量和解构等技术&#xff0c;研发并推出算力网络应用平台。该系统通过提供一种即开即用、按需付费的零感知算网应用服务&#xff0c;使…

Python基础语法(上)——基本语法、顺序语句、判断语句、循环语句(有C++基础快速掌握Python语言)

文章目录 0.python小技巧与易错点1.python 与 c 语法有哪些区别2.Python基本语法2.1python的变量类型2.2python中的运算符2.3python中的表达式2.4python中的输入输出 3.python判断语句3.1基本用法&#xff1a;3.2关于else if 的用法3.3关于pass语句3.4python变量的作用域3.5pyt…

THB6128两相四线步进电机PWM驱动控制

THB6128两相四线步进电机驱动控制模块&#xff0c;可以驱动57及以下两相四线步进电机。该模块有以下优点&#xff1a; 芯片使用双全桥MOSFET驱动&#xff0c;低导通电阻Ron 0.55Ω最高耐压36V&#xff0c;峰值电流2.2A&#xff0c;持续电流2A&#xff0c;电流设定通过拨码开关…

大模型LLM在 Text2SQL 上的应用实践

一、前言 目前&#xff0c;大模型的一个热门应用方向Text2SQL&#xff0c;它可以帮助用户快速生成想要查询的SQL语句&#xff0c;再结合可视化技术可以降低使用数据的门槛&#xff0c;更便捷的支持决策。本文将从以下四个方面介绍LLM在Text2SQL应用上的基础实践。 Text2SQL概…

常用注解/代码解释(仅个人使用)

目录 第一章、代码解释①trim() 方法以及(Arrays.asList(str.split(reg)));②查询字典项②构建后端镜像shell命令解释 第二章、注解解释①PropertySource注解与Configurationproperties注解的区别 第三章、小知识①Linux系统中使用$符号表示变量 友情提醒: 先看文章目录&#…

Android学习(四):常用布局

Android学习&#xff08;四&#xff09;&#xff1a;常用布局 五种常用布局 线性布局&#xff1a;以水平或垂直方向排列相对布局&#xff1a;通过相对定位排列帧布局&#xff1a;开辟空白区域&#xff0c;帧里的控件(层)叠加表格布局&#xff1a;表格形式排列绝对布局&#x…

C语言之三子棋小游戏的应用

文章目录 前言一、前期准备模块化设计 二、框架搭建三、游戏实现打印棋盘代码优化玩家下棋电脑下棋判断输赢 四、结束 前言 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#…

2024--Django平台开发-Django知识点(六)

day06 Django知识点 今日概要&#xff1a; Form和ModelForm组件【使用】【源码】缓存【使用】ORM【使用】其他&#xff1a;ContentTypes、Admin、权限、分页、信号等 1.Form和ModelForm组件 背景&#xff1a;某个公司后台管理项目。 垃圾 def register(request):"&quo…

PowerDesigner简介以及简单使用

软件简介&#xff1a; PowerDesigner是Sybase公司开发的数据库设计工具&#xff0c;开发人员能搞利用PowerDesigner开发数据流程图、各数据模型如物理数据模型&#xff0c;可以分别从概念数据模型(Conceptual Data Model)和物理数据模型(Physical Data Model)两个层次对数据库…

互联网加竞赛 基于大数据的社交平台数据爬虫舆情分析可视化系统

文章目录 0 前言1 课题背景2 实现效果**实现功能****可视化统计****web模块界面展示**3 LDA模型 4 情感分析方法**预处理**特征提取特征选择分类器选择实验 5 部分核心代码6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于大数据…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷5

某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenStack搭建企业内部私有云平台&#xff0c;开源Kubernetes搭建云原生服务平台&#xff0c;选…

[Excel]如何找到非固定空白格數列的條件數據? 以月份報價表單為例

在群組中看到上述問題&#xff0c;研判應是一份隨月份變動的產品報價表單&#xff0c;空白欄可能表示該月份價格與上個月份一致。這個問題是需要取得最近一次單價和倒數第二次單價&#xff0c;常用且實務的excel案例值得紀錄。 最近一次單價: INDEX($B2:$G2,1,LARGE(IF(ISBLAN…

鸿蒙原生应用再添新丁!京东入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;京东入局鸿蒙 来自 HarmonyOS 微博1月10日消息&#xff0c;#京东启动鸿蒙原生应用开发#&#xff01;优惠信息、派送进度都可以随时随地便捷查询。双方将携手为消费者带来全场景“多快好省”购物体验&#xff0c;更智能&#xff0c;更贴心&…

【Python机器学习】SVM——线性模型与非线性特征

SVM&#xff08;核支持向量机&#xff09;是一种监督学习模型&#xff0c;是可以推广到更复杂模型的扩展&#xff0c;这些模型无法被输入空间的超平面定义。 线模型在低维空间中可能非常受限&#xff0c;因为线和平面的灵活性有限&#xff0c;但是有一种方式可以让线性模型更加…

three.js实现扩散波效果

three.js实现扩散波效果 图例 步骤 创建一个圆柱&#xff0c;不要顶与底面材质允许透明&#xff0c;双面显示动态修改缩放与透明度 代码 <template><div class"app"><div ref"canvesRef" class"canvas-wrap"></div>…