Input子系统

前言

本片文章是从设备驱动程序层面整理总结,目的是想记录大佬的知识,为了后面能够学以致用。
<阿杰。>写的真不错。

Input子系统简介
input子系统就是管理输入的子系统,和pinctrl和gpio子系统一样,都是Linux内核针对某一类设备而创建的框架。
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点。
对于用户空间,所有的输入设备以文件的形式供用户应用程序使用。

在这里插入图片描述
在这里插入图片描述

Input子系统代码实现框架

驱动层:输入设备的具体驱动程序,比如按键驱动程序。主要是实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。如:input_register_device; 通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。 设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。
事件层:主要是和用户空间交互。用户编程的接口(设备节点),并处理驱动层提交的数据处理。(Linux中在用户空间将所有的设备都当初文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev/input下生成相应的设备文件node,这些操作在输入子系统中由事件处理层完成)。
在这里插入图片描述

Linux Input子系统支持的数据类型
数据类型解释
EV_SYN0x00同步事件
EV_KEY0x01按键事件
EV_REL0x02相对坐标(如:鼠标移动,报告相对最后一次位置的偏移)
EV_ABS0x03绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置)
EV_MSC0x04其它
EV_SW0x05开关
EV_LED0x11按键/设备灯
EV_SND0x12声音/警报
EV_REP0x14重复
EV_FF0x15力反馈
EV_PWR0x16电源
EV_FF_STATUS0x17力反馈状态
EV_MAX0x1f事件类型最大个数和提供位掩码支持
input核心层

CODE PATH : drivers/input/input.c
input核心层会向Linux内核注册一个input类,系统启动后会在/sys/class目录下生成一个input子目录

static int __init input_init(void)
{
	int err;

	err = class_register(&input_class);  // 创建class类
	if (err) {
		pr_err("unable to register input_dev class\n");
		return err;
	}

	err = input_proc_init();  // 初始化proc相关
	if (err)
		goto fail1;

	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),   // 向linux内核注册input字符设备
				     INPUT_MAX_CHAR_DEVICES, "input");
    .....
}
static int __init input_proc_init(void)
{
	struct proc_dir_entry *entry;

	proc_bus_input_dir = proc_mkdir("bus/input", NULL);  // 在proc目录下创建bus/input目录
	if (!proc_bus_input_dir)
		return -ENOMEM;

	entry = proc_create("devices", 0, proc_bus_input_dir,   //input目录下创建devices文件
			    &input_devices_proc_ops);
	if (!entry)
		goto fail1;

	entry = proc_create("handlers", 0, proc_bus_input_dir,  // input目录下创建handlers文件
			    &input_handlers_proc_ops);
	if (!entry)
		goto fail2;
	.....

CODE PATH : kernel-4.19/include/linux/input.h // 头文件中有注释结构体成员含义

struct input_dev {
	const char *name;			//提供给用户的输入设备名称
	const char *phys;			//提供给编程者的设备节点名称
	const char *uniq;			//指定唯一的ID号,就像MAC地址一样
	struct input_id id;			//输入设备标识ID,用于和事件处理层进行匹配

	unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
    // 下面的成员是用来表示该input设备能够上报的事件类型有哪些,用位的方式来表示
	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)];
	
	int (*open)(struct input_dev *dev);    // 设备的open函数
	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);   // 上报事件
设备驱动层

在驱动加载模块中,设置input设备支持的事件类型
注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
将输入设备注册到输入子系统中(在使用input子系统时,只需要注册一个input设备)。

input_allocate_device 与 函数

(1)使用input_allocate_device函数申请一个input_dev

struct input_dev *input_allocate_device(void)
{
	struct input_dev *dev;
	动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数 
	dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
	if (dev) {
		dev->dev.type = &input_dev_type;
		dev->dev.class = &input_class;		//添加进input类设备模型中
		device_initialize(&dev->dev);
		mutex_init(&dev->mutex);			//初始化互斥锁 
		spin_lock_init(&dev->event_lock);	//初始化自旋锁
		INIT_LIST_HEAD(&dev->h_list);		//初始化handle链表 
		INIT_LIST_HEAD(&dev->node);			//初始化输入设备链表

		__module_get(THIS_MODULE);
	}

