Linux INPUT 子系统实验-基于正点原子IMX6ULL开发板

按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备,只是在此基础上套上了 input 框架,用户只需要负责上报输入事件,比如按键值、坐标等信息,input 核心层负责处理这些事件

1 input 子系统

1.1 input 子系统简

input 就是输入的意思,因此 input 子系统就是管理输入的子系统,和 pinctrl、gpio 子系统一样,都是 Linux 内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。为此 input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图所示:

在这里插入图片描述
图中左边就是最底层的具体设备,比如按键、USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边的就是用户空间,所有的输入设备以文件的形式供用户应用程序使用。可以看出 input 子系统用到了我们前面讲解的驱动分层模型,**我们编写驱动程序的时候只需要关注中间的驱动层、核心层和事件层,**这三个层的分工如下:

**驱动层:**输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
**核心层:**承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
**事件层:**主要和用户空间进行交互。

1.2 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 即可。

1、注册 input_dev

在使用 input 子系统的时候我们只需要注册一个 input 设备即可,input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中,定义如下(有省略):

121 struct input_dev {
122 const char *name;
123 const char *phys;
124 const char *uniq;
125 struct input_id id;
126
127 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
128
129 unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; /* 事件类型的位图 */
130 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
131 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */ 
132 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
133 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
134 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
135 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
136 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
137 unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
......
189 bool devres_managed;
190 };

第 129 行,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 事件。

继续回到示例代码 input_dev 结构体中,第 129 行~137 行的 evbit、keybit、relbit 等等都是存放不同事件对应的值。比如我们本章要使用按键事件,因此要用到 keybit,keybit 就是按键事件使用的位图,Linux 内核定义了很多按键值,这些按键值定义在 include/uapi/linux/input.h 文件中,按键值如下:

215 #define KEY_RESERVED 0
216 #define KEY_ESC 1
217 #define KEY_1 2
218 #define KEY_2 3
219 #define KEY_3 4
220 #define KEY_4 5
221 #define KEY_5 6
222 #define KEY_6 7
223 #define KEY_7 8
224 #define KEY_8 9
225 #define KEY_9 10
226 #define KEY_0 11
......
794 #define BTN_TRIGGER_HAPPY39 0x2e6
795 #define BTN_TRIGGER_HAPPY40 0x2e7

我们可以将开发板上的按键值设置为示例代码中的任意一个,比如我们本章实验会将 I.MX6U-ALPHA 开发板上的 KEY 按键值设置为 KEY_0。

在编写 input 设备驱动的时候我们需要先申请一个 input_dev 结构体变量,使用input_allocate_device 函数来申请一个 input_dev,此函数原型如下所示:

struct input_dev *input_allocate_device(void)

函数参数和返回值含义如下:
参数:无。
返回值:申请到的 input_dev。

如果要注销的 input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的input_dev,input_free_device 函数原型如下:

void input_free_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要释放的 input_dev。
返回值:无。

申请好一个 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 注册失败。

同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev,input_unregister_device 函数原型如下:

void input_unregister_device(struct input_dev *dev)

函数参数和返回值含义如下:
dev:要注销的 input_dev 。
返回值:无。

综上所述,input_dev 注册过程如下
①、使用 input_allocate_device 函数申请一个 input_dev。
②、初始化 input_dev 的事件类型以及事件值。
③、使用 input_register_device 函数向 Linux 系统注册前面初始化好的 input_dev。
④、卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,
然后使用 input_free_device 函数释放掉前面申请的 input_dev。

input_dev 注册过程示例代码如下所示:

1 struct input_dev *inputdev; /* input 结构体变量 */
2 
3 /* 驱动入口函数 */
4 static int __init xxx_init(void) 5 { 6 ......
7 inputdev = input_allocate_device(); /* 申请 input_dev */
8 inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */
9 
10 /*********第一种设置事件和事件值的方法***********/
11 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
12 __set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
13 __set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值 */
14 /************************************************/
15 
16 /*********第二种设置事件和事件值的方法***********/
17 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
18 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
BIT_MASK(KEY_0);
19 /************************************************/
20
21 /*********第三种设置事件和事件值的方法***********/
22 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
BIT_MASK(EV_REP);
23 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
24 /************************************************/
25 
26 /* 注册 input_dev */
27 input_register_device(inputdev);
28 ......
29 return 0;
30 }
31
32 /* 驱动出口函数 */
33 static void __exit xxx_exit(void)
34 {
35 input_unregister_device(inputdev); /* 注销 input_dev */
36 input_free_device(inputdev); /* 删除 input_dev */
37 }

第 1 行,定义一个 input_dev 结构体指针变量。

第 4~30 行,驱动入口函数,在此函数中完成 input_dev 的申请、设置、注册等工作。第 7行调用 input_allocate_device 函数申请一个 input_dev。第 10~23 行都是设置 input 设备事件和按键值,这里用了三种方法来设置事件和按键值。第 27 行调用 input_register_device 函数向 Linux内核注册 inputdev。

