目录
一、input子系统简介
二、input驱动API
2.1 input字符设备
2.2 input_dev结构体
2.3 上报输入事件
2.4 input_event结构体
三、代码
3.1 驱动代码
3.2 测试代码
四、平台测试
一、input子系统简介
input子系统是管理输入的子系统,和pinctrl、gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同。
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点:
最左边是最底层的具体设备,比如按键、USB键盘/鼠标等;中间部分是Linux内核空间,分为驱动层、核心层和事件层;最右边是用户空间,所有的输入设备以文件的形式供用户应用程序使用:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:为驱动层提供输入设备注册和操作接口,通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
二、input驱动API
2.1 input字符设备
input核心层会向Linux内核注册一个字符设备——drivers/input/input.c即input输入子系统的核心层:
struct class input_class = {
.name = "input",
.devnode = input_devnode,
};
......
static int __init input_init(void)
{
int err;
err = class_register(&input_class);
if (err) {
pr_err("unable to register input_dev class\n");
return err;
}
err = input_proc_init();
if (err)
goto fail1;
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
INPUT_MAX_CHAR_DEVICES, "input");
if (err) {
pr_err("unable to register char major %d", INPUT_MAJOR);
goto fail2;
}
return 0;
fail2: input_proc_exit();
fail1: class_unregister(&input_class);
return err;
}
其中,以下代码是注册一个input类:
err = class_register(&input_class);
这样系统启动以后就会在/sys/class目录下有一个input子目录:
其中,以下代码是注册一个字符设备:
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input");
主设备号为INPUT_MAJOR INPUT_MAJOR定义在include/uapi/linux/major.h文件中:
#define INPUT_MAJOR 13
因此,input子系统的所有设备主设备号都为13。在使用input子系统处理输入设备时不需要注册字符设备,只需要向系统注册一个input_device结构体即可。
2.2 input_dev结构体
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事件。
其中,keybit表示按键事件使用的位图。Linux内核定义的keybit定义在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 BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
编写input设备驱动则需要先申请一个input_dev结构体变量,使用input_allocate_device函数来申请一个input_dev:
struct input_dev *input_allocate_device(void)
返回值:要申请的input_dev。
如果要释放的input设备则需要使用input_free_device函数来释放掉申请到的input_dev:
void input_free_device(struct input_dev *dev)
dev:要释放的input_dev。
返回值:无。
申请好input_dev后要初始化input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)。input_dev初始化好后则需要使用input_register_device函数向Linux内核注册input_dev:
int input_register_device(struct input_dev *dev)
dev:要注册的input_dev。
返回值:0,input_dev注册成功;负值,input_dev注册失败。
如果要注销input设备则需要使用input_unregister_device函数来注销掉注册到的input_dev:
void input_unregister_device(struct input_dev *dev)
dev:要注销的input_dev 。
返回值:无。
综上,按键功能的input_dev注册流程如下:
struct input_dev *inputdev; /* input结构体变量 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
......
/* 初始化input_dev */
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 */
}
其中,input_set_capability函数表示设置输入设备可以上报的输入事件——该函数一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用该函数来进行设置:
input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
dev:该设备的input_dev结构体变量。
type:设备可以上报的事件类型,即evbit的值。
code:设备可以上报的具体事件,本文即keybit的值。
2.3 上报输入事件
input设备都具有输入功能,但Linux内核并不知道具体的输入值。在向Linux内核注册好input_dev后还需要获取具体的输入值作为输入事件上报给Linux内核。
不同的事件,其上报事件的API函数不同。input_event函数用于上报指定的事件以及对应的值。此函数可以上报所有的事件类型和事件值:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
dev:需要上报的input_dev。
type: 上报的事件类型,如EV_KEY、EV_SYN等等。
code:事件码,如EV_KEY事件中的KEY_0、KEY_1等等。
value:事件值。如EV_KEY事件中0表示按键松开,1表示按键按下,2表示按键连按。
返回值:无。
如果是上报按键事件,则可以使用input_report_key函数,此函数本质即input_event函数:
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
input_event(dev, EV_KEY, code, !!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_mt_sync(struct input_dev *dev)
上报事件后还要使用input_sync函数告知Linux内核input子系统上报结束,该函数本质即上报一个同步事件:
void input_sync(struct input_dev *dev)
dev:需要上报同步事件的input_dev。
返回值:无。
综上,按键的上报事件代码流程如下:
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); /* 同步事件 */
}
2.4 input_event结构体
Linux内核使用input_event结构体(区别于上文的input_event函数!)表示所有的输入事件,定义在include/uapi/linux/input.h文件中:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time:此事件发生时的时间,为timeval结构体类型:
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; /* 微秒 */
};
type: 上报的事件类型,如EV_KEY、EV_SYN等等。
code:事件码。如EV_KEY事件中的KEY_0、KEY_1等等。
value:事件值。如EV_KEY事件中0表示按键松开,1表示按键按下,2表示按键连按。
所有的输入设备最终都按照input_event结构体呈现给用户,用户应用程序可以通过input_event结构体来获取到具体的输入事件或相关的值,比如按键值等。
三、代码
配合Linux驱动开发——(六)按键中断实验讲解。
3.1 驱动代码
在设备结构体里添加input结构体:
struct keyinput_dev{
......
struct input_dev *inputdev; /* input结构体 */
}
在定时器服务函数里更改按键按下和释放代码为:
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); /* 同步事件 */
}
在按键初始化函数里添加:
/* 初始化input_dev */
inputdev = input_allocate_device(); /* 申请input_dev */
inputdev->name = "test_inputdev"; /* 设置input_dev名字 */
/* 设置事件和事件值 */
keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/* 注册输入设备 */
ret = input_register_device(keyinputdev.inputdev);
if (ret) {
printk("register input device failed!\r\n");
return ret;
}
return 0;
在驱动出口函数里添加:
/* 释放input_dev */
input_unregister_device(keyinputdev.inputdev);
input_free_device(keyinputdev.inputdev);
3.2 测试代码
定义一个input_event变量,存放输入事件信息:
static struct input_event inputevent;
更改主函数中while函数的代码为:
while (1) {
err = read(fd, &inputevent, sizeof(inputevent));
if (err > 0) { /* 读取数据成功 */
switch (inputevent.type) {
case EV_KEY:
if (inputevent.code < BTN_MISC) { /* 键盘键值 */
printf("key %d %s\r\n", inputevent.code,
inputevent.value ? "press" : "release");
} else {
printf("button %d %s\r\n", inputevent.code,
inputevent.value ? "press" : "release");
}
break;
/* 其他类型的事件,自行处理 */
case EV_REL:
break;
case EV_ABS:
break;
case EV_MSC:
break;
case EV_SW:
break;
}
} else {
printf("读取数据失败\r\n");
}
}
向Linux内核成功注册input_dev设备后,会在/dev/input目录下生成一个名为“ eventX(X=0….n)”的文件(对应的input设备文件)。使用read函数读取该输入设备文件,读取到的数据(如按键值等等)按照input_event结构体组织起来。获取到输入事件以后(input_event结构体类型)再使用 switch case语句来判断事件类型。
四、平台测试
在加载该驱动模块之前,/dev/input目录下只有以下两个文件:
加载该驱动模块后,/dev/input目录下有以下三个文件:
因此/dev/input/event1即注册的驱动所对应的设备文件。使用测试代码读取/dev/input/event1该文件,然后按下按键,查看获取的输入事件信息:
也可以使用hexdump命令来直接查看/dev/input/event1(input_event结构体类型的)原始事件数据值:
原始事件数据值的含义如下:
编号 | tv_sec | tv_usec | type | code | value |
---|---|---|---|---|---|
0000000 | 52f7 0000 | be6b 0001 | 0001 | 000b | 0001 0000 |
0000010 | 52f7 0000 | be6b 0001 | 0000 | 0000 | 0000 0000 |
0000020 | 52f7 0000 | 451d 0003 | 0001 | 000b | 0000 0000 |
0000030 | 52f7 0000 | 451d 0003 | 0000 | 0000 | 0000 0000 |