Linux Input子系统
一、基本概念
按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。本质属于字符设备。
1. input子系统结构如下:
input 子系统分为 input 驱动层、input 核心层、input 事件处理层,最终给用户空间提供可访问的设备节点。
(1)驱动层
输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
(2)核心层
a.承上启下,为驱动层提供输入设备注册和操作接口;
b.通知事件层对输入事件进行处理。
(3)事件层
主要和用户空间进行交互。
2. input 子系统的所有设备主设备号都为 13,在drivers/input/input.c文件(核心层)中可以看到, 在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可。
二、input驱动编写流程
1.注册 input_dev
input_dev 结构体表示 input设备,此结构体定义在 include/linux/input.h 文件中,结构体中包含了各种事件输入类型,如evbit[BITS_TO_LONGS(EV_CNT)]存放着不同事件对应的值,可选的输入事件类型定义在input/uapi/linux/input.h 文件中,比如常见的输入事件类型有同步事件、按键事件、重复事件等。
注册过程:
a.申请input_dev结构体变量 struct input_dev *input_allocate_device(void)
b.初始化input_dev的事件类型以及事件值。
c.向Linux系统注册input_dev设备 input_register_device(struct input_dev *dev)
d.卸载驱动的时候要注销该设备并释放前面申请的input_dev。
void input_unregister_device(struct input_dev *dev)
void input_free_device(struct input_dev *dev)
2.上报输入事件
首先是 input_event 函数,此函数用于上报指定的事件以及对应的值
void input_event(struct input_dev *dev, //需要上报的 input_devunsigned int type, //上报的事件类型,比如 EV_KEYunsigned int code, //事件码,也就是我们注册的按键值,比如 KEY_0、KEY_1 等等
int value //事件值,比如 1 表示按键按下,0 表示按键松开
)
input_event 函数可以上报所有的事件类型和事件值,Linux 内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了 input_event 函数。
当我们上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束,input_sync 函数本质是上报一个同步事件。
三、实验内容
利用input子系统进行按键输入实验。
1.思路
input子系统在input.h文件中已经注册了字符设备,所以我们在写驱动的时候不需要再注册字符设备了,我们需要做的是从设备树中获取到按键的节点以及gpio、然后初始化gpio为中断模式并申请中断、初始化定时器(按键消抖使用),完成以上操作后,我们再初始化input_dev结构体变量、注册input_dev、设置事件和事件值、注册inpu_dev设备、上报事件。
2.代码
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/input.h>#define IMX6UIRQ_NAME "imx6uirq"
#define IMX6UIRQ_CNT 1
#define KEY_NUM 1
#define KEY0_VALUE 0X01 /* KEY0 按键值 */
#define INVAKEY 0xff
#define KEYINPUT_NAME "keyinput"
/*cmd*/
/*
_IO(type,nr) //没有参数的命令_IOR(type,nr,size) //该命令是从驱动读取数据_IOW(type,nr,size) //该命令是从驱动写入数据_IOWR(type,nr,size) //双向数据传输
*/
// #define CLOSE_CMD _IO(0xef, 1)
// #define OPEN_CMD _IO(0xef, 2)
// #define SETPERIOD_CMD _IOW(0xef, 3, int)
struct keydevice_dev
{int gpio; // IOchar name[10]; // IO nameint irqnum; //中断号unsigned char value; /* 按键对应的键值 */irqreturn_t (*handler)(int, void *); /* 中断服务函数 */
};struct imx6uirq_dev
{dev_t devid; /*设备号*/struct cdev cdev;struct class *class;struct device *device;struct device_node *nd;int major;int minor;struct timer_list timer; //int timeperiod; /* 定时周期,单位为 ms */spinlock_t lock; //自旋锁struct keydevice_dev keydecs[KEY_NUM];atomic_t key_value; /* 有效的按键键值 */atomic_t release_key; /* 标记是否完成一次完成的按键*/unsigned char current_keynum; /* 当前的按键号 */struct input_dev *inputdev; /* input 结构体 */
};
struct imx6uirq_dev imx6uirq;/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
void timer_function(unsigned long arg)
{struct keydevice_dev *keydecs;struct imx6uirq_dev *dev =(struct imx6uirq_dev*)arg;int ret = 0;unsigned char num;unsigned char value;num = dev->current_keynum;keydecs = &dev->keydecs[num];value = gpio_get_value(keydecs->gpio);/* 读取 IO 值 */if(value == 0) /*按键按下*/{// printk("KEY0_PUSH\r\n");/*上报按键值*/input_report_key(dev->inputdev,keydecs->value,1);input_sync(dev->inputdev);}else if(value==1)//释放{// printk("KEY0_RELEASE\r\n");/*上报按键值*/input_report_key(dev->inputdev,keydecs->value,0);input_sync(dev->inputdev);}}/* @description : 中断服务函数,开启定时器,延时 10ms,* 定时器用于按键消抖。* @param - irq : 中断号* @param - dev_id : 设备结构。* @return : 中断执行结果*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;dev->current_keynum = 0;dev->timer.data = (volatile long)dev_id;mod_timer(&imx6uirq.timer,jiffies+msecs_to_jiffies(10));//消抖
// printk("irq handler\r\n");return IRQ_RETVAL(IRQ_HANDLED);
}static int keyirq_init(void)
{int ret = 0;int i = 0;imx6uirq.nd = of_find_node_by_path("/key");if (imx6uirq.nd == NULL){ret = -EINVAL;goto fail_findnd;printk("find node failed");}/* 提取 GPIO */for (i = 0; i < KEY_NUM; i++){ imx6uirq.keydecs[i].gpio = of_get_named_gpio(imx6uirq.nd, "key-gpios", i);if (imx6uirq.keydecs[i].gpio < 0){printk("get gpio %d failed\r\n", i);}printk("imx6uirq.keydecs[%d].gpio = %d",i,imx6uirq.keydecs[i].gpio);}/* 初始化 key 所使用的 IO,并且设置成中断模式 */for (i = 0; i < KEY_NUM; i++){memset(imx6uirq.keydecs[i].name, 0, sizeof(imx6uirq.keydecs[i].name)); //给数组清0,按字节赋值sprintf(imx6uirq.keydecs[i].name, "KEY%d", i); //给数组赋值gpio_request(imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].name); //申请IOgpio_direction_input(imx6uirq.keydecs[i].gpio); //设置为输入模式imx6uirq.keydecs[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i); //获取中断号printk("gpio %d irqnum=%d\r\n", imx6uirq.keydecs[i].gpio, imx6uirq.keydecs[i].irqnum);}/* 申请中断 */imx6uirq.keydecs[0].handler = key0_handler;imx6uirq.keydecs[0].value = KEY_0;/*根据按键的个数申请中断*/for (i = 0; i < KEY_NUM; i++){ret = request_irq(imx6uirq.keydecs[i].irqnum, imx6uirq.keydecs[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,imx6uirq.keydecs[i].name, &imx6uirq);if (ret < 0){printk("irq %d request failed", imx6uirq.keydecs[i].irqnum);ret = -EINVAL;goto fail_request_irq;}}/* 创建定时器 */init_timer(&imx6uirq.timer);imx6uirq.timer.function = timer_function;/*申请input_dev*/imx6uirq.inputdev = input_allocate_device();imx6uirq.inputdev->name = KEYINPUT_NAME;__set_bit(EV_KEY,imx6uirq.inputdev->evbit);/*按键事件*/__set_bit(EV_REP,imx6uirq.inputdev->evbit);/*重复事件*/__set_bit(KEY_0,imx6uirq.inputdev->keybit);/* 初始化 input_dev,设置产生哪些按键 */// imx6uirq.inputdev->evbit[0]=BIT_MASK(EV_KEY)|BIT_MASK(EV_REP);// input_set_capability(imx6uirq.inputdev,EV_KEY,KEY_0);/* 注册输入设备 */ret = input_register_device(imx6uirq.inputdev);if(ret){printk("register failed\r\n");return ret;}return 0;fail_findnd:
fail_request_irq:return ret;
} /*驱动入口函数*/
static int __init imx6uirq_init(void)
{ keyirq_init();return 0;
}/*驱动出口函数*/
static void __exit imx6uirq_exit(void)
{int i = 0;/* 删除定时器 */del_timer_sync(&imx6uirq.timer);/* 释放中断 */for (i = 0; i < KEY_NUM; i++){free_irq(imx6uirq.keydecs[i].irqnum, &imx6uirq);}input_unregister_device(imx6uirq.inputdev);input_free_device(imx6uirq.inputdev);printk("imx6uirq_exit !!!\r\n");
}module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("dongdong");
3.代码分析
4.编写测试APP
1 #include "stdio.h"2 #include "unistd.h"3 #include "sys/types.h"4 #include "sys/stat.h"5 #include "sys/ioctl.h"6 #include "fcntl.h"7 #include "stdlib.h"8 #include "string.h"9 #include <poll.h>
10 #include <sys/select.h>
11 #include <signal.h>
12 #include <fcntl.h>
13 #include <linux/input.h>
14
15 /* 定义一个 input_event 变量,存放输入事件信息 */
16 static struct input_event inputevent;
17 /*
18 * @description : main主程序
19 * @param - argc : argv数组元素个数
20 * @param - argv : 具体参数
21 * @use: ./timerAPP /dev/gpioled
22 * @return : 0 成功;其他 失败
23 */
24 int main(int argc, char *argv[])
25 {
26
27 char *filename;
28 int fd;
29 int ret = 0;
30
31 /*打开文件*/
32 filename = argv[1];
33
34 if (argc != 2) //检查输入参数个数
35 {
36 printf("useage error\r\n");
37 return -1;
38 }
39
40 fd = open(filename, O_RDWR);
41 if (fd < 0)
42 {
43 printf("can't open file %s\r\n", filename);
44 return -1;
45 }
46 /* 循环读取按键值数据! */
47 while (1)
48 {
49 ret = read(fd, &inputevent,sizeof(inputevent));
50 if(ret<0)
51 {
52 printf("读取数据失败\r\n");
53 }
54 else{
55 switch(inputevent.type)
56 {
57 case EV_KEY:
58 if(inputevent.code < BTN_MISC)
59 {
60 printf("key press\r\n");
61 printf("key %d %s\r\n",inputevent.code,inputevent.value ? "press":"release");
62 }
63 else
64 {
65 printf("button %d %s\r\n",inputevent.code,inputevent.value?"press":"release");
66 }
67 break;
68 /* 其他类型的事件,自行处理 */
69 case EV_REL:
70 break;
71 case EV_ABS:
72 break;
73 case EV_MSC:
74 break;
75 case EV_SW:
76 break;
77
78 }
79
80 }
81 }
82 ret = close(fd);
83 if (ret < 0)
84 {
85 printf("file %s close failed!\r\n", argv[1]);
86 return -1;
87 }
88 return 0;
89
90 }
5.实验结果
按下按键与松开按键
从上图实验结果可以看出inpu_dev结构体的成员变量的值,从左到右依次是:
此事件发生的时间(s、us,均为32位)、事件类型(16位)、事件编码(16位)、按键值(32位)
四、也可以用Linux自带的按键驱动
1.make menuconfig配置
-> Device Drivers
-> Input device support
-> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])
-> Keyboards (INPUT_KEYBOARD [=y])
->GPIO Buttons
2.修改设备树文件
可以参考Linux内核文档(Documentation/devicetree/bindings/input/gpio-keys.txt)
参考上述文件修改开发板按键为回车键为LCD实验作准备。
1 gpio-keys {2 compatible = "gpio-keys";3 #address-cells = <1>;4 #size-cells = <0>;5 autorepeat;6 key0 {7 label = "GPIO Key Enter";8 linux,code = <KEY_ENTER>;9 gpios = <&gpio1 18 GPIO_ACTIVE_LOW>;
10 };
11 };
3.最后,实验结果
相关文章:

