Input子系统概述
按键、鼠标、键盘、触摸屏等都属于输入
(input)
设备,
Linux
内核为此专门做了一个叫做
input
子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了
input
框
架,用户只需要负责上报输入事件,比如按键值、坐标等信息,
input
核心层负责处理这些事件。
input
子系统分为
input
驱动 层、input
核心层、
input
事件处理层,最终给用户空间提供可访问的设备节点,
input
子系统框架如图所示:
左边就是最底层的具体设备,比如按键、
USB
键盘
/
鼠标等,中间部分属于
Linux
内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文
件的形式供用户应用程序使用。可以看出
input
子系统用到了我们前面讲解的驱动分层模型,我
们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
Input驱动编写流程
input
核心层会向
Linux
内核注册一个字符设备,找到
drivers/input/input.c
这个文件,
input.c
就是
input
输入子系统的核心层,此文件里面有如下所示代码:
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
子
目录,如图所示:
第
2428~2429
行,注册一个字符设备,主设备号为
INPUT_MAJOR
,
INPUT_MAJOR
定义
在
include/uapi/linux/major.h
文件中,定义如下:
#define INPUT_MAJOR 13
input
子系统的所有设备主设备号都为
13
,我们在使用
input
子系统处理输入设备
的时候就不需要去注册字符设备了,我们只需要向系统注册一个
input_device。
注册 input_dev
。。。
上报输入事件
。。。
input_event 结构体
实验程序编写
修改设备树
使用key按键节点的设备树
按键input驱动程序编写
keyinput.c
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of.h>
12 #include <linux/of_address.h>
13 #include <linux/of_gpio.h>
14 #include <linux/input.h>
15 #include <linux/semaphore.h>
16 #include <linux/timer.h>
17 #include <linux/of_irq.h>
18 #include <linux/irq.h>
19 #include <asm/mach/map.h>
20 #include <asm/uaccess.h>
21 #include <asm/io.h>
32 #define KEYINPUT_CNT 1 /* 设备号个数 */
33 #define KEYINPUT_NAME "keyinput" /* 名字 */
34 #define KEY0VALUE 0X01 /* KEY0 按键值 */
35 #define INVAKEY 0XFF /* 无效的按键值 */
36 #define KEY_NUM 1 /* 按键数量 */
37
38 /* 中断 IO 描述结构体 */
39 struct irq_keydesc {
40 int gpio; /* gpio */
41 int irqnum; /* 中断号 */
42 unsigned char value; /* 按键对应的键值 */
43 char name[10]; /* 名字 */
44 irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
45 };
46
47 /* keyinput 设备结构体 */
48 struct keyinput_dev{
49 dev_t devid; /* 设备号 */
50 struct cdev cdev; /* cdev */
51 struct class *class; /* 类 */
52 struct device *device; /* 设备 */
53 struct device_node *nd; /* 设备节点 */
54 struct timer_list timer; /* 定义一个定时器 */
55 struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */
56 unsigned char curkeynum; /* 当前的按键号 */
57 struct input_dev *inputdev; /* input 结构体 */
58 };
59
60 struct keyinput_dev keyinputdev; /* key input 设备 */
61
62 /* @description : 中断服务函数,开启定时器,延时 10ms,
63 * 定时器用于按键消抖。
64 * @param - irq : 中断号
65 * @param - dev_id : 设备结构。
66 * @return : 中断执行结果
67 */
68 static irqreturn_t key0_handler(int irq, void *dev_id)
69 {
70 struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;
71
72 dev->curkeynum = 0;
73 dev->timer.data = (volatile long)dev_id;
74 mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
75 return IRQ_RETVAL(IRQ_HANDLED);
76 }
77
78 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
79 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
80 * @param - arg : 设备结构变量
81 * @return : 无
82 */
83 void timer_function(unsigned long arg)
84 {
85 unsigned char value;
86 unsigned char num;
87 struct irq_keydesc *keydesc;
88 struct keyinput_dev *dev = (struct keyinput_dev *)arg;
89
90 num = dev->curkeynum;
91 keydesc = &dev->irqkeydesc[num];
92 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
93 if(value == 0){ /* 按下按键 */
94 /* 上报按键值 */
95 //input_event(dev->inputdev, EV_KEY, keydesc->value, 1);
96 input_report_key(dev->inputdev, keydesc->value, 1);/*1,按下*/
97 input_sync(dev->inputdev);
98 } else { /* 按键松开 */
99 //input_event(dev->inputdev, EV_KEY, keydesc->value, 0);
100 input_report_key(dev->inputdev, keydesc->value, 0);
101 input_sync(dev->inputdev);
102 }
103 }
104
105 /*
106 * @description : 按键 IO 初始化
107 * @param : 无
108 * @return : 无
109 */
110 static int keyio_init(void)
111 {
112 unsigned char i = 0;
113 char name[10];
114 int ret = 0;
115
116 keyinputdev.nd = of_find_node_by_path("/key");
117 if (keyinputdev.nd== NULL){
118 printk("key node not find!\r\n");
119 return -EINVAL;
120 }
121
122 /* 提取 GPIO */
123 for (i = 0; i < KEY_NUM; i++) {
124 keyinputdev.irqkeydesc[i].gpio =
of_get_named_gpio(keyinputdev.nd,"key-gpio", i);
125 if (keyinputdev.irqkeydesc[i].gpio < 0) {
126 printk("can't get key%d\r\n", i);
127 }
128 }
129
130 /* 初始化 key 所使用的 IO,并且设置成中断模式 */
131 for (i = 0; i < KEY_NUM; i++) {
132 memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name));
133 sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);
134 gpio_request(keyinputdev.irqkeydesc[i].gpio,
keyinputdev.irqkeydesc[i].name);
135 gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);
136 keyinputdev.irqkeydesc[i].irqnum =irq_of_parse_and_map(keyinputdev.nd, i);
137 }
138 /* 申请中断 */
139 keyinputdev.irqkeydesc[0].handler = key0_handler;
140 keyinputdev.irqkeydesc[0].value = KEY_0;
141
142 for (i = 0; i < KEY_NUM; i++) {
143 ret = request_irq(keyinputdev.irqkeydesc[i].irqnum,
keyinputdev.irqkeydesc[i].handler,
144 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
keyinputdev.irqkeydesc[i].name, &keyinputdev);
145 if(ret < 0){
146 printk("irq %d request failed!\r\n",keyinputdev.irqkeydesc[i].irqnum);
147 return -EFAULT;
148 }
149 }
150
151 /* 创建定时器 */
152 init_timer(&keyinputdev.timer);
153 keyinputdev.timer.function = timer_function;
154
155 /* 申请 input_dev */
156 keyinputdev.inputdev = input_allocate_device();
157 keyinputdev.inputdev->name = KEYINPUT_NAME;
158 #if 0
159 /* 初始化 input_dev,设置产生哪些事件 */
160 __set_bit(EV_KEY, keyinputdev.inputdev->evbit); /*按键事件 */
161 __set_bit(EV_REP, keyinputdev.inputdev->evbit); /* 重复事件 */
162
163 /* 初始化 input_dev,设置产生哪些按键 */
164 __set_bit(KEY_0, keyinputdev.inputdev->keybit);
165 #endif
166
167 #if 0
168 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_ MASK(EV_REP);
169 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=BIT_MASK(KEY_0);
170 #endif
171
172 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
173 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
174
175 /* 注册输入设备 */
176 ret = input_register_device(keyinputdev.inputdev);
177 if (ret) {
178 printk("register input device failed!\r\n");
179 return ret;
180 }
181 return 0;
182 }
183
184 /*
185 * @description : 驱动入口函数
186 * @param : 无
187 * @return : 无
188 */
189 static int __init keyinput_init(void)
190 {
191 keyio_init();
192 return 0;
193 }
194
195 /*
196 * @description : 驱动出口函数
197 * @param : 无
198 * @return : 无
199 */
200 static void __exit keyinput_exit(void)
201 {
202 unsigned int i = 0;
203 /* 删除定时器 */
204 del_timer_sync(&keyinputdev.timer);
205
206 /* 释放中断*/
207 for (i = 0; i < KEY_NUM; i++) {
208 free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);
209 }
210 /* 释放 IO */
211 for (i = 0; i < KEY_NUM; i++) {
212 gpio_free(keyinputdev.irqkeydesc[i].gpio);
213 }
214 /* 释放 input_dev */
215 input_unregister_device(keyinputdev.inputdev);
216 input_free_device(keyinputdev.inputdev);
217 }
218
219 module_init(keyinput_init);
220 module_exit(keyinput_exit);
221 MODULE_LICENSE("GPL");
222 MODULE_AUTHOR("zipeng");
编写测试 APP
keyinputApp.c
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "sys/ioctl.h"
6 #include "fcntl.h"
7 #include "stdlib.h"
8 #include "string.h"
9 #include <poll.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <signal.h>
13 #include <fcntl.h>
14 #include <linux/input.h>
27 /* 定义一个 input_event 变量,存放输入事件信息 */
28 static struct input_event inputevent;
29
30 /*
31 * @description : main 主程序
32 * @param - argc : argv 数组元素个数
33 * @param - argv : 具体参数
34 * @return : 0 成功;其他 失败
35 */
36 int main(int argc, char *argv[])
37 {
38 int fd;
39 int err = 0;
40 char *filename;
41
42 filename = argv[1];
43
44 if(argc != 2) {
45 printf("Error Usage!\r\n");
46 return -1;
47 }
48
49 fd = open(filename, O_RDWR);
50 if (fd < 0) {
51 printf("Can't open file %s\r\n", filename);
52 return -1;
53 }
54
55 while (1) {
56 err = read(fd, &inputevent, sizeof(inputevent));
57 if (err > 0) { /* 读取数据成功 */
58 switch (inputevent.type) {
59 case EV_KEY:
60 if (inputevent.code < BTN_MISC) { /* 键盘键值 */
61 printf("key %d %s\r\n", inputevent.code,
inputevent.value ? "press" : "release");
62 } else {
63 printf("button %d %s\r\n", inputevent.code,
inputevent.value ? "press" : "release");
64 }
65 break;
66
67 /* 其他类型的事件,自行处理 */
68 case EV_REL:
69 break;
70 case EV_ABS:
71 break;
72 case EV_MSC:
73 break;
74 case EV_SW:
75 break;
76 }
77 } else {
78 printf("读取数据失败\r\n");
79 }
80 }
81 return 0;
82 }
运行测试
编译驱动程序
KERNELDIR := /home/zipeng/linux/myKernel/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := keyinput.o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
make -j16
编译测试 APP
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp
运行测试
将前面编译出来 keyinput.ko 和 keyinputApp 这两个文件拷贝到rootfs/lib/modules/4.1.15
目录中,重启开发板,进入到目录
lib/modules/4.1.15
中。在加载
keyinput.ko
驱动模块之前,先
看一下
/dev/input
目录下都有哪些文件,结果如图
所示。
当前
/dev/input
目录只有
event0
和
mice
这两个文件。输入 如下命令加载 keyinput.ko
这个驱动模块。
depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //
加载驱动模块
当驱动模块加载成功以后再来看一下
/dev/input
目录下有哪些文件,结果如图:
输入如下测试命令:
./keyinputApp /dev/input/event1
Linux 自带按键驱动程序的使用
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons