【linux kernel】一文总结linux输入子系统

文章目录

    • 一、导读
    • 二、重要数据数据结构
      • (2-1)struct input_dev
      • (2-2)input_dev_list和input_handler_list
      • (2-3)struct input_handler
    • 三、input核心的初始化
    • 四、常用API
    • 五、输入设备驱动开发总结
      • (1)查看输入事件
      • (2)查看输入设备信息
      • (3)使用evtest查看输入事件
      • (4)使用hexdump直接查看输入事件内容

一、导读

linux内核的输入子系统是一个“一对多”的驱动程序,对上层,为应用程序提供统一的事件接口;对下层,统一形态各一的输入设备的处理功能,例如:各种鼠标,不管是PS/2、USB还是蓝牙的,都进行相同的处理。

👉相关源码文件:

  • drivers/input/input-core.c
  • drivers/input/input.c 输入子系统的核心
  • drivers/input/input-compat.c 输入子系统的32位兼容性包装器
  • drivers/input/input-mt.c 输入多点触控库
  • drivers/input/input-poller.c 支持输入设备的轮询模式。
  • drivers/input/ff-core.c 支持Linux输入子系统的强制反馈
  • drivers/input/touchscreen.c 用于触摸屏和其他二维指向设备的通用助手函数

二、重要数据数据结构

(2-1)struct input_dev

input_dev代表一个input设备,定义如下(include/linux/input.h):

struct input_dev {
	const char *name;  //输入设备的名称
	const char *phys;  //输入设备的物理地址
	const char *uniq;  //输入设备的唯一标识符
	struct input_id id; //输入设备的标识符信息,包括总线类型、供应商 ID、产品 ID 和版本号

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; //输入设备的属性位图,表示设备支持的属性。

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)];        //事件类型的位图 ,表示设备支持的事件类型。
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];      //按键值的位图,表示设备支持的按键。
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];      //相对坐标的位图,表示设备支持的相对坐标事件。
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];      //绝对坐标的位图,表示设备支持的绝对坐标事件。
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];      //杂项事件的位图,表示设备支持的其他杂项事件。
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];      //LED 相关的位图,表示设备支持的 LED 灯。
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];      //sound 有关的位图,表示设备支持的声音事件。
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];        //压力反馈的位图,表示设备支持的力反馈功能。
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];        //开关状态的位图,表示设备支持的开关状态。

	unsigned int hint_events_per_packet;     //每个数据包中事件数量的提示值。

	unsigned int keycodemax; //按键编码的最大值。
	unsigned int keycodesize; //按键编码的大小。
	void *keycode; //按键编码数据的指针。

	int (*setkeycode)(struct input_dev *dev,    //设置按键编码的函数指针。
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
              
	int (*getkeycode)(struct input_dev *dev,  //获取按键编码的函数指针。
			  struct input_keymap_entry *ke);

	struct ff_device *ff;  //力反馈设备的指针。

	unsigned int repeat_key; //重复按键的标志。
	struct timer_list timer; //用于定时任务的定时器。

	int rep[REP_CNT]; //重复计数器数组。

	struct input_mt *mt; //多点触控设备信息的指针。

	struct input_absinfo *absinfo; //绝对坐标信息的指针。

	unsigned long key[BITS_TO_LONGS(KEY_CNT)]; //按键状态的位图。
	unsigned long led[BITS_TO_LONGS(LED_CNT)]; //LED 灯状态的位图。
	unsigned long snd[BITS_TO_LONGS(SND_CNT)]; //声音状态的位图。
	unsigned long sw[BITS_TO_LONGS(SW_CNT)];   //开关状态的位图。

	int (*open)(struct input_dev *dev);       //打开设备的函数指针。
	void (*close)(struct input_dev *dev);     //关闭设备的函数指针。
	int (*flush)(struct input_dev *dev, struct file *file); //刷新设备的函数指针。
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件处理函数的指针。

	struct input_handle __rcu *grab; //指向当前占用该设备的输入处理程序的指针。

	spinlock_t event_lock; //事件锁,用于保护事件数据的并发访问。
	struct mutex mutex; //互斥锁,用于保护设备的状态数据。

	unsigned int users;  //设备的用户数。
	bool going_away; //设备的用户数。

	struct device dev; //设备结构体,用于设备管理。

	struct list_head	h_list; //连接到输入处理程序链表的链表头。
	struct list_head	node;   //连接到全局输入设备链表的链表头。

	unsigned int num_vals;  //输入值的数量。
	unsigned int max_vals;  //输入值的最大数量。
	struct input_value *vals; //输入值数组的指针。
 
	bool devres_managed;  //标志,指示设备是否由设备资源管理器管理。
};