Linux Input子系统
一、基本概念 按键、鼠标、键盘、触摸屏等都属于输入(input)设备,Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。本质属于字符设备。 1. input子系统结构如下: input 子系统分为 input 驱动层、input 核心层、input 事件处理层&…...

commet与websocket
commet与websocket Comet 前言 Comet是一种用于web的技术,能使服务器能实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,长轮询和iframe流。 实现方式 长轮询 长轮询是在打开一条连接以后保持&…...

python3 简易 http server:实现本地与远程服务器传大文件
在个人目录下创建新文件httpserver.py : vim httpserver.py文件内容为python3代码: # !/usr/bin/env python3 import datetime import email import html import http.server import io import mimetypes import os import posixpath import re import…...

Microsoft Edge 主页启动diy以及常用的扩展、收藏夹的网站
一、Microsoft Edge 主页启动diy 二、常用的扩展 1、去广告:uBlock Origin 2、翻译: 页面翻译:右键就有了,已经内置了划词翻译 3、超级复制 三、收藏夹的网站...

文末送书!谈谈原型模式在JAVA实战开发中的应用(附源码+面试题)
作者主页:Designer 小郑 作者简介:3年JAVA全栈开发经验,专注JAVA技术、系统定制、远程指导,致力于企业数字化转型,CSDN博客专家,蓝桥云课认证讲师。 本文讲解了 Java 设计模式中的原型模式,并给…...

