【北京迅为】i.MX6ULL终结者Linux INPUT子系统实验Input子系统

1 input子系统简介

input 子系统就是管理输入的子系统,和 pinctrl 和 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。 input子系统处理输入事务,任何输入设备的驱动程序都可以通过input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。
input子系统是分层结构的,总共分为三层: 硬件驱动层,子系统核心层,事件处理层。
(1)硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
(2)子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
(3)事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
在这里插入图片描述

图 1.1

各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件驱动层–>子系统核心–>事件处理层–>用户空间。

2 input驱动程序编写流程

首先来看一下在input核心层实现了哪些功能,input核心层文件是input.c,路径:drivers/input/input.c,部分内容如下:

1767 struct class input_class = {
    
     
1768       .name = "input",
1769       .devnode = input_devnode, 
1770 }; 
...... 
2414 static int __init input_init(void) 
2415 {
    
     
2416       int err;
2417 
2418       err = class_register(&input_class); 
2419       if (err) {
    
     
2420               pr_err("unable to register input_dev class\n"); 
2421               return err; 
2422       } 
2423 
2424       err = input_proc_init(); 
2425       if (err) 
2426           goto fail1; 
2427 
2428       err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), 
2429                   INPUT_MAX_CHAR_DEVICES, "input"); 
2430       if (err) {
    
     
2431           pr_err("unable to register char major %d", INPUT_MAJOR); 
2432           goto fail2; 
2433       } 
2434 
2435   return 0; 
2436 
2437   fail2: input_proc_exit(); 
2438   fail1: class_unregister(&input_class); 
2439       return err; 
2440 } 

第2418行,注册了一个input类,在系统启动后会在/sys/class目录下生成一个input类的子目录,如图 2.1所示:
在这里插入图片描述

图 2.1

第2428、2489行,注册了一个字符设备,所以input子系统本质上也是字符设备驱动,主设备号为 INPUT_MAJOR,INPUT_MAJOR 定义在 include/uapi/linux/major.h 文件中,定义如下:
#define INPUT_MAJOR 13
所以input 子系统的所有设备主设备号都为 13,在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可。
1、input_dev 结构体
input_dev结构体是input设备基本的设备结构,每个input驱动程序中都必须分配初始化这样一个结构,结构体定义在 include/linux/input.h 文件中,定义如下:

121 struct input_dev {
    
     
122        const char *name; 
123        const char *phys; 
124        const char *uniq; 
125        struct input_id id; 
126 
127        unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)]; 
128 
129        unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */ 
130        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */ 
131        unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */ 
132        unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */ 
133        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */ 
134        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */ 
135        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */ 
136        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */ 
137        unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */ 
...... 
189        bool devres_managed; 
190 };

第 129 行,evbit 表示输入事件类型,可选的事件类型定义在 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 	/* LED */ 
#define 	EV_SND 			0x12 	/* sound(声音) */ 
#define 	EV_REP 			0x14 	/* 重复事件 */ 
#define 	EV_FF 			0x15 	/* 压力事件 */ 
#define 	EV_PWR 			0x16 	/* 电源事件 */ 
#define 	EV_FF_STATUS 	0x17 	/* 压力状态事件 */

根据使用的不同设备选择不同的事件类型,在本章的实验中我们会用到按键设备,那么我们就需要选择EV_KEY 事件类型。

在看input_dev结构体中的第129~137行的evbit、keybit等成员变量,都是对应的不同事件类型的值。比如按键事件对应的keybit成员,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:

215 #define 	KEY_RESERVED 	0 
216 #define 	KEY_ESC 			1 
217 #define 	KEY_1	 		2 
218 #define 	KEY_2 			3 
219 #define 	KEY_3 			4 
220 #define 	KEY_4 			5 
221 #define 	KEY_5 			6 
222 #define 	KEY_6 			7 
223 #define 	KEY_7 			8 
224 #define 	KEY_8 			9 
225 #define 	KEY_9 			10 
226 #define 	KEY_0 			11 
...... 
794 #define 	BTN_TRIGGER_HAPPY39 	0x2e6 
795 #define 	BTN_TRIGGER_HAPPY40 	0x2e7

当我们编写input设备驱动时需要先创建一个input_dev 结构体变量,但是不用我们手动创建,input子系统提供了下面两个函数用于创建和注销input_dev 结构体变量。

struct input_dev *input_allocate_device(void)	//申请input_dev结构体
void input_free_device(struct input_dev *dev) 	//注销input_dev结构体
input_allocate_device函数不需要参数,直接返回申请到的input_dev结构体。
input_free_device函数用来释放掉前面申请到的input_dev结构体。

