RK3568驱动指南|第十三篇 输入子系统-第143章 多对多的匹配关系分析

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


第143章 多对多的匹配关系分析

143.1 joydev.c事件处理层匹配分析

drivers/input/joydev.c文件的input_handler结构体内容如下所示:

static struct input_handler joydev_handler = {
	.event		= joydev_event,
	.match		= joydev_match,
	.connect	= joydev_connect,
	.disconnect	= joydev_disconnect,
	.legacy_minors	= true,
	.minor		= JOYDEV_MINOR_BASE,
	.name		= "joydev",
	.id_table	= joydev_ids,
};

static int __init joydev_init(void)
{
	return input_register_handler(&joydev_handler);
}

与上面讲解的通用设备驱动层evdev.c的evdev_handler结构体不同的是,joydev_handler结构体中有着对应的匹配函数,也就是说当设备驱动层与事件处理层进行匹配的时候,需要joydev_ids结构体数组和相应的匹配函数共同决定,首先来看joydev_ids结构体,joydev_ids结构体内容如下所示:

static const struct input_device_id joydev_ids[] = {
    // 第一个标识符,匹配X轴(ABS_X)的绝对事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) },    // 匹配的事件类型是EV_ABS(绝对事件)
		.absbit = { BIT_MASK(ABS_X) },    // 匹配的绝对事件类型是ABS_X(X轴)
	},
	// 第二个标识符,匹配Z轴(ABS_Z)的绝对事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) },    // 匹配的事件类型是EV_ABS(绝对事件)
		.absbit = { BIT_MASK(ABS_Z) },    // 匹配的绝对事件类型是ABS_Z(Z轴)
	},
	// 第三个标识符,匹配滚轮(ABS_WHEEL)的绝对事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) },    // 匹配的事件类型是EV_ABS(绝对事件)
		.absbit = { BIT_MASK(ABS_WHEEL) },    // 匹配的绝对事件类型是ABS_WHEEL(滚轮)
	},
	// 第四个标识符,匹配油门(ABS_THROTTLE)的绝对事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_ABSBIT,
		.evbit = { BIT_MASK(EV_ABS) },    // 匹配的事件类型是EV_ABS(绝对事件)
		.absbit = { BIT_MASK(ABS_THROTTLE) },    // 匹配的绝对事件类型是ABS_THROTTLE(油门)
	},
	// 第五个标识符,匹配游戏杆(BTN_JOYSTICK)的按键事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_KEYBIT,
		.evbit = { BIT_MASK(EV_KEY) },    // 匹配的事件类型是EV_KEY(按键事件)
		.keybit = { [BIT_WORD(BTN_JOYSTICK)] = BIT_MASK(BTN_JOYSTICK) },    // 匹配的按键类型是BTN_JOYSTICK(游戏杆)
	},
	// 第六个标识符,匹配游戏手柄(BTN_GAMEPAD)的按键事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_KEYBIT,
		.evbit = { BIT_MASK(EV_KEY) },    // 匹配的事件类型是EV_KEY(按键事件)
		.keybit = { [BIT_WORD(BTN_GAMEPAD)] = BIT_MASK(BTN_GAMEPAD) },    // 匹配的按键类型是BTN_GAMEPAD(游戏手柄)
	},
	// 第七个标识符,匹配快乐键(BTN_TRIGGER_HAPPY)的按键事件
	{
		.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
				INPUT_DEVICE_ID_MATCH_KEYBIT,
		.evbit = { BIT_MASK(EV_KEY) },    // 匹配的事件类型是EV_KEY(按键事件)
		.keybit = { [BIT_WORD(BTN_TRIGGER_HAPPY)] = BIT_MASK(BTN_TRIGGER_HAPPY) },    // 匹配的按键类型是BTN_TRIGGER_HAPPY(快乐键)
	},
	{ }	/* 终止项 */
};

