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_dev
 unsigned int type,   //上报的事件类型,比如 EV_KEY
 unsigned 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;                             // IO
    char name[10];                         // IO name
    int 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);       //申请IO
        gpio_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.最后,实验结果

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

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

相关文章

[Linux]进程

文章目录 1. 进程控制1.1 进程概述1.1.1 并行和并发1.1.2 PCB1.1.4 进程状态1.1.5 进程命令 1.2 进程创建1.2.1 函数1.2.2 fork() 剖析 1.3 父子进程1.3.1 进程执行位置1.3.2 循环创建子进程1.3.3 终端显示问题1.3.4 进程数数 1.4 execl和execlp函数1.4.1 execl()1.4.2 execlp(…

什么是浏览器缓存(browser caching)?如何使用HTTP头来控制缓存?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 浏览器缓存和HTTP头控制缓存⭐ HTTP头控制缓存1. Cache-Control2. Expires3. Last-Modified 和 If-Modified-Since4. ETag 和 If-None-Match ⭐ 缓存策略⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击…

攻防世界-web-fakebook

1. 题目描述 打开链接&#xff0c;这样一个界面 貌似没啥特殊的。。。没关系&#xff0c;我们每个页面都点击一下 login页面&#xff1a;一个简单的登录页面 join界面&#xff1a;不出意外&#xff0c;这应该是一个注册界面 目前&#xff0c;我们什么都不做&#xff0c;能获…

Jmeter性能综合实战 —— 签到及批量签到

提取性能测试的三个方面&#xff1a;核心、高频、基础功能 签 到 请 求 步 骤 1、准备工作&#xff1a; 签到线程组n HTTP请求默认值n HTTP cookie 管理器n 首页访问请求n 登录请求n 查看结果树n 调试取样器l HTTP代理服务器 &#xff08;1&#xff09;创建线程组 &#xf…

多线程使用HashMap,HashMap和HashTable和ConcurrentHashMap区别(面试题常考),硬盘IO,顺便回顾volatile

一、回顾&#x1f49b; 谈谈volatile关键字用法 volatile能够保证内存可见性&#xff0c;会强制从主内存中读取数据&#xff0c;此时如果其他线程修改被volatile修饰的变量&#xff0c;可以第一时间读取到最新的值。 二、&#x1f499; HashMap线程不安全没有锁,HashTable线程…

【Kafka】Kafka Stream简单使用

一、实时流式计算 1. 概念 一般流式计算会与批量计算相比较。在流式计算模型中&#xff0c;输入是持续的&#xff0c;可以认为在时间上是无界的&#xff0c;也就意味着&#xff0c;永远拿不到全量数据去做计算。同时&#xff0c;计算结果是持续输出的&#xff0c;也即计算结果…

跳槽面试:如何转换工作场所而不失去优势

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【数学建模】清风数模正课7 多元线性回归模型

多元线性回归分析 回归分析就是&#xff0c;通过研究自变量X和因变量Y的相关关系&#xff0c;来解释Y的形成机制&#xff0c;从而达到通过X去预测Y的目的。 所以回归分析需要完成三个使命&#xff0c;首先是识别重要变量&#xff0c;其次是判断正负相关&#xff0c;最后是估计…

【大数据知识】大数据平台和数据中台的定义、区别以及联系

数据行业有太多数据名词&#xff0c;例如大数据、大数据平台、数据中台、数据仓库等等。但大家很容易混淆&#xff0c;也很容易产生疑问&#xff0c;今天我们就来简单聊聊大数据平台和数据中台的定义、区别以及联系。 大数据平台和数据中台的定义 大数据平台&#xff1a;一个…

Gradio入门(1)输入输出、表格、文本高亮

本文将会介绍gradio的入门使用&#xff0c;并结合大模型&#xff08;LLM&#xff09;&#xff0c;给出三个使用例子。   Gradio 是通过友好的 Web 界面演示机器学习模型的最快方式&#xff0c;以便任何人都可以在任何地方使用它。其官网网址为&#xff1a;https://www.gradio…

手写一个简单爬虫--手刃豆瓣top250排行榜

#拿到页面面源代码 request #通过re来提取想要的有效信息 re import requests import re url"https://movie.douban.com/top250"headers{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/11…

Qt 查找文件夹下指定类型的文件及删除特定文件

一 查找文件 bool MyXML::findFolderFileNames() {//指定文件夹名QDir dir("xml");if(!dir.exists()){qDebug()<<"folder does not exist!";return false;}//指定文件后缀名&#xff0c;可指定多种类型QStringList filter("*.xml");//指定…

蓝蓝设计ui设计公司作品案例-中节能现金流抗压测试软件交互及界面设计

中国节能是以节能环保为主业的中央企业。中国节能以生态文明建设为己任&#xff0c;长期致力于让天更蓝、山更绿、水更清&#xff0c;让生活更美好。经过多年发展&#xff0c;中国节能已构建起以节能、环保、清洁能源、健康和节能环保综合服务为主业的41产业格局&#xff0c;成…

数据结构 day6

1->xmind 2->递归实现程序&#xff1a;输入一个数&#xff0c;输出该数的每一位

lab11 net

background 在开始写代码之前&#xff0c;回顾一下xv6book的第五章会有帮助你将使用E1000去处理网络通信 E1000会和qemu模拟的lan通信在qemu模拟的lan中 xv6的地址是10.0.2.15qemu模拟的计算机的地址是10.0.2.2 qemu会将所有的网络包都记录在packets.pcap中文件kernel/e1000.…

JVM 之字节码(.class)文件

本文中的内容参考B站尚硅谷宋红康JVM全套教程 你将获得&#xff1a; 1、掌握字节码文件的结构 2、掌握Java源代码如何在JVM中执行 3、掌握一些虚拟机指令 4、回答一些面试题 课程介绍 通过几个面试题初始字节码文件为什么学习class字节码文件什么是class字节码文件分析c…

做区块链卡牌游戏有什么好处?

区块链卡牌游戏是一种基于区块链技术的创新性游戏形式&#xff0c;它将传统的卡牌游戏与区块链技术相结合&#xff0c;实现了去中心化、数字化资产的交易和收集。这种新型游戏形式正逐渐在游戏行业引起了广泛的关注和热潮。本文将深入探讨区块链卡牌游戏的定义、特点以及其在未…

App与小程序工具总结

文章目录 前言Burpsuite抓包问题LPosedJustTrustMe 绕过 SSL Pining小程序的反编译APP脱壳&#xff0c;反射大师、frida反射大师Frida 总结 前言 在进行渗透工作的时候&#xff0c;遇到过的App、小程序也不少了&#xff0c;有简单的&#xff0c;也有加固的比较不错的&#xff…

Servlet与过滤器

目录 Servlet 过滤器 Servlet Servlet做了什么 本身不做任何业务处理,只是接收请求并决定调用哪个JavaBean去处理请求,确定用哪个页面来显示处理返回的数据 Servlet是什么 ServerApplet&#xff0c;是一种服务器端的Java应用程序 只有当一个服务器端的程序使用了Servlet…

Ansible学习笔记12

playbook&#xff1a; playbook&#xff08;剧本&#xff09;&#xff1a;是ansible用于配置、部署和管理被控节点的剧本&#xff0c;用于Ansible操作的编排。 使用的是yaml格式&#xff0c;&#xff08;saltstack、elk、docker、docker-compose、k8s都会使用到yaml格式。&am…