Linux驱动——input子系统

目录

一、总体框架

二、驱动框架

三、核心数据结构

3.1、input_dev

3.2、input_handler

3.3、input_handle

四、匹配规则

[五、connect 函数](#五、connect 函数)

六、evdev_client

七、事件传输流程

7.1、input_dev向用户空间汇报事件

7.2、用户空间向input子系统注入事件

八、实验结果


Linux Version:linux-5.4.239

Linux input子系统是内核为统一管理所有输入设备(如键盘、鼠标、按键、触摸屏)而设计的通用驱动框架。它为驱动开发者提供标准接口,同时向上为用户空间应用程序提供统一的设备访问方式。

一、总体框架

例如,当手指触摸屏幕时会发送时产生一个中断,然后 CPU 通过 SPI 总 线读取触摸坐标数据,并放入一个buffer,由字符驱动管理这个buffer,而驱动的 read 接 口可以让用户直接读取坐标数据。Input子系统框架如图1所示。

图1.Input子系统整体框架

二、驱动框架

为了在硬件和软件之间建立明确的分工,input子系统采用三层结构:设备驱动层、Input核心层、事件处理层,具体如图2所示。

图2.Input驱动框架

  • 驱动层:负责初始化硬件、处理中断、读取原始数据,并将这些原始数据转换成统一的内核事件格式,然后向核心层报告。

  • 核心层:为驱动层和事件处理层提供注册、数据传输等相关API。

  • 事件处理层 :实现了标准的文件操作接口,并在/dev/input/目录下创建对应的设备文件(如event0),应用程序通过读写这些文件来获取输入事件。

三、核心数据结构

Input子系统的三个核心数据结构为:input_dev(驱动层)、input_handler(事件处理层)、input_handle(核心层)三者的关系如图3所示。

图3.Input子系统层级关系

**input_dev:**表示一个具体的输入设备(如键盘),由驱动分配、初始化并注册到核心层。

input_handler: 表示一种事件处理方式(如 evdevjoydev),负责将内核事件发送到用户空间接口(如 /dev/input/eventX)。

input_handle: 连接 input_devinput_handler,记录两者之间的绑定关系,kernel可通过事件处理程序找到对应的设备,实现事件的分发。

3.1、input_dev

input_dev用来描述一个输入设备,其结构如下:

objectivec 复制代码
/**
 * struct input_dev - represents an input device
 * @name: name of the device
 * @phys: physical path to the device in the system hierarchy
 * @uniq: unique identification code for the device (if device has it)
 * @id: id of the device (struct input_id)
 * @propbit: bitmap of device properties and quirks
 * @evbit: bitmap of types of events supported by the device (EV_KEY,
 *	EV_REL, etc.)
 * @keybit: bitmap of keys/buttons this device has
 * @relbit: bitmap of relative axes for the device
 * @absbit: bitmap of absolute axes for the device
 * @mscbit: bitmap of miscellaneous events supported by the device
 * @ledbit: bitmap of leds present on the device
 * @sndbit: bitmap of sound effects supported by the device
 * @ffbit: bitmap of force feedback effects supported by the device
 * @swbit: bitmap of switches present on the device
 * @hint_events_per_packet: average number of events generated by the
 *	device in a packet (between EV_SYN/SYN_REPORT events). Used by
 *	event handlers to estimate size of the buffer needed to hold
 *	events.
 * @keycodemax: size of keycode table
 * @keycodesize: size of elements in keycode table
 * @keycode: map of scancodes to keycodes for this device
 * @getkeycode: optional legacy method to retrieve current keymap.
 * @setkeycode: optional method to alter current keymap, used to implement
 *	sparse keymaps. If not supplied default mechanism will be used.
 *	The method is being called while holding event_lock and thus must
 *	not sleep
 * @ff: force feedback structure associated with the device if device
 *	supports force feedback effects
 * @poller: poller structure associated with the device if device is
 *	set up to use polling mode
 * @repeat_key: stores key code of the last key pressed; used to implement
 *	software autorepeat
 * @timer: timer for software autorepeat
 * @rep: current values for autorepeat parameters (delay, rate)
 * @mt: pointer to multitouch state
 * @absinfo: array of &struct input_absinfo elements holding information
 *	about absolute axes (current value, min, max, flat, fuzz,
 *	resolution)
 * @key: reflects current state of device's keys/buttons
 * @led: reflects current state of device's LEDs
 * @snd: reflects current state of sound effects
 * @sw: reflects current state of device's switches
 * @open: this method is called when the very first user calls
 *	input_open_device(). The driver must prepare the device
 *	to start generating events (start polling thread,
 *	request an IRQ, submit URB, etc.)
 * @close: this method is called when the very last user calls
 *	input_close_device().
 * @flush: purges the device. Most commonly used to get rid of force
 *	feedback effects loaded into the device when disconnecting
 *	from it
 * @event: event handler for events sent _to_ the device, like EV_LED
 *	or EV_SND. The device is expected to carry out the requested
 *	action (turn on a LED, play sound, etc.) The call is protected
 *	by @event_lock and must not sleep
 * @grab: input handle that currently has the device grabbed (via
 *	EVIOCGRAB ioctl). When a handle grabs a device it becomes sole
 *	recipient for all input events coming from the device
 * @event_lock: this spinlock is taken when input core receives
 *	and processes a new event for the device (in input_event()).
 *	Code that accesses and/or modifies parameters of a device
 *	(such as keymap or absmin, absmax, absfuzz, etc.) after device
 *	has been registered with input core must take this lock.
 * @mutex: serializes calls to open(), close() and flush() methods
 * @users: stores number of users (input handlers) that opened this
 *	device. It is used by input_open_device() and input_close_device()
 *	to make sure that dev->open() is only called when the first
 *	user opens device and dev->close() is called when the very
 *	last user closes the device
 * @going_away: marks devices that are in a middle of unregistering and
 *	causes input_open_device*() fail with -ENODEV.
 * @dev: driver model's view of this device
 * @h_list: list of input handles associated with the device. When
 *	accessing the list dev->mutex must be held
 * @node: used to place the device onto input_dev_list
 * @num_vals: number of values queued in the current frame
 * @max_vals: maximum number of values queued in a frame
 * @vals: array of values queued in the current frame
 * @devres_managed: indicates that devices is managed with devres framework
 *	and needs not be explicitly unregistered or freed.
 * @timestamp: storage for a timestamp set by input_set_timestamp called
 *  by a driver
 */
struct input_dev {
	const char *name;   // 设备名称
	const char *phys;   // 物理路径(/dev/input/event0)
	const char *uniq;    
	struct input_id id; // 设备ID

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

	unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //支持的事件类型(如EV_KEY、EV_SYN)
	unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//按键/按钮 (KEY_xxx, BTN_xxx)
	unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//相对轴 (REL_xxx)
	unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//绝对轴 (ABS_xxx)
	unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//杂项事件 (MSC_xxx)
	unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];//LED 指示灯 (LED_xxx)
	unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//声音效果 (SND_xxx)
	unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//力反馈效果 (FF_xxx)
	unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//开关状态 (SW_xxx)

	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;// 关联的input_handle链表(设备匹配的handler)
	struct list_head	node; // 用于挂载到内核的input_dev_list链表

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

	bool devres_managed;

	ktime_t timestamp[INPUT_CLK_MAX];
};

