十五、(正点原子)Linux INPUT子系统

        按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件。
 

INPUT子系统

        1、简介

        input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。
        为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点, input 子系统框架如图所示:

        左边就是最底层的具体设备,比如按键、 USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。
         input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:

        驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
        核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
        事件层:主要和用户空间进行交互。

        2、INPUT驱动编写流程

        input 核心层会向 Linux 内核注册一个字符设备,在drivers/input/input.c 这个文件,input.c 就是 input 输入子系统的核心层,此文件里面有如下所示代码:

        首先,会先向系统注册一个input类 ,系统启动后在/sys/class目录下会有一个名为input的子目录。

         然后注册一个字符设备主设备号为 INPUT_MAJOR,定义如下:

定义在 include/uapi/linux/major.h 中

#define INPUT_MAJOR 13

         因此, input 子系统的所有设备主设备号都为 13,我们在使用 input 子系统处理输入设备的时候就不需要去注册字符设备。

        (1)、注册input_dev

        input子系统其实也是使用结构体steuct input_dev来表示一个设备驱动,所以我们要使用input子系统我们只需要注册一个 input 设备即可。input_dev 结构体表示 input设备,此结构体如下:

定义在 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)];    /* 事件类型的位图 */
	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 相关的位图 */
	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)];    /* 开关状态的位图 */

    。。。

	bool devres_managed;
};

        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 事件,如果要使用连按功能的话还需要注册 EV_REP 事件。
        evbit keybitrelbit 等等都是存放不同事件对应的值。如果我们要使用按键事件,要用到 keybit keybit 就是按键事件使用的位图, Linux 内核定义了很多按键值,这些按键值如下:

定义在 include/uapi/linux/input.h 文件中

#define KEY_RESERVED		0
#define KEY_ESC			1
#define KEY_1			2
#define KEY_2			3
#define KEY_3			4
#define KEY_4			5
#define KEY_5			6
#define KEY_6			7
#define KEY_7			8
#define KEY_8			9
#define KEY_9			10
#define KEY_0			11
#define KEY_MINUS		12
#define KEY_EQUAL		13
#define KEY_BACKSPACE		14
#define KEY_TAB			15
#define KEY_Q			16
#define KEY_W			17
#define KEY_E			18
#define KEY_R			19
#define KEY_T			20
...

/* Code 255 is reserved for special needs of AT keyboard driver */

#define BTN_MISC		0x100
#define BTN_0			0x100
#define BTN_1			0x101
#define BTN_2			0x102
#define BTN_3			0x103
#define BTN_4			0x104
#define BTN_5			0x105
#define BTN_6			0x106
#define BTN_7			0x107
#define BTN_8			0x108
#define BTN_9			0x109

#define BTN_MOUSE		0x110
#define BTN_LEFT		0x110
#define BTN_RIGHT		0x111
#define BTN_MIDDLE		0x112
#define BTN_SIDE		0x113
#define BTN_EXTRA		0x114
#define BTN_FORWARD		0x115
...

        (2)、申请注册input 设备驱动

        在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:

定义在 include/linux/input.h 文件中

struct input_dev __must_check *input_allocate_device(void);

         返回值: 申请到的 input_dev

        申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。有三种初始化方式:

        1)、第一种

/*********第一种设置事件和事件值的方法***********/

 __set_bit(EV_KEY, inputdev->evbit);     /* 设置产生按键事件 */
 __set_bit(EV_REP, inputdev->evbit);     /* 重复事件 */
 __set_bit(KEY_0,  inputdev->keybit);     /*设置产生哪些按键值 */

 /************************************************/

        2)、第二种 

/*********第二种设置事件和事件值的方法***********/

    inputdev->evbit[0] = BIT_MASK(EV_KEY) |
                         BIT_MASK(EV_REP);
    inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);

/************************************************/

        3)、第三种 

/*********第三种设置事件和事件值的方法***********/
    inputdev->evbit[0] = BIT_MASK(EV_KEY) |
                         BIT_MASK(EV_REP);
    input_set_capability(inputdev, EV_KEY, KEY_0);
/************************************************/

        input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数,此函数原型如下:

定义在 include/linux/input.h 文件中

int input_register_device(struct input_dev *dev)

        dev:要注册的 input_dev
        返回值: 0, input_dev 注册成功;负值, input_dev 注册失败。

        (3)、注销和删除input设备驱动

        注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev input_unregister_device 函数原型如下:

定义在 include/linux/input.h 文件中

void input_unregister_device(struct input_dev *dev)

        dev:要注销的 input_dev

        注销 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_devinput_free_device 函数原型如下:

定义在 include/linux/input.h 文件中

