Linux输入子系统

版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/82967594

一、什么是input输入子系统?

1、Linux系统支持的输入设备繁多,例如键盘、鼠标、触摸屏、手柄或者是一些输入设备像体感输入等等,Linux系统是如何管理如此之多的不同类型、不同原理、不同的输入信息的输入设备的呢?其实就是通过input输入子系统这套软件体系来完成的。从整体上来说,input输入子系统分为3层:上层(输入事件驱动层)、中层(输入核心层)、下层(输入设备驱动层),如下图所示:
在这里插入图片描述

2、图中设备驱动层对应的就是下层设备驱动层,对应各种各样不同的输入设备,Input Core对应的就是中层核心层,Handlers对应的就是上层输入事件驱动层,最右边的代表的是用户空间(设备节点)。

(1)从图中可以看出,系统中可以注册多个输入设备,每个输入设备的类型可以是不同的,例如一台电脑上可以带有鼠标,键盘…。

(2)上层中的各个handler(Keyboard/Mouse/Joystick/Event)是属于平行关系,他们都是属于上层。不同的handler下对应的输入设备在应用层中的接口命名方式不一样,例如

Mouse下的输入设备在应用层的接口是 /dev/input/mousen (n代表0、1、2…),Joystick下的输入设备在应用层的接口是 /dev/input/jsn(n代表0、1、2…),

Event下的输入设备在应用层的接口是 /dev/input/eventn(n代表0、1、2…),这个是在input输入子系统中实现的,下面会分析其中的原由。

(3)输入核心层其实是负责协调上层和下层,使得上层和下层之间能够完成数据传递。当下层发生输入事件的时候,整个系统就被激活了,事件就会通过核心层传递到上层对应的一个/多个handler中,最终会传递到应用空间。

3、输入子系统解决了什么问题?

(1)在GUI界面中,用户的自由度太大了,可以做的事情太多了,可以响应不同的输入类设备,而且还能够对不同的输入类设备的输入做出不同的动作。例如window中的一个软件既可以响应鼠标输入事件,也可以相应键盘输入事件,而且这些事件都是预先不知道的。

(2)input子系统解决了不同的输入类设备的输入事件与应用层之间的数据传输,使得应用层能够获取到各种不同的输入设备的输入事件,input输入子系统能够囊括所有的不同种类的输入设备,在应用层都能够感知到所有发生的输入事件。

4、input输入子系统如何工作?

例如以一次鼠标按下事件为例子来说明我们的input输入子系统的工作过程:

当我们按下鼠标左键的时候就会触发中断(中断是早就注册好的),就会去执行中断所绑定的处理函数,在函数中就会去读取硬件寄存器来判断按下的是哪个按键和状态 ---->将按键信息上报给input core层 —> input core层处理好了之后就会上报给input event层,在这里会将我们的输入事件封装成一个input_event结构体放入一个缓冲区中 —> 应用层read就会将缓冲区中的数据读取出去。

二、一个简单实例

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/timer.h>  /*timer*/
#include <asm/uaccess.h>  /*jiffies*/
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/input.h>
 
struct input_dev *dev ;
 
 
static irqreturn_t irq_fuction(int irq, void *dev_id)
{	
	struct input_dev *keydev ;
	#if  0 
	if(in_interrupt()){
	     printk("%s in interrupt handle!\n",__FUNCTION__);
	}
	#endif
	keydev = dev_id ; //上报键值
	input_report_key(keydev, KEY_HOME,  \
				!gpio_get_value(EXYNOS4_GPX3(2)));
	input_sync(keydev); //上报一个同步事件	
	printk("irq:%d\n",irq);
	return IRQ_HANDLED ;
}
	