关键字段说明

**struct input_id id:**总线类型、供应商、产品、版本号

**unsigned long evbit:**支持的事件大类(EV_KEY, EV_REL, EV_ABS ...)

**unsigned long keybit:**支持的按键/按钮(KEY_A, BTN_LEFT ...)

**unsigned long relbit:**支持的相对轴(REL_X, REL_Y, REL_WHEEL ...)

**int (*event)(struct input_dev *dev, ...):**处理发往设备的事件(如设置LED)

input_dev 完整描述了设备的能力、状态以及内核连接关系。示例代码如下所示:

objectivec 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

#include <linux/input.h>

static struct input_dev *test_dev;

static void timer_function(struct timer_list *data);
DEFINE_TIMER(test_timer, timer_function);

static void timer_function(struct timer_list *data)
{
    static int val;
    val = val ? 0 : 1;

    input_report_key(test_dev, KEY_1, val);
    input_sync(test_dev);

    mod_timer(&test_timer, jiffies + msecs_to_jiffies(1000));
}

static int __init test_init(void)
{
    int ret;

    test_dev = input_allocate_device();
    test_dev->name = "test_key";

    __set_bit(EV_KEY, test_dev->evbit);
	__set_bit(EV_SYN, test_dev->evbit);
    __set_bit(KEY_1, test_dev->keybit);

    ret = input_register_device(test_dev);
    if(ret < 0){
        printk(KERN_INFO " input_register_device error \n");
        goto input_err;
    }

    test_timer.expires = jiffies + msecs_to_jiffies(1000);
    add_timer(&test_timer);

    return 0;

input_err:
    input_unregister_device(test_dev);
    return -1;
}