void input_free_device(struct input_dev *dev)

        dev:需要释放的 input_dev


        (4)、input驱动编写总结

        ①、使用 input_allocate_device 函数申请一个 input_dev

        ②、初始化 input_dev 的事件类型以及事件值。

        ③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev

        ④、卸载input驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev,然后使用 input_free_device 函数释放掉前面申请的 input_dev
       

注:当我们使用input子系统添加设备驱动模块时,当加载模块后,会在/dev/input目录下生成一个evenX(X = 1.2.3...)的设备文件。


        示例代码如下所示:

struct input_dev *inputdev; /* input 结构体变量 */

/* 驱动入口函数 */
    static int __init xxx_init(void)
{
    ......
    inputdev = input_allocate_device();     /* 申请 input_dev */
    inputdev->name = "test_inputdev";       /* 设置 input_dev 名字 */

     /*********第一种设置事件和事件值的方法***********/
     __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
     __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
     __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
     /************************************************/

     /*********第二种设置事件和事件值的方法***********/
     keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
     BIT_MASK(EV_REP);
     keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
     BIT_MASK(KEY_0);
     /************************************************/

     /*********第三种设置事件和事件值的方法***********/
     keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
     BIT_MASK(EV_REP);
     input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
     /************************************************/

     /* 注册 input_dev */
     input_register_device(inputdev);
     ......
     return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
    input_unregister_device(inputdev); /* 注销 input_dev */
    input_free_device(inputdev);       /* 删除 input_dev */
}

        3、上报输入事件

        当我们向 Linux 内核注册好 input_dev 以后还不能使用 input 设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。
        比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。
        首先是 input_event 函数,input_event 函数用于上报指定的事件以及对应的值,可以上报所有的事件类型和事件值。函数原型如下:

定义在 include/linux/input.h 文件中

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

        dev:需要上报的input子系统结构体。

        type: 上报的事件类型,比如 EV_KEY

        code: 事件码,也就是我们注册的按键值,比如 KEY_0、 KEY_1 等等。

        value:事件值,比如 1 表示按键按下, 0 表示按键松开。

        Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。内容如下:

定义在 include/linux/input.h 文件中

/* 上报按键事件  */
static inline void input_report_key(struct input_dev *dev,
                                    unsigned int code, int value)
{
    input_event(dev, EV_KEY, code, !!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_sync(struct input_dev *dev)

。。。

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

        按键的上报事件的示例代码如下所示:

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
    unsigned char value;

    value = gpio_get_value(keydesc->gpio);     /* 读取 IO 值 */
    if(value == 0){         /* 按下按键 */
        /* 上报按键值 */
        input_report_key(inputdev, KEY_0, 1);  /* 最后一个参数 1, 按下 自己设置*/
        input_sync(inputdev);                  /* 同步事件 */
    } else {                 /* 按键松开 */
        input_report_key(inputdev, KEY_0, 0);  /* 最后一个参数 0, 松开 自己设置*/
        input_sync(inputdev);                  /* 同步事件 */
    } 
}

        4、输入事件

        当input子系统设备驱动上报输入事件后,这个事件会传递给用户空间,那么上报的内容是什么呢?用户空间如何获取呢?

       Linux 内核使用 struct input_event 这个结构体来表示所有的输入事件,结构体内容如下:

 定义在include/uapi/linux/input.h 文件中

struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
};


 定义在include/uapi/linux/timer.h 文件中
struct timeval {
	__kernel_time_t		tv_sec;		    /* seconds  秒*/
	__kernel_suseconds_t	tv_usec;	/* microseconds 微妙*/
};

typedef long		            __kernel_long_t;
typedef __kernel_long_t	        __kernel_time_t;
typedef __kernel_long_t	    	__kernel_suseconds_t;

        time:事件,也就是事件发生的事件可以看出, tv_sectv_usec 这两个成员变量都为 long类型,32位。
        type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。

        code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、 KEY_1等等这些按键。此成员变量为 16 位。
        value: 值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的
话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了。

        示例(使用命令hexdump 操作input子系统设备驱动,会打印出上报的原始数据):

        input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

/* 编号 */  /* tv_sec */  /* tv_usec */ /* type */ /* code */  /* value */
0000000     820d 0000     0fa6 000b      0001         001c     0001 0000   按下上报
0000010     820d 0000     0fa6 000b      0000         0000     0000 0000   同步

0000020     820d 0000     bd42 000c      0001         001c     0000 0000   松开上报
0000030     820d 0000     bd42 000c      0000         0000     0000 0000   同步

        所有的输入设备最终都是按照 input_event 结构体呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tofu_Cabbage

你的打赏是我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值