static int __init tiny4412_Key_irq_test_init(void) 
{
	int err = 0 ;
	int irq_num1 = 0;
	int ret ;
	struct input_id id ;
	dev = input_allocate_device();
	if(IS_ERR_OR_NULL(dev)){
	    ret = -ENOMEM ;
	    goto ERR_alloc;
	}
	//对input_id的成员进行初始化
	dev->name = "tiny4412_home_key" ;
	dev->phys = "YYX_create_key" ;
	dev->uniq = "20170410" ; 
	dev->id.bustype = BUS_HOST ; 
	dev->id.vendor = ID_PRODUCT ;
	dev->id.version = ID_VENDOR ;
	set_bit(EV_SYN,dev->evbit);	//设置为同步事件,这个宏可以在input.h中找到
	set_bit(EV_KEY,dev->evbit);	//因为是按键,所以要设置成按键事件
	set_bit(KEY_HOME,dev->keybit);	//设置这个按键表示为KEY_HOME这个键,到时用来上报
	ret = input_register_device(dev); //注册input设备
	if(IS_ERR_VALUE(ret))
	    goto ERR_input_reg ;
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));//申请中断号,并注册中断,下降沿触发,设备就是input设备
	err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,"tiny4412_key1",dev);
	if(err != 0)
	     goto free_irq_flag ;
	//以下除了return 0 都为出错处理
	return 0 ;
	ERR_input_reg:
	input_unregister_device(dev);	
	free_irq_flag:
	free_irq(irq_num1,(void *)"key1");
	ERR_alloc:
	return ret ;
}
 
static void __exit tiny4412_Key_irq_test_exit(void) 
{	//为了简单,我这里不需要验证exit的功能,只要在开机init成功就可以了,日后再完善
	int irq_num1 ;
	printk("irq_key exit\n");
	irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
	free_irq(irq_num1,dev);
}
 
module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");

2.1 input_allocate_device

这个实例程序代码比较简单,在初始化函数tiny4412_Key_irq_test_init()中注册了一个中断处理函数,然后调用input_allocate_device()函数分配了一个input_dev结构体,并调用input_register_device()函数对其进行了注册。在中断处理函数irq_fuction()中,实例将接收到的按键信息上报给input子系统。从而通过input子系统,向用户态程序提供按键输入信息。

本实例采用了中断方式,除了中断相关的代码外,实例中包含了一些input子系统提供的函数,现对其中一些重要的函数进行分析。
input_allocate_device()函数在内存中为输入设备结构体分配一个空间,并对其主要的成员进行了初始化。该函数的代码如下

struct input_dev *input_allocate_device(void)
{
    struct input_dev *dev;                                 //   定义一个 input_dev  指针

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);   /* 分配一个input_dev结构体,并初始化为0*/
    if (dev) {
        dev->dev.type = &input_dev_type;                   //  初始化input设备的 设备类型     input_dev_type
        dev->dev.class = &input_class;                     //  初始化input设备所属的设备类   class
        device_initialize(&dev->dev);                      //  input设备的初始化
        mutex_init(&dev->mutex);                           //  互斥锁初始化
        spin_lock_init(&dev->event_lock);                  //  自旋锁初始化
        INIT_LIST_HEAD(&dev->h_list);                      //  input_dev -> h_list 链表初始化
        INIT_LIST_HEAD(&dev->node);                        //  input_dev -> node 链表初始化

        __module_get(THIS_MODULE);                         /*模块引用计数加1*/
    }

    return dev;
}

2.2 input_register_device

tiny4412_Key_irq_test_init()函数调用了input_register_device()函数注册输入设备结构体。input_register_device()函数是输入子系统核心(input core)提供的函数。该函数将input_dev结构体注册到输入子系统核心中,input_dev结构体必须由前面讲的input_allocate_device()函数来分配。input_register_device()函数如果注册失败,必须调用input_free_device()函数释放分配的空间。如果该函数注册成功,在卸载函数中应该调用input_unregister_device()函数来注销输入设备结构体。