视频汇聚/视频云存储/视频监控管理平台EasyCVR启动时打印starting server:listen tcp,该如何解决?
视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同,可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、H.265自动转码H.264、平台级联等。为了便于用户二次开发、调用与集成,…...

【Linux从入门到精通】通信 | 管道通信(匿名管道 命名管道)
本派你文章主要是对进程通信进行详解。主要内容是介绍 为什么通信、怎么进行通信。其中本篇文章主要讲解的是管道通信。希望本篇文章会对你有所帮助。 文章目录 一、进程通信简单介绍 1、1 什么是进程通信 1、2 为什么要进行通信 1、3 进程通信的方式 二、匿名管道 2、1 什么是…...

实践和项目:解决实际问题时,选择合适的数据结构和算法
文章目录 选择合适的数据结构数组链表栈队列树图哈希表 选择合适的算法实践和项目 🎉欢迎来到数据结构学习专栏~实践和项目:解决实际问题时,选择合适的数据结构和算法 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒🍹✨博客主页:IT…...

上线检查工具(待完善)
根据V11《CEBPM系统上线CheckList》整理而得,适用于V11,DHERP,Oracle和MSSQL数据库,检查内容还不完善。 上图: 1)数据库连接 2)双击[连接别名],可选择历史连接 3)主界面…...