	return dev;
}
input_set_capability

(2)初始化input_dev的事件类型以及事件值(设置输入设备可以上报哪些输入事件)

input_register_device 函数

(3)input_dev初始化完成以后就需要向Linux内核注册input_dev了,需要用到input_register_device函数。
定义在drivers/input/input.c文件中。

int input_register_device(struct input_dev *dev)
{
	static atomic_t input_no = ATOMIC_INIT(0);
	struct input_handler *handler;
	const char *path;
	int error;

	/* 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成员,如果设备驱动没有指定的函数,将赋予系统默认的函数 */
	if (!dev->hint_events_per_packet)
		dev->hint_events_per_packet =
				input_estimate_events_per_packet(dev);

	init_timer(&dev->timer);
	if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
		dev->timer.data = (long) dev;
		dev->timer.function = input_repeat_key;
		dev->rep[REP_DELAY] = 250;
		dev->rep[REP_PERIOD] = 33;
	}
	
	if (!dev->getkeycode)
		dev->getkeycode = input_default_getkeycode;

	if (!dev->setkeycode)
		dev->setkeycode = input_default_setkeycode;
	
	/* 动态获取input设备的ID号,名称为input*, *为id号 */
	/* 例如:input5 */
	dev_set_name(&dev->dev, "input%ld",
		     (unsigned long) atomic_inc_return(&input_no) - 1);
	
	/* 在/sys目录下创建设备目录和文件 */
	/* 例如:/sys/devices/virtual/input/input5/ */
	error = device_add(&dev->dev);
	if (error)
		return error;
		
	/* 在终端上打印设备的绝对路径名称 */ 
	/* 例如:input: keyinput as /devices/virtual/input/input5 */
	path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
	pr_info("%s as %s\n",
		dev->name ? dev->name : "Unspecified device",
		path ? path : "N/A");
	kfree(path);

	error = mutex_lock_interruptible(&input_mutex);
	if (error) {
		device_del(&dev->dev);
		return error;
	}
	/* 把设备挂到全局的input子系统设备链表input_dev_list上 */
	list_add_tail(&dev->node, &input_dev_list);
	/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 
     * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
     * 方式与设备模型的device和driver匹配过程很相似,所有的input 
     * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list 
     * 上,进行“匹配相亲” */  
	list_for_each_entry(handler, &input_handler_list, node)
		input_attach_handler(dev, handler);

	input_wakeup_procfs_readers();

	mutex_unlock(&input_mutex);

	return 0;
}
input_unregister_device 与 input_free_device 函数

(4)卸载input驱动的时候需要先使用input_unregister_device函数注销掉注册的input_dev,然后使用input_free_device函数释放掉前面申请的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_attach_handler函数
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);		//匹配事件驱动,事件驱动一共有三种evdev,mousedev,joydev
	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;
}

input_attach_handler 函数里面有两个函数比较重要,input_match_device 和 handler->connect, 看看具体是怎样实现的。

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

	for (id = handler->id_table; id->flags || id->driver_info; id++) {
	
		/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID 
         * 如果没有匹配上将进行MATCH_BIT匹配 */  
		if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
			if (id->bustype != dev->id.bustype)
				continue;

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

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

		if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
			if (id->version != dev->id.version)
				continue;
		/* MATCH_BIT用于匹配设备驱动中是否设置了这些位,MATCH_BIT的宏 
         * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的 
         * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL 
         */  
		MATCH_BIT(evbit,  EV_MAX);
		MATCH_BIT(keybit, KEY_MAX);
		MATCH_BIT(relbit, REL_MAX);
		MATCH_BIT(absbit, ABS_MAX);
		MATCH_BIT(mscbit, MSC_MAX);
		MATCH_BIT(ledbit, LED_MAX);
		MATCH_BIT(sndbit, SND_MAX);
		MATCH_BIT(ffbit,  FF_MAX);
		MATCH_BIT(swbit,  SW_MAX);

		if (!handler->match || handler->match(handler, dev))
			return id;
	}

	return NULL;
}

