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

目录

一、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_sectv_usectypecodevalue
000000052f7 0000be6b 00010001000b0001 0000
000001052f7 0000be6b 0001000000000000 0000
000002052f7 0000451d 00030001000b0000 0000
000003052f7 0000451d 0003000000000000 0000

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/596198.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

#9松桑前端后花园周刊-React19beta、TS5.5beta、Node22.1.0、const滥用、jsDelivr、douyin-vue

行业动态 Mozilla 提供 Firefox 的 ARM64 Linux二进制文件 此前一直由发行版开发者或其他第三方提供&#xff0c;目前Mozilla提供了nightly版本&#xff0c;正式版仍需要全面测试后再推出。 发布 React 19 Beta 此测试版用于为 React 19 做准备的库。React团队概述React 19…

【driver4】锁,错误码,休眠唤醒,中断,虚拟内存,tasklet

文章目录 1.互斥锁和自旋锁选择&#xff1a;自旋锁&#xff08;开销少&#xff09;的自旋时间和被锁住的代码执行时间成正比关系2.linux错误码&#xff1a;64位系统内核空间最后一页地址为0xfffffffffffff000~0xffffffffffffffff&#xff0c;这段地址是被保留的&#xff0c;如果…

全新桥隧坡安全监测解决方案,24h监测效率提升30%

4月26日&#xff0c;交通运输部党组书记、部长李小鹏在部务会上强调&#xff0c;要高度重视公路桥梁隧道结构监测工作&#xff0c;抓紧推进公路桥梁隧道结构监测系统建设&#xff0c;进一步健全完善公路桥梁隧道结构监测长效运行机制。 中海达积极参与公路桥梁隧道结构监测工作…

触摸OpenNJet,感悟云原生

小程一言 云原生使得应用充分利用云计算、容器化和微服务架构等现代技术来构建和运行应用程序。 云原生技术的用处在于提高应用程序的可靠性、可伸缩性和灵活性&#xff0c;加快开发和部署速度&#xff0c;降低成本&#xff0c;提升整体的效率和竞争力。通过采用云原生技术&a…

Flink窗口理论到实践 | 大数据技术

⭐简单说两句⭐ ✨ 正在努力的小叮当~ &#x1f496; 超级爱分享&#xff0c;分享各种有趣干货&#xff01; &#x1f469;‍&#x1f4bb; 提供&#xff1a;模拟面试 | 简历诊断 | 独家简历模板 &#x1f308; 感谢关注&#xff0c;关注了你就是我的超级粉丝啦&#xff01; &a…

嵌入式学习

笔记 作业 有如下结构体 struct Student{ char name[16]; int age; double math_score; double chinese_score; double english_score; double physics_score; double chemistry…

图片浏览器-PicView

一、前言 PicView 是一款适用于 Windows 10 或 11 的快速高效的图像查看器&#xff0c;配备了干净简洁的用户界面&#xff0c;可以在不需要时方便地隐藏。 二、支持类型 它支持广泛的图像文件类型&#xff0c;包括&#xff1a;WEBP、GIF、SVG、PNG、JXL、HEIC、PSD 三、软件特…

软件设计师-应用技术-数据库设计题2

基础知识及技巧&#xff1a; 1. 数据库设计过程&#xff1a; 四个阶段&#xff1a;需求分析、概念结构设计、逻辑结构设计、物理设计。每个阶段的产物&#xff1a; 需求分析&#xff1a;数据流图、数据字典、需求说明书。概念结构设计&#xff1a;ER模型逻辑机构设计&#xf…

AndroidStudio的Iguana版的使用

1.AndroidStudio介绍 Android Studio 是用于开发 Android 应用的官方集成开发环境 (IDE)。Android Studio 基于 IntelliJ IDEA 强大的代码编辑器和开发者工具&#xff0c;还提供更多可提高 Android 应用构建效率的功能&#xff0c;例如&#xff1a; 基于 Gradle 的灵活构建系统…

esp32+mqtt协议+paltformio+vscode+微信小程序+温湿度检测

花费两天时间完成了这个项目&#xff08;不完全是&#xff0c;属于是在resnet模型训练和温湿度检测两头跑......模型跑不出来&#xff0c;又是第一次从头到尾独立玩硬件&#xff0c;属于是焦头烂额了......&#xff0c;完成这个项目后&#xff0c;我的第一反应是写个csdn&#…

毕设:邮件分发系统

文章目录 前言一、登录1.邮箱登录2.账号登录 二、注册三、首页四、写邮件五、收邮件六、草稿箱七、垃圾箱八、已发送九、通讯录十、用户管理十一、邮件管理十二、登录日志总结 前言 分享一下邮件分发系统 一、登录 1.邮箱登录 2.账号登录 二、注册 三、首页 首页有邮件信息&…

华为ensp中USG6000V防火墙双机热备VRRP+HRP原理及配置

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 华为防火墙双机热备是一种高可用性解决方案&#xff0c;可以将两台防火墙设备组成一个双机热备组&#xff0c;实现主备切换。当主用防火墙出现故障时&…

Linux 第十九章

&#x1f436;博主主页&#xff1a;ᰔᩚ. 一怀明月ꦿ ❤️‍&#x1f525;专栏系列&#xff1a;线性代数&#xff0c;C初学者入门训练&#xff0c;题解C&#xff0c;C的使用文章&#xff0c;「初学」C&#xff0c;linux &#x1f525;座右铭&#xff1a;“不要等到什么都没有了…

【数据可视化-02】Seaborn图形实战宝典

Seaborn介绍 Seaborn是一个基于Python的数据可视化库&#xff0c;它建立在matplotlib的基础之上&#xff0c;为统计数据的可视化提供了高级接口。Seaborn通过简洁美观的默认样式和绘图类型&#xff0c;使数据可视化变得更加简单和直观。它特别适用于那些想要创建具有吸引力且信…

后端接口返回二进制数据流,前端如何将其转换成对应的excel、csv和json文件格式并下载

本文主要是介绍在工作中遇到的后端接口返回一个二进制数据流&#xff0c;前端在界面上创建下载按钮并下载成对应格式的文件导出。 downloadData({start: startTime,end: endTime,exportType: 0, // 0-excel, 1-csv, 2-json }).then((res) > {download(res, startTime, endTi…

Flutter笔记:Widgets Easier组件库 - 使用标签(Tag)

Flutter笔记 Widgets Easier组件库 - 使用标签&#xff08;Tag&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this …

【网络原理】IP协议详解

一.与IP协议相关的基本概念 IP协议&#xff0c;即网际互连协议&#xff08;Internet Protocol&#xff09;&#xff0c;是TCP/IP体系中的核心网络层协议。 网络层IP协议解决的问题 数据传输的过程中,不是直接进行的传输,而是经过层层的封装和分用的过程才能到达对端. IP协议主…

计算机是如何执行指令的

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

19 内核开发-内核源码编译

19 内核开发-内核源码编译 (1)开始准备 安装好virtual box ubuntu 系统后&#xff0c;即可下载内核代码&#xff0c;进行编译 历史内核源码地址&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/ 下载 linux-5.10.102.tar.gz 的包,可以使用wget 命令 创建编译目…

CasaOS玩客云安装memos开源云笔记并实现随时随地远程记笔记

文章目录 前言1. 使用Docker部署memos2. 注册账号与简单操作演示3. 安装cpolar内网穿透4. 创建公网地址5. 创建固定公网地址 前言 本文主要介绍如何在CasaOS玩客云&#xff0c;使用Docker本地部署21.6K stars的热门开源云笔记服务memos&#xff0c;并结合cpolar内网穿透工具打…