static void __exit test_exit(void)
{
    del_timer(&test_timer);
    input_unregister_device(test_dev);
}

module_init(test_init);
module_exit(test_exit);
 
MODULE_LICENSE("GPL v2");

这个示例代码会每隔 1 秒自动模拟一次按键 KEY_1 的按下和释放,在test_init函数中首先通过input_allocate_device函数分配一个 input_dev 结构体,然后再设置支持的事件类型和具体按键,最后调用input_register_device函数将其注册到kernel中,这样就实现了一个input_dev设备。

3.2、input_handler

input_handler 位于 input 子系统的事件处理层,负责将底层驱动上报的输入事件转换为用户空间可以获取的格式(例如通过 /dev/input/eventX 节点)。

内核已实现多个通用input_handler,如下:

  • evdev_handler:通用处理程序,处理几乎所有输入设备事件(键盘、鼠标等)。

  • joydev_handler:游戏手柄专用,专门处理游戏手柄、摇杆的输入。

  • mousedev_handler:鼠标专用,专门处理鼠标事件。

objectivec 复制代码
/**
 * struct input_handler - implements one of interfaces for input devices
 * @private: driver-specific data
 * @event: event handler. This method is being called by input core with
 *	interrupts disabled and dev->event_lock spinlock held and so
 *	it may not sleep
 * @events: event sequence handler. This method is being called by
 *	input core with interrupts disabled and dev->event_lock
 *	spinlock held and so it may not sleep
 * @filter: similar to @event; separates normal event handlers from
 *	"filters".
 * @match: called after comparing device's id with handler's id_table
 *	to perform fine-grained matching between device and handler
 * @connect: called when attaching a handler to an input device
 * @disconnect: disconnects a handler from input device
 * @start: starts handler for given handle. This function is called by
 *	input core right after connect() method and also when a process
 *	that "grabbed" a device releases it
 * @legacy_minors: set to %true by drivers using legacy minor ranges
 * @minor: beginning of range of 32 legacy minors for devices this driver
 *	can provide
 * @name: name of the handler, to be shown in /proc/bus/input/handlers
 * @id_table: pointer to a table of input_device_ids this driver can
 *	handle
 * @h_list: list of input handles associated with the handler
 * @node: for placing the driver onto input_handler_list
 *
 * Input handlers attach to input devices and create input handles. There
 * are likely several handlers attached to any given input device at the
 * same time. All of them will get their copy of input event generated by
 * the device.
 *
 * The very same structure is used to implement input filters. Input core
 * allows filters to run first and will not pass event to regular handlers
 * if any of the filters indicate that the event should be filtered (by
 * returning %true from their filter() method).
 *
 * Note that input core serializes calls to connect() and disconnect()
 * methods.
 */
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;
};

关键字段说明

connect :若 match 成功,调用 connect 方法,将 handler 正式绑定到设备上。

event /events:常规 handler 通过此方法接收并处理一个事件。

id_table :指向一个 input_device_id 数组,用于快速匹配设备。

h_list :链接所有由此 handler 创建的 input_handle(每个 handle 代表 handler 与一个设备之间的连接)。

node :将 handler 挂载到全局链表 input_handler_list

这里以evdev handler为例,代码如下:

objectivec 复制代码
static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};

MODULE_DEVICE_TABLE(input, evdev_ids);

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

static void __exit evdev_exit(void)
{
	input_unregister_handler(&evdev_handler);
}

module_init(evdev_init);
module_exit(evdev_exit);

MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Input driver event char devices");
MODULE_LICENSE("GPL");