int input_register_device(struct input_dev *dev)      //  注册input输入设备
{
    static atomic_t input_no = ATOMIC_INIT(0);
    struct input_handler *handler;                          //  定义一个  input_handler 结构体指针
    const char *path;
    int error;

    /* Every input device generates EV_SYN/SYN_REPORT events. */
    __set_bit(EV_SYN, dev->evbit);                  //   每一个input输入设备都会发生这个事件

    /* KEY_RESERVED is not supposed to be transmitted to userspace. */
    __clear_bit(KEY_RESERVED, dev->keybit);  //  清除KEY_RESERVED 事件对应的bit位,也就是不传输这种类型的事件

    /* Make sure that bitmasks not mentioned in dev->evbit are clean. */
    input_cleanse_bitmasks(dev);           //   确保input_dev中的用来记录事件的变量中没有提到的位掩码是干净的。

    /*
     * If delay and period are pre-set by the driver, then autorepeating
     * is handled by the driver itself and we don't do it in input.c.
     */
    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;

    dev_set_name(&dev->dev, "input%ld",                                  //   设置input设备对象的名字    input+数字
             (unsigned long) atomic_inc_return(&input_no) - 1);

    error = device_add(&dev->dev);         //   添加设备       例如:          /sys/devices/virtual/input/input0     
    if (error)
        return error;

    path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);  //  获取input设备对象所在的路径      /sys/devices/virtual/input/input_xxx   
    printk(KERN_INFO "input: %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;
    }

    list_add_tail(&dev->node, &input_dev_list);             //   链表挂接:    将 input_dev->node 作为节点挂接到 input_dev_list  链表上

    list_for_each_entry(handler, &input_handler_list, node)  //  遍历input_handler_list 链表上的所有handler
        input_attach_handler(dev, handler);                        //  将handler与input设备进行匹配

    input_wakeup_procfs_readers();                //  更新proc 文件系统

    mutex_unlock(&input_mutex);

    return 0;
}

从上面的代码中我们可以知道,遍历input_handler_list 链表上的所有handler,将handler与input设备进行匹配,而dev就是我们在input_register_device()函数中传进去的参数,所以input_register_device()中会调用input_attach_handler将dev与input_handler_list 链表上的所有handler进行匹配。
下面我们来看一下input_attach_handler函数是如何将handler与input设备进行匹配

2.3 input_attach_handler函数:

input_attach_handler就是input_register_device函数中用来对下层的设备驱动和上层的handler进行匹配的一个函数,只有匹配成功之后就会调用上层handler中的connect函数

进行连接绑定。

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
	const struct input_device_id *id;                                 //   定义一个input_device_id 的指针
	int error;

	id = input_match_device(handler, dev);                            //  通过这个函数进行handler与input设备的匹配工作
	if (!id)
		return -ENODEV;

	error = handler->connect(handler, dev, id);                       //  匹配成功则调用 handler 中的 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_match_device函数如何进行handler与input设备的匹配工作

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 (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;

		if (!bitmap_subset(id->evbit, dev->evbit, EV_MAX))
			continue;

		if (!bitmap_subset(id->keybit, dev->keybit, KEY_MAX))
			continue;

		if (!bitmap_subset(id->relbit, dev->relbit, REL_MAX))
			continue;

		if (!bitmap_subset(id->absbit, dev->absbit, ABS_MAX))
			continue;

		if (!bitmap_subset(id->mscbit, dev->mscbit, MSC_MAX))
			continue;

		if (!bitmap_subset(id->ledbit, dev->ledbit, LED_MAX))
			continue;

		if (!bitmap_subset(id->sndbit, dev->sndbit, SND_MAX))
			continue;

		if (!bitmap_subset(id->ffbit, dev->ffbit, FF_MAX))
			continue;

		if (!bitmap_subset(id->swbit, dev->swbit, SW_MAX))
			continue;

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

	return NULL;
}

由上面的代码可以知道只有当每一项都一样才会匹配成功return id

input_attach_handler函数做的事情有两件:调用input_match_device函数进行设备与handler的匹配、匹配成功调用handler的连接函数handler->connect进行连接(至于如何连接将会在后面说到)。

下面我们来看一下这个连接函数,首先先来看一下input_handler 结构体

三 、input_handler

(1)input_handler 结构体

struct input_handler {

    void *private;            //  私有数据

    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);   //  handler用于向上层上报输入事件的函数
    bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    bool (*match)(struct input_handler *handler, struct input_dev *dev);            //   match 函数用来匹配handler 与 input_dev 设备
    int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);   //  当handler 与 input_dev 匹配成功之后用来连接
    void (*disconnect)(struct input_handle *handle);          //  断开handler 与 input_dev 之间的连接
    void (*start)(struct input_handle *handle);                    

    const struct file_operations *fops;             //  一个file_operations 指针
    int minor;                                      //  该handler 的编号 (在input_table 数组中用来计算数组下标) input_table数组就是input子系统用来管理注册的handler的一个数据结构
    const char *name;                               //  handler的名字

    const struct input_device_id *id_table;      //  指向一个 input_device_id  类型的数组,用来进行与input设备匹配时用到的信息
 
    struct list_head    h_list;       //  用来挂接handler 上连接的所有handle 的一个链表头
    struct list_head    node;        //  作为一个链表节点挂接到 input_handler_list 链表上(input_handler_list 链表是一个由上层handler参维护的一个用来挂接所有注册的handler的链表头)
};

