Linux下input子系统

news2024/11/22 19:18:19

文章目录

  • input子系统简单介绍
  • 相关的函数
  • input_dev注册过程
  • 上报输入事件
  • 按键的input子系统实验

input子系统简单介绍

input子系统是管理输入的子系统,和pinctrl和gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。比如按键输入、键盘、鼠标、触摸屏等这些都属于输入设备,不同的输入设备所代表的含义不同,按键和键盘就是代表按键信息,鼠标和触摸屏代表坐标信息,因此在应用层的处理就不同,对于驱动编写者而言不需要去关心应用层的事情,我们只需要按照要求上报这些输入事件即可。input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点,input子系统框架如下图所示。
在这里插入图片描述
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
input核心层会向Linux内核注册一个字符设备,具体的代码在/drivers/input/input.c文件中,input.c就是input输入子系统的核心层,其中注册了一个input类,系统启动后会在/sys/class目录下有一个input文件。
在这里插入图片描述
input子系统的所有设备主设备号都为13,我们在使用input子系统处理输入设备的时候不需要注册字符设备,只需要向系统注册一个input_dev即可。input_dev结构体定义在/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)];
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

	unsigned int hint_events_per_packet;
	...
};

其中,keybit是按键事件使用的位图,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事件。
Linux内核定义了很多按键值,这些按键值定义在/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
...

可以将开发板上的按键值设置为其中的一个。


相关的函数

编写input设备驱动的时候需要先申请一个input_dev结构体变量, 使用input_allocate_device函数来申请一个input_dev,该函数原型如下。

struct input_dev *input_allocate_device(void)

返回值是申请到的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_unregister_device函数,函数原型如下。

void input_unregister_device(struct input_dev *dev)

注销的input设备需要使用input_free_device函数来释放input_dev,input_free_device函数原型如下。

void input_free_device(struct input_dev *dev)

input_dev注册过程

使用input_allocate_device函数申请一个input_dev;初始化input_dev的事件类型以及事件值;使用input_register_device函数向Linux系统注册前面初始化好的input_dev;卸载input 驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用input_free_device函数释放掉前面申请的input_dev。
input_dev注册过程的大体框架如下。

struct input_dev *inputdev;  //定义input子系统结构体变量

static int __init xxx_init(void)  //驱动入口函数
{
	...
	
	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设备,input设备都是具有输入功能的,但是具体是什么样的输入值Linux内核是不知道的,因此需要获取到具体的输入值或输入事件,然后将其上报给Linux内核。比如按键,我们需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给Linux内核,这样Linux内核才能获取到正确的输入值。
input_event函数用于上报指定的事件以及对应的值,函数原型如下。

void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)

dev是需要上报的input_dev;type是上报的事件类型,比如EV_KEY;code是事件码,即注册的按键值,比如KEY_0、KEY_1等;value表示事件值,比如1表示按键按下,0表示按键松开。
input_event函数可以上报所有的事件类型和事件值,Linux内核也提供了其他的针对具体事件的上报函数, 这些函数其实都用到了input_event函数。比如上报按键所使用的input_report_key函数,此函数内容如下。

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

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

void input_sync(struct input_dev *dev)

input_event结构体定义在/include/uapi/linux/input.h文件中,其内容如下。

struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

time是此事件发生的时间,type是事件类型,code是事件码,value是值。


按键的input子系统实验

输入子系统实验的源代码如下。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h> 
#include <linux/of_irq.h>
#include <linux/input.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/timer.h>

struct device_node	*dev_node;   /* 设备节点 */
struct input_dev *inputdev;  //定义input子系统结构体变量
int key_gpio;			  /* key所使用的GPIO编号*/
int irq;
int count = 0;
int value = 0;
int wq_flags = 0;  //标志位

static void timer_function(unsigned long data);
DEFINE_TIMER(key_timer,timer_function,0,0);     //静态定义结构体变量并且初始化function,expires,data成员
DECLARE_WAIT_QUEUE_HEAD(key_wq);   // 定义并初始化等待队列头

static void timer_function(unsigned long data)
{
	if(count%2 == 1)
	{
		value = 1;
		printk("key_value = %d\r\n",value);
		input_report_key(inputdev,KEY_0,value);  //上报输入事件
		input_sync(inputdev);   //同步
	}
	else
	{
		value = 0;
		printk("key_value = %d\r\n",value);
		input_report_key(inputdev,KEY_0,value);  //上报输入事件
		input_sync(inputdev);   //同步
	}
	printk("key trigger count: %d\r\n",count);
}

