如何理解linux input输入子系统

何为input输入子系统?

linux系统支持的输入设备繁多,比如鼠标,键盘,游戏杆,触摸屏等,在这些输入设备中种类繁多,类型不一,不同原理、不同的输入信息,那么问题来了,如何管理这些信息呢?
答案就是:input输入子系统就是完成这套软件体系的。

input子系统分为3层上层(输入事件驱动层)中层(输入核心层)下层(输入设备驱动层)
在这里插入图片描述
在这里插入图片描述
从图中可以看出:
1.Drivers对应输入设备驱动层,对应着各种各样不同的输入设备
2.Input core对应的是中间层输入核心层,起到承上启下的作用,使得上层和下层之间能够完成数据传递
3.Handlers对应的就是上层输入事件驱动层,最右边的代表的是用户空间

不同的输入设备供给上层的接口不同,如下:

ubuntu@ubuntu-Lenovo:/dev/input$ ls
by-id  by-path  event0  event1  event10  event11  event12  event2  event3  event4  event5  event6  event7  event8  event9  js0  mice  mouse0

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

目前的设备界面可操控的自由度贼多,比如,android 手机的触摸屏,如果支持otg的话,还可以接入鼠标,键盘,那么这些不同种类的输入设备所上报的数据类型对于应用层来说是预先不知道的,input子系统解决了不同的输入类设备的输入事件与应用层之间的数据传输,使得应用层能够获取到各种不同的输入设备的输入事件,input输入子系统能够囊括所有的不同种类的输入设备,在应用层都能够感知到所有发生的输入事件。

input输入子系统如何工作?

举个栗子:
手机触摸屏,当我们手指点击到屏幕的时候,会触发一个中断给到soc,soc去读取触摸芯片的数据,从而解析出手指点击的位置,然后将该信息上报给input core层 —> input core层处理好了之后就会上报给input event层,在这里会将我们的输入事件封装成一个input_event结构体放入一个缓冲区中 —> 应用层read就会将缓冲区中的数据读取出去,然后应用层去做相应的响应操作。

相关的数据结构

input core的代码实现在input.c中
input 事件驱动层(上层)的代码实现在evdev.c中
input 设备驱动层(下层)的代码一般会有供应商提供

struct input_dev {
    const char *name;             //  input设备的名字
    const char *phys;              //  
    const char *uniq;              //
    struct input_id id;             //  

//  这些是用来表示该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)];

    unsigned int keycodemax;
    unsigned int keycodesize;
    void *keycode;
    int (*setkeycode)(struct input_dev *dev,
              unsigned int scancode, unsigned int keycode);
    int (*getkeycode)(struct input_dev *dev,
              unsigned int scancode, unsigned int *keycode);

    struct ff_device *ff;

    unsigned int repeat_key;
    struct timer_list timer;

    int sync;

    int abs[ABS_CNT];
    int rep[REP_MAX + 1];

    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 absmax[ABS_CNT];
    int absmin[ABS_CNT];
    int absfuzz[ABS_CNT];
    int absflat[ABS_CNT];
    int absres[ABS_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);     //  上报事件

    struct input_handle *grab;

    spinlock_t event_lock;
    struct mutex mutex;

    unsigned int users;
    bool going_away;

    struct device dev;                 //  内置的device结构体变量

    struct list_head    h_list;    //  用来挂接input_dev 设备连接的所有handle 的一个链表头
    struct list_head    node;    //  作为链表节点挂接到  input_dev_list 链表上  (input_dev_list链表是input核心层维护的一个用来挂接所有input设备的一个链表头)
};
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的链表头)
};
struct input_handle {

    void *private;               //   handle  的私有数据

    int open;                     //  这个也是用来做打开计数的
    const char *name;       //   该handle 的名字

    struct input_dev *dev;                //  用来指向该handle 绑定的input_dev 结构体
    struct input_handler *handler;    //  用来指向该handle 绑定的 handler 结构体

    struct list_head    d_node;      //  作为一个链表节点挂接到与他绑定的input_dev ->hlist 链表上
    struct list_head    h_node;      //  作为一个链表节点挂接到与他绑定的handler->hlist 链表上
};
struct input_device_id {

    kernel_ulong_t flags;    //  这个flag 表示我们的这个 input_device_id 是用来匹配下面的4个情况的哪一项
                                    //  flag == 1表示匹配总线  2表示匹配供应商   4表示匹配产品  8表示匹配版本
    __u16 bustype;
    __u16 vendor;
    __u16 product;
    __u16 version;