结构体input_device_id的作用是描述输入设备的特征,以便内核能够识别和匹配正确的驱动程序。在通用设备驱动层evdev.c中的evdev_ids结构体数组设置的是driver_info表示匹配全部设备,而joydev.c中的joydev_ids结构体数组包含以下字段:

(1)flags:标识符的标志位,用于指定匹配方式。在这里,使用flags字段的INPUT_DEVICE_ID_MATCH_EVBIT和INPUT_DEVICE_ID_MATCH_ABSBIT标志表示匹配事件类型和绝对事件类型。

(2)evbit:事件类型的位掩码,用于指定要匹配的事件类型。在这里,evbit字段的位掩码表示匹配的事件类型是EV_ABS(绝对事件)或EV_KEY(按键事件)。

(3)absbit:绝对事件类型的位掩码,用于指定要匹配的绝对事件类型。在这里,absbit字段的位掩码表示匹配的绝对事件类型是ABS_X(X轴)、ABS_Z(Z轴)、ABS_WHEEL(滚轮)或ABS_THROTTLE(油门)。

(4)keybit:按键类型的位掩码,用于指定要匹配的按键类型。在这里,keybit字段的位掩码表示匹配的按键类型是BTN_JOYSTICK(游戏杆)、BTN_GAMEPAD(游戏手柄)或BTN_TRIGGER_HAPPY(快乐键)。

这个结构体数组中的每个元素都描述了一种输入设备的特征,包括事件类型和按键类型。内核可以使用这些标识符来匹配输入设备并加载适当的驱动程序,以便正确解析和处理设备的输入数据。

然后来看相应的匹配链接相关的函数,具体内容如下所示:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
    const struct input_device_id *id;
    int error;

    // 通过输入设备和处理程序的匹配函数来确定是否适用于该设备
    id = input_match_device(handler, dev);
    if (!id)
        return -ENODEV;

    // 调用处理程序的连接函数来建立设备和处理程序之间的连接
    error = handler->connect(handler, dev, id);
    if (error && error != -ENODEV)
        pr_err("failed to attach handler %s to device %s, error: %d\n",
               handler->name, kobject_name(&dev->dev.kobj), error);
    // 如果连接失败且错误码不是-ENODEV,则打印错误消息

    return error;
}

第7行通过input_match_device函数,进行输入设备和处理程序的匹配,input_match_device函数内容如下所示:

static const struct input_device_id *input_match_device(struct input_handler *handler, struct input_dev *dev)
{
    const struct input_device_id *id;

    // 遍历处理程序的输入设备ID表,直到找到匹配的ID或遍历完所有ID为止
    for (id = handler->id_table; id->flags || id->driver_info; id++) {
        // 使用输入设备ID匹配函数判断给定的输入设备是否与当前ID匹配
        if (input_match_device_id(dev, id) &&
            (!handler->match || handler->match(handler, dev))) {
            // 如果输入设备与ID匹配,并且处理程序的匹配函数返回true(或者没有匹配函数),则返回该ID
            return id;
        }
    }

    return NULL;
}

在6-13行的for循环中首先会依次取出id_table的值,即上面讲解的joydev_ids结构体数组中的值,一一进行匹配,在for循环中由于每个joydev_ids结构体数组中都存在flags参数且值不为零,所以for循环的条件是成立的,在for循环中会调用input_match_device_id函数判定给定的输入设备是否与当前ID匹配,input_match_device_id函数内容如下所示:

bool input_match_device_id(const struct input_dev *dev,
                           const struct input_device_id *id)
{
    // 检查设备的总线类型是否匹配
    if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
        if (id->bustype != dev->id.bustype)
            return false;

    // 检查设备的厂商ID是否匹配
    if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
        if (id->vendor != dev->id.vendor)
            return false;

    // 检查设备的产品ID是否匹配
    if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
        if (id->product != dev->id.product)
            return false;

    // 检查设备的版本号是否匹配
    if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
        if (id->version != dev->id.version)
            return false;

    // 检查设备的事件位图是否是给定ID的子集
    if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
        !bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
        !bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
        !bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
        !bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
        !bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
        !bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
        !bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
        !bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
        !bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
        return false;
    }

    // 所有匹配条件都满足,返回true
    return true;
}

