目录
一、input 子系统简介
input_dev 结构体
二、驱动编写
1、宏定义
2、按键结构体和按键中断结构体
3、中断处理函数
4、定时器处理函数
5、注册input_dev
6、驱动出口
代码
验证
打印现象解析
1、input_event 结构体
2、打印解释
三、APP编写
app代码如下
验证
按键、鼠标、键盘、触摸屏等都属于输入(input)设备, Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息, input 核心层负责处理这些事件
一、input 子系统简介
input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、 gpio 子系统
一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点
中左边就是最底层的具体设备,比如按键、 USB 键盘/鼠标等,
中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,
最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用
可以看出 input 子系统用到了我们前面讲解的驱动分层模型,我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,这三个层的分工如下:
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
input_dev 结构体
表示 input设备,此结构体定义在 include/linux/input.h 文件中
struct input_dev {
2 const char *name;
3 const char *phys;
4 const char *uniq;
5 struct input_id id;
6
7 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
8
9 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
10 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
11 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */
12 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
13 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
14 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
15 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
16 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
17 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
......
18 bool devres_managed;
};
9行, 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 事件。
10行,本章要使用按键事件,因此要用到 keybit, keybit 就是按键事件使用的位图, Linux 内核定义了很多按键值,这些按键值定义在 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
....
任意一个,这里开发板上的 KEY 按键值设置为 KEY_0
二、驱动编写
本章实验在“中断实验”的基础上修改,修改makefiel,添加头文件#include <linux/input.h>
1、宏定义
2、按键结构体和按键中断结构体
在设备key结构体里面定义inputdev
3、中断处理函数
和中断实验的一样,不变
4、定时器处理函数
62-69行,当下按键或者释放的时候,都要上报,input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。这里设置EV_KEY按键事件,按键值选了KEY_0
input_event 函数,此函数用于上报指定的事件以及对应的值,函数原型如下
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 函数本质是上报一个同步事件,此函数原型如下所示:
void input_sync(struct input_dev *dev)
dev:需要上报同步事件的 input_dev。返回值: 无
5、注册input_dev
140行,编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用
input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:
struct input_dev *input_allocate_device(void)
参数:无。返回值: 申请到的 input_dev。
146行,设置 input_dev 名字
152行,设置产生按键事件、重复事件和设置产生哪些按键值.
154行,申请好一个 input_dev 以后就需要初始化这个 input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册input_dev了,需要用到 input_register_device 函数,此函数原型如下:
int input_register_device(struct input_dev *dev)
dev:要注册的 input_dev 。
返回值: 0, input_dev 注册成功;负值, input_dev 注册失败。
6、驱动出口
177-178行,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册
的 input_dev, input_unregister_device 函数原型如下:
void input_unregister_device(struct input_dev *dev)
dev:要注销的 input_dev 。返回值: 无。
注销的 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_dev, input_free_device 函数原型如下:
void input_free_device(struct input_dev *dev)
dev:需要释放的 input_dev。返回值: 无。
代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#define KEYINPUT_CNT 1
#define KEYINPUT_NAME "keyinput"
#define KEY_NUM 1 /* 按键数量 */
#define KEY0VALUE 0X01 /* KEY0按键值 */
#define INVAKEY 0XFF /* 无效的按键值 */
/*key结构体*/
struct irq_keydesc{
int gpio; /*io编号*/
int irqnum; /*中断号*/
unsigned char value; /*键值*/
char name[10]; /*名字*/
irqreturn_t(*handler) (int ,void *); /*中断处理函数*/
};
/*keyinput结构体*/
struct keyinput_dev{
struct device_node *nd;
struct irq_keydesc irqkey[KEY_NUM];
struct timer_list timer;
struct input_dev *inputdev;/*输入设备*/
};
struct keyinput_dev keyinputdev;
/*按键中断处理函数*/
static irqreturn_t key0_handler(int irq,void *dev_id){
struct keyinput_dev * dev = dev_id;
dev->timer.data = (volatile unsigned long)dev_id;
mod_timer(&dev->timer,jiffies + msecs_to_jiffies(20));/*20ms*/
return IRQ_HANDLED;
}
/*定时器处理函数*/
static void timer_func(unsigned long arg){
int value = 0;
struct keyinput_dev *dev = (struct keyinput_dev *)arg;
value = gpio_get_value(dev->irqkey[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 int keyio_init(struct keyinput_dev *dev){
int i,ret =0 ;
/*按鍵初始化*/
dev->nd = of_find_node_by_path("/key");
if(dev->nd == NULL){
ret = -EINVAL;
goto fail_nd;
}
for(i=0;i<KEY_NUM;i++){
dev->irqkey[i].gpio = of_get_named_gpio(dev->nd,"key-gpios",i);
if(dev->irqkey[i].gpio < 0){
ret = -EINVAL;
goto fail_gpio;
}
}
for(i=0;i<KEY_NUM;i++){
memset(dev->irqkey[i].name,0,sizeof(dev->irqkey[i].name));
sprintf(dev->irqkey[i].name,"KEY%d",i);
ret = gpio_request(dev->irqkey[i].gpio , dev->irqkey[i].name);
if(ret){
ret = -EBUSY;
printk("IO %d can't request\r\n",dev->irqkey[i].gpio);
goto fail_request;
}
gpio_direction_input(dev->irqkey[i].gpio);
/*获取中断号*/
dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);
#if 0
dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
}
dev->irqkey[0].handler = key0_handler;
dev->irqkey[0].value = KEY_0;
/*按键中断初始化*/
for(i=0;i<KEY_NUM;i++){
ret = request_irq(dev->irqkey[i].irqnum,dev->irqkey[i].handler,
IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING,
dev->irqkey[i].name,&keyinputdev);
if(ret){
printk("irq %d request failed!\r\n",dev->irqkey[i].irqnum);
goto fail_irq;
}
}
/*初始化定时器*/
init_timer(&keyinputdev.timer);
keyinputdev.timer.function = timer_func;
return 0;
fail_irq:
for(i=0;i<KEY_NUM;i++){
gpio_free(dev->irqkey[i].gpio);
}
fail_request:
fail_gpio:
fail_nd:
return ret;
}
static int __init keyinput_init(void){
int ret = 0;
/*初始化IO*/
ret = keyio_init(&keyinputdev);
if(ret < 0){
goto fail_keyinit;
}
/*注册input_dev*/
keyinputdev.inputdev = input_allocate_device();
if(keyinputdev.inputdev == NULL)
{
ret = -EINVAL;
goto fail_keyinit;
}
keyinputdev.inputdev->name = KEYINPUT_NAME;
/*按键事件*/
__set_bit(EV_KEY , keyinputdev.inputdev->evbit);
/*重复事件*/
__set_bit(EV_REP , keyinputdev.inputdev->evbit);
/*按键值*/
__set_bit(KEY_0 , keyinputdev.inputdev->keybit);
ret = input_register_device(keyinputdev.inputdev);
if(ret){
goto fail_input_register;
}
return 0;
fail_input_register:
input_free_device(keyinputdev.inputdev);
fail_keyinit:
return ret;
}
static void __exit keyinput_exit(void){
int i=0;
/*释放中断*/
for(i=0;i<KEY_NUM;i++){
free_irq(keyinputdev.irqkey[i].irqnum,&keyinputdev);
}
/*释放IO*/
for(i=0;i<KEY_NUM;i++){
gpio_free(keyinputdev.irqkey[i].gpio);
}
/*删除定时器*/
del_timer_sync(&keyinputdev.timer);
/*注销inpu_dev*/
input_unregister_device(keyinputdev.inputdev);
input_free_device(keyinputdev.inputdev);
printk("keyinputdev_exit\r\n");
}
module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");
验证
先查看一下开发板input目录
这里自带event0和mice
加载驱动和卸载驱动
每一次加载,input会自增
加载驱动之后,再次查看input目录
多了一个event1
利用hexdump,接收 显示=原始的按键数据
输入命令之后,按下按键就会打印出数据,一直按着不放一直打印
打印现象解析
先了解input_event 结构体
1、input_event 结构体
Linux 内核使用 input_event 这个结构体来表示所有的输入事件, input_envent 结构体定义在
include/uapi/linux/input.h 文件中,结构体内容如下:
struct input_event {
struct timeval time;
__u16 type;__u16 code;
__s32 value;
};
time:时间,也就是此事件发生的时间,为 timeval 结构体类型, 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; /* 微秒 */
};
可以看出, tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32位
type: 事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。
code: 事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如: KEY_0、 KEY_1等等这些按键。此成员变量为 16 位。
value: 值,比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了
input_envent 这个结构体非常重要,因为所有的输入设备最终都是按照 input_event 结构体
呈现给用户的,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值,比如
按键值等
2、打印解释
抽出部分打印结果,如下图
基本都是对应上面介绍的input_event结构体,type 为事件类型, EV_KEY 按键事件值为 1, EV_SYN 同步事件值为0。因此第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件
code 为事件编码,也就是按键号,KEY_0 这个按键编号为 11,对应的十六进制为 0xb,因此第
1 行表示 KEY_0 这个按键事件
value 就是按键值,为 1 表示按下,为 0 的话表示松开
第 1 行,按键(KEY_0)按下事件。
第 2 行, EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。
第 3 行,按键(KEY_0)松开事件。
第 4 行, EV_SYN 同步事件,和第 2 行一样,后面的都一样
下面编写APP测试
三、APP编写
主要就是定义一个input_event结构体
*input_event结构图变量*/
static struct input_event inputevent;
45行,判断是属于key还是属于btn事件,查看宏定义
可以看到,超过BTN_MISC的就是btn事件,一般key用来表示键盘的,一般都用btn作为按键,这次就不修改了,以后使用btn做按键即可
app代码如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <linux/input.h>
/*
argc:应用程序参数个数(argv数组元素个数)
argv:具体参数,也可以写作char **argv
./keyinputAPP <filename>
./keyinputAPP /dev/input/ecent1
*/
/*input_event结构图变量*/
static struct input_event inputevent;
int main(int argc, char *argv[])
{
int fd,err;
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 open failed\r\n",filename);
return -1;
}
while(1){
err = read(fd,&inputevent,sizeof(inputevent));
if(err>0){/*数据读取成功*/
switch(inputevent.type){
case EV_KEY:
if(inputevent.code < BTN_MISC){
printf("EV_KEY事件,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_SYN:
printf("EV_SYN事件\r\n");
break;
case EV_ABS:
printf("EV_ABS事件");
break;
}
}else{
printf("读取数据失败\r\n");
}
}
/*关闭文件*/
close(fd);
return 0;
}
验证
加载驱动之后,按下按键就会打印对应事件,并且打印按键号以及同步事件