input_handler ->connect就是前面说到的handler->connect

(2) input_register_handler函数:

int input_register_handler(struct input_handler *handler)    //  向核心层注册handler
{
    struct input_dev *dev;          //  定义一个input_dev 指针
    int retval; 

    retval = mutex_lock_interruptible(&input_mutex);
    if (retval)
        return retval;

    INIT_LIST_HEAD(&handler->h_list);      //  初始化 handler->h_list 链表

    if (handler->fops != NULL) {          //  如果 handler -> fops 存在
        if (input_table[handler->minor >> 5]) {  //  如果input_table 数组中没有该handler  的位置了 则返回
            retval = -EBUSY;
            goto out;
        }
        input_table[handler->minor >> 5] = handler;  //  将 handler 指针存放在input_table 数组中去
    }

    list_add_tail(&handler->node, &input_handler_list);   //  将 handler 通过 handler -> node 节点 挂接到 input_handler_list 链表上

    list_for_each_entry(dev, &input_dev_list, node)     //  遍历 input_dev_list 链表下挂接的所有的 input_dev 设备
        input_attach_handler(dev, handler);          //  然后进行匹配

    input_wakeup_procfs_readers();             //  更新proc 文件系统

 out:
    mutex_unlock(&input_mutex);
    return retval;
}

通过分析了上面的input_register_device和这里的input_register_handler函数可以知道:注册设备的时候,不一定是先注册了handler才能够注册设备。当注册设备时,会先将设备挂接到设备管理链表(input_dev_list)上,然后再去遍历input_handler_list链表匹配hander。同样对于handler注册的时候,也会先将handler挂接到handler管理链表(input_handler_list)上,然后再去遍历input_dev_list链表匹配设备。所以从这里可以看出来,这种机制好像之前说过的platform总线下设备和驱动的匹配过程。而且一个input_dev可以与多个handler匹配成功,从而可以在sysfs中创建多个设备文件,也可以在/dev/目录下创建多个设备节点,并且他们的次设备号是不一样的,这个很好理解。所以就是导致一个设备对应多个次设备号,那这样有没有错呢?当然是没有错的。例如在我们的Ubuntu中,/dev/input/event3 和 /dev/input/mouse1 都是对应鼠标这个设备。

(3)handler 类型

每一个handler(如Keyboard/Mouse/Joystick/Event)都会调用input_register_handler将自己注册到input core中。下面以evdev为例:
input输入子系统的输入事件驱动层(上层)其实是由各个handler构成的,各个handler之间是属于平行关系,不存在相互调用的现象。目前用的最多是event,今天就以这个handler为例分析他的源代码,以便对handler的实现有一定的了解,前面说到过,input输入子系统的源代码都在 drivers\input\这个目录下,其中 drivers\input\evdev.c就是event的源代码文件。从evdev.c文件的末尾可以看到使用了module_init、module_exit这些宏,说明内核中将这部分实现为模块的方式,这其实很好理解,因为input核心层都是实现为模块的方式,而上层是要依赖于核心层才能够注册、才能够工作的,而核心层都已经实现为模块了,那么上层不更得需要这样做吗。好了,废话不多说开始分析代码。

1、模块注册函数:

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

evdev_handler变量就是本次分析的handler对应的结构体变量,变量中填充最重要的有3个:

evdev_event函数:

evdev_connect函数:从上面的代码中我们可以知道,通过 input_register_handler(&evdev_handler)将evdev这个handler添加到input_handler_list链表中,在前面的input_attach_handler函数匹配的时候会从input_handler_list链表中取出所有的handler进行匹配,当取出这个handler并匹配成功之后,就会调用handler->connect,即evdev_connect这个函数,下面会讲解evdev_connect这个函数