input_dev结构中定义了几个用于描述具体输入行为的类型,其中evbit表示输入事件类型,在linux内核中为许多常见的事件类型都进行了定义(/include/uapi/linux/input.h):

#define EV_SYN			0x00
#define EV_KEY			0x01
#define EV_REL			0x02
#define EV_ABS			0x03
#define EV_MSC			0x04
#define EV_SW			0x05
#define EV_LED			0x11
#define EV_SND			0x12
#define EV_REP			0x14
#define EV_FF			0x15
#define EV_PWR			0x16
#define EV_FF_STATUS		0x17
#define EV_MAX			0x1f
#define EV_CNT			(EV_MAX+1)

在/include/uapi/linux/input.h文件中也能找到输入子系统下许多的宏定义,这些宏定义可以在基于输入子系统的驱动程序中使用。

(2-2)input_dev_list和input_handler_list

在input输入子系统中定义了两个重要的全局链表:

static LIST_HEAD(input_dev_list);  //dev链表
static LIST_HEAD(input_handler_list);  //handler链表

input_dev_list表示input输入子系统中的所有设备,当使用input_register_device()这次input设备时,所注册的设备会被添加到input_dev_list链表中:

list_add_tail(&dev->node, &input_dev_list);

input_handler_list表示input输入子系统的handler,当使用input_register_handler()注册一个handler时,会将该handler添加到input_handler_list链表中:

list_add_tail(&handler->node, &input_handler_list);

(2-3)struct input_handler

input_handler结构用于描述一个输入设备的操作接口,定义如下:

struct input_handler {

  //驱动特殊数据
	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

input_handler结构体用于描述输入事件的处理程序,结构中各元素功能如下:

  • 1、void *private;:指向驱动特殊数据的指针,通常用于存储与该输入处理程序相关的私有数据。
  • 2、void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);:指向事件处理函数的指针。当输入事件发生时,此函数将被调用以处理事件。
  • 3、void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);:指向批量事件处理函数的指针。类似于 event 函数,但允许处理多个输入值。
  • 4、bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);:指向事件过滤器函数的指针。该函数用于确定是否应该处理特定的输入事件。
  • 5、bool (*match)(struct input_handler *handler, struct input_dev *dev);:指向匹配函数的指针。用于确定输入设备是否与此输入处理程序匹配。
  • 6、int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);:指向连接函数的指针。用于将输入设备与输入处理程序连接起来。
  • 7、void (*disconnect)(struct input_handle *handle);:指向断开连接函数的指针。用于在输入设备断开连接时执行清理操作。
  • 8、void (*start)(struct input_handle *handle);:指向启动函数的指针。用于启动输入处理程序的某些操作。
  • 9、bool legacy_minors;:指示是否使用传统的设备编号(例如 /dev/input/eventX 中的 X)。
  • 10、int minor;:输入处理程序的次设备号,用于确定其在系统中的唯一标识符。
  • 11、const char *name;:输入处理程序的名称。
  • 12、const struct input_device_id *id_table;:指向输入设备标识符表的指针。用于描述输入设备与输入处理程序之间的匹配关系。
  • 13、struct list_head h_list;:用于将输入处理程序连接到输入设备的处理程序链表。
  • 14、struct list_head node;:用于将输入处理程序连接到全局输入处理程序链表。

总而言之,struct input_handler结构体描述了输入事件处理程序的各种属性和操作函数指针,用于与输入设备交互并处理输入事件。

