按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件。
1.input子系统
- input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。
- 为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点 。
input子系统结构图
- 驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
- 核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
- 事件层:主要和用户空间进行交互。
input子系统也是字符设备,input核心层会帮我们注册input字符设备驱动,既然内核已经帮我们写好了input驱动,input 子系统的所有设备主设备号都为 13,我们在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可。
1.1注册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 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 文件中,事件类型如下:
1
2
3
4
5
6
7
8
9
10
11
12比如本章我们要使用到按键,那么就需要注册 EV_KEY 事件,如果要使用连按功能的话还需要注册 EV_REP 事件。
比如我们本章要使用按键事件,因此要用到 keybit, keybit 就是按键事件使用的位图, Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
......我们可以将开发板上的按键值设置为任意一个。
申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev了,需要用到 input_register_device 函数,此函数原型如下:
1 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 函数原型如下:
1 void input_unregister_device(struct input_dev *dev)
dev:要注销的 input_dev
返回值: 无
input_dev 注册过程如下:
①、使用 input_allocate_device 函数申请一个 input_dev
②、初始化 input_dev 的事件类型以及事件值
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev
④、卸载input驱动的时候需要先使用input_unregister_device 函数注销掉注册的input_dev
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 /*********第一种设置事件和事件值的方法***********/
__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);
/************************************************/
1.2上报输入事件
input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。
比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。
不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。
input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下:
1
2
3
4 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_sync数来告诉 Linux内核 input子系统上报结束, input_sync 函数本质是上报一个同步事件,此函数原型如下所示:
1 void input_sync(struct input_dev *dev)
- dev:需要上报同步事件的 input_dev。
- 返回值:无。
1.3input_event结构体
Linux 内核使用 input_event 这个结构体来表示所有的输入事件, input_envent 结构体:
1
2
3
4
5
6 struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:时间,也就是此事件发生的时间,为 timeval 结构体类型, timeval 结构体定义如下:
1
2
3
4
5
6
7
8 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; /* 微秒 */
};
- 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 的话说明按键没有被按下或者按键松开了。
展开:
1
2
3
4 struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
}最终将input展开后;
1
2
3
4
5
6
7
8
9 struct input_event {
struct timeval{
long tv_sec; //seconds 32位表示秒
long tv_usec; //microseconds 32位表示微秒
}
__u16 type; //16位的事件类型
__u16 code; //16位的事件码,对于按键而言就是键码
__s32 value; //32位的值,对于按键就是按键或抬起
};以下来自驱动实验hexdump输出的原始值(3.1驱动程序最后):
1
2
3
4
5
6
7
8
9
10
11
12
13
14 //以下都是十六进制显示
//编号 //秒tv_sec //微秒tv_usec //type //code //value
0000000 ac82 0007 e2f1 0006 0001 000b 0001 0000
//type:EV_KEY事件 code:KEY_0按键(11=0xb) value:1 表示按键按下
0000010 ac82 0007 e2f1 0006 0000 0000 0000 0000
//type:EV_SYN事件
0000020 ac82 0007 173f 000a 0001 000b 0000 0000
//type:EV_KEY事件 code:KEY_0按键(11=0xb) value:0 表示按键释放
0000030 ac82 0007 173f 000a 0000 0000 0000 0000
//type:EV_SYN事件
0000040 ac83 0007 835b 0005 0001 000b 0001 0000
0000050 ac83 0007 835b 0005 0000 0000 0000 0000
0000060 ac83 0007 53e7 0009 0001 000b 0002 0000
0000070 ac83 0007 53e7 0009 0000 0000 0001 0000
2.使用input框架
2.1设备树:
打开dts文件,在根节点下添加key节点:
1
2
3
4
5
6
7
8
9
10 key{
compatible = "zwl,key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
status = "okay";
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
2.2基本框架
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
struct irq_key_dev
{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[10]; //名字
irqreturn_t (*handler)(int,void *); //中断处理函数
struct tasklet_struct tasklet;
};
struct keyinput
{
struct device_node *nd; /* 设备节点 */
struct irq_key_dev irq_key[KEY_NUM];
struct timer_list timer;
struct input_dev *inputdev; //输入设备
};
struct keyinput keyinput;
//中断处理函数
static irqreturn_t key0_irq_handler(int irq,void *dev_id)
{
struct keyinput *dev = dev_id;
// dev->timer.data = (volatile unsigned long)dev_id;
// mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
tasklet_schedule(&dev->irq_key[0].tasklet);
return IRQ_HANDLED;
}
/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct keyinput *dev = (struct keyinput *)arg;
value = gpio_get_value(dev->irq_key[0].gpio);
if (value == 0) //按下
{
//上报按键值
}else if (value == 1)
{
}
}
static void key_tasklet(unsigned long data)
{
struct keyinput *dev = (struct keyinput *)data;
dev->timer.data = (volatile unsigned long)data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
}
static int keyio_init(struct keyinput *dev)
{
int ret;
int i;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL)
{
printk("fail find nd\r\n");
ret = -EINVAL;
goto fail_nd;
}
for (i = 0; i < KEY_NUM; i++)
{
ret = dev->irq_key[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if (ret < 0){
printk("can't get key%d\r\n",i);
goto fail_get_gpio;
}
}
for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irq_key[i].name,0,sizeof(dev->irq_key[i].name)); /* 缓冲区清零 */
sprintf(dev->irq_key[i].name,"key%d",i);
gpio_request(dev->irq_key[i].gpio,dev->irq_key[i].name);
gpio_direction_input(dev->irq_key[i].gpio);
dev->irq_key[i].irqnum = gpio_to_irq(dev->irq_key[i].gpio); /* 获取中断号 */
}
dev->irq_key[0].handler = key0_irq_handler;
dev->irq_key[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irq_key[i].irqnum,dev->irq_key[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irq_key[i].name,&keyinput);
if (ret)
{
printk("request %d irq failed\r\n",dev->irq_key[i].irqnum);
ret = -EINVAL;
goto fail_requese_irq;
}
tasklet_init(&dev->irq_key[0].tasklet,key_tasklet,(unsigned long)dev);
}
//初始化定时器
init_timer(&dev->timer);
dev->timer.function = timer_func;
return 0;
fail_requese_irq:
for (i = 0; i < KEY_NUM; i++){
gpio_free(dev->irq_key[i].gpio);
}
fail_get_gpio:
fail_nd:
return ret;
}
static int __init keyinput_init(void)
{
int ret;
//1.初始按键
ret = keyio_init(&keyinput);
if (ret)
{
printk("fail keyio_init\r\n");
goto fail_keyio_init;
}
//2.注册input_dev
printk("all init suggess!!!\r\n");
return 0;
fail_keyio_init:
return ret;
}
static void __exit keyinput_exit(void)
{
int i = 0;
//1.释放中断
for (i = 0; i < KEY_NUM; i++) {
free_irq(keyinput.irq_key[i].irqnum,&keyinput);
}
//2.释放gpio
for (i = 0; i < KEY_NUM; i++){
gpio_free(keyinput.irq_key[i].gpio);
}
//3.删除定时器
del_timer_sync(&keyinput.timer);
//4.注销input_dev
printk("keyinput exit!\r\n");
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwl");
3.实验
3.1驱动程序
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
struct irq_key_dev
{
int gpio; //io编号
int irqnum; //中断号
unsigned char value; //键值
char name[10]; //名字
irqreturn_t (*handler)(int,void *); //中断处理函数
struct tasklet_struct tasklet;
};
struct keyinput
{
struct device_node *nd; /* 设备节点 */
struct irq_key_dev irq_key[KEY_NUM];
struct timer_list timer;
struct input_dev *inputdev; //输入设备
};
struct keyinput keyinput;
//中断处理函数
static irqreturn_t key0_irq_handler(int irq,void *dev_id)
{
struct keyinput *dev = dev_id;
tasklet_schedule(&dev->irq_key[0].tasklet);
return IRQ_HANDLED;
}
/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
int value = 0;
struct keyinput *dev = (struct keyinput *)arg;
value = gpio_get_value(dev->irq_key[0].gpio);
if (value == 0) //按下
{
//上报按键值
input_event(dev->inputdev,EV_KEY,KEY_0,1);
input_sync(dev->inputdev);
}else if (value == 1){
input_event(dev->inputdev,EV_KEY,KEY_0,0);
input_sync(dev->inputdev);
}
}
static void key_tasklet(unsigned long data)
{
struct keyinput *dev = (struct keyinput *)data;
dev->timer.data = (volatile unsigned long)data;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); //20ms
}
static int keyio_init(struct keyinput *dev)
{
int ret;
int i;
/* 1.按键初始化 */
dev->nd = of_find_node_by_path("/key");
if (dev->nd == NULL){
printk("fail find nd\r\n");
ret = -EINVAL;
goto fail_nd;
}
for (i = 0; i < KEY_NUM; i++)
{
ret = dev->irq_key[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if (ret < 0){
printk("can't get key%d\r\n",i);
goto fail_get_gpio;
}
}
for (i = 0; i < KEY_NUM; i++)
{
memset(dev->irq_key[i].name,0,sizeof(dev->irq_key[i].name)); /* 缓冲区清零 */
sprintf(dev->irq_key[i].name,"key%d",i);
gpio_request(dev->irq_key[i].gpio,dev->irq_key[i].name);
gpio_direction_input(dev->irq_key[i].gpio);
dev->irq_key[i].irqnum = gpio_to_irq(dev->irq_key[i].gpio); /* 获取中断号 */
}
dev->irq_key[0].handler = key0_irq_handler;
dev->irq_key[0].value = KEY0VALUE;
/* 2.按键中断初始化 */
for(i = 0; i < KEY_NUM; i++)
{
ret = request_irq(dev->irq_key[i].irqnum,dev->irq_key[i].handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
dev->irq_key[i].name,&keyinput);
if (ret)
{
printk("request %d irq failed\r\n",dev->irq_key[i].irqnum);
ret = -EINVAL;
goto fail_requese_irq;
}
tasklet_init(&dev->irq_key[0].tasklet,key_tasklet,(unsigned long)dev);
}
//初始化定时器
init_timer(&dev->timer);
dev->timer.function = timer_func;
return 0;
fail_requese_irq:
for (i = 0; i < KEY_NUM; i++){
gpio_free(dev->irq_key[i].gpio);
}
fail_get_gpio:
fail_nd:
return ret;
}
static int __init keyinput_init(void)
{
int ret;
//1.初始按键
ret = keyio_init(&keyinput);
if (ret){
printk("fail keyio_init\r\n");
goto fail_keyio_init;
}
//2.注册input_dev
keyinput.inputdev = input_allocate_device();
if (keyinput.inputdev == NULL){
printk("fail input_allocate_device\r\n");
ret = -EINVAL;
goto fail_keyio_init;
}
keyinput.inputdev->name = keyinput_name;
__set_bit(EV_KEY,keyinput.inputdev->evbit); //按键事件
__set_bit(EV_REP,keyinput.inputdev->evbit); //重复事件
__set_bit(KEY_0,keyinput.inputdev->keybit); //按键值
ret = input_register_device(keyinput.inputdev);
if (ret ){
goto fail_register_input;
}
printk("all init suggess!!!\r\n");
return 0;
fail_register_input:
input_free_device(keyinput.inputdev);
fail_keyio_init:
return ret;
}
static void __exit keyinput_exit(void)
{
int i = 0;
//1.释放中断
for (i = 0; i < KEY_NUM; i++){
free_irq(keyinput.irq_key[i].irqnum,&keyinput);
}
//2.释放gpio
for (i = 0; i < KEY_NUM; i++){
gpio_free(keyinput.irq_key[i].gpio);
}
//3.删除定时器
del_timer_sync(&keyinput.timer);
//4.注销input_dev
input_unregister_device(keyinput.inputdev);
input_free_device(keyinput.inputdev);
printk("keyinput exit!\r\n");
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zwl");
加载驱动:
测试是否能够使用:新多出来的enent1就是按键。使用``查看原始数据。一直按住不放会一直输出。
至此驱动部分已经完成,对数据的分析在
1.3input_event
章节。
3.2应用程序
前面已经分析了
hexdump
输出的原始值。现在就是应用程序分析。按键驱动对应的文件就是
/dev/input/eventX
,X=(1,2,3…)应用程序读取/dev/input/event1
来得到按键信息,也就是按键有没有被按下。我们通过
/dev/input/event1
读到的信息是input_event
结构体形式的.
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static struct input_event inputevent;
int main(int argc, char *argv[]){
int fd,ret;
char *filename;
if (argc != 2){
printf("Error Usage!!!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if (fd < 0){
printf("file %s open failed!!!\r\n",filename);
return -1;
}
while (1){
ret = read(fd,&inputevent,sizeof(inputevent));
if (ret)
{
switch (inputevent.type){
case EV_KEY:{
if (inputevent.code < BTN_MISC){ //key
printf("key %d %s\r\n",inputevent.code,inputevent.value ? "pressed" : "released");
}
else{ //BTN
printf("button %d %s\r\n",inputevent.code,inputevent.value ? "pressed" : "released");
}
}
break;
case EV_SYN:
break;
default:
break;
}
}
else{
printf("read failed!!!\r\n");
return -1;
}
}
close(fd);
return 0;
}
3.3实验演示
加载应用程序,按下按键。一直按住会一直打印pressed,松开会打印released。
4.内核自带按键驱动
Linux 内核也自带了 KEY 驱动,如果要使用内核自带的 KEY 驱动的话需要配置 Linux 内核,不过 Linux 内核一般默认已经使能了 KEY 驱动。
1
2
3
4
5 -> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons.config文件,可以看到
CONFIG_KEYBOARD_GPIO
默认打开了。打开drivers/input/keyboard/Makefile,可以看到
经过查找,内核自带的KEY程序是gpio_keys.c。打开文件,可以看到这是一个platform驱动。
其中,
{ .compatible = "gpio-keys", }
,因此我们在设备树中添加对应的节点的时候,其compatible属性必须是“gpio-keys”。
设备树根节点下添加:
1
2
3
4
5
6
7
8
9
10
11
12
13 gpio-keys {
compatible = "gpio-keys";
pintctrl-names = "default";
pintctrl-0 = <&pinctrl_key>;
autorepeat; //autorepeat 表示按键支持连按。
key0 {
abel = "GPIO Key Enter";
linux,code = <KEY_ENTER>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
};
};
- autorepeat 表示按键支持连按。
- ALPHA 开发板 KEY 按键信息,名字设置为“GPIO Key Enter”,这里我们将开发板上的 KEY 按键设置为“EKY_ENTER”这个按键,也就是回车键,效果和键盘上的回车键一样。后面学习 LCD 驱动的时候需要用到此按键。
- KEY 所使用的 IO 为 GPIO1_IO18,一定要检查一下设备树看看此 GPIO 有没有被用到其他外设上,如果有的话要删除掉相关代码!
分别查看event1和0,可以看到1是对应的按键。