第 33~37 行,驱动出口函数,第 35 行调用 input_unregister_device 函数注销前面注册的input_dev,第 36 行调用 input_free_device 函数删除前面申请的 input_dev。

2、上报输入事件

当我们向 Linux 内核注册好 input_dev 以后还不能高枕无忧的使用 input 设备,input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,我们需要获取到具体的输入值,或者说是输入事件,然后将输入事件上报给 Linux 内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,我们依次来看一下一些常用的事件上报 API 函数。

首先是 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_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。比如上报按键所使用的input_report_key 函数,此函数内容如下:

static inline void input_report_key(struct input_dev *dev,unsigned int code, int value) 
{
 input_event(dev, EV_KEY, code, !!value);
}

从示例代码 可以看出,input_report_key 函数的本质就是 input_event 函数,如果要上报按键事件的话还是建议大家使用 input_report_key 函数。

同样的还有一些其他的事件上报函数,这些函数如下所示:

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 子系统上报结束,
input_sync 函数本质是上报一个同步事件,此函数原型如下所示:

void input_sync(struct input_dev *dev)

函数参数和返回值含义如下:
dev:需要上报同步事件的 input_dev。
返回值:无。

综上所述,按键的上报事件的参考代码如下所示:

1 /* 用于按键消抖的定时器服务函数 */
2 void timer_function(unsigned long arg) 
3 { 
4 unsigned char value; 
5 
6 value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
7 if(value == 0){ /* 按下按键 */
8 			/* 上报按键值 */
9 			input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1,按下 */
10 			input_sync(inputdev); /* 同步事件 */
11 		} else { /* 按键松开 */
12 		input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0,松开 */
13 		input_sync(inputdev); /* 同步事件 */
14	 } 
15 }

第 6 行,获取按键值,判断按键是否按下。

第 9~10 行,如果按键值为 0 那么表示按键被按下了,如果按键按下的话就要使用input_report_key 函数向 Linux 系统上报按键值,比如向 Linux 系统通知 KEY_0 这个按键按下了。

第 12~13 行,如果按键值为 1 的话就表示按键没有按下,是松开的。向 Linux 系统通知KEY_0 这个按键没有按下或松开了。

1.3 input_event 结构体

Linux 内核使用 input_event 这个结构体来表示所有的输入事件,input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下:

24 struct input_event {
25 struct timeval time;
26 __u16 type;
27 __u16 code;
28 __s32 value;
29 };

我们依次来看一下 input_event 结构体中的各个成员变量:
**time:**时间,也就是此事件发生的时间,为 timeval 结构体类型,timeval 结构体定义如下:

1 typedef long __kernel_long_t; 
2 typedef __kernel_long_t __kernel_time_t; 
3 typedef __kernel_long_t __kernel_suseconds_t; 
4
5 struct timeval { 
6 __kernel_time_t tv_sec; /* 秒 */
7 __kernel_suseconds_t tv_usec; /* 微秒 */
8 };

从示例代码可以看出,tv_sec 和 tv_usec 这两个成员变量都为 long 类型,也就是 32位,这个一定要记住,后面我们分析 event 事件上报数据的时候要用到。

**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 来获取到具体的输入事件或相关的值,比如按键值等。关于 input 子系统就讲解到这里,接下来我们就以开发板上的 KEY0 按键为例,讲解一下如何编写 input 驱动。

2 硬件原理图分析

在这里插入图片描述

3 实验程序编写

3.1 修改设备树文件

	key{
		compatible = "alientek,key";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key>;
		key-gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;  //此处gpio1_io18作为输入,电平可随便输入,可为高,也可为低
		status= "okay";
		interrupt-parent = <&gpio1>;
		interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
	};

3.2 按键 input 驱动程序编写

keyinput.c 驱动文件内容如下所示

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.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/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/input.h>

#define KEYINPUT_CNT 1
#define KEYINPUT_NAME "keyinput"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF

/*key 结构体*/
struct irq_keydesc
{
    int gpio;                            /*io 编号*/
    int irqnum;                          /*中断号*/
    int 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; /*irq设备 */

/*按键中断处理函数*/
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, BTN_0, 1);
        input_sync(dev->inputdev);
    }
    else if (value == 1) /*释放*/
    {
        /*上报按键值*/
        input_event(dev->inputdev, EV_KEY, BTN_0, 0);
        input_sync(dev->inputdev);
    }
}

/*按键初始化*/
static int keyio_init(struct keyinput_dev *dev)
{
    int ret = 0;
    int i = 0;
    /*1,按键初始化*/
    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);
    }

    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);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        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 = BTN_0;
    /*2,按键中断初始化*/
    for (i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[0].handler,
                          IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                          dev->irqkey[i].name, &keyinputdev);
        if (ret)
        {
            printk("irq %d 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_nd:
    return ret;
}

/*驱动入口函数*/
static int __init keyinput_init(void)
{
    int ret = 0;

    /*1、初始化IO*/
    ret = keyio_init(&keyinputdev);
    if (ret < 0)
    {
        goto fail_keyinit;
    }

    /* 2、注册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(BTN_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;
    /*1、释放中断*/
    for (i = 0; i < KEY_NUM; i++)
    {
        free_irq(keyinputdev.irqkey[i].irqnum, &keyinputdev);
    }

    /*2、释放IO*/
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(keyinputdev.irqkey[i].gpio);
    }

    /*3、删除定时器*/
    del_timer(&keyinputdev.timer);

    /*4、注销input_dev*/
    input_unregister_device(keyinputdev.inputdev);
    input_free_device(keyinputdev.inputdev);
}