三、input核心的初始化

input输入子系统的核心由/drivers/input/input.c文件实现,该文件中内容基于linux内核驱动模型而设计,模块出口是input_init(),由subsys_initcall(input_init)语句导出,如下代码:

static int __init input_init(void)
{
	int err;
  
  //注册input类
	err = class_register(&input_class);
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}
  
  //在proc文件系统中创建与input相关目录
	err = input_proc_init();
	if (err)
		goto fail1;
  
  //为input输入子系统注册主设备号
	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				     INPUT_MAX_CHAR_DEVICES, "input");
	if (err) {
		pr_err("unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	return err;
}

//模块导出
subsys_initcall(input_init);

input_proc_init()定义如下:

static int __init input_proc_init(void)
{
	struct proc_dir_entry *entry;

	proc_bus_input_dir = proc_mkdir("bus/input", NULL);
	if (!proc_bus_input_dir)
		return -ENOMEM;

	entry = proc_create("devices", 0, proc_bus_input_dir,
			    &input_devices_fileops);
	if (!entry)
		goto fail1;

	entry = proc_create("handlers", 0, proc_bus_input_dir,
			    &input_handlers_fileops);
	if (!entry)
		goto fail2;

	return 0;

 fail2:	remove_proc_entry("devices", proc_bus_input_dir);
 fail1: remove_proc_entry("bus/input", NULL);
	return -ENOMEM;
}

上述代码将在 /proc/bus/input 目录下创建两个文件 “devices” 和 “handlers”,用于显示输入设备和输入事件处理程序的信息。例如:

四、常用API

//申请input_dev结构变量
struct input_dev *input_allocate_device(void)

//注销input_dev设备
void input_free_device(struct input_dev *dev)

//向linux内核注册input设备
int input_register_device(struct input_dev *dev)

//从linux内核注销input设备
void input_unregister_device(struct input_dev *dev)

//上报一个输入事件,包括事件类型 (type)、事件代码 (code) 和事件值 (value)。
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
    
//上报一个相对坐标事件,即相对于上一个位置的变化。
void input_report_rel(struct input_dev *dev, unsigned int code, int value)

//上报一个绝对坐标事件,即绝对位置的变化。
void input_report_abs(struct input_dev *dev, unsigned int code, int value)

//上报一个力反馈状态事件,通常用于通知应用程序力反馈设备的状态。
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)

//上报一个开关状态事件,通常用于通知应用程序开关设备的状态。
void input_report_switch(struct input_dev *dev, unsigned int code, int value)

//上报一个多点触摸同步事件,用于通知输入子系统多点触摸操作的结束
void input_mt_sync(struct input_dev *dev)

//上报一个同步事件,用于通知linux内核input子系统上报操作结束
void input_sync(struct input_dev *dev)
/*##############################################################*/

五、输入设备驱动开发总结

(1)查看输入事件

很多时候,我们需要获取系统目前存在哪些输入事件,这时候可以使用:

ls /dev/input/

该目录导出的信息是输入事件对应的文件节点。

(2)查看输入设备信息

获取到输入事件,在开发中可能还不够,还想更进一步获取到更详细的关于输入事件和输入设备的信息,这时候可以使用:

cat /proc/bus/input/devices

将会输出如下格式的数据:

I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/platform/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0

I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="iMX6UL TouchScreen Controller"
P: Phys=
S: Sysfs=/devices/platform/soc/2000000.aips-bus/2040000.tsc/input/input1
U: Uniq=
H: Handlers=mouse0 event1 
B: PROP=0
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=3

(3)使用evtest查看输入事件

通过上述两种方式查看输入事件获取到的信息是关于具体的输入事件和输入设备,如果想要获取到输入事件更为详细的信息(数据层),则可以使用evtest工具进行查看。

在发行版的linux系统中,例如(ubuntu),可以使用如下命令:

sudo apt install evtest

安装evtest,在安装完evtest后,就可以使用了(必需以root运行):

sudo evtest


将会列出目前系统中可用的输入设备,我们可以选择一个对应的事件号进行监听,上图中可选的事件为0-4,对应五个输入设备。

