IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 137.Linux输入子系统基本概念 -> 正文阅读

[系统运维]137.Linux输入子系统基本概念

0.前言

正点原子Linux驱动笔记,本章不涉及具体代码示例

1.简介

Linux内核专门为输入设备做了一个input子系统框架,用来管理按键,鼠标,触摸屏等输入设备,输入设备本质上还是字符设备,input子系统在内核空间分为驱动层,核心层,事件处理层,最终给用户空间提供可访问的设备节点 ,对于驱动编写者只需要在驱动层上报输入事件即可,比如按键值,坐标值等信息

2.框架

Linux下输入子系统内核空间分为三层,驱动层,核心层,事件处理层

img

  • 上层为事件处理层:主要与用户空间进行交互,内核已经实现
  • 中间为核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理,内核已经实现,主要作用为:构造file_operation(open read ioctl),注册,入口函数,出口函数
  • 驱动层:输入设备具体的驱动程序,比如按键驱动程序,向内核层报告输入内容

3.输入子系统核心层

代码位置:drivers/input/input.c

input核心层会向Linux内核注册一个字符设备,示例代码为:

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;
}

其中class_register(&input_class)会注册一个class类,系统起来之后会在/sys/class目录下有一个input子目录,如图所示

在这里插入图片描述

其中register_chrdev_region函数为注册一个字符设备,主设备号为INPUT_MAJOR

#define INPUT_MAJOR		13

INPUT_MAX_CHAR_DEVICES定义为:

#define INPUT_MAX_CHAR_DEVICES		1024

所以,input子系统所有设备主设备号都是13,最多支持1024个输入设备我们在使用 input 子系统处理输入设备的时候就不需要去注册字符设备了,我们只需要向系统注册一个 input_device 即可

4.输入子系统驱动编写

4.1 基本变量

在输入子系统驱动编写的时候我们只需要注册一个input设备即可,input设备使用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)];
	...
	int (*open)(struct input_dev *dev);
	void (*close)(struct input_dev *dev);
	int (*flush)(struct input_dev *dev, struct file *file);
	int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
	...
	struct device dev;

	struct list_head	h_list;
	struct list_head	node;

	unsigned int num_vals;
	unsigned int max_vals;
	struct input_value *vals;

	bool devres_managed;
};
  • name:输入设备名

  • id:输入设备与事件处理器的匹配信息

  • evbit:指定支持的事件类型

    • 同步事件、按键事件、坐标事件…
  • keybit:指定支持的按键值类型

    • key1、key2…
  • relbit:指定支持相对坐标类型

    • x轴、y轴、z轴、滑轮…
  • absbit:指定支持绝对坐标类型

    • x轴、y轴、z轴、滑轮…

其中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事件,evbit表示不同事件的值,keybit,relbit中存放了不同事件的位图,其中keybit表示按键事件的位图,即按键值,在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
...

我们可以将开发板上的按键值设置为上面中的任何一个,这里我们设置开发板的KEY按键值为KEY_0

4.2 input_dev的注册

1.在编写input设备驱动的时候我们需要使用input_allocate_device 函数来分配初始化一个input_dev结构体,原型为:

struct input_dev *input_allocate_device(void)

参数:无
return:input_dev

2.第2步使用input_register_device()函数来向系统注册一个输入设备

int input_register_device(struct input_dev *dev)

dev:要注册的设备

return: 0:注册成功,负数:注册失败

3.input_unregister_device():向系统注销输入设备

void input_unregister_device(struct input_dev *dev)

dev:要注销的 input_dev

4.input_free_device()函数:释放input_dev设备

void input_free_device(struct input_dev *dev)

dev:要释放的input_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

4.2.1 设置事件值的方法代码

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);
	/************************************************/

	/*********第三种设置事件和事件值的方法***********/
    __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件 */
	__set_bit(EV_REP, inputdev->evbit); /* 重复事件 */
	input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
	/************************************************/
	/* 注册 input_dev */
	input_register_device(inputdev);
	......
	return 0;
 }

input_set_capability()函数:设置产生哪些值

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
	switch (type) {
	case EV_KEY:
		__set_bit(code, dev->keybit);
		break;

	case EV_REL:
		__set_bit(code, dev->relbit);
		break;

	case EV_ABS:
		input_alloc_absinfo(dev);
		if (!dev->absinfo)
			return;

		__set_bit(code, dev->absbit);
		break;
	...
}  

BIT_MASK宏

include/asm-generic/bitops/non-atomic.h

