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

相关推荐
古希腊数通小白(ip在学)8 分钟前
HCIA实现不同vlan间的通信
linux·服务器·网络
半桔22 分钟前
【Linux手册】从接口到管理:Linux文件系统的核心操作指南
android·java·linux·开发语言·面试·系统架构
禁默30 分钟前
Linux Vim 编辑器详解:从入门到进阶(含图示+插件推荐)
linux·vim·excel
许白掰2 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
longze_76 小时前
Ubuntu连接不上网络问题(Network is unreachable)
linux·服务器·ubuntu
Dirschs6 小时前
【Ubuntu22.04安装ROS Noetic】
linux·ubuntu·ros
qianshanxue116 小时前
ubuntu 操作记录
linux
AmosTian9 小时前
【系统与工具】Linux——Linux简介、安装、简单使用
linux·运维·服务器
这我可不懂11 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
车车不吃香菇12 小时前
java idea 本地debug linux服务
java·linux·intellij-idea