Linux驱动开发——(十一)INPUT子系统

目录

一、input子系统简介

二、input驱动API

[2.1 input字符设备](#2.1 input字符设备)

[2.2 input_dev结构体](#2.2 input_dev结构体)

[2.3 上报输入事件](#2.3 上报输入事件)

[2.4 input_event结构体](#2.4 input_event结构体)

三、代码

[3.1 驱动代码](#3.1 驱动代码)

[3.2 测试代码](#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输入子系统的核心层:

cpp 复制代码
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类:

cpp 复制代码
err = class_register(&input_class);

这样系统启动以后就会在/sys/class目录下有一个input子目录:

其中,以下代码是注册一个字符设备:

cpp 复制代码
err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0), INPUT_MAX_CHAR_DEVICES, "input");

主设备号为INPUT_MAJOR INPUT_MAJOR定义在include/uapi/linux/major.h文件中:

cpp 复制代码
#define INPUT_MAJOR 13

因此,input子系统的所有设备主设备号都为13。在使用input子系统处理输入设备时不需要注册字符设备只需要向系统注册一个input_device结构体即可。

2.2 input_dev结构体

input_dev结构体表示input设备,定义在include/linux/input.h文件中:

cpp 复制代码
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文件中:

cpp 复制代码
#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文件中:

cpp 复制代码
#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:

cpp 复制代码
struct input_dev *input_allocate_device(void)

返回值:要申请的input_dev。

如果要释放的input设备 则需要使用input_free_device函数来释放掉申请到的input_dev:

cpp 复制代码
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

cpp 复制代码
int input_register_device(struct input_dev *dev)

dev:要注册的input_dev。

返回值:0,input_dev注册成功;负值,input_dev注册失败。

如果要注销input设备 则需要使用input_unregister_device函数来注销掉注册到的input_dev:

cpp 复制代码
void input_unregister_device(struct input_dev *dev)

dev:要注销的input_dev 。

返回值:无。

综上,按键功能的input_dev注册流程如下:

cpp 复制代码
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函数表示设置输入设备可以上报的输入事件------该函数一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用该函数来进行设置:

cpp 复制代码
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函数用于上报指定的事件以及对应的值。此函数可以上报所有的事件类型和事件值:

cpp 复制代码
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函数:

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

还有一些其他的事件上报函数,如:

cpp 复制代码
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子系统上报结束,该函数本质即上报一个同步事件

cpp 复制代码
void input_sync(struct input_dev *dev) 

dev:需要上报同步事件的input_dev。

返回值:无。

综上,按键的上报事件代码流程如下:

cpp 复制代码
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文件中:

cpp 复制代码
struct input_event { 
    struct timeval time; 
    __u16 type; 
    __u16 code; 
    __s32 value; 
};

time:此事件发生时的时间,为timeval结构体类型:

cpp 复制代码
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结构体:

cpp 复制代码
struct keyinput_dev{

    ......

    struct input_dev *inputdev; /* input结构体 */
}

在定时器服务函数里更改按键按下和释放代码为:

cpp 复制代码
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); /* 同步事件 */ 
} 

在按键初始化函数里添加:

cpp 复制代码
/* 初始化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; 

在驱动出口函数里添加:

cpp 复制代码
/* 释放input_dev */ 
input_unregister_device(keyinputdev.inputdev); 
input_free_device(keyinputdev.inputdev);

3.2 测试代码

定义一个input_event变量,存放输入事件信息:

cpp 复制代码
static struct input_event inputevent;

更改主函数中while函数的代码为:

cpp 复制代码
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
相关推荐
朝九晚五ฺ34 分钟前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
自由的dream37 分钟前
Linux的桌面
linux
xiaozhiwise1 小时前
Makefile 之 自动化变量
linux
意疏3 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu4 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿4 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
我的K84095 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900435 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo5 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器