Yuhang Zheng

第二十八节、输入子系统

N 人看过

本节用于介绍输入子系统

1、什么是输入子系统?

输入子系统是Linux专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都输入设备。但是这些输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。

统一了以后,在节点/dev/input 下面则是我们输入设备的节点,如下图所示:

image-20210819165316034

这些节点对应的则是我们当前系统的输入设备,我们要怎么查看当前系统都有哪些输入设备呢?我们可以使用命令来查看:

cat /proc/bus/input/devices

image-20210819165454556

那么我们要怎么确定哪个设备对应哪个节点呢?这里教大家一个简单的方法,我们可以使用命令hexdump确定,hexdump命令是Linux下查看二进制文本的工具。这里我给大家举一个例子:

比如我想确定键盘对应的是哪个节点,我就可以使用命令:

hexdump /dev/input/event0
hexdump /dev/input/event1
hexdump /dev/input/event2
...

输入完一条命令以后,我们按键盘的上的按键,如果有数据打印出来,则证明当前我们查看的这个节点是键盘这个设备对应的节点。

 root@imx6qsabresd:~# hexdump /dev/input/event1
0000000 f472 5948 5741 000a 0004 0004 001c 0007
0000010 f472 5948 5741 000a 0001 0015 0001 0000
0000020 f472 5948 5741 000a 0000 0000 0000 0000
0000030 f472 5948 6a7a 000c 0004 0004 001c 0007
0000040 f472 5948 6a7a 000c 0001 0015 0000 0000
0000050 f472 5948 6a7a 000c 0000 0000 0000 0000
0000060 f473 5948 d439 000a 0004 0004 001c 0007
0000070 f473 5948 d439 000a 0001 0015 0001 0000
0000080 f473 5948 d439 000a 0000 0000 0000 0000
0000090 f473 5948 a8f3 000c 0004 0004 001c 0007

那么这些打印的信息都是什么意思呢?

我们上报的数据要按照具体的格式上报给输入子系统的核心层,我们的应用就可以通过设备节点来获得按照具体格式上报来的数据了。

封装数据是输入子系统的核心层来帮我们完成的,我们只需要按照指定的类型和这个类型对应的数据来上报给输入子系统的核心层即可。

那我们要怎么指定类型呢?这个我们就需要先了解一下 struct input_event这个结构体,

文件位置路径:include/uapi/linux/input.h

struct input_event {
        struct timeval time;    //上报事件的时间
        __u16 type;                //类型
        __u16 code;                //编码
        __s32 value;            //值
};

对于上报数据的类型有哪些呢?

文件位置路径:include/uapi/linux/input-event-codes.h

/*
 * Event types
 */

#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        //压力状态事件
#define EV_MAX                  0x1f
#define EV_CNT                  (EV_MAX+1)

在源码中还有每一个事件类型所对应的全部的编码,这些内容再次不再展示

当我们键盘上的按键按下的时候,value如果为1,就代表按下,如果为0,就代表抬起,如果为2,就代码长按。

了解了这些概念以后,我们来看一下这个例子:

当我使用命令 hexdump /dev/input/event1后,按下键盘上的回车按键,打印以下内容:

root@imx6qsabresd:~# hexdump /dev/input/event1
0000000 fce7 5948 ca0e 000d 0004 0004 0028 0007
0000010 fce7 5948 ca0e 000d 0001 001c 0001 0000
0000020 fce7 5948 ca0e 000d 0000 0000 0000 0000
0000030 fce8 5948 ba50 0000 0004 0004 0028 0007
0000040 fce8 5948 ba50 0000 0001 001c 0000 0000
0000050 fce8 5948 ba50 0000 0000 0000 0000 0000

那么在这6条信息里面,只有第2条信息代表的是我们回车按键按下的信息,如下图所示:

root@imx6qsabresd:~# hexdump /dev/input/event1
                            type  code    value
0000000 fce7 5948 ca0e 000d 0004  0004  0028 0007 
0000010 fce7 5948 ca0e 000d[0001][001c][0001 0000]<---这里实际上时0000 0001
0000020 fce7 5948 ca0e 000d 0000  0000  0000 0000
0000030 fce8 5948 ba50 0000 0004  0004  0028 0007
0000040 fce8 5948 ba50 0000[0001][001c][0000 0000]
0000050 fce8 5948 ba50 0000 0000  0000  0000 0000

其中,type [0001]的含义为:
/*
 * Event types
 */
#define EV_KEY                  0x01        //按键事件

code [001c]的含义为:
/*
 * Keys and buttons
 */
#define KEY_ENTER               28            //回车按键

2、使用输入子系统设计按键驱动

我们可以将开发板上的按键值设置为input.h文件里面的宏定义的任意一个,比如我们本次实验将开发板上的KEY按键值设置为KEY_1。

在编写input设备驱动的时候我们需要先申请一个input_dev结构体变量,然后使用input_allocate_device函数来申请一个 input_dev。

文件位置路径:include/linux/input.h

struct input_dev {
        const char *name;    //这个输入设备的名字
        const char *phys;    //物理路径
        const char *uniq;
        struct input_id id;

        unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];        //里面有很多元素,每一个元素代表一种设备,当某一元素置为1时,则代表该设备被支持
        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];    //对于按键事件,表示这个设备支持哪些按键,也就是上一节的code
        unsigned long relbit[BITS_TO_LONGS(REL_CNT)];    //对于相对坐标事件,表示这个设备支持哪些code
        //同理...
}

文件位置路径:include/linux/input.h

函数原型如下所示:

struct input_dev __must_check *input_allocate_device(void);
//返回值:
//申请到的input_dev的指针

如果要注销的input设备的话需要使用input_free_device函数来释放掉前面申请到的input_dev,

文件位置路径:include/linux/input.h

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函数

文件位置路径:include/linux/input.h

此函数原型如下:

int __must_check input_register_device(struct input_dev *);
//参数:
//dev:需要注册的input_dev
//返回值:
//0:input_dev注册成功
//负值:input_dev注册失败

同样的,注销 input 驱动的时候也需要使用input_unregister_device函数来注销掉前面注册

void input_unregister_device(struct input_dev *);
//参数:
//dev:需要注销的input_dev

最终我们需要把事件上报上去,上报事件我们使用的函数要针对具体的时间来上报。比如,按键我们使用input_report_key函数。同样的还有一些其他的事件上报函数,函数如下所示:

static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value)
static inline void input_mt_sync(struct input_dev *dev)

当我们上报事件以后还需要使用input_sync 函数来告诉 Linux内核input子系统上报结束,input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

文件位置路径:include/linux/input.h

static inline void input_sync(struct input_dev *dev)
{
        input_event(dev, EV_SYN, SYN_REPORT, 0);
}
//参数:
//dev:需要上报同步事件的input_dev