linux内核input子系统概述

目录

  • 一、input子系统
  • 二、关键数据结构和api
    • [2.1 数据结构](#2.1 数据结构)
      • [2.1.1 input_dev](#2.1.1 input_dev)
      • [2.1.2 input_handler](#2.1.2 input_handler)
      • [2.1.3 input_event](#2.1.3 input_event)
      • [2.1.4 input_handle](#2.1.4 input_handle)
    • [2.2 api接口](#2.2 api接口)
      • [2.2.1 input_device 相关接口](#2.2.1 input_device 相关接口)
        • [input_device 注册流程](#input_device 注册流程)
        • 事件上报
      • [2.2.2 input handle 相关接口](#2.2.2 input handle 相关接口)
        • [注册 handle](#注册 handle)
        • [指定 handle](#指定 handle)
      • [2.2.3 input handler 相关接口](#2.2.3 input handler 相关接口)
  • [三、input handler](#三、input handler)
    • [3.1 evdev handler](#3.1 evdev handler)
      • [3.1.1 handler 注册](#3.1.1 handler 注册)
      • [3.1.2 evdev_connect](#3.1.2 evdev_connect)
      • [3.1.3 evdev_events](#3.1.3 evdev_events)
      • [3.1.4 file_operations](#3.1.4 file_operations)
    • [3.2 mousedev handler](#3.2 mousedev handler)

一、input子系统

input子系统处理Linux下的输入事件。

驱动层:输入设备的驱动程序,负责检测和接收输入设备的输入事件,将输入事件上报给核心层;
核心层:提供设备驱动、事件 handler 注册和操作的接口;接收驱动层的输入事件并上报给事件处理层;
事件处理层:通过提供 sysfs 接口等方式和用户空间交互,例如用户空间打开特定设备,当有输入数据时就会上传给用户空间。

input子系统框架结构图(总结来自这里):

input driver 接收到硬件的输入事件 ==> 发送到input core,input core 根据事件类型 ==> 将事件交给对应的input handler处理 ==> input handler 上报用户空间,用户空间收收到事件后进行对应的处理。

二、关键数据结构和api

2.1 数据结构

2.1.1 input_dev

input_dev 描述输入设备,结构体中的多个 bitmap 描述了输入设备的类型和支持的输入事件。这些事件类型相关的宏定义在 input-event-codes.h 头文件中。

c 复制代码
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)];    // 设备支持的事件类型的bitmap
	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)];  // 声音
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];    // 压力反馈事件
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];    // 开关

	unsigned int hint_events_per_packet;

	unsigned int keycodemax;
	unsigned int keycodesize;
	void *keycode;

	int (*setkeycode)(struct input_dev *dev,
			  const struct input_keymap_entry *ke,
			  unsigned int *old_keycode);
	int (*getkeycode)(struct input_dev *dev,
			  struct input_keymap_entry *ke);

	struct ff_device *ff;

	struct input_dev_poller *poller;

	unsigned int repeat_key;
	struct timer_list timer;

	int rep[REP_CNT];

	struct input_mt *mt;

	struct input_absinfo *absinfo;

	unsigned long key[BITS_TO_LONGS(KEY_CNT)];
	unsigned long led[BITS_TO_LONGS(LED_CNT)];
	unsigned long snd[BITS_TO_LONGS(SND_CNT)];
	unsigned long sw[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 input_handle __rcu *grab;

	spinlock_t event_lock;
	struct mutex mutex;

	unsigned int users;
	bool going_away;

	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;

	ktime_t timestamp[INPUT_CLK_MAX];
};

2.1.2 input_handler

input_handler 提供了对一类设备输入事件处理的接口。

c 复制代码
struct input_handler {

	void *private;

	void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	void (*events)(struct input_handle *handle,
		       const struct input_value *vals, unsigned int count);
	bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
	bool (*match)(struct input_handler *handler, struct input_dev *dev);
	int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
	void (*disconnect)(struct input_handle *handle);
	void (*start)(struct input_handle *handle);

	bool legacy_minors;
	int minor;
	const char *name;

	const struct input_device_id *id_table;

	struct list_head	h_list;
	struct list_head	node;
};

以 evdev handler 为例,

connect 接口,通过 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定,并创建对应输入设备的字符设备;
event/events 接口,将设备的输入拷贝到 buffer 中,当用户空间调用字符设备的 read 接口时,就可以从 buffer 中读取输入信息;

2.1.3 input_event

handler 上报事件到用户层的时候,以 input_event 格式进行上报。

c 复制代码
struct input_event {
	struct timeval time;  // 事件发生事件
	__u16 type;           // 事件类型,例如 EV_KEY 按键类型
	__u16 code;           // 事件编码,例如 KEY_0 按键
	__s32 value;          // 事件值
};

2.1.4 input_handle

input_handle 实现将 input_device 和 input_handler 绑定的功能,上面已经介绍到,evdev handler 的 connect 接口中,会调用 input_register_handle 接口,实现将 input_dev 和 input_handler 绑定。

c 复制代码
struct input_handle {

	void *private;

	int open;  // 当前handle是否open
	const char *name;

	struct input_dev *dev;
	struct input_handler *handler;

	struct list_head	d_node;
	struct list_head	h_node;
};

open 记录了当前 handle 是否被 open,以 evdev 为例,当用户空间 open 字符设备的时候,会调用到input_open_device 接口,接口内部实现 input_handle->open++。

2.2 api接口

2.2.1 input_device 相关接口

input核心层提供了如下一系列input device相关的接口,事件input device的注册、事件的上报等功能:

c 复制代码
// 申请
struct input_dev *input_allocate_device(void);
struct input_dev *devm_input_allocate_device(struct device *dev);

// 设置支持的事件类型
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code);

// 注册、注销
int input_register_device(struct input_dev *dev);
void input_unregister_device(struct input_dev *dev);

// 释放
void input_free_device(struct input_dev *dev);

// 事件上报
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value);
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
static inline void input_report_ff_status(struct input_dev *dev, unsigned int code, int value);
static inline void input_report_switch(struct input_dev *dev, unsigned int code, int value);
static inline void input_sync(struct input_dev *dev);  // 同步通知事件发送完成
static inline void input_mt_sync(struct input_dev *dev);
input_device 注册流程

注册接口中主要做了以下动作:

  1. 检查 bitmap 等参数设置是否正确;
  2. 将 device 添加到 input_device_list 链表中;
  3. 对于 input_handler_list 中的每一个 handler,调用 input_attach_handler 接口尝试将 device 和 handler 绑定,在接口内部会检查 device 和 handler 是否 match,match 的话则调用 handler 的 connect 接口完成绑定动作。
c 复制代码
int input_register_device(struct input_dev *dev)
{

    // 检查bitmap等参数、配置input_dev部分参数
	/* Every input device generates EV_SYN/SYN_REPORT events. */
	__set_bit(EV_SYN, dev->evbit);

	/* KEY_RESERVED is not supposed to be transmitted to userspace. */
	__clear_bit(KEY_RESERVED, dev->keybit);

	/* Make sure that bitmasks not mentioned in dev->evbit are clean. */
	input_cleanse_bitmasks(dev);


	dev->max_vals = dev->hint_events_per_packet + 2;
	dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);

    // device_add
	error = device_add(&dev->dev);

    // device 和 handler绑定
	error = mutex_lock_interruptible(&input_mutex);
	list_add_tail(&dev->node, &input_dev_list);

	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	if (dev->devres_managed) {
		dev_dbg(dev->dev.parent, "%s: registering %s with devres.\n",
			__func__, dev_name(&dev->dev));
		devres_add(dev->dev.parent, devres);
	}
	return 0;
}
EXPORT_SYMBOL(input_register_device);
事件上报

input 子系统中封装了针对不同类型事件的上报接口,例如 input_report_key\input_report_abs 等,这些接口实际都是调用 input_event 接口完成事件上报,只不过接口参数中的 type 类型不同,以 input_report_key 为例:

c 复制代码
input_report_key(struct input_dev *dev, unsigned int code, int value)
	-> input_event(dev, EV_KEY, code, !!value);
        -> input_handle_event(dev, type, code, value);
            -> input_get_disposition(dev, type, code, &value); // 获取事件类型
            -> input_pass_values(dev, dev->vals, dev->num_vals);  
                -> input_to_handler(handle, vals, count);
                    -> handler->events(handle, vals, count); // 通知handler处理事件

在 input_handle_event 接口中,会将事件缓存在 dev->vals 中,并记录事件数目到 dev-num_vals,当检测到 dev->num_vals >= dev->max_vals - 2 或者 input_sync 事件时,将所有缓存事件通知 handler 处理。

c 复制代码
static void input_handle_event(struct input_dev *dev,
			       unsigned int type, unsigned int code, int value)
{
    // 获取事件类型
	int disposition = input_get_disposition(dev, type, code, &value);

	if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
		add_input_randomness(type, code, value);

    // 如果 INPUT_PASS_TO_DEVICE并且device实现了event,则通知device
	if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
		dev->event(dev, type, code, value);

	if (!dev->vals)
		return;

    // 记录要通知给handler的事件
	if (disposition & INPUT_PASS_TO_HANDLERS) {
		struct input_value *v;

		if (disposition & INPUT_SLOT) {
			v = &dev->vals[dev->num_vals++];
			v->type = EV_ABS;
			v->code = ABS_MT_SLOT;
			v->value = dev->mt->slot;
		}

		v = &dev->vals[dev->num_vals++];
		v->type = type;
		v->code = code;
		v->value = value;
	}

    // sync事件或者要超出缓存,则将缓存的vals flush到handler
	if (disposition & INPUT_FLUSH) {
		if (dev->num_vals >= 2)
			input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
		dev->timestamp[INPUT_CLK_MONO] = ktime_set(0, 0);
	} else if (dev->num_vals >= dev->max_vals - 2) {
		dev->vals[dev->num_vals++] = input_value_sync;
		input_pass_values(dev, dev->vals, dev->num_vals);
		dev->num_vals = 0;
	}

}

2.2.2 input handle 相关接口

c 复制代码
int input_register_handle(struct input_handle *handle);
void input_unregister_handle(struct input_handle *handle);
注册 handle

input_register_handle 接口实现注册一个input_handle,将 device 和 handler 绑定,例如在 evdev handler 的 connect 接口中,就调用了 input_register_handle 接口。

接口流程:

c 复制代码
int input_register_handle(struct input_handle *handle)
{

    // 将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler
	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);

    // 将 handle->h_node 加入到 handler->h_list,实现遍历handler->h_list就能找到所有关联的input_handler,进而找到input_device
	list_add_tail_rcu(&handle->h_node, &handler->h_list);

	if (handler->start)
		handler->start(handle);

	return 0;
}

在 input_register_handle 接口中,会将 handle->d_node 加入到 dev->h_list,实现遍历dev->h_list就能找到所有关联的input_handle,进而找到input_handler。

实际上在 input_pass_values 中,如果未指定 input_device 的 input_handle, 就是通过遍历列表的方式,将事件通过所有关联的 input_handle 发送到 input_handler 中。

也就是说默认input_event的事件上报是一个广播行为:

c 复制代码
	handle = rcu_dereference(dev->grab);
	if (handle) {
		count = input_to_handler(handle, vals, count);  // 指定handle
	} else {
		list_for_each_entry_rcu(handle, &dev->h_list, d_node)   // 广播
			if (handle->open) {
				count = input_to_handler(handle, vals, count);
				if (!count)
					break;
			}
	}
指定 handle

在 input_grab_device 接口中,实现了 dev->grab 与 handle 的绑定:

c 复制代码
int input_grab_device(struct input_handle *handle)
{
	if (dev->grab) {
		retval = -EBUSY;
		goto out;
	}
	rcu_assign_pointer(dev->grab, handle);
}

以 evdev handler 为例,ioctl 中实现了 EVIOCGRAB,用于 input_device 指定 input_handle:

c 复制代码
// ioctl接口中,调用evdev_grab或evdev_ungrab事件绑定和解绑:
	case EVIOCGRAB:
		if (p)
			return evdev_grab(evdev, client);
		else
			return evdev_ungrab(evdev, client);

// evcev_grab中调用 input_grab_device 实现 dev->grab 与 handle 的绑定
static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
	int error;

	if (evdev->grab)
		return -EBUSY;

	error = input_grab_device(&evdev->handle);
	if (error)
		return error;

	rcu_assign_pointer(evdev->grab, client);

	return 0;
}

2.2.3 input handler 相关接口

c 复制代码
// 注册、注销
int input_register_handler(struct input_handler *handler);
void input_unregister_handler(struct input_handler *handler);
注册handler

input_register_handler 接口中,将 handler 添加到 input_handler_list 中,遍历 input_dev_list,执行input_attach_handler(dev, handler):

c 复制代码
int input_register_handler(struct input_handler *handler)
{
	INIT_LIST_HEAD(&handler->h_list);

	list_add_tail(&handler->node, &input_handler_list);

	list_for_each_entry(dev, &input_dev_list, node)
		input_attach_handler(dev, handler);

	return 0;
}

三、input handler

3.1 evdev handler

3.1.1 handler 注册

在evdev_init中调用 input_register_handler 实现 handler 的注册。

c 复制代码
static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.events		= evdev_events,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.legacy_minors	= true,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

static int __init evdev_init(void)
{
	return input_register_handler(&evdev_handler);
}

3.1.2 evdev_connect

evdev_connect 接口在 input_attach_handler 中被调用,接口实现以下功能:

  • 以 evdev_fops 为 file_operations 创建 cdev,设备名称为 event%d;
  • 调用 input_register_handle 实现 input_device 与 input_handler 的绑定;
c 复制代码
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{

	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);

	INIT_LIST_HEAD(&evdev->client_list);

	dev_no = minor;
	dev_set_name(&evdev->dev, "event%d", dev_no);

	evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);
	evdev->dev.class = &input_class;
	evdev->dev.parent = &dev->dev;
	evdev->dev.release = evdev_free;
	device_initialize(&evdev->dev);

	error = input_register_handle(&evdev->handle);

	cdev_init(&evdev->cdev, &evdev_fops);

	error = cdev_device_add(&evdev->cdev, &evdev->dev);

}

3.1.3 evdev_events

evdev_events 接口负责处理 input_device 上报的事件,并上报给用户层:

c 复制代码
handler->events(handle, vals, count); // evdev_events,读时间
    --> evdev_pass_values(client, vals, count, ev_time);  // 组input_event
        --> __pass_event(client, &event);  // 将event存在evdev_client的buffer中
            --> kill_fasync(&client->fasync, SIGIO, POLL_IN); // 异步信号通知用户层
        --> wake_up_interruptible(&evdev->wait);  // 唤醒等待队列

3.1.4 file_operations

evdev handler 的 file_operations 提供了 fasync\poll\read 等接口,供用户层读取 input event。

c 复制代码
static const struct file_operations evdev_fops = {
	.owner		= THIS_MODULE,
	.read		= evdev_read,
	.write		= evdev_write,
	.poll		= evdev_poll,
	.open		= evdev_open,
	.release	= evdev_release,
	.unlocked_ioctl	= evdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= evdev_ioctl_compat,
#endif
	.fasync		= evdev_fasync,
	.flush		= evdev_flush,
	.llseek		= no_llseek,
};

evdev_fasync接口实现异步通知处理函数,当有input_event事件时,在evdev_events接口中,最终会调用 kill_fasync实现发送异步通知信号,用户层接收到状态变化后,可知晓有input_event事件需要处理。
evdev_read接口为用户空间提供了读input_event事件的接口,实际是将evdev_events接口中缓存在buffer中的数据copy到用户空间。当缓存中没有数据是,调用wait_event_interruptible 等待 evdev_events 唤醒。

3.2 mousedev handler

TODO

参考链接:https://www.cnblogs.com/arnoldlu/p/17952329

相关推荐
Ven%7 分钟前
centos查看硬盘资源使用情况命令大全
linux·运维·centos
TeYiToKu1 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
dsywws1 小时前
Linux学习笔记之时间日期和查找和解压缩指令
linux·笔记·学习
yeyuningzi1 小时前
Debian 12环境里部署nginx步骤记录
linux·运维·服务器
上辈子杀猪这辈子学IT1 小时前
【Zookeeper集群搭建】安装zookeeper、zookeeper集群配置、zookeeper启动与关闭、zookeeper的shell命令操作
linux·hadoop·zookeeper·centos·debian
minihuabei2 小时前
linux centos 安装redis
linux·redis·centos
lldhsds2 小时前
书生大模型实战营第四期-入门岛-1. Linux前置基础
linux
wowocpp3 小时前
ubuntu 22.04 硬件配置 查看 显卡
linux·运维·ubuntu
jjyangyou3 小时前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
山河君3 小时前
ubuntu使用DeepSpeech进行语音识别(包含交叉编译)
linux·ubuntu·语音识别