在嵌入式linux中,可能就没有evtest工具的直接二进制文件,这时候我们可以自行编译源码得到。

  • 源码URL:https://github.com/freedesktop-unofficial-mirror/evtest/tree/master


例如上图,笔者自行编译了一个运行在ARM32上的evtest,下图为监测hid鼠标输入设备的一个应用场景:首先会打印出输入hid设备的相关信息,和支持的事件:

在evtest运行的情况下,如果点击鼠标左键/右键、滑动鼠标滚轮和移动鼠标,evtest都会将监测到的事件打印出(下图为点击鼠标左键/右键输出的信息):

(4)使用hexdump直接查看输入事件内容

如果需要获取到更为直接的输入设备而数据,则可以使用hexdump命令直接查看/dev/input/eventx:

hexdump /dev/input/event2

例如:

参考链接:
https://docs.kernel.org/input/index.html

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

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

相关文章

Linux:基础IO(三.软硬链接、动态库和静态库、动精态库的制作和加载)

上次介绍了基础IO(二):Linux:基础IO(二.缓冲区、模拟一下缓冲区、详细讲解文件系统) 文章目录 1.软硬链接1.1硬链接1.2软链接使用场景 2.动态库和静态库1.1回顾1.2静态库的制作和使用为什么要有库制作者角度…

搜维尔科技:「案例」NBA新科冠军与Xsens运动捕捉的缘分

北京时间昨日,凯尔特人在主场106比88击败独行侠,以总比分4比1获胜,夺得队史第18冠,超越湖人队(17冠)成为历史上夺冠次数最多的球队。凯尔特人队上一次夺冠还是在2007-2008赛季。 凯尔特人队主力Jayson Tat…

Kotlin明明很优秀,为啥没像Java那样火?

在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」, 点个关注在评论区回复“666”之后私信回复“666”,全部无偿共享给大家!!!因为 kotlin 优化一件事&…

太全了吧?CISP全类别详细介绍,看完不迷惑

今天聊聊CISP,注册信息安全专业人员证。 很多人以为说CISP就是个证书,没这么简单,这里面区别可大了。 CISP根据工作领域和实际岗位需要,分为综合型、攻防领域、IT审计、软件开发、数据治理、电子取证和云安全领域等17项证书。 这么…

基于Openmv的追小球的云台

介绍 在这篇文章,我会先介绍需要用到且需要注意的函数,之后再给出整体代码 在追小球的云台中,比较重要的部分就是云台(实质上就是舵机)的控制以及对识别的色块位置进行处理得到相应信息后控制云台进行运动 1、舵机模…

LeetCode.32最长有效括号详解

问题描述 给你一个只包含 ( 和 ) 的字符串,找出最长有效(格式正确且连续)括号子串的长度。 解题思路1 有效的括号字符串意味着每一个左括号 ( 都可以找到一个相匹配的右括号 )。栈可以帮助我们追踪尚未匹配的括号,并有效地处理…

推荐一款AI修图工具,支持AI去水印,AI重绘,AI抠图...

不知道大家有没有这样的一个痛点,发现了一张不错的“素材”, 但是有水印,因此不能采用,但找来找去,还是觉得初见的那个素材不错,怎么办? 自己先办法呗。 二师兄发现了一款功能强大的AI修图工具…

猫头虎 AI 前沿科技探索之路(持续更新):ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略

猫头虎 AI 前沿科技探索之路(持续更新):ChatGPT/GPT-4 科研应用、论文写作、数据分析与 AI 绘图及文生视频实战全攻略 背景介绍 随着人工智能技术的飞速发展,AI 的应用已经渗透到各个领域,从商业决策到医疗健康,再到日常生活中的…

win10改远程桌面端口,Windows 10 修改远程桌面端口号的专业指南

在Windows 10系统中,远程桌面(Remote Desktop)功能允许用户从一台计算机远程访问和控制另一台计算机。为了增加远程连接的安全性,减少潜在的安全风险,修改默认的远程桌面端口号是一个常见的安全措施。以下是在Windows …