evdev_fops变量:

2、相关的数据结构

struct evdev {     
    int exist;
    int open;                                //  这个是用来作为设备被打开的计数
    int minor;                               //   handler 与 input设备匹配成功之后创建的设备对应的device的次设备号相对于基准次设备号的偏移量
    struct input_handle handle;   //   内置的一个  handle ,里面记录了匹配成功的input_dev 和 handler            
    wait_queue_head_t wait;
    struct evdev_client *grab;
    struct list_head client_list;       //   用来挂接与 evdev 匹配成功的evdev_client 的一个链表头 
    spinlock_t client_lock; /* protects client_list */
    struct mutex mutex;             //  互斥锁
    struct device dev;                 //  这个是handler 与 input设备匹配成功之后创建的设备对应的device
};
struct evdev_client {
    struct input_event buffer[EVDEV_BUFFER_SIZE];    //  用来存放input_dev 事件的缓冲区
    int head;
    int tail;
    spinlock_t buffer_lock; /* protects access to buffer, head and tail */
    struct fasync_struct *fasync;
    struct evdev *evdev;              //   evdev 指针
    struct list_head node;            //  作为一个链表节点挂接到相应的 evdev->client_list 链表上
    struct wake_lock wake_lock;
    char name[28];            //  名字
};
struct input_event {
    struct timeval time;        //  事件发生的事件
    __u16 type;                    //  事件的类型
    __u16 code;                   //   事件的码值
    __s32 value;                   //  事件的状态
};

3、函数详解

(1)evdev_connect函数分析:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
    struct evdev *evdev;                 //  定义一个 evdev 指针
    int minor;
    int error;

    for (minor = 0; minor < EVDEV_MINORS; minor++)  //  从evdev_table 数组中找到一个没有被使用的最小的数组项  最大值32
        if (!evdev_table[minor])
            break;

    if (minor == EVDEV_MINORS) {
        printk(KERN_ERR "evdev: no more free evdev devices\n");
        return -ENFILE;
    }

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //  给evdev 申请分配内存
    if (!evdev)
        return -ENOMEM;

    INIT_LIST_HEAD(&evdev->client_list);            //  初始化 evdev->client_list 链表
    spin_lock_init(&evdev->client_lock);              //  初始化自旋锁 evdev->client_lock
    mutex_init(&evdev->mutex);                          //  初始化互斥锁 evdev->mutex
    init_waitqueue_head(&evdev->wait);

    dev_set_name(&evdev->dev, "event%d", minor);  // 设置input设备的名字
    evdev->exist = 1;
    evdev->minor = minor;                                     //  input设备的次设备号的偏移量 

    evdev->handle.dev = input_get_device(dev);              //  将我们传进来的 input_dev 指针存放在 evdev->handle.dev 中
    evdev->handle.name = dev_name(&evdev->dev);     //  设置 evdev -> dev 对象的名字,并且把名字赋值给 evdev->handle.name
    evdev->handle.handler = handler;          //  将我们传进来的 handler 指针存放在 handle.handler 中
    evdev->handle.private = evdev;             //  把evdev 作为handle 的私有数据

    evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);        //  设置 evdev->device 设备的设备号
    evdev->dev.class = &input_class;                                                  //  将 input_class 作为 evdev->device 的设备类
    evdev->dev.parent = &dev->dev;                                                // 将input_dev  -> device 作为evdev->device 的父设备
    evdev->dev.release = evdev_free;                   //  evdev -> device 设备的卸载函数
    device_initialize(&evdev->dev);                      //  设备初始化

    error = input_register_handle(&evdev->handle);       //  注册handle 
    if (error)
        goto err_free_evdev;

    error = evdev_install_chrdev(evdev);       // 安装evdev   其实就是将evdev 结构体指针存放在evdev_table数组当中  下标就是evdev->minor
    if (error)
        goto err_unregister_handle;

    error = device_add(&evdev->dev);     //  添加设备到系统          /sys/devices/virtual/input/input0/event0        event0就是表示建立的设备文件
    if (error)
        goto err_cleanup_evdev;

    return 0;

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

