Linux驱动学习—输入子系统

1、什么是输入子系统?

输入子系统是Linux专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都是输入设备,但是这邪恶输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。

统一了以后,在节点/dev/input下面则是我们输入设备的节点,如下图所示:

这些节点对应的则是我们当前系统的输入设备,我们要怎么查看当前系统都有哪些输入设备呢?我们可以使用命令来查看:

cat /proc/bus/input/devices

如下图所示:

2、如何确定哪个设备对应那个节点呢?

可以使用hexdump确定,hexdump命令是Linux下查看二进制文本的工具。

举例:如果想确定键盘对应是哪个节点,可以使用命令:

hexdump /dev/input/event0 或者
hexdump /dev/input/event1 或者
hexdump /dev/input/event2 或者
...

输入完一条命令之后,按键盘上的按键,如果有数据打印出来,则证明当前查看的这个节点就是键盘这个设备对应 的节点。

比如,我现在在Ubuntu上输入命令:

hexdump /dev/input/event1

然后按键盘的按键,这时候有打印信息的出现,则证明/dev/input/event1为键盘对应的节点,如下图所示:

3、hedump出来的打印信息都是什么意思呢?

上报的数据要按照具体的格式上百给输入子系统的核心层,我们的应用就可以通过设备节点来获得按照具体格式上报来的数据了。

封装数据是输入子系统的核心层来帮我们完成的,我们只需要按照指定的类型和这个类型对应的数据来上报给输入子系统的核心层即可。

那怎么指定类型呢?这个就需要了解struct input_event这个结构体,这个结构体在include/uapi/linux/input.h,如下所示:

struct input_event {
    struct timeval time;//上报事件的事件
    __u16 type;//类型
    __u16 code;//编码
    __s32 value;//值
};

这里值得注意的是,当type不同的时候,code和value所代表的意义也是不一样的。include/uapi/linux/input-event-codes.h:这个头文件里面找到type的定义,每一个定义都是一个类型,如下所示:

#define EV_SYN          0x00 //同步事件
#define EV_KEY          0x01 //按键事件
#define EV_REL          0x02 //相对坐标事件
#define EV_ABS          0x03 //相对坐标事件
#define EV_MSC          0x04 //杂项(其他)事件
#define EV_SW           0x05 //开关事件

按下按键1:

4、实验:在应用层读取键盘按下的键值

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/input.h>
​
int main(int argc, char *argv[])
{
    int fd;
    struct input_event test_event;
    
    fd = open("dev/input/event1",O_RDWE\R);
    if (fd < 0) {
        perror("open error");
        return fd;
    }
    
    while (1) {
        read(fd, &test_event, sizeof(test_event));
        if (test_event.type == EV_KEY) {
            printf("type is %#x\n", test_event.type);
            printf("value is %#x\n", test_event.value);
        }
    }
    return 0;
}

在ubuntu上编译运行:

按下回车以及长按回车:

5、使用输入子系统设计按键驱动

5.1 申请和释放input_dev结构体的函数

将开发板上的按键值设置为input.h文件中宏定义的任意一个,比如这次实验将开发板上的KEY按键设置为KEY_0。

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

struct input_dev *input_allocate_device(void);
返回值:申请到的input_dev。

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

void input_free_device(struct input_dev *dev);
dev:需要释放的input_dev。

5.2注册及注销input_dev的函数

申请好一个input_dev以后就需要初始化这个input_dev,需要初始化的内容主要为事件类型(evbit)和事件值(keybit)这两种。初始化完成之后就需要向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,函数原型如下:

void input_unregister_device(struct input_dev *dev);
dev:要注销的input_dev。
返回值:无

5.3 事件上报函数

最终我们需要把事件上报上去,上报事件我们使用的函数要针对具体的时间来上报。比如,按键我们使用input_report_key函数。同样的含有一些其他的事件上报函数,函数如下所示:

