LV10-12-input子系统-01-input子系统基础
本文主要是input子系统基础知识的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
Linux开发板 | 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2 |
u-boot | 2013.01 |
点击查看本文参考资料
参考方向 | 参考原文 |
--- | --- |
点击查看相关文件下载
文件 | 下载链接 |
--- | --- |
一、 input 子系统
1. input 子系统简介
input 子系统就是管理输入的子系统,和 pinctrl 、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。
2. 各个分层
接下来我们详细了解一下各个层都有什么用,先来重新看一张图片:
- 事件处理层
接收来自核心层上报的事件,并选择对应的 handler (事件处理器 struct input_handler )去处理,与用户空间进行交互。内核维护着多个事件处理器对象,每个 input_handler 对象专门处理一类事件,所有产生同类事件的设备驱动共用同一个 handler 。
linux 中在用户空间将所有的设备都当初文件来处理,在一般的驱动程序中需要提供 fops 接口,我的理解就是操作函数集,以及在 /dev 下生成相应的设备文件 nod ,这些操作在输入子系统中由事件处理层完成。
- 核心层
负责连接设备驱动层和事件处理层,为设备驱动层提供输入设备驱动的接口( struct input_dev )以及输入设备驱动的注册函数( input_register_device ),为事件处理层提供输入事件驱动的接口,通知事件处理层对事件进行处理。会在 /proc 下产生相应的设备信息。
- 设备驱动层
主要实现获取硬件设备的数据信息(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),并转换为核心层定义的规范事件后提交给核心层,该层每个设备对应一个 struct input_dev 对象。
二、基本数据结构
1. struct input_dev
在使用 input 子系统的时候我们只需要注册一个 input 设备即可, input_dev 结构体表示 input 设备,这个结构体定义在 linux 内核源码的下边这个文件中:
1 | include/linux/input.h |
我们打开这个文件,找到 struct input_dev 结构体如下:
1 | struct input_dev { |
【成员说明】
- evbit : unsigned long 类型数组,这个我们用的比较多,它表示输入事件的类型。
点击查看可选事件类型
可选的事件类型定义在 linux 内核源码的这个文件中:
1 | include/uapi/linux/input.h |
我们打开这个文件,可以看到常见的事件类型定义如下:
1 | /* |
- keybit : unsigned long 类型数组,主要是一些记录支持的按键值的位图。
点击查看可选键值定义
按键键值定义在 linux 内核源码的这个文件中:
1 | include/uapi/linux/input.h |
我们打开这个文件,可以看到常见的键值定义如下:
1 | /* |
2. struct input_event
Linux 内核使用 input_event 这个结构体来表示所有的输入事件 ,这个结构体定义在 linux 内核源码的下边这个文件中:
1 | include/uapi/linux/input.h |
我们打开这个文件,找到 struct input_event 结构体如下:
1 | /* |
【成员介绍】
- time : struct timeval 类型,表示此事件发生的时间。
点击查看 struct timeval
这个结构体定义在 linux 内核源码的这个目录下:
1 | include/uapi/linux/time.h |
我们打开这个文件,可以看到结构体定义如下:
1 | /* include/uapi/asm-generic/posix_types.h */ |
- type : __u16 类型,表示事件类型,比如 EV_KEY ,表示此次事件为按键事件,此成员变量为 16 位。
- code : __u16 类型,事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0 、 KEY_1
等等这些按键,此成员变量为 16 位。 - value : __s32 类型,表示值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等。
每个事件的发生都使用事件(type)→子事件(code)→值(value)。
三、类、设备号和设备节点
前边我们在编写驱动的时候都是需要自己创建类和设备节点或者是需要在程序中编程实现节点的自动创建,但是 input 子系统会自动创建相应的类和结点,我们的程序中就不再需要这部分的代码了。在这里边会有类和设备节点的创建相关代码。
1. 类的定义
我们打开 linux 内核源码中的 drivers/input/input.c 这个文件, 这个文件就是 input 子系统的核心层,里边有这样一个结构体变量:
1 | /* linux3.14版本内核的话,应该在1758行 */ |
这个就是定义的一个类的结构体变量,然后在下边有这样一个函数:
1 | /* linux3.14版本内核的话,应该在2401行 */ |
在该函数中,会注册一个 input 类,这样系统启动以后就会在 /sys/class 目录下有一个 input 子目录。
2. 设备号
在 linux 内核源码的 drivers/input/input.c 这个文件的 input_init 函数中有这么一条语句:
1 | /* linux3.14版本内核的话,应该在2401行 */ |
这里会注册一个字符设备,主设备号为 INPUT_MAJOR ,这个宏定义在 linux 内核源码的这个文件中:
1 | include/uapi/linux/major.h |
我们打开这个文件会发现:
1 |
所以说, input 子系统的所有设备主设备号都为 13 。
3. 设备节点
关于这个设备节点问题,目前还没有找到,在哪里创建的,不过暂时不重要,知道怎么找我们加载的驱动的节点就好啦。
在 input 子系统中,设备节点也会自动创建,我们加载驱动之后,会在在 /dev/input/ 目录下生成节点,之前测试用的 key 和 mpu6050 的例子得到的设备节点都是 event 开头的,如:
1 | /dev/input/event1 |
我们加载驱动前看一下这个目录,加载后看一下多了哪个节点,这个就是我们加载的驱动对应节点啦。
三、基本函数
1. input_dev 对象创建与销毁
1.1 input_allocate_device()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_allocate_device -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于创建一个 input_dev 对象 。
【函数参数】 none
【返回值】 struct input_dev * 类型,成功返回创建好的 input_dev 对象地址,失败返回 NULL 。
【使用格式】 none
【注意事项】 none
1.2 input_free_device()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_free_device -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于销毁一个 input_dev 对象 。
【函数参数】
- dev : struct input_dev * 类型,表示要销毁的 input_dev 对象。
【返回值】 none
【使用格式】 none
【注意事项】 none
2. input_dev 对象注册与注销
创建好 input_dev 对象后,还需要向内核注册这个对象。
2.1 input_register_device()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_register_device -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于向内核注册一个 input_dev 对象 。
【函数参数】
- dev : struct input_dev * 类型,表示要注册的 input_dev 对象。
【返回值】 int 类型,注册成功返回 0 ,失败返回一个负数。
【使用格式】 none
【注意事项】 none
2.2 input_unregister_device()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_unregister_device -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于从内核注销一个 input_dev 对象
【函数参数】
dev : struct input_dev * 类型,表示要注销的 input_dev 对象。
【返回值】 none
【使用格式】 none
【注意事项】 none
3.设置事件和事件值
这一步是在创建好 input_dev 对象之后进行,可以看做是对 input_dev 对象的初始化。
3.1 __set_bit()
我们使用以下命令查询一下函数所在头文件:
1 | grep __set_bit -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于设置会产生的事件类型 。
【函数参数】
- nr : int 类型,表示事件的位,比如 EV_KEY , KEY_0 等。
- addr : volatile unsigned long * 类型,表示 input_dev 对象的不同位图,例如 evbit 成员, keybit 成员。
【返回值】 none
【使用格式】
1 | struct input_dev *pdev; |
【注意事项】 none
3.2 set_bit()
我们使用以下命令查询一下函数所在头文件:
1 | grep __set_bit -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于设置事件和事件值,格式和上边的 __set_bits 一样 。
【函数参数】
- nr : int 类型,表示事件的位,比如 EV_KEY , KEY_0 等。
- addr : volatile unsigned long * 类型,表示 input_dev 对象的不同位图,例如 evbit 成员, keybit 成员。
【返回值】 none
【使用格式】
1 | struct input_dev *pdev; |
【注意事项】 none
3.3 input_set_abs_params()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_set_abs_params -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数也是用于设置事件和事件值,不过还没有用过,以后用到了再在这里补充笔记。
【函数参数】
- dev : struct input_dev * 类型,表示 input_dev 对象。
- axis : unsigned int 类型。
- min : int 类型。
- max : int 类型。
- flat : int 类型。
【返回值】 none
【使用格式】 none
【注意事项】 none
3.4 input_set_capability()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_set_capability -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数也是用于设置事件和事件值,不过还没有用过,以后用到了再在这里补充笔记。
【函数参数】
- dev : struct input_dev * 类型,表示 input_dev 对象,也就是要设置的输入设备。
- type : unsigned int 类型,设置输入类型,可以看到,函数实现中根据 type 设置不同的 input_dev 结构体参数。例如 type =EV_KEY , 那么设置的是 input_dev->keybit ,也就是键值。
- code : unsigned int 类型,不同类型的输入信号含义不同,如果是按键,则表示的是要设置的按键的键值。
【返回值】 none
【使用格式】 none
【注意事项】 none
4.上报事件
4.1 input_event()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_event -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于向内核上报事件。
【函数参数】
- dev : struct input_dev * 类型,表示指定的 input_dev 对象。
- type : unsigned int 类型,表示事件类型。我们在根据实际输入设备配置 input_dev 结构体时会设置 input_dev-> evbit 参数, 用于设置输入设备能够产生的事件类型(可能是多个)。上报事件时要从“能够产生”的这些事件类型中选择。
- code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
- value , int 类型,指定事件的值。
【返回值】 none
【使用格式】 none
【注意事项】 none
4.2 input_report_key()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_report_key -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于上报按键事件 。
【函数参数】
- dev : struct input_dev * 类型,表示指定的 input_dev 对象。
- code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
- value , int 类型,指定事件的值。
【返回值】 none
【使用格式】 none
【注意事项】 none
4.3 input_report_abs()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_report_abs -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于上报绝对坐标事件。
【函数参数】
- dev : struct input_dev * 类型,表示指定的 input_dev 对象。
- code : unsigned int 类型,表示编码。以按键为例,按键的编码就是我们设置的按键键值。
- value , int 类型,指定事件的值。
【返回值】 none
【使用格式】 none
【注意事项】 none
4.4 input_sync()
我们使用以下命令查询一下函数所在头文件:
1 | grep input_sync -r -n ~/5linux/linux-3.14/include |
经过查找,我们可以得到如下信息:
1 | /* 需包含的头文件 */ |
【函数说明】该函数用于事件同步 。
【函数参数】
- dev : struct input_dev * 类型,表示发生事件的 input_dev 对象。
【返回值】 none
【使用格式】 none
【注意事项】 none