这里搞清楚: /sys/devices/virtual/input/input0 这个设备是在注册input_dev时创建的,而input0/event0就是在handler和input_dev匹配成功之后创建的,也会在/dev/目录下创建设备节点。

四、 input_handle

下面我们来看一下evdev_connect中的evdev->handle,也就是input_handle。input_handle的主要功能是连接input_dev和input_handler

/**
 * 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;
};

name 变量:表示handle的名字
dev变量指针,表示该handle依附的input_dev设备
handler变量指针,指向input_handler
d_node变量,使用这个变量将handle放到设备相关的链表中,也就是input_dev->h_list表示的链表中
h_node变量,使用这个变量将handle放到input_handler相关的链表中,也就是handler->h_list表示的链表中
即将handle放到了handler->h_list和input_dev->h_list,同时将input_dev和input_handler放到了handle中,这样就大家都能相互找到了

input_handle是用来连接input_dev和input_handler的一个中间结构体。事件通过input_handle从input_dev发送到input_handler,或者从input_handler发送到input_dev进行处理,在使用input_handle之前,需要对其进行注册,注册函数是input_register_handle
(2)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;

	/*
	 * We take dev->mutex here to prevent race with
	 * input_release_device().
	 */
	error = mutex_lock_interruptible(&dev->mutex);
	if (error)
		return error;

	/*
	 * Filters go to the head of the list, normal handlers
	 * to the tail.
	 */
	if (handler->filter)
		list_add_rcu(&handle->d_node, &dev->h_list);
	else
		list_add_tail_rcu(&handle->d_node, &dev->h_list);

	mutex_unlock(&dev->mutex);

	/*
	 * Since we are supposed to be called from ->connect()
	 * which is mutually exclusive with ->disconnect()
	 * we can't be racing with input_unregister_handle()
	 * and so separate lock is not needed here.
	 */
	list_add_tail_rcu(&handle->h_node, &handler->h_list);

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

	return 0;
}

这个函数的作用就是注册一个handle,也就是实现上图中的将各个handle连接起来构成一个环形的结构,再调用这个函数之前已经将handle中的dev和handler已经是填充好了的,具体的这个函数代码就不去分析了。

其实handler、input_dev、handle3这之间的关系,在之前就已经接触过了,讲Linux设备驱动模型底层架构的时候遇到过,下面用一副关系图来描述他们之间的一个关系:

在这里插入图片描述

从本质上讲,input_dev与handler是多对多的关系,从上图可以看出来,一个input_dev可以对应多个handler,一个handler也可以对应多个input_dev。因为在匹配的时候,一个input_dev会与所有的handler都进行匹配的,并不是匹配成功一次就退出。

从图中可以看出来,一个handle就是用来记录系统中一对匹配成功的handler和device,我们可以从这个handle出发得到handler的信息,还可以得到device的信息。所以正因为有这样的功能,所以可以由handler经过handle最终获取到device的信息,同理也可以从device从发经过handle最终获取到handler的信息。这种运用方法将会在后面的分析中看到。

(3)evdev_open分析

static int evdev_open(struct inode *inode, struct file *file)
{
    struct evdev *evdev;                       //  定义一个 evdev 结构体指针
    struct evdev_client *client;             //   定义一个evdev_client 指针
    int i = iminor(inode) - EVDEV_MINOR_BASE;   //  通过inode 获取 需要打开的设备对应的evdev_table 数组中的下标变量
    int error;

    if (i >= EVDEV_MINORS)
        return -ENODEV;

    error = mutex_lock_interruptible(&evdev_table_mutex);
    if (error)
        return error;
    evdev = evdev_table[i];             //  从evdev_table  数组中找到evdev 
    if (evdev)
        get_device(&evdev->dev);
    mutex_unlock(&evdev_table_mutex);

    if (!evdev)
        return -ENODEV;

    client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL);      //  给 client 申请分配内存
    if (!client) {
        error = -ENOMEM;
        goto err_put_evdev;
    }

    spin_lock_init(&client->buffer_lock);
    snprintf(client->name, sizeof(client->name), "%s-%d",
            dev_name(&evdev->dev), task_tgid_vnr(current));
    wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);
    client->evdev = evdev;                          //  通过client->evdev 指针指向 evdev
    evdev_attach_client(evdev, client);   //  其实这个函数就是做了一个链表挂接:  client->node  挂接到 evdev->client_list

    error = evdev_open_device(evdev); //  打开 evdev 设备   最终就会打开 input_dev -> open 函数
    if (error)
        goto err_free_client;

    file->private_data = client;              //   将evdev_client 作为file 的私有数据存在
    nonseekable_open(inode, file);

    return 0;

 err_free_client:
    evdev_detach_client(evdev, client);
    kfree(client);
 err_put_evdev:
    put_device(&evdev->dev);
    return error;
}