static irqreturn_t key_handler(int irq, void *args)
{
	mod_timer(&key_timer,jiffies + msecs_to_jiffies(20));
	++count;
	wq_flags = 1;  //这里只有先置1,下面的函数才能唤醒
	wake_up(&key_wq);  //唤醒
	return IRQ_RETVAL(IRQ_HANDLED);
}

static int input_probe(struct platform_device *pdev)
{
	int ret=0;
	dev_node = of_find_node_by_path("/key");
	if (dev_node == NULL) 
		return -EINVAL;
	printk("Find node key!\n");
	key_gpio = of_get_named_gpio(dev_node,"key-gpio",0);
	if (key_gpio < 0) 
	{
		printk("of_get_named_gpio failed!\r\n");
		return -EINVAL;
	}
	printk("key_gpio = %d\r\n",key_gpio);
	gpio_request(key_gpio,"gpiokey");	    //申请gpio
	gpio_direction_input(key_gpio);   	//将gpio设置为输入

	irq = gpio_to_irq(key_gpio);   //获得gpio中断号
	//irq = irq_of_parse_and_map(dev_node,0);
	printk("irq is %d\n", irq);
	ret = request_irq(irq,key_handler,IRQF_TRIGGER_FALLING,"test_key",NULL);  //申请中断
	if(ret < 0)
	{
		printk("request_irq error!\r\n");
	}
	return 0;
}

static int input_remove(struct platform_device *pdev)
{
	gpio_free(key_gpio);
	free_irq(irq,NULL);
	del_timer(&key_timer);
	return 0;
}

const struct of_device_id of_match_table_key[] = {
	{.compatible = "gpio_bus_key"},         //与设备树中的compatible属性匹配
	{}
};

struct platform_driver dts_device = {    
	.probe = input_probe,
	.remove = input_remove,
	.driver = {
		.owner = THIS_MODULE,
		.name = "keygpio",
		.of_match_table = of_match_table_key
	}
};

static int __init input_key_init(void)
{	
	int ret;
	platform_driver_register(&dts_device); 
	inputdev = input_allocate_device();    // 申请input_dev
	inputdev->name = "key_inputdev";      //设置input_dev名字
	__set_bit(EV_KEY, inputdev->evbit);  //设置产生按键事件
	//__set_bit(EV_REP, inputdev->evbit);  //重复事件
	__set_bit(KEY_0, inputdev->keybit);  //设置产生哪些按键值
	ret = input_register_device(inputdev);  //注册input_dev
	if(ret < 0)
		printk("input_register_device error!\n");
	printk("input_register_device ok!\n");
	return 0;
}

static void __exit input_key_exit(void)
{
	input_unregister_device(inputdev);   //注销input_dev
	input_free_device(inputdev);     //释放input_dev
	platform_driver_unregister(&dts_device);
	printk("driver exit!\n");
}

module_init(input_key_init);
module_exit(input_key_exit);
MODULE_LICENSE("GPL");

经过自己验证,不要在代码中设置EV_REP,因为这样收到的值不太正确,具体的原因自己也没找到。
测试代码的内容如下。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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/event2", O_RDWR);   //这里的"/dev/input/event2"根据开发板上新生成的输入设备添加
    if(fd < 0)
    {
        perror("open key error\n"); 
        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 %d.\n",test_event.code);
            printf("value is %d.\n",test_event.value);
        }
    }
    return 0;
}

上面代码中打开的文件可以通过下图所示的方式得知。
在这里插入图片描述
首先在没有加载驱动的时候看看/dev/input下的文件,然后加载驱动后多出来的那一个就是要打开的。
可以通过命令 cat /proc/bus/input/devices查看驱动加载成功后的输入设备信息,如下图所示
在这里插入图片描述
名字和代码中设置的一致,事件也对应的是驱动加载成功以后多出来的文件。
超级终端打印的信息如下图所示。
在这里插入图片描述
type的类型值为0x1,表示这是一个按键事件。

#define EV_KEY			0x01  //按键事件

code的值是11,这是因为我在代码中使用了KEY_0。

#define KEY_0			11

value的值就是通过输入子系统传来的,定义输入事件结构体读取到传来的值,可以看到,其和key_value的值相等,这就说明传过来的值是正确的。
进一步拓展,input子系统也可以配合LED的驱动来使用,分别加载按键和LED的驱动,然后在测试程序中将读取到的值写入LED中,这样,LED的亮灭就是通过按键控制的。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
    int fd,fd_led;
    struct input_event test_event;
    fd = open("/dev/input/event2", O_RDWR);   //这里的"/dev/input/event2"根据开发板上新生成的输入设备添加

    fd_led = open("/dev/gpioled",O_RDWR); 
    if(fd < 0)
    {
        perror("open key error\n"); 
        return fd;
    }
    if(fd_led < 0)
    {
        perror("open led error\n"); 
        return fd_led;
    }
    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 %d.\n",test_event.code);
            printf("value is %d.\n",test_event.value);
            write(fd_led,&test_event.value,sizeof(test_event.value));  //给led写值
        }
    }
    return 0;
}