handler 是事件驱动结构体的指针,事件驱动一般有三种evdev,mousedev,joydev,分别对应三个结构体evdev_handler ,mousedev_handler ,joydev_handler ,如果匹配成功了会返回id,再回看 input_attach_handler 函数,若id不为NULL,就会调用 handler->connect 函数。假如现在匹配到 evdev_handler 这个事件驱动,然后就会调用evdev_handler->connect 函数,下面来看看evdev_handler->connect 函数做了什么。

evdev_connect 函数

drivers/input/evdev.c文件中有如下内容:

static struct input_handler evdev_handler = {
	.event		= evdev_event,
	.connect	= evdev_connect,
	.disconnect	= evdev_disconnect,
	.fops		= &evdev_fops,
	.minor		= EVDEV_MINOR_BASE,
	.name		= "evdev",
	.id_table	= evdev_ids,
};

在注册evdev驱动时,evdev_handler结构体的connect成员指向evdev_connect函数,所以evdev_handler->connect 就是 evdev_connect 函数了,evdev_connect 函数也是定义在drivers/input/evdev.c文件中,内容如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
			 const struct input_device_id *id)
{
	struct evdev *evdev;
	int minor;
	int error;

	for (minor = 0; minor < EVDEV_MINORS; minor++)
		if (!evdev_table[minor])
			break;

	if (minor == EVDEV_MINORS) {
		pr_err("no more free evdev devices\n");
		return -ENFILE;
	}
	
	/* 给evdev事件层驱动分配空间 ,
	 * 可以不要关心 evdev ,只看 evdev->handle 即可,这里构建了一个 handle ,
	 * 注意不是handler, handle 就是个中间件,可以理解成胶带,
	 * 它把 handler 与 dev 连在一起 */
	evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
	if (!evdev)
		return -ENOMEM;

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

	dev_set_name(&evdev->dev, "event%d", minor);	
	evdev->exist = true;
	evdev->minor = minor;
	evdev->hw_ts_sec = -1;
	evdev->hw_ts_nsec = -1;

	/* 第一次建立联系,在 handle 中记录 dev 与 handle 的信息,
	 * 这样通过handle就可以找到dev与handler, 即是实现
	 * handle -> dev ,  handle -> hander 的联系 */
	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, EVDEV_MINOR_BASE + 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);		// 注册 handle
	if (error)
		goto err_free_evdev;

	error = evdev_install_chrdev(evdev);
	if (error)
		goto err_unregister_handle;

	error = device_add(&evdev->dev);	//在 /dev/input 类下面创建设备节点, 名字为event%d
	if (error)
		goto err_cleanup_evdev;
    ......
}
内容里面比较重要就是evdev->handle这个成员了,它的作用就是把 hander 和 dev 连在一起,hander 表示事件驱动层,dev表示设备驱动层,这样 事件驱动层 和 设备驱动层 之间就通过handle这个 ”红娘“ 建立了关系了。现在可以通过 handle 找到 hander 或者 dev,不过还差一步,就是实现双向性,通过hander 或者 dev 也能找到 handle ,这样才能实现 handle —hander — dev 三者之间畅通无阻,而这一实现就体现在 handle 注册函数input_register_handle里面。
input_register_handle 函数
int input_register_handle(struct input_handle *handle)
{
	/* 第二次建立联系	*/
	struct input_handler *handler = handle->handler;
	struct input_dev *dev = handle->dev;
	int error;

	error = mutex_lock_interruptible(&dev->mutex);
	if (error)
		return error;

	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);	// 将handle 记录在 dev->h_list 链表中

	mutex_unlock(&dev->mutex);

	list_add_tail_rcu(&handle->h_node, &handler->h_list);	// 将handle 记录在 handler->h_list 链表中


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

	return 0;
}

input_register_handle函数注册handle时,把handle作为节点分别加入到dev->h_list 链表和handler->h_list 链表中去,至此,dev 与 hander 也可以找到handle了,dev <-> handle <-> handler 之间畅通无阻了。
在这里插入图片描述

通过上图可以看到input输入设备匹配关联的关键过程,以及涉及到的关键函数和数据。
以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。

参考链接

http://t.zoukankan.com/zhaobinyouth-p-6257662.html
https://blog.csdn.net/qq_17639223/article/details/119489552
https://blog.csdn.net/qq_43286311/article/details/117437595

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

多维不语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值