在5、10、15、20行的if判断中,由于flags的值和后面的宏都不为零,所以if判断都是成立的,但是第6、11、16、21四行中,dev->id在我们编写的最简单设备驱动层代码中并没有赋值,所以值为零,而在joydev.c中id->bustype、id->vendor、id->product和id->version同样没有赋值,所以值同样为0,所以第二层if判断不成立。

然后来看25-36行的if判断,bitmap_subset在上面讲解过,它是一个内联函数,用于判断两个位图是否具有子集关系,这时就需要用到上面讲解的joydev_ids结构体数组的描述特征了。在编写的最简单的设备驱动层代码中的设置如下所示:

    __set_bit(EV_KEY, myinput_dev->evbit);    // 设置支持按键事件

    __set_bit(KEY_1, myinput_dev->keybit);    // 设置支持按键1

结合joydev_ids结构体数组的描述特征来对比,在上述的if判断中并不能与之匹配,所以最终会返回false,导致匹配失败。

讲解joydev.c的目的是让了解匹配规则是有多种多样的,对设备驱动层和事件处理层的知识重新进行梳理,在下次遇到相似的情况时可以自行分析。

143.2 扩展:多对多匹配关系

143.2.1 内核源码修改

首先对内核源码进行修改,修改涉及到两个函数分别是input_match_device_id函数和input_match_device函数,修改完成的代码内容如下所示:

bool input_match_device_id(const struct input_dev *dev,
			   const struct input_device_id *id)
{
	if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
		if (id->bustype != dev->id.bustype)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
		if (id->vendor != dev->id.vendor)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
		if (id->product != dev->id.product)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
		if (id->version != dev->id.version)
			return false;

	if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
	    !bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
	    !bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
	    !bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
	    !bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
	    !bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
	    !bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
	    !bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
	    !bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
	    !bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
			printk("input dev is error %s\n", dev->name);
			return false;
	}
	printk("input dev is ok %s\n", dev->name);
	return true;
}
EXPORT_SYMBOL(input_match_device_id);

static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;
	printk("handler name is %s\n", handler->name);
	for (id = handler->id_table; id->flags || id->driver_info; id++) {
		if (input_match_device_id(dev, id) &&
		    (!handler->match || handler->match(handler, dev))) {
			return id;
		}
	}

	return NULL;
}

相较于原函数只是在第30、33和42三行添加了printk打印,目的是在匹配过程中通过打印更好的了解匹配流程,由于修改的是内核源码,所以需要在修改之后重新编译内核源码,并将编译完成后生成的boot.img内核镜像烧写到开发板上。

注:joydev事件处理层代码在内核中默认是没有勾选的,需要根据输入子系统的裁剪和配置相关章节对内核的默认配置文件进行修改,勾选"Joystick interface"选项即可,勾选完成如下所示:

修改编译完成的boot.img内核镜像的网盘路径为“iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\92_myinput_dev_02\01_img,如下图所示:

143.2.2 运行测试

本节测试仍旧使用前面编写的最简单的设备驱动层的ko文件,需要注意的是要想得到跟实验效果相同的结论,必须已经按照上一小节的操作修改过了内核源码,并重新烧写到了开发板上。

开发板上电正常启动之后如下所示:

 由于myinput_dev.ko在之前已经拷贝到了开发板上,所以这里可以直接使用下面的命令加载myinput_dev.ko文件,打印内容如下所示:

 insmod  myinput_dev.ko

