Input子系统

input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点。

驱动部分包含三个部分:驱动层,核心层,事件层

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

注册input_dev

在使用 input 子系统的时候我们只需要注册一个 input 设备即可, input_dev 结构体表示 input设备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 };

evbit 表示输入事件类型,可选的事件类型定义在 include/uapi/linux/input.h 文件中,事件类型如下:

1
2
3
4
5
6
7
8
9
10
11
12
#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 /* 压力状态事件 */

Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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_allocate_device 函数来申请一个 input_dev,

1
struct input_dev *input_allocate_device(void)

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

1
void input_free_device(struct input_dev *dev)

申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数

1
int input_register_device(struct input_dev *dev)

input_dev 注册过程如下:

①、使用 input_allocate_device 函数申请一个 input_dev。
②、初始化 input_dev 的事件类型以及事件值。
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
④、卸载input驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev,然后使用 input_free_device 函数释放掉前面申请的 input_dev。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
struct input_dev *inputdev;
static int __init xxx_init(void)
{
...
inputdev = input_allocate_device();//申请input_dev
inputdev -> name = "test_inputdev";//设置名字
//第一种设置事件和键值
__set_bit(EV_KEY, inputdev->evbit); //设置产生按键事件
__set_bit(EV_REP, inputdev->evbit);//重复事件
__set_bit(KEY_0, inputdev->keyit);//设置产生哪些按键值
//第二种设置事件和键值
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY)|BIT_MASK(EV_REP);
keyinputdev.inputdev->keyit[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_register_device(inputdev);
...
return 0;
}
static void __exit xxx_exit(void)
{
input_unregister_device(inputdev);
input_free_device(inputdev);
}

上报输入事件

Linux 内核注册好 input_dev 不能顺利的使用input设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同。

1
2
3
4
5
//用于上报指定事件以及对应的值
void input_event(struct input_dev *dev, //dev需要上报inputdev
unsigned int type, //上报的事件类型如EV_KEY
unsigned int code, //事件码也就是注册的键值
int value) //事件值

nput_event 函数可以上报所有的事件类型和事件值, Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。

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

同样的还有一些其他的事件上报函数,这些函数如下所示:

1
2
3
4
5
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 函数本质是上报一个同步事件,

1
void input_sync(struct input_dev *dev)	//dev需要上报的同步事件的input_dev

eg:按键的上报事件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 用于按键消抖的定时器服务函数 */
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); /* 同步事件 */
}
}

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

1
2
3
4
5
6
struct input_event {
struct timeval time; //此事件发生的时间
__u16 type; //事件类型
__u16 code; //事件码
__s32 value; //按键值
};

关于时间timeval的结构体

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

Linux 自带按键驱动程序的使用

编译内核make menuconfig

使 用 Linux 内 核 自 带 的 按 键 驱 动 程 序 添加设备节点。

①、节点名字为“gpio-keys”。
②、 gpio-keys 节点的 compatible 属性值一定要设置为“gpio-keys”。
③、所有的 KEY 都是 gpio-keys 的子节点,每个子节点可以用如下属性描述自己:
gpios: KEY 所连接的 GPIO 信息。
interrupts: KEY 所使用 GPIO 中断信息,不是必须的,可以不写。
label: KEY 名字
linux,code: KEY 要模拟的按键,也就是示例代码 58.1.2.4 中的这些按键。
④、如果按键要支持连按的话要加入 autorepeat。
gpio-keys 节点内容

1
2
3
4
5
6
7
8
9
10
11
gpio-keys {
compatible = "gpio-keys";
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key0 {
label = "GPIO Key Enter";
linux,code = <KEY_ENTER>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
};