Linux之Input子系统

目录

一、Input 子系统简介

二、input 驱动流程

1.注册 input_dev

 2.上报输入事件

三、input应用程序


一、Input 子系统简介

Input 子系统是管理输入的子系统, 和 pinctrl 和 gpio 子系统一样, 都是 Linux 内核针对某一类设备而创建的框架。 input 子系统处理输入事务, 任何输入设备的驱动程序都可以通过 input 输入子系统提供的接口注册到内核, 利用子系统提供的功能来与用户空间交互。

输入设备总类繁杂, 包括按键, 键盘, 触摸屏, 鼠标, 摇杆等, 它们本身是字符设备, 不过内核为了能将这些设备的共性抽象出来, 简化驱动的开发, 建立了一个 Input 子系统。 用户只需要根据内核提供的 input 子系统下提供的 API 函数接口, 完成设备的注册即可。

对于驱动开发者不需要去关心应用层,只需要按照要求上报这些输入事件即可。 input 子系统分为 input 驱动层、 input 核心层、 input 事件处理层,最终给用户空间提供可访问的设备节点,input 子系统框架如图
Linux之Input子系统_第1张图片

左边是最底层的具体设备,比如按键、 USB 键盘/鼠标等,中间部分属于Linux 内核空间,分为驱动层、核心层和事件层,最右边是用户空间,所有的输入设备以文件的形式供用户应用程序使用。

input 子系统用到了驱动分层模型,编写驱动程序的时只需要关注中间的驱动层、核心层和事件层

驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容

核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理,链接其他两个层之间的纽带与桥梁, 向下提供驱动层的接口, 向上提供事件处理层的
接口。

事件层:主要和用户空间进行交互,将硬件驱动层传来的事件报告给用户程序

 各层之间通信的基本单位是事件, 任何一个输入设备的动作都可以抽象成一种事件, 如键盘的按下,触摸屏的按下, 鼠标的移动等。 事件有三种属性: 类型(type), 编码(code),值(value), input 子系统支持的所有事件都定义在 input.h 中, 包括所有支持的类型, 所属类型支持的编码等。 事件传送的方向:硬件驱动层-->子系统核心-->事件处理层-->用户空间。 在节点/dev/input 下面则是输入设备的节点。

二、input 驱动流程

1.注册 input_dev

input 核心层会向 Linux 内核注册一个字符设备,在drivers/input/input.c 这个文件就是输入子系统的核心层源码。

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

注册一个 input 类, 在系统启动后会在/sys/class 目录下生成一个 input 类的子目录。 

Linux之Input子系统_第2张图片

注册一个字符设备,主设备号为 INPUT_MAJOR, 定义在include/uapi/linux/major.h 文件中
 

#define INPUT_MAJOR 13

input 子系统的所有设备主设备号都为 13在使用 input 子系统处理输入设备的时就不需要去注册字符设备,只需要向系统注册一个 input_device 即可

input_dev 结构体表示 input设备,此结构体定义在 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)]; /*LED 相关的位图 */
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];/* sound 有关的位图 */
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; /* 压力反馈的位图 */
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; /*开关状态的位图 */
	......
	bool devres_managed;
};

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 事件类型。

evbit、 keybit 等成员变量, 都是对应的不同事件类型的值。 比如按键事件对应的 keybit 成员, 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
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
......
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7

当编写 input 设备驱动时需要先创建一个 input_dev 结构体变量, 但是不用手动创建, input子系统提供了下面两个函数用于创建和注销 input_dev 结构体变量

struct input_dev *input_allocate_device(void) //申请 input_dev 结构体
void input_free_device(struct input_dev *dev) //注销 input_dev 结构体

input_allocate_device 函数不需要参数, 直接返回申请到的 input_dev 结构体

input_free_device 函数用来释放前面申请到的 input_dev 结构体,申请完 input_dev 结构体后, 需要进行初始化, 根据自己的设备来指定事件类型(evbit)事件值(keybit), 比如按键设备的事件类型是 evbit, 事件值是 keybit。