    kernel_ulong_t evbit[INPUT_DEVICE_ID_EV_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t keybit[INPUT_DEVICE_ID_KEY_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t relbit[INPUT_DEVICE_ID_REL_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t absbit[INPUT_DEVICE_ID_ABS_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t mscbit[INPUT_DEVICE_ID_MSC_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t ledbit[INPUT_DEVICE_ID_LED_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t sndbit[INPUT_DEVICE_ID_SND_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t ffbit[INPUT_DEVICE_ID_FF_MAX / BITS_PER_LONG + 1];
    kernel_ulong_t swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];

    kernel_ulong_t driver_info;
};

事件上报流程

设备驱动层

接下来我将从设备驱动层介绍下整个流程
核心层提供给设备驱动层的接口函数主要有3个:
1.input_dev = input_allocate_device()分配input_dev结构内存,初始化链表
2.调用input_register_devices去向系统注册一个input设备,将该设备添加到input_dev_list全局链表中。
3.调用input_report_key, input_report_abs等接口去上报事件和数据。

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

    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);   //  申请分配内存
    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 链表初始化
		dev_set_name(&dev->dev, "input%lu",
			     (unsigned long)atomic_inc_return(&input_no));

		__module_get(THIS_MODULE);
	}
	return dev;
}
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;
}
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_KEY, code, !!value);
}
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_REL, code, value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{
	input_event(dev, EV_ABS, code, value);
}

在这里插入图片描述

input core

接下来来到了input core中的处理
在input_register_device中,会调用
// 遍历input_handler_list 链表上的所有handler
list_for_each_entry(handler, &input_handler_list, node)
// 将handler与input设备进行匹配
input_attach_handler(dev, 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)
        printk(KERN_ERR
            "input: failed to attach handler %s to device %s, "
            "error: %d\n",
            handler->name, kobject_name(&dev->dev.kobj), error);

    return error;
}



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

    for (id = handler->id_table; id->flags || id->driver_info; id++) {  //  依次遍历handler->id_table 所指向的input_device_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;

    //    下面的这些是匹配我们上传的事件是否属实
        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 中的 connect 函数
handler->connect(handler, dev, id);
这里就会回调到输入事件驱动层(上层)中,这里后面介绍

在input.c中

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

核心层提供给事件驱动层的接口函数

在input输入核心层向事件驱动层提供的接口主要有两个:
input_register_handler。事件驱动层向核心层注册handler
input_register_handle。事件驱动层向核心层注册handle, handler->connect(handler, dev, id)回调到此处。
注意上面的是handler,这里是handle,不一样,后面会说到。

输入事件驱动层

在evdev.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);
}

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

(1)input_register_handler函数://在input core中实现

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 都是对应鼠标这个设备。

(2)input_register_handle函数

回调到此处 .connect = evdev_connect,
在evdev_connect中向input core注册input_register_handle

这个函数的作用就是注册一个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的信息。这种运用方法将会在后面的分析中看到。

总结

(1)其实下层可以上报的事件都在我们的内核中是定义好的,我们都可以上报这些事,但是input子系统的上层输入事件驱动层的各个handler只能够处理某一些事件(event除外),

例如joy handler只能处理摇杆类型的事件,key handler只能处理键盘,内部实现的原理就是会在核心层做handler和device匹配的过程。如果我们的上报的事件与多个handler都

能够匹配成功,那么绑定之后核心层会向这多个handler都上报事件,再由handler上报给应用层。

(2)input设备注册的流程:

下层通过调用核心层的函数来向子系统注册input输入设备


input_register_device
device_add: /sys/devices/virtual/input/input0
链表挂接: input_dev->node -------> input_dev_list
input_attach_handler // 进行input_dev和handler之间的匹配
调用handler->connect进行连接
构建evdev结构体,加入evdev_table数组
input_register_handle
device_add: /sys/devices/virtual/input/input0/event0


(3)handler注册流程


input_register_handler
input_table[handler->minor >> 5] = handler
链表挂接: handler->node -----> input_handler_list
input_attach_handler
handler->connect // 调用handler的connect函数进行连接


(4)事件如何传递到应用层


input子系统下层通过调用input_event函数项核心层上报数据
input_event
–》input_handle_event
-----》input_pass_event
--------》 handler->event() // 最终会调用到handler 中的event函数
-----------》evdev_pass_event
client->buffer[client->head++] = *event; // 会将input输入事件数据存放在evdev_client结构体中的缓冲去中

当我们的应用层通过open打开event0这个设备节点时最终会调用到input_init函数中注册的字符设备input时注册的file_operations->open() 函数
input_open_file
handler = input_table[iminor(inode) >> 5]
handler->fops->open()
evdev = evdev_table[i];
evdev_open_device
input_open_device
input_dev->open() // 最终就是执行input设备中的open函数
file->private_data = evdev_client;

所以当我们在应用层调用read函数时,最终会调用到handler->fops->read函数
evdev_read
evdev_fetch_next_event
*event = client->buffer[client->tail++] // 将evdev_client->buffer中的数据取走
input_event_to_user
copy_to_user // 拷贝到用户空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

linux顿悟吧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值