其中红色框中的是joydev相关的打印,可以看到joydev是匹配失败的,证明在第一小节中我们的推理是正确的,而红色框下方的蓝色框中的是evdev通用设备驱动层程序,根据打印可以看到evdev匹配成功了,这时候细心的小伙伴可能发现了,除了evdev之外还有kbd、cpufreq_interactive和dmcfreq也匹配成功了,这是为什么呢?

143.2.3 结论

在输入子系统中,输入设备和输入处理器之间的关系是多对多的。这意味着一个输入设备可以与多个输入处理器关联,而一个输入处理器也可以处理多个输入设备的事件。

这种多对多的关系设计是为了提供更大的灵活性和可扩展性。不同的输入设备可能具有不同的特性和事件类型,而不同的输入处理器可能针对特定的事件类型提供不同的处理逻辑。通过将输入设备和输入处理器解耦并建立多对多的关系,可以使输入子系统更加灵活,可以根据需求将特定的输入设备与适合的输入处理器进行匹配,以实现定制化的事件处理。例如,对于鼠标输入设备,它可以产生鼠标移动事件、鼠标点击事件和滚轮滚动事件等,对于键盘输入设备,它可以产生按键事件,包括按下和释放按键的事件。

所以在上个小节中最简单的设备驱动层代码会跟内核中全部的事件处理层想匹配,最终匹配成功了evdev、kbd、cpufreq_interactive和dmcfreq四个事件处理层程序。

 

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

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

相关文章

有哪些强大好用的AI表格数据处理工具或者 AI Excel工具?

在繁忙的工作和生活中,处理大量的表格数据往往令人感到头疼。面对一列列数字、一行行文字,我们需要花费大量的时间和精力去整理、核对。然而,随着科技的飞速发展,人工智能(AI)技术正逐渐改变这一现状。 如…

LLM 面试知识点——模型基础知识

1、主流架构 目前LLM(Large Language Model)主流结构包括三种范式,分别为Encoder-Decoder、Causal Decoder、Prefix Decode。对应的网络整体结构和Attention掩码如下图。 、 各自特点、优缺点如下: 1)Encoder-Decoder 结构特点:输入双向注意力,输出单向注意力。 代表…

Flutter开发进阶之瞧瞧RenderObject

Flutter开发进阶之瞧瞧RenderObject 通过上回我们了解到Flutter执行buildTree的逻辑线,当Tree构建完成后会交给Flutter底层的渲染事件循环去执行将内容渲染到屏幕的操作。 但是渲染的操作到底是如何串起来的呢?这篇文章将会从Element联系到RenderObject…

点餐小程序php毕设项目

主要技术框架: 主要功能模块: 商品管理 订单管理 用户管理 优惠券管理 商品分类管理 评论管理 轮播图管理 截图 获取源码 https://blog.lusz.top/article?article_id-2

【Linux系统编程(进程编程)】创建进程的场景,fork和vfork的使用及区别

文章目录 一、进程关键概念二、创建进程函数fork的使用一、进程创建实战 三、创建进程函数fork的使用补充四、进程创建发生了什么事?五、创建新进程的实际应用场景 & fork总结一、fork创建一个子进程的一般目的?二、fork编程实战 六、vfork也能创建进…

grid布局

文章目录 1. 概念2. 组成2.1. 网格行2.2. 网格列2.3. 网格间距2.4. 网格线2.5. 网格容器2.6. fr 单位 3. 网格跨行跨列3.1. 跨列3.2. 跨行 4. 网格布局案例4.1. 演示效果4.2. 分析思路4.3. 代码实现 1. 概念 网格是一组相交的水平线和垂直线,它定义了网格的列和行。…

【排序算法】实现快速排序值(霍尔法三指针法挖坑法优化随即选key中位数法小区间法非递归版本)

文章目录 📝快速排序🌠霍尔法🌉三指针法🌠挖坑法✏️优化快速排序 🌠随机选key🌉三位数取中 🌠小区间选择走插入,可以减少90%左右的递归🌉 快速排序改非递归版本&#x1…

工业相机采图方式、图像格式(BYTE、HObject和Mat)转换