input_dev结构体初始化完成后, 使用 input_register_device 向 Linux 内核注册 input_dev 设备

int input_register_device(struct input_dev *dev)

dev:要注册的 input_dev 
返回值: 0, input_dev 注册成功;负值, input_dev 注册失败

注销 input 驱动的时需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev

void input_unregister_device(struct input_dev *dev)

dev:要注销的 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 设备事件和按键值,这里用了三种方法来设置事件和按键值。

①使用__set_bit函数

static inline void __set_bit(int nr, volatile unsigned long *addr)

__set_bit :将一个指针指向的数据的第nr位置一,与set_bit()不同,此函数是非原子的,可以重新排序。如果同时调用同一个内存区域,则可能只有一个操作成功。

②直接设置相应的成员变量

③使用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_LED: // LED灯事件
		__set_bit(code, dev->ledbit);
		break;

    .......

	default:
		pr_err("input_set_capability: unknown type %u (code %u)\n",
		       type, code);
		dump_stack();
		return;
	}

	__set_bit(type, dev->evbit);
}

input_set_capability函数用来设置设备的输入类型和设备的键值,最后还是调用了__set_bit。 

input_dev 注册过程:

①使用 input_allocate_device 函数申请一个 input_dev


②初始化 input_dev的事件类型以及事件值


③使用 input_register_device 函数向 Linux 系统注册初始化好的 input_dev

④先使用input_unregister_device函数注销掉注册的input_dev,然后使用 input_free_device 函数释放掉申请的 input_dev

 2.上报输入事件

在 input 设备驱动中申请、 注册完成 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 函数。

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

常用的事件上报函数:

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_mt_sync(struct input_dev *dev)

当上报事件以还需要使用 input_sync 函数来通知Linux 内核 input 子系统上报结束,本质是上报一个同步事件

void input_sync(struct input_dev *dev)

dev:需要上报同步事件的 input_dev

按键的上报事件的参考代码

/* 用于按键消抖的定时器服务函数 */
void timer_function(unsigned long arg)
{
	unsigned char value;

	value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
	if(value == 0) /* 按下按键 */
	{
		/* 上报按键值 */
		input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1, 按下 */
		input_sync(inputdev); /* 同步事件 */
	} 
	else /* 按键松开 */
	{ 
		input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0, 松开 */
		input_sync(inputdev); /* 同步事件 */
	}
}

如果按键值为0表示按键被按下,如果按键按下就要使用input_report_key 函数向 Linux 系统上报按键值,向 Linux 系统通知 KEY_0 这个按键按下。如果按键值为1就表示按键没有按下,是松开的,向 Linux 系统通知KEY_0按键没有按下或松开。

 

三、input应用程序

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

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

time时间,此事件发生的时间,为 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; /* 微秒 */
};

tv_sec 和 tv_usec 这两个成员变量都为 long 类型,32位,event 事件上报数据的时候要用到。

type事件类型,比如 EV_KEY,表示此次事件为按键事件,此成员变量为 16 位。


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


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

所有的输入设备都是按照 input_event 结构体传递给用户,用户应用程序可以通过 input_event 来获取到具体的输入事件或相关的值

在加载驱动模块之后,会在/dev/input 目录下event2文件。


然后运行测试程序:

Linux之Input子系统_第3张图片

当按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。

Linux之Input子系统_第4张图片

 使用 hexdump 命令来查看/dev/input/event1 文件内容

 hexdump /dev/input/event2

Linux之Input子系统_第5张图片

以上就是input_event 类型的原始事件数据值,采用十六进制表示
 Linux之Input子系统_第6张图片

type 为事件类型,EV_KEY 事件值为 1, EV_SYN 事件值为0。第 1 行表示 EV_KEY 事件,第 2 行表示 EV_SYN 事件。

Linux之Input子系统_第7张图片

 

code 为事件编码,按键号, KEY_0 这个按键编号为 11,对应的十六进制为 0xb,第1 行表示 KEY_0 这个按键事件,value 是按键值,为 1 表示按下,为 0 的话表示松开。

 

你可能感兴趣的:(#,Linux驱动开发,驱动开发)