Linux INPUT 子系统实验

目录

一、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 事件处理层,最终给用户空间提供可访问的设备节点

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;
}

验证

加载驱动之后,按下按键就会打印对应事件,并且打印按键号以及同步事件 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值