• Linux驱动:输入子系统(input-subsystem) 分析


    Linux驱动:输入子系统 分析

    参考:

    介绍

    设计背景

    以前我们写一些输入设备(键盘、鼠标等)的驱动都是采用字符设备、混杂设备处理的。问题由此而来,Linux开源社区的大神们看到了这大量输入设备如此分散不堪,实现了一种机制,可以对分散的、不同类别的输入设备进行统一的驱动,这也就是输入子系统。

    Linux内核为了能够处理各种不同类型的输入设备,(比如 触摸屏 ,鼠标 , 键盘 , 操纵杆 ),设计并实现了为驱动层程序的实现提供统一接口函数;为上层应用提供试图统一的抽象层 , 即是Linux 输入子系统 。

    例如,在终端系统中,我们不需要去管有多少个键盘,多少个鼠标。应用程序只要从输入子系统中去取对应的事件(按键,鼠标移位等)就可以了。而底层设备也不需要直接对接应用程序,只要处理并处理对应的事件即可。

    引入输入子系统的好处:

    • 统一了物理形态各异的相似的输入设备的处理功能。例如,各种鼠标,不论PS/2、USB、还是蓝牙,都被同样处理。
    • 提供了用于分发输入报告给用户应用程序的简单的事件(event)接口。你的驱动不必创建、管理/dev节点以及相关的访问方法。因此它能够很方便的调用输入API以发送鼠标移动、键盘按键,或触摸事件给用户空间。X windows这样的应用程序能够无缝地运行于输入子系统提供的event接口之上。
    • 抽取出了输入驱动的通用部分,简化了驱动,并提供了一致性。例如,输入子系统提供了一个底层驱动(成为serio)的集合,支持对串口和键盘控制器等硬件输入的访问。

    一句话概括,输入子系统是所有I/O设备驱动与应用程序之间的中间层。

    框架

    linux输入子系统(linux input subsystem)由三层实现,分别为:

    • 输入子系统 设备驱动层(Driver):

    对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。

    将底层的硬件输入转化为统一事件形式,向输入核心(Input Core)汇报。

    • 输入子系统 核心层(Input Core):

    对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。

    它承上启下:

    • 为驱动层提供输入设备注册与操作接口,如:input_register_device;
    • 通知事件处理层对事件进行处理;在/proc下产生相应的设备信息。
    • 输入子系统 事件处理层(Event Handler)。

    对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。

    主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。

    /dev/input/下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

    事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

    输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

    由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

    内核:v3.10

    核心层Input Core

    路径:deivers/input/input.c

    核心层 实现了:

    • 在入口函数中申请主设备号,注册进内核
    • 提供input_register_device用于注册deviceinput_register_handler函数注册handler处理器;
    • 提供input_register_handle函数用于注册一个事件处理,代表一个成功配对的input_dev和input_handler;

    入口函数

    申请主设备号的事情就是input_init完成的;毕竟,输入子系统是作为一个驱动模块存在。

    // include/uapi/linux/major.h:29:#define INPUT_MAJOR		13
    
    static int __init input_init(void)
    {
        int err;
    
        err = class_register(&input_class); //(1)注册类,放在/sys/class
        if (err) {
            pr_err("unable to register input_dev class
    ");
            return err;
        }
    
        err = input_proc_init(); //在/proc下面建立相关的文件
        if (err)
            goto fail1;
    
        err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),
                         INPUT_MAX_CHAR_DEVICES, "input"); // //(2)注册驱动,主设备号为13
        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;
    }
    
    // ... ...
    
    subsys_initcall(input_init);
    module_exit(input_exit);
    

    注册class

    class_register时用到了这个类,最终会在/sys/class/input中出现。

    struct class input_class = {
        .name       = "input",
        .devnode    = input_devnode,
    };
    

    至于input core只注册了一个类,而没有继续注册对应的设备;是因为:

    核心层作为一个中转层存在,不涉及具体硬件设备的注册,倒是更符合他存在的逻辑。

    设备的注册到底会在Input driver 还是Event hanlder呢?

    注册proc

    这里用到了proc注册的接口

    static const struct file_operations input_handlers_fileops = {
        .owner      = THIS_MODULE,
        .open       = input_proc_handlers_open,
        .read       = seq_read,
        .llseek     = seq_lseek,
        .release    = seq_release,
    };
    
    static int __init input_proc_init(void)
    {
        struct proc_dir_entry *entry;
    
        proc_bus_input_dir = proc_mkdir("bus/input", NULL);
        if (!proc_bus_input_dir)
            return -ENOMEM;
    
        entry = proc_create("devices", 0, proc_bus_input_dir,
                    &input_devices_fileops);
        if (!entry)
            goto fail1;
    
        entry = proc_create("handlers", 0, proc_bus_input_dir,
                    &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子系统中进行处理了。

    接口函数

    功能 接口
    设备驱动层 向核心层注册一个输入设备input device input_register_device
    事件处理层 注册一个输入事件处理器 input handler input_register_handler
    事件处理层 向内核注册一个handle结构 input_register_handle

    device与handler匹配

    input core 层在中间,承上启下完成了 device 与 handler的匹配。

    我们看看是怎么实现的。

    驱动层注册device

    input_dev对象

    struct input_dev {      
    
        void *private;          //输入设备私有指针,一般指向用于描述设备驱动层的设备结构  
        const char *name;  // 提供给用户的输入设备的名称  
        const char *phys;  // 提供给编程者的设备节点的名称  文件路径,比如 input/buttons
        const char *uniq;  // 指定唯一的ID号,就像MAC地址一样
        struct input_id id;//输入设备标识ID,用于和事件处理层进行匹配  
    
        unsigned long evbit[NBITS(EV_MAX)];      //位图,记录设备支持的事件类型(可以多选)
        /* 
             *  #define EV_SYN          0x00    //同步事件 
             *  #define EV_KEY          0x01    //按键事件 
             *  #define EV_REL          0x02    //相对坐标 
             *  #define EV_ABS          0x03    //绝对坐标 
             *  #define EV_MSC          0x04    //其它 
             *  #define EV_SW           0x05    //开关事件 
             *  #define EV_LED          0x11    //LED事件 
             *  #define EV_SND          0x12 
             *  #define EV_REP          0x14    //重复上报 
             *  #define EV_FF           0x15 
             *  #define EV_PWR          0x16 
             *  #define EV_FF_STATUS    0x17 
             *  #define EV_MAX          0x1f 
             */ 
    
        unsigned long keybit[NBITS(KEY_MAX)];    //位图,记录设备支持的按键类型  
        unsigned long relbit[NBITS(REL_MAX)];    //位图,记录设备支持的相对坐标  
        unsigned long absbit[NBITS(ABS_MAX)];    //位图,记录设备支持的绝对坐标  
        unsigned long mscbit[NBITS(MSC_MAX)];    //位图,记录设备支持的其他功能  
        unsigned long ledbit[NBITS(LED_MAX)];    //位图,记录设备支持的指示灯  
        unsigned long sndbit[NBITS(SND_MAX)];    //位图,记录设备支持的声音或警报  
        unsigned long ffbit[NBITS(FF_MAX)];      //位图,记录设备支持的作用力功能  
        unsigned long swbit[NBITS(SW_MAX)];      //位图,记录设备支持的开关功能 
    
        unsigned int keycodemax;                //设备支持的最大按键值个数  
        unsigned int keycodesize;               //每个按键的字节大小  
        void *keycode;                          //指向按键池,即指向按键值数组首地址  
        int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);        //修改按键值  
        int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);       //获取按键值  
    
        struct ff_device *ff;                          
    
        unsigned int repeat_key;                //支持重复按键  
        struct timer_list timer;                //设置当有连击时的延时定时器  
    
        int state;                  
    
        int sync;       //同步事件完成标识,为1说明事件同步完成  
    
        int abs[ABS_MAX + 1];                //记录坐标的值  
        int rep[REP_MAX + 1];                //记录重复按键的参数值  
    
        unsigned long key[NBITS(KEY_MAX)];   //位图,按键的状态  
        unsigned long led[NBITS(LED_MAX)];   //位图,led的状态  
        unsigned long snd[NBITS(SND_MAX)];   //位图,声音的状态  
        unsigned long sw[NBITS(SW_MAX)];     //位图,开关的状态  
    
        int absmax[ABS_MAX + 1];             //位图,记录坐标的最大值  
        int absmin[ABS_MAX + 1];             //位图,记录坐标的最小值  
        int absfuzz[ABS_MAX + 1];            //位图,记录坐标的分辨率  
        int absflat[ABS_MAX + 1];            //位图,记录坐标的基准值  
    
        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 *grab;              
    
        struct mutex mutex;                     //用于open、close函数的连续访问互斥  
        unsigned int users;                  
    
        struct class_device cdev;               //输入设备的类信息  
        union {                                 //设备结构体  
            struct device *parent;  
        } dev;  
    
        struct list_head        h_list;         //handle链表  
        struct list_head        node;           //input_dev链表  
    };  
    

    input_register_device

    还记得之前提到过的:在Linux驱动中使用input子系统,一般的驱动使用输入子系统只需要使用以下的接口。

    struct input_dev *input_allocate_device(void);
    // 分配输入设备函数
    
    int input_register_device(struct input_dev *dev);
    // 注册输入设备函数
    
    void input_unregister_device(struct input_dev *dev);
    // 注销输入设备函数
    
    void __set_bit();
    // 事件支持(初始化),告诉input输入子系统支持哪些事件,哪些按键
    
    
    void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
    // 在发生输入事件时,向子系统报告事件。
    
    void input_sync();
    // 同步用于告诉input core子系统报告结束
    

    其中,涉及到匹配的有关函数就是input_register_device

    /**
     * input_register_device - register device with input core
     * @dev: device to be registered
     *
     * This function registers device with input core. The device must be
     * allocated with input_allocate_device() and all it's capabilities
     * set up before registering.
     * If function fails the device must be freed with input_free_device().
     * Once device has been successfully registered it can be unregistered
     * with input_unregister_device(); input_free_device() should not be
     * called in this case.
     *
     * Note that this function is also used to register managed input devices
     * (ones allocated with devm_input_allocate_device()). Such managed input
     * devices need not be explicitly unregistered or freed, their tear down
     * is controlled by the devres infrastructure. It is also worth noting
     * that tear down of managed input devices is internally a 2-step process:
     * registered managed input device is first unregistered, but stays in
     * memory and can still handle input_event() calls (although events will
     * not be delivered anywhere). The freeing of managed input device will
     * happen later, when devres stack is unwound to the point where device
     * allocation was made.
     */
    int input_register_device(struct input_dev *dev)
    {
        static atomic_t input_no = ATOMIC_INIT(0);
        struct input_devres *devres = NULL;
        struct input_handler *handler;
        unsigned int packet_size;
        const char *path;
        int error;
    
        if (dev->devres_managed) {
            devres = devres_alloc(devm_input_device_unregister,
                          sizeof(struct input_devres), GFP_KERNEL);
            if (!devres)
                return -ENOMEM;
    
            devres->input = dev;
        }
    
        /* 默认所有的输入设备都支持EV_SYN同步事件 */
        __set_bit(EV_SYN, dev->evbit);
    
        /* 阻止 KEY_RESERVED 事件传递到用户空间 */
        __clear_bit(KEY_RESERVED, dev->keybit);
    
        /* 确保 没有用到的掩码 是空的 */
        input_cleanse_bitmasks(dev);
    
        packet_size = input_estimate_events_per_packet(dev);
        if (dev->hint_events_per_packet < packet_size)
            dev->hint_events_per_packet = packet_size;
    
        dev->max_vals = max(dev->hint_events_per_packet, packet_size) + 2;
        dev->vals = kcalloc(dev->max_vals, sizeof(*dev->vals), GFP_KERNEL);
        if (!dev->vals) {
            error = -ENOMEM;
            goto err_devres_free;
        }
    
        /*
         * 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;
        }
        
        /* 没有定义设备的getkeycode函数,则使用默认的获取键值函数 */
        if (!dev->getkeycode)
            dev->getkeycode = input_default_getkeycode;
        /*没有定义设备的setkeycode函数,则使用默认的设定键值函数*/
        if (!dev->setkeycode)
            dev->setkeycode = input_default_setkeycode;
    
        dev_set_name(&dev->dev, "input%ld",
                 (unsigned long) atomic_inc_return(&input_no) - 1);
        
    	/* 重点1:添加设备 */
        error = device_add(&dev->dev);
        if (error)
            goto err_free_vals;
        
        /* 获取并打印设备的绝对路径名称 */  
        path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
        pr_info("%s as %s
    ",
            dev->name ? dev->name : "Unspecified device",
            path ? path : "N/A");
        kfree(path);
    
        error = mutex_lock_interruptible(&input_mutex);
        if (error)
            goto err_device_del;
        
        /* 重点2: 把设备挂到全局的input子系统设备链表 input_dev_list 上 */  
        list_add_tail(&dev->node, &input_dev_list);
    
       /*  重点3: nput设备在增加到input_dev_list链表上之后,会查找 
        * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 
        * 方式与设备模型的device和driver匹配过程很相似,所有的input devicel
        * 都挂在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);
    
        if (dev->devres_managed) {
            dev_dbg(dev->dev.parent, "%s: registering %s with devres.
    ",
                __func__, dev_name(&dev->dev));
            devres_add(dev->dev.parent, devres);
        }
        return 0;
    
    err_device_del:
        device_del(&dev->dev);
    err_free_vals:
        kfree(dev->vals);
        dev->vals = NULL;
    err_devres_free:
        devres_free(devres);
        return error;
    }
    EXPORT_SYMBOL(input_register_device);
    

    上面的函数实现了:

    • 添加设备
    • 把输入设备挂到输入设备链表input_dev_list
    • 遍历input_handler_list链表,查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用handlerconnnect函数进行连接。

    设备就是在此时注册的,下面分析handler就清晰了。 (input_attach_handler放到分析handler时再做讲解,更容易理解。)

    事件层注册handler

    事件层通过input_register_handler来完成注册。

    不同的事件处理层主要是用来支持输入设备并与用户空间交互,这部分代码一般由供应商编写,不需要我们自己去编写。

    因为Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如Evdev.c、mousedev.c、joydev.c等。

    对于Event handler:根据事件注册一个handler,将handler挂到链表input_handler_list下,然后遍历input_dev_list链表。查找并匹配输入设备对应的事件处理层,如果匹配上了,就调用connect函数进行连接,并创建input_handle结构。

    Linux中默认的事件层不多;默认地,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册(input_register_handler)到input子系统中。

    $ cd drivers/input
    $ grep -w -nR  "input_register_handler"
    misc/keychord.c:301:	ret = input_register_handler(&kdev->input_handler);
    keycombo.c:225:	ret = input_register_handler(&state->input_handler);
    apm-power.c:113:	return input_register_handler(&apmpower_handler);
    evbug.c:110:	return input_register_handler(&evbug_handler);
    evdev.c:1117:	return input_register_handler(&evdev_handler);
    joydev.c:947:	return input_register_handler(&joydev_handler);
    input.c:2155: * input_register_handler - register a new input handler
    input.c:2162:int input_register_handler(struct input_handler *handler)
    input.c:2183:EXPORT_SYMBOL(input_register_handler);
    mousedev.c:1107:	error = input_register_handler(&mousedev_handler);
    

    下面,我们以evdev.c(事件驱动)为例子进行讲解。

    evdev.c实现了对evdev的管理,根据Docuentation/input/input.txt的描述,evdev is the generic input event interface. It passes the events generated in the kernel straight to the program, with timestamps.

    因此,evdev只是对输入设备这一类设备的管理,并不涉及具体如鼠标、键盘以及游戏摇杆等设备的管理,但是驱动中完全可以使用关于evdev的API直接给用户空间程序上报事件。

    input_handler对象

    // include/linux/input.h
    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;
    };
    

    input_register_handler

    事件处理层的处理器会调用input_register_handler来注册。

    // drivers/input/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);
    }
    
    module_init(evdev_init);
    module_exit(evdev_exit);
    

    evdev_init直接调用input_register_handler 注册一个evdev_handler的input_handler对象。

    /**
     * input_register_handler - register a new input handler
     * @handler: handler to be registered
     *
     * This function registers a new input handler (interface) for input
     * devices in the system and attaches it to all input devices that
     * are compatible with the handler.
     */
    int input_register_handler(struct input_handler *handler)
    {
        struct input_dev *dev;
        int error;
    
        error = mutex_lock_interruptible(&input_mutex);
        if (error)
            return error;
    
        INIT_LIST_HEAD(&handler->h_list);
    
        /* 重点1 : 把设备处理器挂到全局的input子系统设备链表input_handler_list上 */
        list_add_tail(&handler->node, &input_handler_list);
    
        /* 重点2 : 遍历input_dev_list,试图与每一个input_dev进行匹配 */
        list_for_each_entry(dev, &input_dev_list, node)
            input_attach_handler(dev, handler);
    
        input_wakeup_procfs_readers();
    
        mutex_unlock(&input_mutex);
        return 0;
    }
    EXPORT_SYMBOL(input_register_handler);
    

    这个注册过程和上面驱动层注册device(input_register_device)的过程非常相识。

    注册过程中的匹配判断

    你注意到了吗,无论是驱动层还是事件层,它们各自在向core注册的时候,都会调用到一个input_attach_handler

    我们看看input_attach_handler是如何匹配dev与handler的。

    全局变量

    在device与handler注册的时候,都用到了下面的两个链表,其实这两个链表是用来俩俩匹配的。

    static LIST_HEAD(input_dev_list);
    static LIST_HEAD(input_handler_list);
    

    对于handler和device,分别用链表input_handler_listinput_dev_list进行维护。当handler或者device增加或减少的时候,分别往这两链表增加或删除节点,这两条都是全局链表。

    input_attach_handler

    实际上就做2个事情,先match(匹配),再connect(连接)

    static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
    {
        const struct input_device_id *id;
        int error;
    
        /* 利用handler->id_table和dev进行匹配 */
        id = input_match_device(handler, dev);
        if (!id)
            return -ENODEV;
        
        /*匹配成功,则调用handler->connect函数进行连接*/
        error = handler->connect(handler, dev, id);
        if (error && error != -ENODEV)
            pr_err("failed to attach handler %s to device %s, error: %d
    ",
                   handler->name, kobject_name(&dev->dev.kobj), error);
    
        return error;
    }
    
    input_match_device

    还记得吗,struct input_dev中也有一个struct input_id id。只是我们当时没有提到。 id用于匹配条件中,对于属性的要求。

    设备和handler的匹配条件:只要handler的id中evbit、keybit等等中的某一位设置了,input设备也得具备这个条件。

    简单来说,必须具有相同ID属性的devive和handler才能匹配在一起。

    //kernel/include/linux/mod_devicetable.h
    struct input_device_id {
    
        kernel_ulong_t flags;
    
        __u16 bustype;
        // ...
        __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 swbit[INPUT_DEVICE_ID_SW_MAX / BITS_PER_LONG + 1];
    
        kernel_ulong_t driver_info;
    }; // 与设备匹配的要求。
    
    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_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 (!handler->match || handler->match(handler, dev))
                return id;
        }
    
        return NULL;
    }
    
    evdev_ids

    由于 evdev.c 中的evdev_ids只有一个成员初始化,其他都为0(NULL):

    // drivers/input/evdev.c
    static const struct input_device_id evdev_ids[] = {
        { .driver_info = 1 },   /* Matches all devices */
        { },            /* Terminating zero entry */
    };
    

    因此,上面的if中,只有最后一个if满足条件,所以最后会以!handler->match而返回这个id。

    这也就是为什么注释中说的,evdev.c 中的 handler 可以适用于任何的 devices(Matches all devices);因为evdev对device的要求很低。

    handler->connect

    对于evdev.c中的handler所对应的connect接口是evdev_connect

    evdev_connect()主要是创建一个新的event设备,相应的设备节点文件是/dev/input/eventX

    1、要注册一个新的evdev设备,首先要获取一个次设备号(之前说过,主设备号是13),失败便不再往下执行。

    成功以后,这个次设备号还会作为一个相对值dev_no,用于决定/dev/input/eventX中的X的值。

    2、一个evdev可以有多个client,所以初始化client_list。

    3、而操作client的时候需要互斥,所以初始化client_lock,等待队列用于实现用户空间读取时的阻塞等待,exist成员置为true表示新的evdev已经存在,minor是新的evdev在evdev_table中的索引,而设备节点文件/dev/input/eventx中的x就是这里minor决定的。

    4、另外一项重要工作就是用handle绑定一组匹配成功的input_devinput_handler

    只要是input_devinput_handler匹配了,handle中的handler成员指向了配对成功的handler:dev成员指向了配对成功的device,而private成员则指向了evdev设备本身。

    struct input_handle {
    
        void *private;
    
        int open;
        const char *name;
    
        struct input_dev *dev;         // 匹配成功的 一对 dev-handler中的 dev
        struct input_handler *handler; // 匹配成功的 一对 dev-handler中的 handler
    
        struct list_head    d_node;
        struct list_head    h_node;
    };
    

    通过上文我们知道,链表input_handler_listinput_dev_list保存了handlerdevice,这两条链表都是全局链表。

    那么input_hande 保存在哪里?实际上,而input_hande 没有一个全局的链表。它注册的时候将自己分别挂在了input_dev_listinput_handler_listh_list上了;

    从此,建立好了三者的铁三角关系,通过input_handler_listinput_dev_list以及input_hande 中任何一方,都可以找到彼此。

    最终:设备(/dev/input/eventX)被创建,我们最终就能够通过基于struct file_operationsopenread等方式在应用层获取数据。

    evdev_connect

    对于connect函数,每种事件处理器的实现都有差异,但原理都相同。

    我们结合evdev.c中的connect看看是不是这样子的:

    static struct input_handler evdev_handler = {
        // ...
        .connect    = evdev_connect,
        // ...
    };
    
    /*
     * 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);
        
        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    
        /* 初始化client_list列表和evdev_wait队列 */
        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;
        
        /*设置设备节点名称,eventX 就是在此时设置 */
        dev_set_name(&evdev->dev, "event%d", dev_no);
    
        /* 重点:初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */
        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);
    
        /* input_dev设备驱动和handler事件处理层的关联,就在这时由handle完成 */
        error = input_register_handle(&evdev->handle);
    
        cdev_init(&evdev->cdev, &evdev_fops);
        evdev->cdev.kobj.parent = &evdev->dev.kobj;
        error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
        
        /* 将设备加入到Linux设备模型,它的内部将找到它的bus,然后让它的bus
        给它找到它的driver,在驱动或者总线的probe函数中,一般会在/dev/目录
        先创建相应的设备节点,这样应用程序就可以通过该设备节点来使用设备了,
        /dev/eventX 设备节点就是在此时生成
        */
        error = device_add(&evdev->dev);
    
        return 0;
    }
    

    事件层fops代码解析

    fops接口在输入子系统中由事件处理层完成。

    当应用程序执行open("/dev/input/eventX", O_RDWR)时等调用的时候,系统调用经过文件系统一系列操作后就会执行file_operations中的成员函数。

    这些函数会从事件处理层到input core层再到驱动程序的input_dev对应的方法(例如open)依次执行下去。

    evdev.c为例:

    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,
        .flush      = evdev_flush,
        .llseek     = no_llseek,
    };
    

    这些file_operations对于子系统都比较重要。

    evdev_open

    evdev_open函数代表注册一个client,此后准备接收数据。

    static int evdev_open(struct inode *inode, struct file *file)
    {
        // 首先,是找到 evdev设备
        struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
        unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
        unsigned int size = sizeof(struct evdev_client) +
                        bufsize * sizeof(struct input_event);
        struct evdev_client *client;
        int error;
    
        // 第二,给evdev_client结构分配内存空间。
        client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
        if (!client)
            client = vzalloc(size);
    
        client->clkid = CLOCK_MONOTONIC;
        client->bufsize = bufsize;
        spin_lock_init(&client->buffer_lock);
        snprintf(client->name, sizeof(client->name), "%s-%d",
                dev_name(&evdev->dev), task_tgid_vnr(current));
        
        // 第三,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上
        client->evdev = evdev;
        evdev_attach_client(evdev, client);
    
        // 第四,调用evdev_open_device()
        error = evdev_open_device(evdev);
    
        file->private_data = client;
        // 第五,设置文件的模式
        nonseekable_open(inode, file);
    
        return 0;
    }
    

    找到edev

    第一件事,是找到 evdev设备,获取的方式很kernel:

    struct evdev *evdev = container_of(inode->i_cdev, struct evdev, cdev);
    

    还记得吗,前面evdev_connect中申请了一个evdev对象。并通过cdev_add(&evdev->cdev, evdev->dev.devt, 1);加入到了字符设备中。

    因此,在这里能够通过container_of将其取出。取出以后是为了继续操作。

    evdev的原型是这样子的:

    struct evdev {
        int open;  // open,当用户open此设备时,open的值加1。
        struct input_handle handle; // 包括了匹配的 dev 与 handler
        wait_queue_head_t wait; // 等待队列
        struct evdev_client __rcu *grab; // 可以为evdev实例指定一个struct evdev_client实例,这样在传递Input消息时就只会传递给这一个struct evdev_client实例,而不会传递给所有的struct evdev_client实例。每open一次就会生成一个struct evdev_client实例。
        struct list_head client_list; // 用来把所有的struct client_list实例连接在一起
        spinlock_t client_lock; /* protects client_list */
        struct mutex mutex; // 同步相关的锁
        struct device dev; // dev,用来嵌入到设备模型中。
        struct cdev cdev; //
        bool exist;  // struct evdev被成功实例化后,exist的值就为true
    };
    

    新建evdev_client对象

        client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
        if (!client)
            client = vzalloc(size);
    
        client->clkid = CLOCK_MONOTONIC;
        client->bufsize = bufsize;
        spin_lock_init(&client->buffer_lock);
        snprintf(client->name, sizeof(client->name), "%s-%d",
                dev_name(&evdev->dev), task_tgid_vnr(current));
    
    evdev_client原型

    这里非常有必要说一下evdev_client结构,此结构定义如下:

    struct evdev_client {
        unsigned int head;
        unsigned int tail;
        unsigned int packet_head; /* [future] position of the first element of next packet */
        spinlock_t buffer_lock; /* protects access to buffer, head and tail */
        struct wake_lock wake_lock;
        bool use_wake_lock;
        char name[28];
        struct fasync_struct *fasync;
        struct evdev *evdev;
        struct list_head node;
        int clkid;
        unsigned int bufsize;
        struct input_event buffer[];
    };
    

    可以看到这个结构中多数成员都是关于buffer的。

    这完全可以理解,一个输入设备要上报输入事件,从代码上来说,肯定有存储事件的缓冲区,而且有时候一个设备会上报多个事件,那么我们需要能够存储多个事件的缓冲区。

    bufsize
        unsigned int bufsize = evdev_compute_buffer_size(evdev->handle.dev);
        unsigned int size = sizeof(struct evdev_client) +
                        bufsize * sizeof(struct input_event);
    
    // ---------------------
    static unsigned int evdev_compute_buffer_size(struct input_dev *dev)
    {
        unsigned int n_events =
            max(dev->hint_events_per_packet * EVDEV_BUF_PACKETS,
                EVDEV_MIN_BUFFER_SIZE);
    
        return roundup_pow_of_two(n_events);
    }
    

    bufsize成员:表示当前输入设备的输入事件缓冲区最多可以存放事件的个数。

    值得注意的是,输入事件缓冲区的大小不是静态的,这里采用的是基于柔型数组进行动态申请;

    在计算bufsize的时候,进行向上对齐2的幂。这样子在判断的时候可以做一个小的优化,例如:

    client->head &= client->bufsize - 1; // 相当于 取模%,但比%快。
    

    将client挂接到evdev的链表上

    第三步,将client的evdev指向当前evdev,并且将client挂接到evdev的链表上。

        client->evdev = evdev;
        evdev_attach_client(evdev, client);
    -----------------------------
    static void evdev_attach_client(struct evdev *evdev,
                    struct evdev_client *client)
    {
        spin_lock(&evdev->client_lock);
        list_add_tail_rcu(&client->node, &evdev->client_list);
        spin_unlock(&evdev->client_lock);
    }
    

    既然这里有挂接链表的操作,说明一个evdev可以对应着多个client,这一点在evdev_disconnect()中已经体现:evdev需要遍历自己的client_list,给所有的client发信号。

    static void evdev_disconnect(struct input_handle *handle)
    {
        struct evdev *evdev = handle->private;
    
        device_del(&evdev->dev);
        evdev_cleanup(evdev);
        input_free_minor(MINOR(evdev->dev.devt));
        input_unregister_handle(handle);
        put_device(&evdev->dev);
    }
    
    void input_unregister_handle(struct input_handle *handle)
    {
        struct input_dev *dev = handle->dev;
    
        list_del_rcu(&handle->h_node);
    
        /*
         * Take dev->mutex to prevent race with input_release_device().
         */
        mutex_lock(&dev->mutex);
        list_del_rcu(&handle->d_node);
        mutex_unlock(&dev->mutex);
    
        synchronize_rcu();
    }
    

    一张简单的描述evdev是怎么把client连接起来的图:

    调用evdev_open_device()

    第四步,调用evdev_open_device(),并且将file的private_data初始化为client。

        error = evdev_open_device(evdev);
    
        file->private_data = client;
    

    evdev_open_device()代码如下:

    static int evdev_open_device(struct evdev *evdev)
    {
        int retval;
    
        retval = mutex_lock_interruptible(&evdev->mutex);
    
        if (!evdev->exist)
            retval = -ENODEV;
        else if (!evdev->open++) {
            retval = input_open_device(&evdev->handle);
            if (retval)
                evdev->open--;
        }
    
        mutex_unlock(&evdev->mutex);
        return retval;
    }
    

    此函数比较简单,上锁、检查参数后调用input_open_device(),代码如下:

    int input_open_device(struct input_handle *handle)
    {
        struct input_dev *dev = handle->dev; // 找到 对应的dev,还记得吗,3者可以互相找到对方
        int retval;
    
        retval = mutex_lock_interruptible(&dev->mutex);
    
        if (dev->going_away) {
            retval = -ENODEV;
            goto out;
        }
    
        handle->open++;
    
        if (!dev->users++ && dev->open)
            retval = dev->open(dev);
    
        if (retval) {
            dev->users--;
            if (!--handle->open) {
                /*
                 * Make sure we are not delivering any more events
                 * through this handle
                 */
                synchronize_rcu();
            }
        }
    
     out:
        mutex_unlock(&dev->mutex);
        return retval;
    }
    

    input_open_device()的核心是对handle->open和dev->users成员自增,调用dev->open()。

    将handle的open计数加1,表示此handle已经打开。

    但是我没搞懂这里的逻辑,为什么只有dev->users为零的时候才会调用dev->open()?还有,dev->open是在哪里操作的?dev->open就是我们使用input_register_device()之前自己赋值操作的方法。

    到这里就分析完了evdev_open(),evdev_poll()就是调用poll_wait(),evdev_fasync()就是调用fasync_helper(),这里不多说了,这两个函数就说明我们可以在应用层使用poll()或者select()实现输入设备的异步阻塞IO以及异步非阻塞IO操作。

    设置文件的模式

    nonseekable_open函数不说了,就是设置文件的模式。

        nonseekable_open(inode, file);
    //-------------------
    int nonseekable_open(struct inode *inode, struct file *filp)
    {
        filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
        return 0;
    }
    

    evdev_read

    首先思考个问题,在应用层调用read函数,是如何读取到实际硬件的按键值的?

    接下来分析evdev_read(),它的作用是从已经打开的evdev中读取输入事件,如果缓冲区中有事件则传递给用户空间,否则阻塞等待:

    1、从file->private_data中获取evdev_client结构,接着根据client获取当前的evdev设备。

    2、判断读取长度是否合法。如果用户空间要读取的长度小于一个事件的长度,那么直接返回;如果当前事件缓冲区为空,并且设备存在,同时当前文件的操作为非阻塞IO方式,那么直接返回-EAGAIN即可。

    3、将数据拷贝给应用空间。我们在下面再介绍这个。

    4、判断当前是否读到了数据,退出或者等待。

    • 等待:调用wait_event_interruptible(),条件为:“输入事件缓冲区非空 或 evdev设备不存在了”,如果是因为设备不存在了,那么直接返回-ENODEV即可,否则读取缓冲区中的事件,传递给用户空间即可。
    • 停止:如果已经传递给用户空间的数据长度已经不小于count或者输入事件缓冲区已经空了,就退出。
    static ssize_t evdev_read(struct file *file, char __user *buffer,
                  size_t count, loff_t *ppos)
    {
        // 1、获取对应的实例,准备操作。
        struct evdev_client *client = file->private_data;
        struct evdev *evdev = client->evdev;
        struct input_event event;
        size_t read = 0;
        int error;
    
        // 2、判断读取长度是否合法
        if (count != 0 && count < input_event_size())
            return -EINVAL;
    
        for (;;) {
            // 设备不存在
            if (!evdev->exist || client->revoked) 
                return -ENODEV;
    
            // 不允许非阻塞读取
            if (client->packet_head == client->tail &&
                (file->f_flags & O_NONBLOCK))
                return -EAGAIN;
    
            /*
             * count == 0 is special - no IO is done but we check
             * for error conditions (see above).
             */
            if (count == 0)
                break;
    
            // 3、将数据拷贝给应用空间。
            while (read + input_event_size() <= count &&
                   evdev_fetch_next_event(client, &event)) {
    
                // 相当于 copy_to_user(),把取出来的事件拷贝给用户空间。
                if (input_event_to_user(buffer + read, &event))
                    return -EFAULT;
    
                read += input_event_size();
            }
    
            // 4、判断当前是否读到了数据,读到数据则结束,不能读则等待。
            if (read)
                break;
            if (!(file->f_flags & O_NONBLOCK)) {
                error = wait_event_interruptible(evdev->wait,
                        client->packet_head != client->tail ||
                        !evdev->exist || client->revoked);
                if (error)
                    return error;
            }
        }
    
        return read;
    }
    

    evdev_fetch_next_event

    此函数就是从输入事件缓冲区中取出一个事件。

    static int evdev_fetch_next_event(struct evdev_client *client,
                      struct input_event *event)
    {
        int have_event;
    
        // 1、加锁
        spin_lock_irq(&client->buffer_lock);
    
        // 2、取出事件
        have_event = client->packet_head != client->tail;
        if (have_event) {
            *event = client->buffer[client->tail++];
            client->tail &= client->bufsize - 1;
            if (client->use_wake_lock &&
                client->packet_head == client->tail)
                wake_unlock(&client->wake_lock);
        }
    
        spin_unlock_irq(&client->buffer_lock);
    
        return have_event;
    }
    

    1、对client加锁

    2、根据client->headclient->tail判断缓冲区是否有事件,若两者不相等那么有,否则没有。

    3、取出事件。由于tail指向将要处理的事件,若要取事件,那么就根据tail就可以得到之。

    evdev_event

    evdev_read()是读取事件,我们知道read的时候是不能 非阻塞读取的,如果读取时没有数据呢,那么是在哪里来唤醒?换句话说,如果读取操作阻塞在了wait队列上,那么我们在哪里将其唤醒呢?

    读取时使用wait_event_interruptible()实现阻塞IO。

    答案在本节揭晓。

    当产生了一个输入事件以后,evdev_event会被调用。

    谁在调用wake_up_interruptible?

    搜索这个evdev->wait这个等待队列变量,找到evdev_event函数里唤醒:

    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_pass_values(client, vals, count, ev_time);
                     ---> wake_up_interruptible(&evdev->wait);
    }
    

    其中evdev_event()evdev.c(事件处理层) 的evdev_handler->event成员,,

    static struct input_handler evdev_handler = {
        .event      = evdev_event,
        .events     = evdev_events,
        // ...
    };
    
    调用关系统计

    那么是谁调用了evdev_event()(即evdev_handler->event())?

    答案:设备中使用input_event进行了事件上报。

    input.c中试搜下handler->eventhandler.event下函数调用:

    /*
    input_event;
    -->input_handle_event;
    ---->input_pass_values
    ------>input_to_handler
    -------->handler->events(handle, vals, count);
    */
    
    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);
        }
    }
    
    static void input_handle_event(struct input_dev *dev,
                       unsigned int type, unsigned int code, int value)
    {
        int disposition;
    
        disposition = input_get_disposition(dev, type, code, value);
    
        if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
            dev->event(dev, type, code, value);
    
        if (!dev->vals)
            return;
    
        if (disposition & INPUT_PASS_TO_HANDLERS) {
            struct input_value *v;
    
            if (disposition & INPUT_SLOT) {
                v = &dev->vals[dev->num_vals++];
                v->type = EV_ABS;
                v->code = ABS_MT_SLOT;
                v->value = dev->mt->slot;
            }
    
            v = &dev->vals[dev->num_vals++];
            v->type = type;
            v->code = code;
            v->value = value;
        }
    
        if (disposition & INPUT_FLUSH) {
            if (dev->num_vals >= 2)
                // 这里
                input_pass_values(dev, dev->vals, dev->num_vals);
            dev->num_vals = 0;
        } else if (dev->num_vals >= dev->max_vals - 2) {
            dev->vals[dev->num_vals++] = input_value_sync;
            // 这里
            input_pass_values(dev, dev->vals, dev->num_vals);
            dev->num_vals = 0;
        }
    
    }
    
    static void input_pass_values(struct input_dev *dev,
                      struct input_value *vals, unsigned int count)
    {
        struct input_handle *handle;
        struct input_value *v;
    
        if (!count)
            return;
    
        rcu_read_lock();
    
        handle = rcu_dereference(dev->grab);
        if (handle) {
            count = input_to_handler(handle, vals, count);
        } else {
            // 遍历client_list链表,每找到一个client就调用evdev_pass_event函数
            list_for_each_entry_rcu(handle, &dev->h_list, d_node)
                if (handle->open)
                    // 这里,注意,这里开始准备使用handle找到对应的handler。
                    count = input_to_handler(handle, vals, count);
        }
    
        rcu_read_unlock();
    
        add_input_randomness(vals->type, vals->code, vals->value);
    
        /* trigger auto repeat for key events */
        for (v = vals; v != vals + count; v++) {
            if (v->type == EV_KEY && v->value != 2) {
                if (v->value)
                    input_start_autorepeat(dev, v->code);
                else
                    input_stop_autorepeat(dev);
            }
        }
    }
    
    static unsigned int input_to_handler(struct input_handle *handle,
                struct input_value *vals, unsigned int count)
    {
        struct input_handler *handler = handle->handler;
        struct input_value *end = vals;
        struct input_value *v;
    
        for (v = vals; v != vals + count; v++) {
            if (handler->filter &&
                handler->filter(handle, v->type, v->code, v->value))
                continue;
            if (end != v)
                *end = *v;
            end++;
        }
    
        count = end - vals;
        if (!count)
            return 0;
    
        if (handler->events)
            // 这里
            handler->events(handle, vals, count);
        else if (handler->event)
            for (v = vals; v != end; v++)
                // 这里
                handler->event(handle, v->type, v->code, v->value);
    
        return count;
    }
    

    显然,就是input_dev通过输入核心为驱动层提供统一的接口input_event,来向事件处理层上报数据并唤醒。

    分析

    现在我们已经知道了通讯的流程,具体看看整个流程都做了什么事情。

    传递事件
    // linux/input.h
    struct input_value {
        __u16 type;
        __u16 code;
        __s32 value;
    };
    
    /*
     * Pass incoming events to all connected clients.
     */
    static void evdev_events(struct input_handle *handle,
                 const struct input_value *vals, unsigned int count)
    {
        struct evdev *evdev = handle->private;
        struct evdev_client *client;
        ktime_t time_mono, time_real;
    
        time_mono = ktime_get();
        time_real = ktime_sub(time_mono, ktime_get_monotonic_offset());
    
        rcu_read_lock();
    
        // evdev的grab成员,这个成员代表exclusice  access的含义
        client = rcu_dereference(evdev->grab);
    
        if (client) // 如果grab不是NULL,那么这个evdev只能被一个client访问使用
            evdev_pass_values(client, vals, count, time_mono, time_real);
        else  // 如果没有grab成员,那么此事件就需要给evdev->client_list上所有成员发消息,发消息的函数是evdev_pass_event(),
        {
            list_for_each_entry_rcu(client, &evdev->client_list, node)
                evdev_pass_values(client, vals, count,
                          time_mono, time_real);
        }
    
        rcu_read_unlock();
    }
    
    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的简单封装。

    evdev_events的作用:把typecode以及value封装进input_value类型的vals中(相当于封装了一个代表的事件),每一个发送给当前evdev对应的client。

    准备删除。最后,如果type是EV_SYN且code是SYN_REPORT,那么我们将调用wake_up_interruptible(),实现读取操作的同步,这也意味着,驱动程序中要显式调用report相关的函数才能解锁读取操作。

    保存事件

    这里重点看evdev_pass_values

    // include/uapi/linux/input.h
    struct input_event {
        struct timeval time;
        __u16 type;
        __u16 code;
        __s32 value;
    };
    
    static void evdev_pass_values(struct evdev_client *client,
                const struct input_value *vals, unsigned int count,
                ktime_t *ev_time)
    {
        struct evdev *evdev = client->evdev;
        const struct input_value *v; // 迭代器
        struct input_event event;
        bool wakeup = false;
    
        if (client->revoked)
            return;
    
        event.time = ktime_to_timeval(ev_time[client->clk_type]);
    
        /* Interrupts are disabled, just acquire the lock. */
        spin_lock(&client->buffer_lock);
    
        // 对每一个事件进行处理,并发送到cline
        for (v = vals; v != vals + count; v++) {
            if (__evdev_is_filtered(client, v->type, v->code))
                continue;
    
            if (v->type == EV_SYN && v->code == SYN_REPORT) {
                /* drop empty SYN_REPORT */
                if (client->packet_head == client->head)
                    continue;
    
                wakeup = true;
            }
    
            event.type = v->type;
            event.code = v->code;
            event.value = v->value;
            // 保存事件
            __pass_event(client, &event);
        }
    
        spin_unlock(&client->buffer_lock);
    	// 唤醒
        if (wakeup)
            wake_up_interruptible(&evdev->wait);
    }
    
    保存
    struct evdev_client {
        unsigned int head;
        unsigned int tail;
        unsigned int packet_head; /* [future] position of the first element of next packet */
        spinlock_t buffer_lock; /* protects access to buffer, head and tail */
        struct fasync_struct *fasync;
        struct evdev *evdev;
        struct list_head node;
        unsigned int clk_type;
        bool revoked;
        unsigned long *evmasks[EV_CNT];
        unsigned int bufsize;
        struct input_event buffer[];
    };
    
    static void __pass_event(struct evdev_client *client,
                 const struct input_event *event)
    {
        // 1、保存事件
        client->buffer[client->head++] = *event;
        client->head &= client->bufsize - 1;
    
        // 2、 如果缓冲区满了
        if (unlikely(client->head == client->tail)) {
            /*
             * This effectively "drops" all unconsumed events, leaving
             * EV_SYN/SYN_DROPPED plus the newest event in the queue.
             */
            client->tail = (client->head - 2) & (client->bufsize - 1);
    
            client->buffer[client->tail].time = event->time;
            client->buffer[client->tail].type = EV_SYN;
            client->buffer[client->tail].code = SYN_DROPPED;
            client->buffer[client->tail].value = 0;
    
            client->packet_head = client->tail;
        }
    
        // 3、异步通知。
        if (event->type == EV_SYN && event->code == SYN_REPORT) {
            client->packet_head = client->head;
            kill_fasync(&client->fasync, SIGIO, POLL_IN);
        }
    }
    

    1、既然每次产生一个输入事件,那么这个函数肯定要将产生的事件存储到事件队列环形缓冲区中。

    上面代码对于client->head的操作为:存储了事件队列以后,还要确保head没超过bufsize。如果超过了bufsize,那么应该从零开始。

    由于在evdev_open中,申请buffsize时对2的幂向上取整,因此只需要进行位与就可以完成相当于%的操作(小优化)。

    2、如果存储了当前事件后,事件队列缓冲区满了怎么办?内核只留下两个事件:

    • 最新的一个事件其实不是事件,它的code为SYN_DROPPED;
    • 而另一个就是newest事件,接着把packet_head成员更新为tail,代表一系列事件的头部。

    3、如果我们的事件type=EV_SYN,code=SYN_REPORT,那么通过kill_fasync()发送SIGIO。因此,如果我们的输入设备文件支持异步IO操作的话,应用层应该能通过异步通知的方式接收SIGIO,从而在信号回调函数中读取到输入事件。

    通常,用input_sync来完成对输入事件的上报。

    如果说我的文章对你有用,只不过是我站在巨人的肩膀上再继续努力罢了。
    若在页首无特别声明,本篇文章由 Schips 经过整理后发布。
    博客地址:https://www.cnblogs.com/schips/
  • 相关阅读:
    [javaSE] 看博客学习多线程的创建方式和优劣比较和PHP多线程
    [javaSE] 看知乎学习反射
    [javaSE] 看知乎学习工厂模式
    [android] 新闻客户端主界面部分
    [PHP] 看博客学习观察者模式
    [javascript] 看知乎学习js事件触发过程
    [javascript] 看知乎学习js闭包
    [PHP] 看博客学习插入排序
    [android] ndk环境的搭建
    [android] 看博客学习hashCode()和equals()
  • 原文地址:https://www.cnblogs.com/schips/p/linux_input_subsystem.html
Copyright © 2020-2023  润新知