void input_report_key(struct input_dev *dev, unsigned int code, int 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_sync(struct input_dev *dev);
void input_mt_sync(struct input_dev *dev);

当上报事件以后还需要使用input_sync函数来告诉Linux内核input子系统上报结束,input_sync函数本质上是上报一个同步事件,函数原型如下:

void input_sync(struct input_dev *dev);
dev:需要上报同步事件的input_dev。

5.4 实验驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> 
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/input.h>
​
struct device_node *test_device_node;
struct property *test_node_property;
int gpio_num;
int irq = 0;
struct input_dev *test_dev;
​
static void timer_funtion(ubsigned long data);
​
DEFINE_TIMER(test_timer, timer_funtion, 0, 0);
​
static void timer_funtion(ubsigned long data)
{
    int value;
    value = !gpio_get_value(gpio_num);
    
    input_repo(rt_key(test_dev, KEY_1, value);
    input_sync(test_dev);
}
​
static const of_device_id of_match_table_test[] = {//匹配表
    {.compatible = "keys"},
};
​
static const platform_device_id beep_id_table ={
    .name = "beep_test",
};
​
irqreturn_t test_key(int irq, void *args)
{
    printk("test_key\n");
    test_timer.expires = jiffies + msec_to_jiffies(20);
    add_timer(&test_timer);
    
    return IRQ_HANDLED;
}
​
/*设备树节点compatible属性与of_match_table_test的compatible相匹配就会进入该函数,pdev是匹配成功后传入的设备树节点*/
int beep_probe(struct platform_device *pdev)
{
    int ret = 0;
    printk("beep_probe\n");
    
    //查找要查找的节点
    test_device_node = of_find_node_by_path("/test_key");
    if (test_device_node == NULL) {
        printk("test_device_node find error\n");
        return -1;
    }
    printk("test_device_node name is %s\n",test_device_node->name);//test_key
    
    gpio_num = of_get_named_gpio(test_device_node, "gpios", 0);
    if (gpio_num < 0) {
        printk("of_get_named_gpio error\n");
        return -1;
    }
    printk("gpio_num name is %d\n",gpio_num);
    
    gpio_direction_input(gpio_num);
    //获取中断号
    //irq = gpio_to_irq(gpio_num);
    irq = irq_of_parse_and_map(test_device_node, 0);
    printk("irq is %d\n",irq);
    
    //申请中断
    ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, "test_key", NULL);//
    if (ret < 0) {
        printk("request_irq error\n");
        return -1;
    }
    
    test_dev = input_allocate_device();
    test_dev->name = "test_key";
    __set_bit(EV_KEY, test_dev->ebit);
    __set_bit(KEY_1, test_dev->keybit);
    ret = input_register_device(test_dev);
    if(ret < 0) {
        printk("input_register_device is error");
        goto error_input_register;
    }
    return 0;
    
error_input_register:
    input_unregister_device(test_dev);
}
int beep_remove(struct platform_device *pdev)
{
    pritnk("beep_remove \n");
    return 0;
}
​
strcut platform_driver beep_device = {
    .probe = beep_probe,
    .remove = beep_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name  = "123",
        .of_match_table = of_match_table_test,//匹配表 
    },
    .id_table = &beep_id_table,
};
​
static int beep_driver_init(void)
{
    int ret = -1;
    ret = platform_driver_register(&beep_device);
    if(ret < 0) {
        printk("platform_driver_register error \n");
    }
    printk("platform_driver_register ok\n");
    return 0;
}
​
static void  beep_driver_exit(void)
{
    free_irq(irq, NULL);
    input_unregister_device(test_dev);
    platform_driver_unregister(&beep_device);
    printk("beep_driver_exit \n");
}
​
module_init(beep_driver_init);
module_exit(beep_driver_exit);
MODULE_LICENSE("GPL"); 

编译加载驱动:

查看当前系统输入设备有哪些:

ls可以看到出现新的event3:

再敲这个命令,每按一次按键会打印相应信息:

5.5 应用层程序

