按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息,input 核心层负责处理这些事件。本章我们就来学习一下 Linux 内核中的 input 子系统。核心思想其实都是将一些设备的共性给抽象出来形成固定的框架。
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图 58.1.1.1 所示:
图 58.1.1.1 中左边就是最底层的具体设备,比如按键、USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
- 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
- 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
- 事件层:主要和用户空间进行交互。
Input输入子系统更多参考:
Linux内核编程(十三)Input输入子系统_linux input-优快云博客
设备驱动层:设备驱动层可以通过获取设备树中硬件的信息,对硬件各寄存器的读写访问和将底层硬件的状态变化转换为标准的输入事件,将相应事件上报,再通过核心层提交给事件处理层。
核心层:用于将设备驱动层和事件处理层进行匹配,处理输入事件的分发和管理,是输入子系统的核心部分。这部分由内核工程师来编写,不需要我们自己编写。
事件处理层:这一层是直接与应用程序交互的部分。事件处理层负责将核心层生成的输入事件传递给系统的高层应用,并确保这些事件被正确处理。应用程序通过这一层接收用户输入(如点击、键盘按键等),并据此进行相应的操作。这部分由内核或设备厂商来实现,也不需要我们编写。
那么对于一个输入子系统驱动程序,我们只需要调用内核实现的接口来编写设备驱动层即可。核心层,和事件处理层的代码不需要我们来编写。
input 驱动编写流程
input 核心层会向 Linux 内核注册一个字符设备,大家找到 drivers/input/input.c 这个文件,input.c 就是 input 输入子系统的核心层,此文件里面有如下所示代码:
第 2418 行,注册一个 input 类,这样系统启动以后就会在/sys/class 目录下有一个 input 子目录,如图 58.1.2.1 所示:
第 2428~2429 行,注册一个字符设备,主设备号为 INPUT_MAJOR,INPUT_MAJOR 定义在 include/uapi/linux/major.h 文件中,定义如下:
#define INPUT_MAJOR 13
因此,input 子系统的所有设备主设备号都为 13,我们在使用 input 子系统处理输入设备的时候就不需要去一步一步地注册字符设备了,我们只需要向系统注册一个 input_device 即可。
1、注册 input_dev
在使用 input 子系统的时候我们只需要注册一个 input 设备即可,input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中,定义如下(有省略):
第 129 行,evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件中,事件类型如下:
注意,这里evbit看起来像是和keybit/relbit等等这些是并列的关系,其实evbit是先指明支持哪种类型事件,然后再用keybit/relbit等去设置对应的事件值。
继续回到示例代码 58.1.2.2 中,第 130 行~137 行的keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到 keybit,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:
我们可以将开发板上的按键值设置为示例代码 58.1.2.4 中的任意一个,比如我们本章实验会将 I.MX6U-ALPHA 开发板上的 KEY 按键值设置为 KEY_0。
在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:
struct input_dev *input_allocate_device(void)
函数参数和返回值含义如下:
参数:无。
返回值:申请到的 input_dev。
如果要注销的 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_dev,input_free_device 函数原型如下:
void input_free_device(struct input_dev *dev)
函数参数和返回值含义如下:
dev:需要释放的 input_dev。
返回值:无。
申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数,此函数原型如下:
int input_r