那么应用层调用open函数的时候又是如何调用到底层的evdev_open的呢?
下面我们来看一下输入核心层代码

五、输入核心层源码分析(内核版本:2.6.35.7)

input输入子系统中的所有源码都放在 drivers\input 这个目录中,input.c文件就是核心层的源代码文件。在input目录中还可以看到一些文件夹,例如gameport、joystickkeyboard、misc、mouse…,这些文件夹里面存放的就是属于这类的input输入设备的设备驱动源代码,可以理解为input输入子系统的下层。

input目录下的evdev.c、joydev.c、mousedev.c…分别对应上层的各个不同的handler的源代码。

1、输入核心层模块注册函数input_init

在Linux中实现为一个模块的方法,所以可以在内核配置的进行动态的加载和卸载,这样做的原由是,存在有些系统中不需要任何的输入类设备,这样就可以将input输入子系统这个模块去掉(上层也是实现为模块的),使得内核尽量变得更小。

static int __init input_init(void)
{
    int err;

    input_init_abs_bypass();

    err = class_register(&input_class);                //  创建设备类    /sys/class/input
    if (err) {
        printk(KERN_ERR "input: unable to register input_dev class\n");
        return err;
    }

    err = input_proc_init();           //    proc文件系统相关的初始化
    if (err)
        goto fail1;

    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);       //   注册字符设备驱动   主设备号13   input_fops 中只实现了open函数,所以他的原理其实和misc其实是一样的
    if (err) {
        printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
        goto fail2;
    }

    return 0;

 fail2:    input_proc_exit();
 fail1:    class_unregister(&input_class);
    return err;
}

调用register_chrdev注册了住设备号为13,次设备号为0~255的字符设备。它的操作指针为input_fops。在这里,可以看到所有主设备号13的字符设备的操作最终都会转入到input_fops中。例如/dev/input/event0等的主设备号都为13,对其的操作都会落在input_fops中。
(1)input_proc_init函数

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,  /* 在/proc/bus/input/目录下创建devices文件 */
                &input_devices_fileops);
    if (!entry)
        goto fail1;

    entry = proc_create("handlers", 0, proc_bus_input_dir, /* 在/proc/bus/input/目录下创建handlers文件 */
                &input_handlers_fileops);
    if (!entry)
        goto fail2;

    return 0;

 fail2:    remove_proc_entry("devices", proc_bus_input_dir);
 fail1: remove_proc_entry("bus/input", NULL);
    return -ENOMEM;
}

当我们启动系统之后进入到proc文件系统中,确实可以看到在/proc/bus/input/目录下有两个文件devices和handlers,这两个文件就是在这里被创建的。我们cat devices 和 cat handlers时对应的操作方法(show)就被封装在input_devices_fileops和input_handlers_fileops结构体中。

(2)input_fops变量
在这里插入图片描述

static int input_open_file(struct inode *inode, struct file *file)
{
    struct input_handler *handler;                                                //  定义一个input_handler指针
    const struct file_operations *old_fops, *new_fops = NULL;   //  定义两个file_operations指针
    int err;

    err = mutex_lock_interruptible(&input_mutex);
    if (err)
        return err;

    /* No load-on-demand here? */
    handler = input_table[iminor(inode) >> 5];         //  通过次设备号在 input_table  数组中找到对应的 handler 
    if (handler)
        new_fops = fops_get(handler->fops);           //  将handler 中的fops 指针赋值给 new_fops

    mutex_unlock(&input_mutex);

    /*
     * That's _really_ odd. Usually NULL ->open means "nothing special",
     * not "no device". Oh, well...
     */
    if (!new_fops || !new_fops->open) {
        fops_put(new_fops);
        err = -ENODEV;
        goto out;
    }

    old_fops = file->f_op;           //   将 file->fops 先保存到 old_fops 中,以便出错时能够恢复
    file->f_op = new_fops;          //   用new_fops 替换 file 中 fops 

    err = new_fops->open(inode, file);       //  执行 file->open  函数
    if (err) {
        fops_put(file->f_op);
        file->f_op = fops_get(old_fops);
    }
    fops_put(old_fops);
out:
    return err;
}