运行过程中的打印信息如下图所示。
在这里插入图片描述
可以看到,LED的亮灭状态随着键值的变化而变化,说明确实是按键控制着LED的状态。
总的来说,input子系统的引入不再需要我们再定义字符设备,input核心层会向Linux内核注册一个字符设备。


本文参考文档:
I.MX6U嵌入式Linux驱动开发指南V1.5——正点原子

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

任务1 部署ChatGLM3-6B大模型并进行对话测试

部署ChatGLM3-6B大模型并进行对话测试 0 介绍&#xff1a;1 趋动云项目创建与环境配置1.1 创建项目&#xff1a;1.2 配置环境1.2.1 进入终端1.2.2 设置镜像源1.2.3 克隆项目,并安装依赖 2 修改代码&#xff0c;改路径以及启动代码3 运行代码3.1 运行gradio界面&#xff1a;3.2 …

JAVA毕业设计109—基于Java+Springboot+Vue的宿舍管理系统(源码+数据库)

基于JavaSpringbootVue的宿舍管理系统(源码数据库)109 一、系统介绍 本系统前后端分离 本系统分为学生、宿管、超级管理员三种角色 1、用户&#xff1a; 登录、我的宿舍、申请调宿、报修申请、水电费管理、卫生检查、个人信息修改。 2、宿管&#xff1a; 登录、用户管理…

python实现从字符串中识别出省市区信息

从字符串中识别出省市区的信息分别存储,是我们经常会碰到的问题。如果用分词的方法去匹配获取比较麻烦,cpca包提供了便捷的调用函数transform。只要把含省市区的信息放进去,即可返回标准的含省市区的数据框。    本文详细阐述如何安装cpca包、transform函数参数定义,以及…

迷宫问题的对比实验研究

对不同的迷宫进行算法问题&#xff0c;分别采用栈、队列、基于红黑树的A*算法、以及图论中的最短路径来解决迷宫问题。 基本要求&#xff1a; &#xff08;1&#xff09; 从文件读入9*9的迷宫&#xff0c;设置入口和出口&#xff0c;分别采用以上方法&#xff0c;输出从入口到出…

CMake引用OSG

从CMake执行find_package(OpenSceneGraph REQUIRED COMPONENTS osgDB osgUtil)这句;情况如下; 当前OSG已经安装好;环境变量添加了OSG_ROOT(其值是OSG安装的根目录),并且 %OSG_ROOT%\bin 添加到了path; 有一个警告,已经done了; Found osgDB: optimized;D:/OSGEarth/l…

网络基础扫盲-多路转发

博客内容&#xff1a;多路转发的常见方式select&#xff0c;poll&#xff0c;epoll 文章目录 一、五种IO模型二、多路转发的常见接口1.select2、poll3、epoll 总结 前言 Linux下一切皆文件&#xff0c;是文件就会存在IO的情况&#xff0c;IO的方式决定了效率的高低。 一、五种…

【Solidity】Remix在线环境及钱包申请

好久没有学习区块链方面的知识了&#xff0c;目前通过自学大致掌握了Fabric联盟链的搭建&#xff0c;链码编写、部署&#xff0c;api调用&#xff0c;可以独立开发出一些基于fabric的应用&#xff0c;感觉开发出去中心化的应用还是很有意思的&#xff0c;因为他与之前开发的ssm…

Git 案例(企业如何使用git开发项目)