3.3、input_handle

input_handle 是连接input_devinput_handler的桥梁。表示某个 handler 通过这个 handle 与某个 device 进行交互,并接收来自该设备的事件。

objectivec 复制代码
/**
 * struct input_handle - links input device with an input handler
 * @private: handler-specific data
 * @open: counter showing whether the handle is 'open', i.e. should deliver
 *	events from its device
 * @name: name given to the handle by handler that created it
 * @dev: input device the handle is attached to
 * @handler: handler that works with the device through this handle
 * @d_node: used to put the handle on device's list of attached handles
 * @h_node: used to put the handle on handler's list of handles from which
 *	it gets events
 */
struct input_handle {

	void *private;

	int open;
	const char *name;

	struct input_dev *dev;
	struct input_handler *handler;

	struct list_head	d_node;
	struct list_head	h_node;
};

关键字段说明

dev**:** 指向一个具体的 input_dev

handler**:** 指向一个具体的 input_handler

d_node**:** 将此 handle 挂入 dev->h_listinput_dev侧链表)。

h_node**:** 将此 handle 挂入 handler->h_listinput_handler侧链表)。

从设备出发 :顺着 dev->h_list 找到所有 handle,再从 handle 拿到 handler

从事件处理测出发 :顺着 handler->h_list 找到所有 handle,再从 handle 拿到 dev

input_devinput_handler通过input_handle建立的并不是一对一的关系,而是多对多关系,即一个设备可被多个handler匹配(如触摸屏同时被evdevtslib处理),而一个handler也可匹配多个设备(如evdev处理所有按键设备)。

四、匹配规则

在 Linux 输入子系统中,当一个输入设备input_dev被注册时,事件input_handler就会与之进行匹配操作,同理,当事件input_handler被注册时,就会去和input_dev进行匹配,当匹配成功就会创建一个input_handle。

input_handler register:

input_register_handler

list_for_each_entry(dev, &input_dev_list, node)

input_attach_handler(dev, handler);

input_register_device:

input_register_device

list_for_each_entry(handler, &input_handler_list, node)

input_attach_handler

可以看到当驱动调用 input_register_device注册一个新的 input_dev时,系统会遍历全局的 input_handler_list,对每个 handler 尝试匹配。反之当注册一个新的 handler,则会遍历input_dev_list,让新 handler 与它们进行匹配。

input_attach_handler:

objectivec 复制代码
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;
	int error;

	id = input_match_device(handler, dev);
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id); // 当匹配成功会调用connect函数
	if (error && error != -ENODEV)
		pr_err("failed to attach handler %s to device %s, error: %d\n",
		       handler->name, kobject_name(&dev->dev.kobj), error);

	return error;
}

具体匹配规则如下:

objectivec 复制代码
bool input_match_device_id(const struct input_dev *dev,
			   const struct input_device_id *id)
{
	if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
		if (id->bustype != dev->id.bustype)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
		if (id->vendor != dev->id.vendor)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
		if (id->product != dev->id.product)
			return false;

	if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
		if (id->version != dev->id.version)
			return false;

	if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX) ||
	    !bitmap_subset(id->keybit, dev->keybit, KEY_MAX) ||
	    !bitmap_subset(id->relbit, dev->relbit, REL_MAX) ||
	    !bitmap_subset(id->absbit, dev->absbit, ABS_MAX) ||
	    !bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX) ||
	    !bitmap_subset(id->ledbit, dev->ledbit, LED_MAX) ||
	    !bitmap_subset(id->sndbit, dev->sndbit, SND_MAX) ||
	    !bitmap_subset(id->ffbit, dev->ffbit, FF_MAX) ||
	    !bitmap_subset(id->swbit, dev->swbit, SW_MAX) ||
	    !bitmap_subset(id->propbit, dev->propbit, INPUT_PROP_MAX)) {
		return false;
	}

	return true;
}
EXPORT_SYMBOL(input_match_device_id);

static const struct input_device_id *input_match_device(struct input_handler *handler,
							struct input_dev *dev)
{
	const struct input_device_id *id;

	for (id = handler->id_table; id->flags || id->driver_info; id++) {
		if (input_match_device_id(dev, id) &&
		    (!handler->match || handler->match(handler, dev))) {
			return id;
		}
	}