PE文件格式详解
摘要 本文描述了Windows系统的PE文件格式。 PE文件格式简介 PE(Portable Executable)文件格式是一种Windows操作系统下的可执行文件格式。PE文件格式是由Microsoft基于COFF(Common Object File Format)格式所定义的,…...

【Alibaba中间件技术系列】「RocketMQ技术专题」RocketMQ消息发送的全部流程和落盘原理分析
RocketMQ目前在国内应该是比较流行的MQ 了,目前本人也在公司的项目中进行使用和研究,借着这个机会,分析一下RocketMQ 发送一条消息到存储一条消息的过程,这样会对以后大家分析和研究RocketMQ相关的问题有一定的帮助。 分析的总体…...

关于vue首屏加载loading问题
注意:网上搜索出来的都是教你在index.html里面<div id"app"><div class"loading"></div>或者在app.vue Mounte生命周期函数控制app和loading的显示和隐藏,这里会有一个问题,就是js渲染页面需要时间,一…...

数据库性能测试实践:慢查询统计分析
01、慢查询 查看是否开启慢查询 mysql> show variables like %slow%’; 如图所示: 系统变量log_slow_admin_statements 表示是否将慢管理语句例如ANALYZE TABLE和ALTER TABLE等记入慢查询日志启用log_slow_extra系统变量 (从MySQL 8.0.14开始提供&a…...

windows wsl ssh 配置流程 Permission denied (publickey)
wsl ssh连接失败配置流程 1、wsl2 ifconfig的网络ip是虚拟的ip,所以采用wsl1 2、wsl1的安装教程。 3、openssh-server重装 sudo apt-get update sudo apt-get remove openssh-server sudo apt-get install openssh-server4、修改ssh配置文件 sudo vim /etc/ss…...

OpenCV(五):图像颜色空间转换
目录 1.图像颜色空间介绍 RGB 颜色空间 2.HSV 颜色空间 3.RGBA 颜色空间 2.图像数据类型间的互相转换convertTo() 3.不同颜色空间互相转换cvtColor() 4.Android JNI demo 1.图像颜色空间介绍 RGB 颜色空间 RGB 颜色空间是最常见的颜色表示方式之一,其中 R、…...

一图胜千言!数据可视化多维讲解(Python)
数据聚合、汇总和可视化是支撑数据分析领域的三大支柱。长久以来,数据可视化都是一个强有力的工具,被业界广泛使用,却受限于 2 维。在本文中,作者将探索一些有效的多维数据可视化策略(范围从 1 维到 6 维)。…...

Hbase相关总结
Hbase 1、Hbase的数据写入流程 由客户端发起写入数据的请求, 首先会先连接zookeeper 从zookeeper中获取到当前HMaster的信息,并与HMaster建立连接从HMaster中获取RegionServer列表信息 连接meta表对应的RegionServer地址, 从meta表获取当前要写入的表对应region被那个RegionS…...

C++ Primer Plus第二章编程练习答案
答案仅供参考,实际运行效果取决于运行平台和运行软件 1.编写一个C程序,它显示您的姓名和地址。 #include <iostream> using namespace std;int main() {cout << "My name is sakuraaa0908 C Primer Plus." << endl;cout &…...

Web后端开发(请求响应)上
请求响应的概述 浏览器(请求)<--------------------------(HTTP协议)---------------------->(响应)Web服务器 请求:获取请求数据 响应:设置响应数据 BS架构:浏览器/服务器架构模式。…...

LeetCode 338. Counting Bits【动态规划,位运算】简单
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...

解释 Git 的基本概念和使用方式。
Git 是一种分布式版本控制系统,它可以跟踪文件的修改历史、协调多个人员的工作、将分支合并到一起等。下面是 Git 的一些基本概念和使用方式。 - 仓库(Repository):存储代码、版本控制历史记录等的地方。 - 分支(Bran…...

计算机网络初识
目录 1、计算机网络背景 网络发展 认识 "协议" 2、网络协议初识 OSI七层模型 TCP/IP五层(或四层)模型 3、网络传输基本流程 网络传输流程图 数据包封装和分用 4、网络中的地址管理 认识IP地址 认识MAC地址 1、计算机网络背景 网络发展 在之前呢&…...

python 笔记(2)——文件、异常、面向对象、装饰器、json
目录 1、文件操作 1-1)打开文件的两种方式: 1-2)文件操作的简单示例: write方法: read方法: readline方法: readlines方法: 2、异常处理 2-1)不会中断程序的异常捕获和处理…...