#define BIT_MASK(nr)		(1UL << ((nr) % BITS_PER_LONG))

4.3 上报输入事件

向 Linux 内核注册好 input_dev 以后还不能使用 input 设备, input 设备都是具有输入功能的,但是具体是什么样的输入值 Linux 内核是不知道的,需要将获取到具体的输入值,或者说是输入事件上报给 Linux 内核。比如按键,需要在按键中断处理函数,或者消抖定时器中断函数中将按键值上报给 Linux 内核,这样 Linux 内核才能获取到正确的输入值。不同的事件,其上报事件的 API 函数不同,依次来看一下一些常用的事件上报 API 函数

通用的事件上报接口

void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
  • dev:需要上报的输入设备

  • type:上报的具体输入事件类型

    • 按键输入类型:EV_KEY

    • 坐标输入类型:EV_REL,EV_REP

    • 特殊类型:EV_SYN:为同步事件,通知用户空间的程序接收消息

  • code:记录输入事件类型中的具体事件,也就是我们注册的按键值,比如KEY_0,KEY_1等等

  • value:具体事件的对应值:比如按键按下,value为1,按键松开,value为0

返回值:无

按键事件上报函数: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);
}

绝对坐标事件上报函数:

static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{    
    input_event(dev, EV_REL, code, value);
}

绝对坐标事件上报接口:

static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_ABS, code, value);
}

当上报事件以后需要使用input_sync来告诉Linux内核input子系统上报事件完毕,本质是上报一个同步事件,同步事件上报接口

static inline void input_sync(struct input_dev *dev)
{
	input_event(dev, EV_SYN, SYN_REPORT, 0);
}

综上来看,按键值的上报参考代码为:

/* 用于按键消抖的定时器服务函数 */
 void timer_function(unsigned long arg)
 {
 	unsigned char value;
	value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
	if(value == 0){ /*按下按键 */
	    /*上报按键值1*/
		input_report_key(inputdev, KEY_0, 1); /* 最后一个参数1,按下*/
		input_sync(inputdev); /* 同步事件 */
	} else { /* 按键松开 */
		input_report_key(inputdev, KEY_0, 0); /*最后一个参数 0,松开 */
		input_sync(inputdev); /* 同步事件 */
	}
}

4.4 input_event结构体

Linux 内核使用 input_event 这个结构体来表示所有的输入事件, input_envent 结构体定义在include/uapi/linux/input.h 文件中,结构体内容如下

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

其中time为发生此事件的时间,为 t为 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,表示此次事件为按键事件,此成员变量为 16 位

code:事件码,比如在 EV_KEY 事件中 code 就表示具体的按键码,如:KEY_0,KEY_1
等等这些按键。此成员变量为 16 位

value:比如 EV_KEY 事件中 value 就是按键值,表示按键有没有被按下,如果为 1 的
话说明按键按下,如果为 0 的话说明按键没有被按下或者按键松开了

4.4.1 使用hexdump分析按键信息

比如按键设备对应/dev/input/event1,可以通过hexdump命令查看/dev/input/event1文件,输入命令为:

hexdump /dev/input/event1

在这里插入图片描述

上述就是input_event类型事件的原始值,采用16进制表示,这些原始数据的含义如下

/* 编号 */ /* tv_sec */ /* tv_usec */ /* type */ /* code */ /* value */
0000000 	0c41 0000 	d7cd 000c 		0001 	000b 		0001 0000
0000010 	0c41 0000 	d7cd 000c 		0000 	0000 		0000 0000
0000020 	0c42 0000 	54bb 0000 		0001 	000b 		0000 0000
0000030 	0c42 0000 	54bb 0000 		0000 	0000 		0000 0000

其中type为事件类型:EV_KEY为事件1,EV_SYN为事件0,所以第1行代码EV_KEY事件,第二行代码EV_SYN事件,code为事件编码,即按键编码,KEY_0对应11,十六进制为0xb,所以,第1行代表KEY_0这个按键事件,最后的value就是按键值,1表示按下,0表示释放

#define EV_SYN			0x00
#define EV_KEY			0x01
#define KEY_0			11

所以:

第 1 行,按键(KEY_0)按下事件。
第 2 行, EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。
第 3 行,按键(KEY_0)松开事件。
第 4 行, EV_SYN 同步事件,和第 2 行一样。

5.补充

产生的设备文件

/dev/input/event0

/dev/input/event1

可以通过如下命令查看输入设备和event的对应关系

cat /proc/bus/input/devices
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-23 17:05:14  更:2021-08-23 17:06:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 9:56:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码