一、企业中我们是如何开发 1) 入职第一天,管理人员分配/git账号密码 2) 开发人员下载代码即文档/ 根据文档将环境搭建成功 3) 团队一般会给你讲讲项目相关的支持 4) 你接到第一个需求(或者某个功能,一般要经过沟通,分析,设计...等过程) 5) 创建feature分支(一般一个需求对应…

【计算机网络实验/wireshark】tcp建立和释放

wireshark开始捕获后&#xff0c;浏览器打开xg.swjtu.edu.cn&#xff0c;网页传输完成后&#xff0c;关闭浏览器&#xff0c;然后停止报文捕获。 若捕获不到dns报文&#xff0c;先运行ipconfig/flushdns命令清空dns缓存 DNS报文 设置了筛选条件&#xff1a;dns 查询报文目的…

【Java对象】一文读懂 Java 对象庐山真面目及指针压缩

文章目录 版本及工具介绍Java 对象结构对象头mark word 标记字mark word 标记字解析Lock Record class point 类元数据指针 实例数据对齐填充为什么需要对齐填充 常见 Java 数据类型对象分析ArrayListLongStringByteBoolean 其它指针压缩前置知识&#xff1a;32位操作系统为什么…

AI写作软件哪个好?这3个AI写作神器用了都说好!

随着信息时代的快速发展&#xff0c;AI写作早已成为人们创作内容的重要途径之一&#xff0c;在使用AI软件进行创作之前&#xff0c;当然要选择一个优质的写作软件&#xff0c;不过只要你拥有了这3款写作神器&#xff0c;你就能轻松创作出高质量的文章&#xff0c;我们一起来看看…

【uniapp】uview1.x 的 u-upload 上传点击删除隐藏 modal 提示框

uview1.x 版本的 upload 默认在图片成功上传后&#xff0c;再点击右上角删除按钮时会弹出提示框&#xff0c;如图&#xff1a; 但是有时又不需要&#xff0c;想要直接提示删除成功即可&#xff0c;由于官网没有给出点击删除按钮时所调用的钩子函数&#xff0c;又无法操作 DOM&…

【unity小技巧】实现由滑动条控制音量的大小

文章目录 前言开始1.配置BGM2.滑动条3.文本组件4.新增音量控制脚本 完结 前言 这期来一个比较基础的课程&#xff0c;也是比较常用的&#xff0c;unity使用滑动条控制音量的大小 开始 1.配置BGM 2.滑动条 3.文本组件 4.新增音量控制脚本 public class VolumeController : M…

数据库实验:SQL的数据视图

目录 视图概述视图的概念视图的作用 实验目的实验内容实验要求实验过程 视图概述 视图是由数据库中的一个表或多个表导出的虚拟表&#xff0c;其作用是方便用户对数据的操作 视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义。同真实的表一样&#xff0c;视图包含一…

关于ROS的网络通讯方式TCP/UDP

一、TCP与UDP TCP/IP协议族为传输层指明了两个协议&#xff1a;TCP和UDP&#xff0c;它们都是作为应同程序和网络操作的中介物。 **TCP&#xff08;Transmission Control Protocol&#xff09;协议全称是传输控制协议&#xff0c;是一种面向连接的、可靠的、基于字节流的传输…

树莓派4无法进入桌面模式(启动后出现彩色画面,然后一直黑屏,但是可以正常启动和ssh)

本文记录了这段比较坎坷的探索之路&#xff0c;由于你的问题不一定是我最终解决方案的&#xff0c;可能是前面探索路上试过的&#xff0c;所以建议按顺序看排除前置问题。 双十一又买了个树莓派 4B&#xff0c;插上之前树莓派 4B 的 TF 卡直接就能使用&#xff08;毕竟是一样规…

Java 8 新特性 Stream 的使用场景(不定期更新)

方便在写代码的过程中直接使用&#xff0c;好记性不如好文章&#xff0c;直接 CV 改了直接用。提高 办&#xff08;摸&#xff09;公&#xff08;鱼&#xff09;效&#xff08;时&#xff09;率&#xff08;间&#xff09;&#xff0c; 不然就直接问 GPT 也不是说不行。 只符合…

win10 + cmake3.17 编译libpng-1.6.34

需要预先编译zlib库当前的根目录为&#xff1a;D:\Depend_3rd_party\libpngx64\ 1. 下载并解压libpng-1.6.34&#xff0c;得到 D:\Depend_3rd_party\libpngx64\libpng-1.6.34 2. 创建build文件夹&#xff0c;install文件夹&#xff0c;得到 D:\Depend_3rd_party\libpngx64\i…

数据库--数据库约束/聚合查询/分组查询/联合查询

前言 逆水行舟&#xff0c;不进则退&#xff01;&#xff01;&#xff01; 目录 数据库约束 聚合查询 分组查询 联合查询 联合查询---内连接与外连接 补充 联合查询用到的代码 数据库约束 not null 约束&#xff1a;在创建表的时候&#xff0c;可以指定列…

【实验记录】为了混毕业·读读论文叭

PR曲线 1. Robust_Place_Recognition_using_an_Imaging_Lidar 在第三节方法中&#xff0c;提到了一些列处理步骤&#xff0c;分析来与vins相似&#xff0c;在vins中是关键帧检索、特征提取、DBoW查询、描述子匹配、PnP RANSAC求解。 第四节的实验部分&#xff0c;没有绘制pr…