申请完input_dev结构体后,需要进行初始化,根据自己的设备来指定事件类型和事件值,比如按键设备的事件类型是evbit,事件值是keybit。
input_dev结构体初始化完成后,使用input_register_device 函数向Linux内核注册input_dev设备。函数原型如下:
int input_register_device(struct input_dev *dev)
dev:要注册的 input_dev 。
返回值:0,input_dev 注册成功;负值,input_dev 注册失败。
同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev,input_unregister_device 函数原型如下:
void input_unregister_device(struct input_dev *dev)
总结上面的内容,input_dev注册过程分为下面几步:
① 首先使用 input_allocate_device 函数申请一个 input_dev。
② 初始化 input_dev 的事件类型以及事件值。
③ input_register_device()函数是输入子系统核心(input core)提供的函数。该函数将input_dev结构体注册到输入子系统核心中
④ input_register_device()函数如果注册失败,必须调用input_free_device()函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用input_unregister_device()函数来注销输入设备结构体。
input_dev注册过程实例代码如下:

1 struct input_dev *inputdev; /* input 结构体变量 */ 
2 
3 /* 驱动入口函数 */ 
4 static int __init xxx_init(void) 
5 {
    
     
6  ...... 
7    inputdev = input_allocate_device(); /* 申请 input_dev */ 
8  inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */ 
9 
10     /*********第一种设置事件和事件值的方法***********/ 
11     __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */ 
12     __set_bit(EV_REP, inputdev->repbit); /* 重复事件 */ 
13     __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */ 
14     /************************************************/ 
15 
16     /*********第二种设置事件和事件值的方法***********/ 
17     keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
18     keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0); 
19     /************************************************/ 
20 
21     /*********第三种设置事件和事件值的方法***********/ 
22     keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); 
23     input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0); 
24     /************************************************/ 
25 
26     /* 注册 input_dev */ 
27     input_register_device(inputdev); 
28     ...... 
29     return 0; 
30 } 
31 
32 /* 驱动出口函数 */ 
33 static void __exit xxx_exit(void)
34 {
    
     
35     input_unregister_device(inputdev); /* 注销 input_dev */ 
36     input_free_device(inputdev); /* 删除 input_dev */ 
37 }

第 10~23 行都是初始化 input 设备事件和按键值,这里用了三种方法来设置事件和按键值。
2、上报输入事件
在input设备驱动中申请、注册完成input_dev结构体后,还不能正常使用input子系统,因为input设备是输入一些信息,但是Linux内核还不清楚输入的信息表示什么意思,有什么作用,所以我们需要驱动获取到具体的输入值,或者说输入事件,然后将输入事件上报给Linux内核。比如按键设备,我们需要在按键产生后将按键值上报给Linux内核,Linux内核获取到具体的按键值后,才会执行相应的功能。不同的事件上报的函数不同,我们分别来看一下有哪些常用的API函数。
input_event函数:用于上报指定的事件以及对应的值。函数原型如下:

void input_event(struct input_dev *dev, 
unsigned int type, 
unsigned int code, 
int value) 

函数参数和返回值含义如下:
dev:需要上报的 input_dev。
type: 上报的事件类型,比如 EV_KEY。
code:事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1 等等。
value:事件值,比如 1 表示按键按下,0 表示按键松开。
返回值:无。
input_event函数可以用于所有事件类型和事件值的上报。

input_report_key 函数:上报按键事件。具体函数内容如下:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value) 
{
    
     
input_event(dev, EV_KEY, code, !!value); 
} 

可以看出,input_report_key 函数的本质就是 input_event 函数,当然使用哪个函数都没有问题,不同的设备使用对应的函数更加合适一点。
同样的还有一些其他事件对应的上报函数:

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)

input_sync 函数:用来告诉Linux内核input子系统上报结束。input_sync 函数本质上是上报一个同步事件,函数原型如下:
void input_sync(struct input_dev *dev)
列举了好几个函数,以按键设备为例,看一下如何使用:

1 /* 用于按键消抖的定时器服务函数 */ 
2 void timer_function(unsigned long arg) 
3 {
    
     
4      unsigned char value;
5 
6      value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */ 
7      if(value == 0){
    
     /* 按下按键 */ 
8           /* 上报按键值 */ 
9          input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1,按下 */ 
10             input_sync(inputdev); /* 同步事件 */ 
11         } else {
    
     /* 按键松开 */ 
12             input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0,松开 */ 
13             input_sync(inputdev); /* 同步事件 */ 
14         } 
15 }

获取按键的值,然后判断按键是否按下,通过input_report_key函数上报按键的值,input_sync函数表示上报结束。

3 input_event结构体

Linux 内核使用 input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

24 struct input_event {
    
     
25         struct timeval time; 
26         __u16 type; 
27       __u16 code; 
28         __s32 value; 
29 };

依次来看一下 input_event 结构体中的各个成员变量:
time:时间,也就是此事件发生的时间,为 timeval 结构体类型,timeval 结构体定义如下:

1 typedef long __kernel_long_t; 
2 typedef __kernel_long_t __kernel_time_t; 
3 typedef __kernel_long_t __kernel_suseconds_t; 
4
5 struct timeval {
    
     
6  __kernel_time_t tv_sec; /* 秒 */ 
7  __kernel_suseconds_t tv_usec; /* 微秒 */ 
8 };

tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32位,这个一定要记住,后面我们分析 event 事件上报数据的时候要用到。
type:事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0、KEY_1等等这些按键。此成员变量为 16 位。
value:值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如按键值等。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/BeiJingXunWei/article/details/112171281