可以知道我们在上层调用open首先调用到的是input_open_file然后再调用对应handler的open函数如evdev_open

在内核版本:3.14.28中

static int __init input_init(void)
{
	int err;

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

	err = input_proc_init();
	if (err)
		goto fail1;

	err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
				     INPUT_MAX_CHAR_DEVICES, "input");
	if (err) {
		pr_err("unable to register char major %d", INPUT_MAJOR);
		goto fail2;
	}

	return 0;

 fail2:	input_proc_exit();
 fail1:	class_unregister(&input_class);
	return err;
}

这里只是调用register_chrdev_region分配了设备号,其是在handler->connect如evdev_connect中调用cdev_init、cdev_add进行操作文件符注册的

六、事件上报

(1)上报的大致过程:设备驱动层->核心层->事件处理层->应用层

(2)具体调用的函数(以evdev为例):
input_event()->input_handle_event() ->input_pass_event() ->handle->handler->event(handle,type, code, value)->evdev_event() ->evdev_pass_event() ,
然后通过client->buffer[client->head++]= *event赋值给上层client(是struct evdev_client)

我们来看一下input_handle_event()这个函数

static void input_handle_event(struct input_dev *dev,  
                   unsigned int type, unsigned int code, int value)  
{  
    int disposition = INPUT_IGNORE_EVENT;  
  
  
    switch (type) {  
        ......  
    case EV_KEY:  
        if (is_event_supported(code, dev->keybit, KEY_MAX) &&  
            !!test_bit(code, dev->key) != value) {  
  
  
            if (value != 2) {  
                __change_bit(code, dev->key);  
                if (value)  
                    input_start_autorepeat(dev, code);  
                else  
                    input_stop_autorepeat(dev);  
            }  
            disposition = INPUT_PASS_TO_HANDLERS;  
        }  
        break;  
        ......  
    if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)  
        dev->sync = 0;  
  
  
    if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)  
        dev->event(dev, type, code, value);  
  
  
    if (disposition & INPUT_PASS_TO_HANDLERS)  
        input_pass_event(dev, type, code, value);  
}  

首先判断disposition 是否等于 INPUT_PASS_TO_DEVICE,然后判断dev->event是否对其指定了一个处理函数,如果条件都满足,则调用自定义的dev->event()函数处理事件。有些事件是发送给设备的,而不是发送给handler处理的。event()函数用来向输入子系统报告一个将要发送给设备的事件,例如让LED灯点亮事件、蜂鸣器鸣叫事件等。当事件报告给输入子系统后,就要求设备处理这个事件。
否则会调用input_pass_event()这个函数,这个函数最终会调用handler->event函数进行事件的处理

static void input_pass_event(struct input_dev *dev,  
                 unsigned int type, unsigned int code, int value)  
{  
    struct input_handle *handle;  
  
  
    rcu_read_lock();  
  
  
    handle = rcu_dereference(dev->grab);  //如果是绑定的handle,则调用绑定的handler->event函数   
    if (handle)  
        handle->handler->event(handle, type, code, value);  
    else  
        //如果没有绑定,则遍历dev的h_list链表,寻找handle,如果handle已经打开,说明有进程读取设备关联的evdev。   
        list_for_each_entry_rcu(handle, &dev->h_list, d_node)  
            if (handle->open)  
                handle->handler->event(handle,  
                            type, code, value);  
        // 调用相关的事件处理器的event函数,进行事件的处理   
    rcu_read_unlock();  
}  

所以我们可以在input子系统的设备驱动函数中指定事件处理函数,例如让LED灯点亮事件、蜂鸣器鸣叫事件等。
input子系统让LED点灯

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值