1、概述 机器视觉项目中,如何采集到合适的图像是项目的第一步,也是最重要的一步,直接关系到后面图像处理算法及最终执行的结果。所以采用不同的工业相机成像以及如何转换成图像处理库所需要的格式成为项目开发中首先要考虑的问题。 2、工业…

分布式组件 Nacos

1.在之前的文章写过的就不用重复写。 写一些没有写过的新东西 2.细节 2.1命名空间 : 配置隔离 默认: public (默认命名空间):默认新增所有的配置都在public空间下 2.1.1 开发 、测试 、生产:有不同的配置文件 比如…

【ZYNQ】基于ZYNQ 7020的OPENCV源码交叉编译

目录 安装准备 检查编译器 安装OpenCV编译的依赖项 下载OpenCV源码 下载CMake 编译配置 编译器说明 参考链接 安装准备 使用的各个程序的版本内容如下: 类别 软件名称 软件版本 虚拟机 VMware VMware-workstation-full-15.5.0-14665864 操作系统 Ub…

【QT入门】 Qt实现自定义信号

往期回顾: 【QT入门】图片查看软件(优化)-CSDN博客 【QT入门】 lambda表达式(函数)详解-CSDN博客 【QT入门】 Qt槽函数五种常用写法介绍-CSDN博客 【QT入门】 Qt实现自定义信号 一、为什么需要自定义信号 比如说现在一个小需求,我们想要实现跨ui通信&a…

Hive入门

什么是hive? - Hive是Facebook开发并贡献给Hadoop开源社区的。它是建立在 Hadoop体系架构上的一层 SQL抽象,使得数据相关人 员使用他们最为熟悉的SQL语言就可以进行海量数据的处理、 分析和统计工作 - Hive将数据存储于HDFS的数据文件映射为一张数据库…

Java程序设计 4、5章 练习题

一、填空题 1.假设有 String s1 "Welcome to Java"; String s2 s1; String s3 new String("Welcome to Java"); 那么下面表达式的结果是什么? (1) s1 s2 ___________true_______________ (2) s1 s3 ______…

SOPHON算能服务器SDK环境配置和相关库安装

目录 1 SDK大包下载 2 安装libsophon 2.1 安装依赖 1.2 安装libsophon 2 安装 sophon-mw 参考文献: 1 SDK大包下载 首先需要根据之前的博客,下载SDK大包:SOPHON算能科技新版SDK环境配置以及C demo使用过程_sophon sdk yolo-CSDN博客 …

第 6 章 ROS-xacro练习(自学二刷笔记)

重要参考: 课程链接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 讲义链接:Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 6.4.3 Xacro_完整使用流程示例 需求描述: 使用 Xacro 优化 URDF 版的小车底盘模型实现 结果演示: 1.编写 X…

idea使用token方式登录GitHub

总体上分为两大步:1.GitHub生成token。2.idea配置token登录GitHub。 注:idea配置GitHub的前提是本地已经安装了git程序。 一、GitHub生成token 1.登录GitHub 2.进入token创建页面(右上角点击头像–>settings–>页面向下滚动左侧菜单栏…

linux热键,man手册介绍

目录 热键 tab ctrl c ctrl r man 区段 快捷键 热键 tab 可以看到以输入的内容为开头的指令,但无法选择: 当输入的内容匹配到的内容只有一个时,可以自动补全 可以用于输入路径时,自动补全文件名 ctrl c 让当前的程序停掉,可以在 程序或指令出问题而自己无法停止时 使用…

HSP_01章_Python 语言概述

文章目录 06 开发环境安装10 注意事项11 学习方法14 Pycharm 常用快捷键14 Python 常用转义字符15 Python 注释Comment16 [Python 中文文档地址](https://docs.python.org/zh-cn/3.11/) 06 开发环境安装 python 版本命令: python cmd 退出: exit() 环境变量配置: 计算机 > 高…