#incldue <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>
​
int main(int argc, char *argv[])
{
    int fd; 
    struct input_event test_event;
    fd = open("/dev/input/event3", O_RDWR);
    if (fd < 0) {
        perror("open error");
        return fd;
    }
    
    while (1) {
        read(fd, &test_event, sizeof(test_event));
        if (test_event.type == EV_KEY) {
            printf("type is %#x\n",test_event.type);
            printf("code is %#x\n",test_event.code);
            printf("value is %#x\n",test_event.value);
        }
    }
    return 0;
}

编译app且拷贝到开发板上:

运行app且按下开发板的按键:

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

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

相关文章

CHS_01.2.1.1+2.1.3+进程的概念、组成、特征

CHS_01.2.1.12.1.3进程的概念、组成、特征 进程进程的概念 进程的组成——PCB进程的组成——PCB进程的组成——程序段、数据段知识滚雪球&#xff1a;程序是如何运行的&#xff1f;进程的组成进程的特征 知识回顾与重要考点 从这个小节开始 我们会正式进入第二章处理机管理相关…

【前端】使用javascript开发一个在线RGB颜色转换

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是是《前端》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌…

领英Linkedin自动跳转中国站点的解决方案

linkedin放弃中国市场后&#xff0c;在国内打开linkedin.com&#xff0c;会自动跳转到 linkedin.cn&#xff0c;无法与国际友人在同一个平台上联系。 按照搜到的方法尝试解决&#xff0c;包括修改浏览器默认语言、清除浏览数据、使用软路由上的插件给 linkedin.com设置从国外线…

CentOS本地部署SQL Server数据库无公网ip环境实现远程访问

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

使用 CMake 和 Ninja 构建 C/C++ 项目的教程

使用 CMake 和 Ninja 构建 C/C 项目的教程 CMake 是一个跨平台的开源构建工具&#xff0c;它简化了项目的构建过程。而 Ninja 是一个快速、轻量级的构建系统&#xff0c;与 CMake 配合使用可以提高项目的构建效率。本教程将向你介绍如何使用 CMake 和 Ninja 来构建你的 C/C 项…

版本控制背景知识

版本控制背景知识 本文是关于 Git 系列文章的导读&#xff0c;我们先介绍一下版本控制的背景知识。 什么是版本控制 版本控制是一种记录一个或若干文件内容变化&#xff0c;以便将来查阅特定版本修订情况的系统。它将什么时候、什么人更改了文件的什么内容等信息如实记录下来…

代码随想录算法训练营第一天|数组理论基础、704二分查找、27移除元素

数组理论基础 一维数组 数组中的元素在内存空间中是连续的数组名与数组中第一个元素的地址相同&#xff08;一维数组&#xff09;数组的下标从0开始删除数组的元素其实是用后面的元素覆盖掉要删除的元素数组的长度不能改变 二维数组 二维数组是按照行存储的&#xff0c;也是…

Vue入门四(组件介绍与定义|组件之间的通信)

文章目录 一、组件介绍与定义介绍定义1&#xff09;全局组件2&#xff09;局部组件 二、组件之间的通信1&#xff09;父组件向子组件传递数据2&#xff09;子传父通信 一、组件介绍与定义 介绍 组件(Component)是Vue.js 最强大的功能之一&#xff0c;它是html、css、js等的一个…

STK 特定问题建模(五)频谱分析(第二部分)

文章目录 简介三、链路分析3.1 星地链路干扰分析3.2 频谱分析 简介 本篇对卫星通信中的频谱利用率、潜在干扰对频谱的影响进行分析&#xff0c;以LEO卫星信号对GEO通信链路影响为例&#xff0c;分析星地链路频谱。 建模将从以下几个部分开展&#xff1a; 1、GEO星地通信收发机…

稀疏矩阵的三元组表示----(算法详解)

目录 基本算法包括&#xff1a;&#xff08;解释都在代码里&#xff09; 1.创建 2.对三元组元素赋值 3.将三元组元素赋值给变量 4.输出三元组 5.转置&#xff08;附加的有兴趣可以看看&#xff09; 稀疏矩阵的概念&#xff1a;矩阵的非零元素相较零元素非常小时&#xff…

