【分析笔记】Linux input 子系统原理分析

本文深入探讨Linux Input子系统,包括其核心层、事件处理层和设备驱动层的职责,关键数据结构如input_dev和input_handler,以及如何注册设备和处理事件。分析了设备驱动如何通过input_register_device()上报事件,事件处理流程涉及input_event()、input_handle_event()等。最后总结了设备驱动和事件处理驱动的关联条件及事件处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、input 子系统简介

输入子系统主要用于支持各种输入设备,可大大简化这类设备驱动的开发难度。以下为个人的理解,可能不同的内核版本会略有差异,在这里分析的内核为 linux-4.9。

无论在 Linux 是什么子系统,其目的都是为了将硬件相关的进行解耦,提高代码的复用性,简化设备驱动的开发难度。Input 子系统也是一样,通过构建一个框架(input.c),分离出硬件相关的(input_dev)和硬件无关的(input_handler)功能实现,内核提供了硬件无关的实现(如 evdev.c),也提供硬件相关的实现(如 gpio_keys.c)。

Linux 子系统主要分为三部分:input core、input handler、input device

input core(核心层):linux-4.9\drivers\input\input.c
与硬件无关的核心框架层实现,提供 handler 和 device 相关 API 和数据结构。通过 input_dev_list、input_handler_list 两个链表来记录所有的输入设备驱动和事件处理驱动。

input handler(事件处理层):linux-4.9\drivers\input\evdev.c
与硬件无关的事件处理层实现,决定如何处理输入事件,不同 handler 有不同的处理方法,如 evdev.c 通过字符设备驱动将事件分发给上层应用程序。

input device(设备驱动层):linux-4.9\drivers\input\keyboard\gpio_keys.c
与硬件息息相关的设备驱动层,决定如何产生输入事件,不同的 device 有不同的事件产生方法,如 gpio_keys.c 通过 gpio 的中断来产生按键事件。

通常的流程是:
输入事件产生 ---> 设备驱动层 ---> 核心层 ---> 事件处理层 ---> 用户进程 

也有反向的情况:如键盘灯事件 EV_LED
用户进程或其它驱动程序 ---> 事件处理层 ---> 核心层 ---> 设备驱动层 ---> 处理具体事件

二、关键的数据结构

struct input_dev:代表的是一种输入设备
struct input_handler:代表的是一种输入事件处理方式
struct input_handle:用于关联 struct input_dev、struct input_handler

1. 一个 input_handler 通过自己的成员 h_list 可以挂接多个 input_handle ,实现支持多个 input_dev 
2. 一个 input_dev 通过自己的成员 h_list 可以挂接多个 input_handle ,实现支持多个 input_handler

struct input_dev {
    ......
	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)];
    ......
	struct list_head	h_list;  // 链接各个 struct input_handle 
	struct list_head	node;	 // 作为子节点被挂接到 input_dev_list 链表内
    ......
};

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);
    ......
	const struct input_device_id *id_table;
	struct list_head	h_list;	// 链接各个 struct input_handle 
	struct list_head	node;	// 作为子节点被挂接到 input_handler_list 链表内
};

struct input_handle {
	void *private;
	int open;
	const char *name;
	struct input_dev *dev;			// 指向具体的输入设备驱动
	struct input_handler *handler;	// 指向具体的事件处理驱动
	struct list_head	d_node;		// 作为子节点被挂接到 input_dev->h_list
	struct list_head	h_node;		// 作为子节点被挂接到 input_handler->h_list
};

三、input device 使用模版

这里简单的演示一个通过 GPIO 中断来检测 GPIO 状态,作为电源按键事件的产生来源。

1. 调用 input_allocate_device() 创建一个 input device,它会初始化一些关键的数据成员。
2. 配置好支持的事件类型和具体的事件,如按键事件 EV_KEY,上报的是 KEY_POWER 键值。
3. 调用 input_register_device() 将该输入设备注册到输入子系统中。
4. 在需要的时候,调用 input_event()\input_sync() 上报 KEY_POWER 按下或弹起事件。

struct input_dev *input = NULL;

static irqreturn_t gpio_key_irq_isr(int irq, void *dev_id)
{
    // 4. 上报电源按键事件,根据 GPIO 状态决定是弹起还是按下
	input_event(input, EV_KEY, KEY_POWER, __gpio_get_value(...));
	input_sync(input);
	return IRQ_HANDLED;
}

static int __init intpu_device_demo_init(void)
{
    // 1. 必须使用 input_allocate_device() 创建,它会初始化一些关键的数据成员
	input = input_allocate_device();
	input->name = "power-keys";
	input->phys = "power-keys/input0";
	input->dev.parent = &pdev->dev;
	
	input->id.bustype = BUS_HOST;
	input->id.vendor = 0x0001;
	input->id.product = 0x0001;
	input->id.version = 0x0100;
	
    // 2. 设置支持的事件类型, 这里设置支持按键上报类型
	set_bit(EV_KEY | EV_SYN, input->evbit);
    // 2. 设置支持具体哪些按键,这里设置支持 KEY_POWER 键值上报
	set_bit(KEY_POWER, input->keybit);
    // 3. 将该设备注册到输入子系统内部
	input_register_device(input);
	
	request_irq(..., &gpio_key_irq_isr, ..., "powerkey", ...);
	return 0;
}

static void __exit intpu_device_demo_exit(void)
{
	free_irq(...);
	input_free_device(input);
}

module_init(intpu_device_demo_init);
module_exit(intpu_device_demo_exit);

四、input handler 使用模版

通常不需要我们自己写,内核提供的 evdev.c、mousedev.c 等等,足以应付普通应用。注意,这里涉及到两种名字很像但其意义完全不同的数据结构 struct input_handler、struct input_handle,前者代表事件处理驱动,后者只是用于来关联 struct input_device 和 struct input_handler。

1. 通过 struct input_device_id 指明能支持哪种类型的输入设备驱动产生的事件,这里的 .driver_info=1,表明支持所有输入设备。
2. 设置 handler 与输入设备驱动连接或断开、事件处理的回调。 
3. 将该 handler 注册进入输入子系统。
4. 在与输入设备驱动连接时,构建 input handle 关联 device 和 handler 并注册到输入子系统中.
5. 增加 handle->open 的计数引用,核心层只会给有计数的 handle 转发事件给 handle 关联的 handler。


void input_handler_demo_events(struct input_handle *handle, const struct input_value *vals, unsigned int count)
{
	// 6. 处理输入设备驱动传递过来的事件
    // 即输入设备驱动调用 input_event()\input_sync() 传的事件
}

static int input_handler_demo_connect(struct input_handler *handler,
			      struct input_dev *dev,
			      const struct input_device_id *id)
{
	// 4. 构建 input handle 关联 device 和 handler 并注册到输入子系统中
	struct input_handle *handle = NULL;
	handle.dev = dev;
	handle.handler = handler;
	handle.name = "demo&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值