Meta AI的Nougat能够将数学表达式从PDF文件转换为机器可读文本
大多数科学知识通常以可移植文档格式(PDF)的形式存储,这也是互联网上第二突出的数据格式。然而,从这种格式中提取信息或将其转换为机器可读的文本具有挑战性,尤其是在涉及数学表达式时。 为了解决这个问题,…...

【Python爬虫笔记】爬虫代理IP与访问控制
一、前言 在进行网络爬虫的开发过程中,有许多限制因素阻碍着爬虫程序的正常运行,其中最主要的一点就是反爬虫机制。为了防止爬虫程序在短时间内大量地请求同一个网站,网站管理者会使用一些方式进行限制。这时候,代理IP就是解决方…...

50、Spring WebFlux 的 自动配置 的一些介绍,与 Spring MVC 的一些对比
Spring WebFlux Spring WebFlux 简称 WebFlux ,是 spring5.0 新引入的一个框架。 SpringBoot 同样为 WebFlux 提供了自动配置。 Spring WebFlux 和 Spring MVC 是属于竞争关系,都是框架。在一个项目中两个也可以同时存在。 SpringMVC 是基于 Servlet A…...

【算法专题突破】双指针 - 和为s的两个数字(6)
目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后: 1. 题目解析 题目链接:剑指 Offer 57. 和为s的两个数字 - 力扣(Leetcode) 这道题题目就一句话但是也是有信息可以提取的, 最重要的就是开始的那句话&#…...

Redis7入门概述
✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏…...

SQL sever命名规范
目录 一、标识符 二、表名(Table): 三、字段名(fields): 四、约束(Constraint): 五、索引(Index): 六、存储过程(Stored Proced…...

BCSP-玄子Share-Java框基础_工厂模式/代理模式
三、设计模式 3.1 设计模式简介 软件设计中的三十六计是人们在长期的软件开发中的经验总结是对某些特定问题的经过实践检验的特定解决方法被广泛运用在 Java 框架技术中 3.1.1 设计模式的优点 设计模式是可复用的面向对象软件的基础可以更加简单方便地复用成功的设计和体系…...