input输入子系统
1、input输入子系统
1.1 简介
input子系统是内核针对某一类输入设备的一个框架,键盘、鼠标、按键和触摸屏等设备都属于输入设备。它主要分成了三部分:
- drivers:驱动层,我们主要编写这部分的驱动代码,驱动里只要上报键盘按下、鼠标点击等事件即可,至于这些事件到底有什么用,就是通过接下来的input core层传达给handlers层;
- input core:核心层,作为中间层负责将drivers层上报的事件“转告”给handlers层,起到承上启下的作用;
- handlers:事件层,这部分是纯软件的,不涉及硬件的操作,drivers层上报的事件到底有何作用就是由它来处理。
虽然说我们编写输入子系统的驱动属于drivers层,但是我们还是可以了解一下input core层,它在内核源码drivers/input/input.c里:
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
static int __init input_init(void)
{
err = class_register(&input_class); // 根据input_class的name成员在/sys/class目录下会创建input目录
...
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), // 主设备号INPUT_MAJOR在include/uapi/linux/major.h里定义为13
INPUT_MAX_CHAR_DEVICES, "input"); // 最多支持1024个设备,在同一个文件中定义
...
}
1.2 相关API函数
前面register_chrdev_region注册字符设备的时候已经指定了主设备号major,所以无需像普通字符设备驱动一样分配主设备号等操作,只需要分配一个input_dev结构体并且在设置好之后注册进input输入子系统中即可。相关结构体和API函数如下:
// 结构体定义
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)]; /* 相对坐标 */
... /* ... */
};
// 为新的input_dev分配内存,成功返回结构体指针,失败返回NULL
struct input_dev *input_allocate_device(void);
// 与input_allocate_device相对应,释放input_dev的内存
void input_free_device(struct input_dev *dev);
// 将dev注册到“input core”中
int input_register_device(struct input_dev *dev);
// 与input_register_device相对应,将dev从“input core”中注销
void input_unregister_device(struct input_dev *dev);
// 不同的事件选用不同的上报函数,code是具体的事件选项,比如KEY_1;value是事件的值,1表示按下,0表示松开
void input_report_key(struct input_dev *dev, unsigned int code, int value);
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_sync(struct input_dev *dev);
// 该函数就就是以上input_report_xxx等函数的内部实现
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
其中,事件类型和事件值在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
#define EV_SND 0x12
#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)
/* 部分按键的选项(事件类型的具体事件选项) */
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
...
1.3 使用流程(驱动框架)
struct input_dev *xxx_input_dev;
static irqreturn_t xxx_irq_hander(int irq, void *dev_id)
{
// ... // 读取按键值
if(xxx){
/* 按键按下 */
input_report_key(xxx_input_dev, KEY_0, 1); // 上报方式1
input_sync(xxx_input_dev);
}else{
/* 按键松开 */
input_event(xxx_input_dev, EV_KEY, KEY_A, 0); // 上报方式2
input_sync(xxx_input_dev);
}
return IRQ_HANDLED;
}
static int __init xxx_init(void)
{
xxx_input_dev = input_allocate_device();// 先分配设备
xxx_input_dev->name = "xxx_input_dev";
/* 设置KEY_0的相关配置 */
#if 1 /* 方法1 */
__set_bit(EV_KEY, xxx_input_dev->evbit);// 设置支持的事件类型
__set_bit(EV_REP, xxx_input_dev->evbit);
__set_bit(KEY_0, xxx_input_dev->keybit);// 设置支持具体的事件
#endif
#if 0 /* 方法2 */
xxx_input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
xxx_input_dev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endif
#if 0 /* 方法3 */
xxx_input_dev->evbit[0] |= BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(xxx_input_dev, EV_KEY, KEY_0);
#endif
input_register_device(xxx_input_dev); // 注册到input core
/* 申请中断等等,略 */
}
static void __exit xxx_exit(void)
{
input_unregister_device(xxx_input_dev); // 先注销
input_free_device(xxx_input_dev); // 后释放内存
}
2、驱动示例
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/input.h>
struct key_drv_info{
struct device_node *node;
int gpio;
int irq_num;
struct timer_list timer;
struct input_dev *inputdev;
};
static struct key_drv_info key_drv;
static void key_timer_irq_function(unsigned long data)
{
if(0 == gpio_get_value(key_drv.gpio)){
/* 按键按下 */
input_report_key(key_drv.inputdev, KEY_A, 1); // 上报事件方式1
input_sync(key_drv.inputdev);
}else{
/* 按键松开 */
input_event(key_drv.inputdev, EV_KEY, KEY_A, 0);// 上报事件方式2
input_sync(key_drv.inputdev); // 上报完成需要同步一下
}
}
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
struct key_drv_info *key_dev = dev_id;
mod_timer(&key_dev->timer, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
static int __init key_drv_init(void)
{
key_drv.node = of_find_node_by_path("/interrupt_key");
key_drv.gpio = of_get_named_gpio(key_drv.node, "key-gpio", 0);
if(gpio_request(key_drv.gpio, "key0")){
printk("gpio_request error!\n");
return -1;
}
gpio_direction_input(key_drv.gpio);
key_drv.irq_num = irq_of_parse_and_map(key_drv.node, 0);
if(request_irq(key_drv.irq_num, key_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key0", &key_drv)){
printk("request_irq error!\n");
return -1;
}
init_timer(&key_drv.timer);
key_drv.timer.function = key_timer_irq_function;
key_drv.inputdev = input_allocate_device(); // 分配
key_drv.inputdev->name = "key_input_dev";
__set_bit(EV_KEY, key_drv.inputdev->evbit); // 设置能产生哪类事件
__set_bit(EV_REP, key_drv.inputdev->evbit);
__set_bit(KEY_A, key_drv.inputdev->keybit); // 设置这类型的具体某个事件
if(input_register_device(key_drv.inputdev)){
// 注册
printk("input_register_device error!\n");
return -1;
}
return 0;
}
static void __exit key_drv_exit(void)
{
input_unregister_device(key_drv.inputdev); // 先注销再释放内存
input_free_device(key_drv.inputdev);
del_timer_sync(&key_drv.timer);
free_irq(key_drv.irq_num, &key_drv);
gpio_free(key_drv.gpio);
}
module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");
3、测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/input.h>
#define EV_KEY 0x01
#define KEY_A 30
int main(int argc, char **argv)
{
int fd;
int ret;
unsigned char key_val;
struct input_event key_event;
if(argc != 2){
printf("Usage: ./keyAPP /dev/input/eventx\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if (fd < 0){
printf("Open %s error!\n", argv[1]);
return -1;
}
while (1)
{
ret = read(fd, &key_event, sizeof(key_event));
if(ret < 0){
printf("Read fd=%d error!\n", fd);
return -1;
}else{
if(key_event.type == EV_KEY && key_event.code == KEY_A){
if(key_event.value){
printf("KEY_A press! val=%d\n", key_event.value); // 注:value值不只是1
}else if(key_event.value == 0){
printf("KEY_A release! val=%d\n", key_event.value);
}
}
}
}
return 0;
}
4、测试结果
/drivers # ls -l /dev/input/
total 0
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 event0
crw-rw---- 1 0 0 13, 63 Jan 1 00:00 mice
/drivers #
/drivers # insmod key_input.ko
input: key_input_dev as /devices/virtual/input/input1
/drivers #
/drivers # ls -l /dev/input/
total 0
crw-rw---- 1 0 0 13, 64 Jan 1 00:00 event0
crw-rw---- 1 0 0 13, 65 Jan 1 06:26 event1
crw-rw---- 1 0 0 13, 63 Jan 1 00:00 mice
/drivers #
/drivers # ./keyAPP_input /dev/input/event1
KEY_A press! val=1
KEY_A release! val=0
KEY_A press! val=1
KEY_A release! val=0
KEY_A press! val=1
KEY_A press! val=2
KEY_A press! val=2
KEY_A press! val=2
KEY_A press! val=2
KEY_A press! val=2
KEY_A release! val=0
^C
/drivers # hexdump /dev/input/event1
0000000 5af3 0000 e0ef 0008 0001 001e 0001 0000
0000010 5af3 0000 e0ef 0008 0000 0000 0000 0000
0000020 5af3 0000 b5a8 000a 0001 001e 0000 0000
0000030 5af3 0000 b5a8 000a 0000 0000 0000 0000
0000040 5af4 0000 be09 0006 0001 001e 0001 0000
0000050 5af4 0000 be09 0006 0000 0000 0000 0000
0000060 5af4 0000 e0e7 0008 0001 001e 0000 0000
0000070 5af4 0000 e0e7 0008 0000 0000 0000 0000
0000080 5af5 0000 c66c 0002 0001 001e 0001 0000
0000090 5af5 0000 c66c 0002 0000 0000 0000 0000
00000a0 5af5 0000 96f8 0006 0001 001e 0002 0000
00000b0 5af5 0000 96f8 0006 0000 0000 0001 0000
00000c0 5af5 0000 333a 0007 0001 001e 0002 0000
00000d0 5af5 0000 333a 0007 0000 0000 0001 0000
00000e0 5af5 0000 cf74 0007 0001 001e 0002 0000
00000f0 5af5 0000 cf74 0007 0000 0000 0001 0000
0000100 5af5 0000 6bb8 0008 0001 001e 0002 0000
0000110 5af5 0000 6bb8 0008 0000 0000 0001 0000
0000120 5af5 0000 07f8 0009 0001 001e 0002 0000
0000130 5af5 0000 07f8 0009 0000 0000 0001 0000
0000140 5af5 0000 a43a 0009 0001 001e 0002 0000
0000150 5af5 0000 a43a 0009 0000 0000 0001 0000
0000160 5af5 0000 407b 000a 0001 001e 0002 0000
0000170 5af5 0000 407b 000a 0000 0000 0001 0000
0000180 5af5 0000 dcbd 000a 0001 001e 0002 0000
0000190 5af5 0000 dcbd 000a 0000 0000 0001 0000
00001a0 5af5 0000 dcd0 000a 0001 001e 0000 0000
00001b0 5af5 0000 dcd0 000a 0000 0000 0000 0000
从上面测试程序可以看到,按下按键时驱动上报的值为1,松开按键时驱动上报的值为0,当按住按键不放的时候上报的值为1,但后面还会上报2表示重复同样的事件。这个也可以从hexdump命令验证,其中这些值的含义如下:
编号 秒 毫秒 type code value
0000000 5af3 0000 e0ef 0008 0001 001e 0001 0000
0000010 5af3 0000 e0ef 0008 0000 0000 0000 0000
0000020 5af3 0000 b5a8 000a 0001 001e 0000 0000
0000030 5af3 0000 b5a8 000a 0000 0000 0000 0000
5、内核自带的input按键驱动
内核自带的input按键驱动在内核源码drivers/input/keyboard/gpio_keys.c里,相应的配置路径为:
Device Drivers --->
Input device support --->
-*- Generic input layer (needed for keyboard, mouse, ...)
[*] Keyboards --->
<*> GPIO Buttons
可以参考内核文档Documentation/devicetree/bindings/input/gpio-keys.txt修改设备树中的节点:
gpio_keys: gpio_keys@0 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
key1@1 {
label = "USER-KEY1";
linux,code = <114>;
gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
};
};
附:对应的设备树节点
arch/arm/boot/dts/imx6ull-14x14-evk.dts:
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
...
interrupt_key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "test-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
status = "okay";
};
...
};