极少数据就能微调大模型,一文详解LoRA等方法的运作原理

原文&#xff1a;极少数据就能微调大模型&#xff0c;一文详解LoRA等方法的运作原理 最近和大模型一起爆火的&#xff0c;还有大模型的微调方法。 这类方法只用很少的数据&#xff0c;就能让大模型在原本表现没那么好的下游任务中“脱颖而出”&#xff0c;成为这个任务的专家…

大气精美网站APP官网HTML源码

源码介绍 大气精美网站APP官网源码&#xff0c;好看实用&#xff0c;记事本修改里面的内容即可&#xff0c;喜欢的朋友可以拿去研究 下载地址 蓝奏云&#xff1a;https://wfr.lanzout.com/itqxN1ko2ovi CSDN免积分下载&#xff1a;https://download.csdn.net/download/huayu…

大型语言模型与知识图谱的完美结合:从LLMs到RAG,探索知识图谱构建的全新篇章

最近,使用大型语言模型(LLMs)和知识图谱(KG)开发 RAG(Retrieval Augmented Generation)流程引起了很大的关注。在这篇文章中,我将使用 LlamaIndex 和 NebulaGraph 来构建一个关于费城费利斯队(Philadelphia Phillies)的 RAG 流程。 我们用的是开源的 NebulaGraph 来…

redis 主从同步和故障切换的几个坑

数据不一致 当我们从节点读取一个数据时&#xff0c;和主节点读取的数据不一致&#xff0c;这是因为主从同步的命令是异步进行的&#xff0c;一般情况下是主从同步延迟导致的&#xff0c;为什么会延迟&#xff0c; 主要二个原因 1、网络状态不好 2、网络没问题&#xff0c;从节…

电脑找不到d3dcompiler43.dll怎么修复,教你5个可靠的方法

d3dcompiler43.dll是Windows操作系统中的一个重要动态链接库文件&#xff0c;主要负责Direct3D编译器的相关功能。如果“d3dcompiler43.dll丢失”通常会导致游戏无法正常运行或者程序崩溃。为了解决这个问题&#xff0c;我整理了以下五个解决方法&#xff0c;希望能帮助到遇到相…

怎么给IP证书更换IP地址

IP证书是由CA认证机构颁发的一种数字证书&#xff0c;可以为只有公网IP地址的网站提供数据加密服务。事实上&#xff0c;IP证书不仅可以提供加密传输服务&#xff0c;还可以验证网站的身份&#xff0c;保证数据传输的安全性。相对于传统基于域名的SSL证书&#xff0c;IP证书无需…

k8s-----存储卷(数据卷)

容器内的目录和宿主机的目录进行挂载。 容器的生命状态是短站的&#xff0c;delete删除&#xff0c;k8s用控制创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态也会回复到初始状态。 一旦回到初始状态&#xff0c;所有的后天编辑的文件都会消失。 容器和节点之间创…

【设计模式】创建型模式之单例模式(Golang实现)

定义 一个类只允许创建一个对象或实例&#xff0c;而且自行实例化并向整个系统提供该实例&#xff0c;这个类就是一个单例类&#xff0c;它提供全局访问的方法。这种设计模式叫单例设计模式&#xff0c;简称单例模式。 单例模式的要点&#xff1a; 某个类只能有一个实例必须…

vscode配置与注意事项

中文设置 https://zhuanlan.zhihu.com/p/263036716 应用搜索输入“Chinese (Simplified) Language Pack for Visual Studio Code”并敲回车键 底部信息窗没有的话 首先使用快捷键ctrlshiftp&#xff0c;Mac用户使shiftcommandp&#xff0c;然后输入settings.json 将下面的选…

C++其他语法总结

目录 《C基础语法总结》《C面向对象语法总结(一&#xff09;》《C面向对象语法总结(二&#xff09;》《C面向对象语法总结(三&#xff09;》 一、运算符重载 运算符重载可以为运算符增加一些新的功能全局函数、成员函数都支持运算符重载常用的运算符重载示例 class Point {…