4.11.1. 输入子系统框架

4.11.1.1. 概述

input驱动程序是linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)、键盘(keyboard)、事件设备(event)。其中事件设备驱动程序是目前通用的驱动程序, 可支持键盘、鼠标、触摸屏等多种输入设备

linux input子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两个层。前者负责从底层硬件采集数据,后者负责与用户程序接口, 将采集到的数据分发给不同的用户接口。通过这样的设计,将千差万别的设备统一到了为数不多的几种驱动接口上,同一种事件驱动可以用来处理多个同类设备,同一个设备也可以和多种 事件驱动相衔接。而事件驱动和设备驱动则由输入核心层进行连接,匹配

首先输入子系统是分为三层的,面对应用层的是 输入事件层(handler) ,处理底层驱动的是 输入设备层(dirver或device) 。而衔接dev和handler的则是 输入核心层(core) 。 真个输入子系统的核心层起到承上启下的作用

上:输入事件驱动层         (打包数据,面向应用)

中:输入核心层             (向下提供注册接口,向上给具体的hander发送数据)

下:输入设备驱动层         (底层驱动,面向硬件)
../../_images/Input_des.png

应用程序使用input子系统的核心是,对驱动层打包好的数据进行分析,其中打包的数据结构如下

/*
 * The event structure itself
 */

struct input_event {
        struct timeval time;    //表示输入的时间
        __u16 type;                             //表示输入设备是哪种类型,鼠标键盘等
        __u16 code;                             //不同的type有不同的code,比如键盘的哪个按键等
        __s32 value;                    //根据不同的type和code决定,比如键盘A键按下和松开,鼠标的移动方向等
};

一个设备可能对应两个设备驱动,比如鼠标既可以对应mouse也可以对应event。event事件设备驱动程序是通用的,也是目前的主流。后面的内容将以此为例进行分析

一次鼠标按下事件为例,说明input输入子系统的工作过程

  1. 设备驱动层:当鼠标左键被按下时就会触发中断,然后去执行中断注册的处理函数,在函数中会读取硬件寄存器来判断按下的是哪个按键和状态

  2. 然后将按键信息上报给input core层,input core层处理完成之后会上报给input event层,input event层会将我们的输入事件封装成一个input_event结构体放入到一个缓冲区

  3. 应用层read就会将缓冲区中的数据读取出去

输入子系统整体由核心层维护,主要使用两条链表,维护着两个层

static LIST_HEAD(input_dev_list);   //维护着所有的dev
static LIST_HEAD(input_handler_list);   //维护着所有的handler

4.11.1.2. 数据结构

4.11.1.2.1. input_handler

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);
        //函数用来匹配handler与input_dev
        bool (*match)(struct input_handler *handler, struct input_dev *dev);
        //当handler与input_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;              //该handler编号(在input_table数组中用来计算数组下标), input_table数组就是input子系统用来管理注册的handler的一个数据结构
        const char *name;

        const struct input_device_id *id_table; //里面保存着dev和handler能匹配在一起的信息

        struct list_head        h_list;
        struct list_head        node;
};

用此数据结构来表述一个handler,主要是面向应用层的。 node用于把该handler链接在input_handler_list链表上,h_list用于指向handle

4.11.1.2.2. input_handle

handle: 用于将input_dev和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;  //用来指向该handle绑定的input_dev结构体
        struct input_handler *handler;  //用来指向该handle绑定的handler结构体

        struct list_head        d_node;
        struct list_head        h_node;
};

这个数据结构用来连接dev和handler,这里要说明的是一个dev可能会连接多个handler,所以会有多个handle负责连接dev和多个handler.

4.11.1.2.3. input_dev

input_dev代表着具体的输入设备,负责底层的实现,它直接从硬件中读取数据,并以事件的形式转发,包括该输入设备支持的输入类型,键值等.

struct input_dev {
        const char *name;       //设备名称
        const char *phys;       //设备在分层系统的路径
        const char *uniq;
        struct input_id id;             //设备信息
        //可以上报的事件类型有哪些,用位图来表示
        unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

        unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
        unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
        unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
        unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
        unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
        unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
        unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
        unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
        unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

        unsigned int hint_events_per_packet;

        unsigned int keycodemax;
        unsigned int keycodesize;
        void *keycode;

        int (*setkeycode)(struct input_dev *dev,
                          const struct input_keymap_entry *ke,
                          unsigned int *old_keycode);
        int (*getkeycode)(struct input_dev *dev,
                          struct input_keymap_entry *ke);

        struct ff_device *ff;

        unsigned int repeat_key;        //重复上报键值,比如键盘A按着一直不松手
        struct timer_list timer;        //重复上报时间

        int rep[REP_CNT];

        struct input_mt *mt;

        struct input_absinfo *absinfo;

        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 (*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 __rcu *grab;

        spinlock_t event_lock;
        struct mutex mutex;

        unsigned int users;
        bool going_away;

        struct device dev;

        struct list_head        h_list; //用来挂接这个struct input_dev和所有struct handler的链表头
        struct list_head        node;   //

        unsigned int num_vals;
        unsigned int max_vals;
        struct input_value *vals;

        bool devres_managed;
};

以上几个数据结构之间的关系可以用下面的图示说明

../../_images/input_struct.png

下面是系统目前为设备定义的次设备号信息

#define JOYDEV_MINOR_BASE       0        /* 游戏手柄类次设备号开始位置 */
#define JOYDEV_MINORS           16       /* 游戏手柄类次设备号个数 */

#define MOUSEDEV_MINOR_BASE     32       /*鼠标类次设备号开始位置 */
#define MOUSEDEV_MINORS         32       /* 鼠标类次设备号个数 */

#define EVDEV_MINOR_BASE        64       /*通用事件类次设备号开始位置 */
#define EVDEV_MINORS            32       /*通用事件类次设备号个数 */

4.11.1.3. 输入子系统初始化

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

subsys_initcall(input_init);

start_kernel中会调用subsys_initcall函数来完成input_init的的调用

在dev和handler匹配上之后,会调用connect, 不同的输入设备有不同的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);
        if (minor < 0) {
                error = minor;
                pr_err("failed to reserve new minor: %d\n", error);
                return error;
        }

        evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
        if (!evdev) {
                error = -ENOMEM;
                goto err_free_minor;
        }

        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;
        dev_set_name(&evdev->dev, "event%d", dev_no);

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

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

        cdev_init(&evdev->cdev, &evdev_fops);
        evdev->cdev.kobj.parent = &evdev->dev.kobj;
        error = cdev_add(&evdev->cdev, evdev->dev.devt, 1);
        if (error)
                goto err_unregister_handle;

        error = device_add(&evdev->dev);
        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);
 err_free_minor:
        input_free_minor(minor);
        return error;
}

真正的设备注册主要由cdev_init和cdev_add函数完成