postman教程-22-Newman结合Jenkins执行自动化测试

上一小节我们学习了Postman Newman运行集合生成测试报告的方法,本小节我们讲解一下Postman Newman结合Jenkins执行自动化测试的方法。 在软件开发过程中,持续集成(CI)是一种实践,旨在通过自动化的测试和构建过程来频繁…

离散数学-再次复习

1.先找到e,这里是0 2.对每个元素求它的阶 3.根据拉格朗日定理,子群的阶必须是群 G 的阶的因数。群 G 的阶为 10,它的因数有 1、2、5 和 10。这意味着子群的阶可能是 1、2、5 或者 10。 4.相同阶的就放为一组,也就是它的一个子群…

华为开发者大会闪耀东莞,康佳电视携手海思惊艳亮相

近日,华为开发者大会(HDC2024)在东莞松山湖举行。 作为电视领域唯一受邀参展的品牌,康佳电视以其优秀的创新实力,携手华为海思共同展示了基于OpenHarmony Standard层级的鸿鹄媒体创新方案。该方案不仅能够为用户带来更…

uni-starter:云端一体应用快速开发的新选择

一、引言 随着移动互联网的快速发展,应用开发面临着日益增长的效率挑战。如何在保证应用功能丰富性的同时,快速迭代和上线,成为了众多开发者关注的焦点。在这样的背景下,uni-starter作为一款集成商用项目常见功能的云端一体应用快…

大型语言模型(LLM)和多模态大型语言模型(MLLM)的越狱攻击

随着大型语言模型(LLMs)的快速发展,它们在各种任务上表现出了卓越的性能,有效地遵循指令以满足多样化的用户需求。然而,随着这些模型遵循指令的能力不断提升,它们也越来越成为对抗性攻击的目标,…

ONLYOFFICE 桌面编辑器 8.1:全新升级,助您轻松高效处理办公文档

ONLYOFFICE 桌面编辑器 一、前言二、轻松编辑器 PDF 文件三、用幻灯片版式快速修改幻灯片四、无缝切换文档编辑、审阅和查看模式五、改进从右至左语言的支持 & 新的本地化选项六、版本 8.1:其他新功能七、ONLYOFFICE 官网:https://www.onlyoffice.co…

msvcp120.dll丢失的解决方法,总结几种有效的解决方法

最近,我在使用计算机时遇到了一个问题,系统提示我丢失了msvcp120.dll文件。这让我感到非常困扰,因为这个问题导致我无法正常运行一些程序。经过一番搜索和尝试,我找到了几种修复这个问题的方法,并成功解决了这个问题。…

Unity面试题 UGUI调整UI与粒子特效的显示层级

首先,必须保证Canvas画布的渲染模式为了相机渲染 方法:一:将需要控制UI显示层级的Image换成Sprite 1.创建一个粒子系统,和两张Sprite. 2.设置Sprite1的Order in Layer为 -1,设置Sprite2的Order in Layer为 1,粒子的Ord…

QT拖放事件之一:初识拖放4大事件处理函数

1、拖放的前提 // 为了产生拖放事件,必须同时具备以下两个条件: // [1.] 必须开启用来接受拖放的窗口,否则无法产生拖放事件,鼠标形状显示为禁用标志; // [2.] 必须重写重新实现dragEnterEvent() {event->accept(); 或 event->acceptProposedAction();}; // 否则,…

VoxEdit 竞赛|为 The Sandbox 土地持有者设计专属奖励资产

邀请大家参与这场精彩的 VoxEdit 竞赛,在元宇宙中发挥你的创造力,并将你的体素技能提升到新的水平! 按此下载 VoxEdit ! https://www.sandbox.game/en/create/vox-edit/ 比赛主题:建筑与古迹 一起潜入建筑和古迹的世…

NetSuite 文件夹 Group Restriction的探究

同一个角色,为什么相同的文件,有的用户可以看,而有的用户不能看呢?这其中与一个隐藏功能相关,即文件夹的Restriction相关,其中一个非常典型的点是Group Restriction(组限制)&#xf…