module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

第一步:在设备结构体中定义一个 input_dev 指针变量。

第二步:使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码(也就是 KEY 模拟成那个按键,这里我们设置为 BTN_0)。

最后使用 input_register_device函数向 Linux 内核注册 input_dev。

第三步:在按键消抖定时器处理函数中上报输入事件,也就是使用 input_event函数上报按键事件以及按键值,最后使用 input_sync 函数上报一个同步事件,这一步一定得做!

第四步:当注销 input 设备驱动的时候使用 input_unregister_device 函数注销掉前面注册的 input_dev,最后使用 input_free_device 函数释放掉前面申请的 input_dev。

3.3 编写测试 APP

keyinputAPP.c 内容如下

#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[]:具体的参数内容,字符串形式
 * ./keyinputAPP <filename> 
 * ./imx6uirqAPP /dev/input/event1
 */

/*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 %s open failed!\r\n", filename);
        return -1;
    }

    printf("while(1)\r\n");
    while (1)
    {
        err = read(fd, &inputevent, sizeof(inputevent));
        if (err > 0)
        { /* 数据读取成功 */
            switch (inputevent.type)
            {
            case EV_KEY:
                if (inputevent.code < BTN_MISC)
                { /*KEY*/
                    printf("key %d %s \r\n", inputevent.code, inputevent.value ? "press" : "release");
                }
                else
                { /*BTN*/
                    printf("key %d %s \r\n", inputevent.code, inputevent.value ? "press" : "release");
                }

                break;
            case EV_SYN:
                break;

            case EV_REL:
                break;

            case EV_ABS:
                break;

            default:
                break;
            }
        }
        else
        {
            printf("读取数据失败\r\n");
        }
    }
    close(fd);
    return 0;
}

第一步:Linux 内核会使用 input_event 结构体来表示输入事件,所以我们要获取按键输入信息,那么必须借助于 input_event 结构体。首先我们定义了一个 inputevent 变量,此变量为 input_event 结构体类型。

第二步:当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件。我们读取这个文件就可以获取到输入事件信息,比如按键值什么的。使用read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。获取到输入事件以(input_event 结构体类型)使用 switch case 语句来判断事件类型,本实验我们设置的事件类型为 EV_KEY,因此只需要处理 EV_KEY 事件即可。比如获取按键编号(KEY_0 的编号为 11)、获取按键状态,按下还是松开的?

4 运行测试

4.1 编译驱动程序和测试 APP

4.2运行测试

将上一小节编译出来 keyinput.ko 和 keyinputAPP 这两个文件拷贝到 rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。在加载驱动之前,我们先看下/dev/input目录下有哪些文件。

在这里插入图片描述
现在加载keyinput.ko,然后再查看 /dev/input/目录下有哪些文件
在这里插入图片描述
从上图看多了一个 event2 文件,因此/dev/input/event2 就是我们注册的驱动所对应的设备文件。

输入如下测试命令,然后按下开发板上的KEY按键,结果如图所示。
在这里插入图片描述
另外,我们也可以不用 keyinputAPP 来测试驱动,可以直接使用 hexdump 命令来查看
/dev/input/event1 文件内容,输入如下命令,然后按下按键,终端输出如下所示信息:
在这里插入图片描述
上图就是 input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

/* 编号 */   /* tv_sec */   /* tv_usec */  /* type */   /* code */       /* value */
0000000     497b 0002     23fa 000a         0001          0100          0001 0000
0000010     497b 0002     23fa 000a         0000          0000          0000 0000
0000020     497b 0002     e328 000c         0001          0100          0000 0000
0000030     497b 0002     e328 000c         0000          0000          0000 0000
0000040     497c 0002     c897 0006         0001          0100          0001 0000
0000050     497c 0002     c897 0006         0000          0000          0000 0000
0000060     497c 0002     9d62 0008         0001          0100          0000 0000

type 为事件类型,EV_KEY 事件值为 1,EV_SYN 事件值为0。因此第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件。code 为事件编码,也就是按键号,BTN_0这个按键编号为 256,对应的十六进制为 0x100,因此第1 行表示 BTN_0这个按键事件,最后的 value 就是按键值,为 1 表示按下,为 0 的话表示松开。

所以,原始数据的含义如下:
第 1 行,按键(BTN_0)按下事件。
第 2 行,EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事
件。
第 3 行,按键(BTN_0)松开事件。
第 4 行,EV_SYN 同步事件,和第 2 行一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值