	return NULL;
}

不同的事件处理方式不同,所以匹配规则也不相同,这里以evdev为例,evdev的匹配规则很直接**,** 它通过一个万能的 id_table,匹配所有输入设备,如下:

objectivec 复制代码
static const struct input_device_id evdev_ids[] = {
	{ .driver_info = 1 },	/* Matches all devices */
	{ },			/* Terminating zero entry */
};

因为没有设置任何硬件ID(bustype, vendor, product, version)和evbit相关的比较条件,根据input_match_device_id 函数的逻辑,所有约束条件都会被跳过,直接返回匹配成功。而evdev_handler也没有实现 match 函数,所以没有二次匹配。

五、connect 函数

connect 函数起到了类似设备驱动模型中 probe 函数的作用。在 input_handler input_dev匹配成功后,调用connect函数将两者真正的相互关联。以evdev为例,如下:

objectivec 复制代码
/*
 * Create new evdev device. Note that input core serializes calls
 * to connect and disconnect.
 */
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int dev_no;
	int error;

	minor = input_get_new_minor(EVDEV_MINOR_BASE, EVDEV_MINORS, true);
	if (minor < 0) {
		error = minor;
		pr_err("failed to reserve new minor: %d\n", error);
		return error;
	}

	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev) {
		error = -ENOMEM;
		goto err_free_minor;
	}

	INIT_LIST_HEAD(&evdev->client_list);
	spin_lock_init(&evdev->client_lock);
	mutex_init(&evdev->mutex);
	init_waitqueue_head(&evdev->wait);
	evdev->exist = true;

	dev_no = minor;
	/* Normalize device number if it falls into legacy range */
	if (dev_no < EVDEV_MINOR_BASE + EVDEV_MINORS)
		dev_no -= EVDEV_MINOR_BASE;
	dev_set_name(&evdev->dev, "event%d", dev_no);

	evdev->handle.dev = input_get_device(dev);
	evdev->handle.name = dev_name(&evdev->dev);
	evdev->handle.handler = handler;
	evdev->handle.private = evdev;

	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);
	if (error)
		goto err_free_evdev;

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

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

	return 0;

 err_cleanup_evdev:
	evdev_cleanup(evdev);
	input_unregister_handle(&evdev->handle);
 err_free_evdev:
	put_device(&evdev->dev);
 err_free_minor:
	input_free_minor(minor);
	return error;
}

evdev_connect函数的主要作用是为该设备创建并注册一个 evdev 实例,并在用户空间生成对应的 /dev/input/eventX 字符设备节点,从而让用户程序能够通过标准的 readwrite 等接口读取输入事件。

填充并注册 input_handle:

evdev->handle.dev = input_get_device(dev); // 指向输入设备

evdev->handle.name = dev_name(&evdev->dev); // 名称如 "event0"

evdev->handle.handler = handler; // 指向 evdev_handler

evdev->handle.private = evdev; // handler 私有数据

error = input_register_handle(&evdev->handle); //将 handle 挂入设备链表 dev->h_list 和处理器链表 handler->h_list

初始化并添加字符设备:

evdev->dev.devt = MKDEV(INPUT_MAJOR, minor);

evdev->dev.class = &input_class; // 归属于 input 类,自动在 /sys/class/input 下创建

evdev->dev.parent = &dev->dev; // 父设备是输入设备

evdev->dev.release = evdev_free; // 释放函数

device_initialize(&evdev->dev); // 初始化设备结构

cdev_init(&evdev->cdev, &evdev_fops); // 初始化字符设备,绑定文件操作表 evdev_fops

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

cdev_device_add 将字符设备添加到内核,并注册对应的设备,成功后在 /dev/input/ 下生成 eventX 节点。evdev_fops 则包含了 openreadwrite等标准文件操作集,供用户空间调用。如下:

objectivec 复制代码
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,
	.llseek		= no_llseek,
};

六、evdev_client

当打开 /dev/input/eventX 的文件描述符evdev_open函数就会被调用,会为本次打开创建一个独立的事件缓冲区client,并增加handle->open次数,如下代码:

objectivec 复制代码
static int evdev_open(struct inode *inode, struct file *file)
{
	struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
	unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
	struct evdev_client *client;
	int error;

	client = kvzalloc(struct_size(client, buffer, bufsize), GFP_KERNEL);
	if (!client)
		return -ENOMEM;

	client->bufsize = bufsize;
	spin_lock_init(&client->buffer_lock);
	client->evdev = evdev;
	evdev_attach_client(evdev, client);

	error = evdev_open_device(evdev);
	if (error)
		goto err_free_client;

	file->private_data = client;
	stream_open(inode, file);

	return 0;

 err_free_client:
	evdev_detach_client(evdev, client);
	kvfree(client);
	return error;
}

evdev_open

evdev_open_device

input_open_device

handle->open++; // 记录有多少个 handler 实例,在input_pass_values // 中会判断是否向该 handle 汇报事件

struct evdev_client *client结构体如下:

objectivec 复制代码
struct evdev_client {
    unsigned int head;               // 环形缓冲区写指针
    unsigned int tail;               // 环形缓冲区读指针
    unsigned int packet_head;       
    spinlock_t buffer_lock;          
    struct fasync_struct *fasync;   
    struct evdev *evdev;             // 指向所属的 evdev 设备
    struct list_head node;           // 用于挂入 evdev->client_list 链表
    enum input_clock_type clk_type;  
    bool revoked;                    
    unsigned long *evmasks[EV_CNT];  
    unsigned int bufsize;            // 环形缓冲区容量,能存放的 input_event 个数
    struct input_event buffer[];     // 存放事件的环形缓冲区
};

这里的 struct input_event buffer会用来存放 input_dev 传递的数据,等待用户空间读取。

七、事件传输流程

7.1、input_dev向用户空间汇报事件

比如电脑键盘驱动收到一个Enter按下的事件要怎么汇报给核心层,然后再汇报给用户呢?如下函数:

objectivec 复制代码
/**
 * input_event() - report new input event
 * @dev: device that generated the event
 * @type: type of the event
 * @code: event code
 * @value: value of the event
 *
 * This function should be used by drivers implementing various input
 * devices to report input events. See also input_inject_event().
 *
 * NOTE: input_event() may be safely used right after input device was
 * allocated with input_allocate_device(), even before it is registered
 * with input_register_device(), but the event will not reach any of the
 * input handlers. Such early invocation of input_event() may be used
 * to 'seed' initial state of a switch or initial position of absolute
 * axis, etc.
 */
void input_event(struct input_dev *dev,
		 unsigned int type, unsigned int code, int value)
{
	unsigned long flags;

	if (is_event_supported(type, dev->evbit, EV_MAX)) {

		spin_lock_irqsave(&dev->event_lock, flags);
		input_handle_event(dev, type, code, value);
		spin_unlock_irqrestore(&dev->event_lock, flags);
	}
}
EXPORT_SYMBOL(input_event);

当按键按下时,驱动程序会调用input_event函数,将事件传递给input核心层,最终分发给所有绑定的 input_handler,进而传递给用户空间的应用程序。

input核心层到input_handler的流程如下:

// drivers/input/input.c

input_handle_event

input_pass_values

list_for_each_entry_rcu(handle, &dev->h_list, d_node) // 通过dev->h_list找到与之对应的 handle,并找到对应的 handler

if (handle->open) // 这个值会在 evdev_open 中被++

count = input_to_handler(handle, vals, count);

handler->events(handle, vals, count);

现在已经调用到evdev handler中的evdev_event函数,那怎么把这个数据传递给用户的呢?如下:

objectivec 复制代码
// drivers/input/evdev.c
/*
 * Pass incoming event to all connected clients.
 */
static void evdev_event(struct input_handle *handle,
			unsigned int type, unsigned int code, int value)
{
	struct input_value vals[] = { { type, code, value } };

	evdev_events(handle, vals, 1);
}

evdev_event

evdev_events(handle, vals, 1);

list_for_each_entry_rcu(client, &evdev->client_list, node) // 找到对应的client

evdev_pass_values

__pass_event

client->buffer[client->head++] = *event; // 将事件复制到buffer

可以看到最后是将事件数据input_event 放入指定的client buffer 中,供用户空间程序通过 read读取,代码如下:

evdev_read

evdev_fetch_next_event(client, &event))

*event = client->buffer[client->tail++]; // 将事件从client_buffer复制到event input_event_to_user(buffer + read, &event)

copy_to_user(buffer, event, sizeof(struct input_event)) //最后拷贝到用户空间

用户空间代码如下:

objectivec 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#include <stdlib.h>

#include <linux/input.h>
 
int main(int argc, char *argv[])
{
    int fd, ret;
    struct input_event *event;
 
    fd = open("/dev/input/event0", O_RDWR);
    if(fd < 0) {
        printf("ERROR: open failed!\n");
        return -1;
    }

    event = (struct input_event*)malloc(sizeof(struct input_event));

    while (1) {
        ret = read(fd, event, sizeof(struct input_event));
        if(ret < 0) {
            printf("ERROR: read failed!\n");
            return -1;
        }

        if (event->type == EV_KEY && event->code == KEY_1) {
            if (event->value == 1) {
                printf("KEY_1 is 1\n");
            } else if (event->value == 0) {
                printf("KEY_1 is 0\n");
            } else {
                break;
            }
        }
    }

    close(fd);
    return 0;
}

7.2、用户空间向input子系统注入事件

用户空间程序可以通过 write/dev/input/eventX 写入一个或多个 struct input_event,这些事件会被注入到input子系统中。如下:

evdev_write

input_event_from_user

copy_from_user(event, buffer, sizeof(struct input_event))

input_inject_event(&evdev->handle,event.type, event.code, event.value);

input_handle_event(dev, type, code, value);

if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)

dev->event(dev, type, code, value);

if (disposition & INPUT_PASS_TO_HANDLERS) {

input_pass_values

如果需要传给设备(INPUT_PASS_TO_DEVICE)且 dev->event 存在,则会调用驱动回调,即input_dev->event。如果需要传给 handlers(INPUT_PASS_TO_HANDLERS),将事件存入 dev->vals 缓冲区。调用 input_pass_values 将事件分发给所有已打开的 input_handle

因此,注入的事件会像真实硬件事件一样被所有监听该输入设备的 handler 接收,用户空间的其他进程也可以 read 到这些注入的事件。例如下代码段:

objectivec 复制代码
struct input_event ev;
ev.type = EV_KEY;
ev.code = KEY_A;
ev.value = 1;   // 按下
write(fd, &ev, sizeof(ev));
ev.value = 0;   // 释放
write(fd, &ev, sizeof(ev));

八、实验结果

实验代码是实现了一个简单的Linux内核输入设备驱动,在前面代码分析中已经给出了源码,它模拟了一个名为 test_key的按键设备,并通过内核定时器每秒自动上报 KEY_1 按键的按下/释放事件,代码已经在前面给出,下面直接运行。

执行结果如下:

原始数据如下:

加载模块后,系统会出现一个/dev/input/event0的input设备节点,每隔1秒,该设备会交替上报 KEY_1 的按下和释放事件,用户态程序可以读取到这些事件,效果等同于每隔1秒有人按下并释放了键盘上的数字键1。

相关推荐
feng_you_ying_li2 小时前
liunx之make/makefile的使用
linux
默|笙2 小时前
【Linux】线程概念与控制(4)_线程封装
linux
仍然探索未知中2 小时前
【Linux内核源码分析】内核数据结构
linux·数据结构
chxii2 小时前
linux 下用 acme.sh 搞定 Nginx 免费 SSL 证书自动续期(下) 对于acme.sh命令安装详解
linux·运维·服务器
Bert.Cai2 小时前
Linux more命令详解
linux·运维
minji...2 小时前
Linux 多线程(四)线程等待,线程分离,线程管理,C++多线程,pthread库
linux·运维·开发语言·网络·c++·算法
ZGUIZ3 小时前
Ubuntu 25.10 无法外接显示器解决方案
linux·运维·ubuntu
QJtDK1R5a3 小时前
V4L2 vs GStreamer vs FFmpeg:Linux多媒体处理的三个层级
linux·运维·ffmpeg
不屈的铝合金3 小时前
Python入门:输入输出(I/O)指南
windows·python·i/o·input·print·输入输出