linux input输入子系统源码分析

总体软件架构

先来个整体的软件层次架构图:
在这里插入图片描述
从上图输入子系统的框架图,可以看出,输入子系统由Input driver(驱动层)、Input core(输入子系统核心)、Event handler(事件处理层)三部分组成。一个输入事件,如鼠标移动、键盘按下等通过Input driver -> Input core -> Event handler -> userspace的顺序到达用户空间的应用程序。

1.系统核心层(Input core)

抽象出来的与具体硬件无关,提供一些通用功能,主要包括如下:

struct input_dev *input_allocate_device(void)
具体的输入设备都被抽象出来了统一用struct input_dev来表示

int input_register_device(struct input_dev *dev)
输入设备注册

功能 接口
分配输入设备结构体 struct input_dev *input_allocate_device(void)
输入设备注册 int input_register_device(struct input_dev *dev)
报告输入事件 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
报告结束 input_sync()同步用于告诉input core子系统报告结束
注册一个事件处理器 int input_register_handler(struct input_handler *handler)
向内核注册一个handle结构 int input_register_handle(struct input_handle *)

input_register_device()用于把输入设备挂到输入设备链表input_dev_list中;
input_register_handler()用于事件处理器挂到input_handler_list中;

下面从代码层面来具体分析:

int input_register_device(struct input_dev *dev)
{
	...
    list_add_tail(&dev->node, &input_dev_list);

    list_for_each_entry(handler, &input_handler_list, node)
        input_attach_handler(dev, handler);
	...
}
int input_register_handler(struct input_handler *handler)                                                                                     
{
	...
    INIT_LIST_HEAD(&handler->h_list);
    list_add_tail(&handler->node, &input_handler_list);
	
	list_for_each_entry(dev, &input_dev_list, node)
	input_attach_handler(dev, handler);
}

可以看出两个函数极为相似,下面重点来分析input_attach_handler()这个函数:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)                                                         
{
    const struct input_device_id *id;
    int error;
    
    id = input_match_device(handler, dev);
    if (!id)
        return -ENODEV;

    error = handler->connect(handler, dev, id);
    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;
}
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 (!handler->match || handler->match(handler, dev))
            return id;
    }
                                                                                                                                              
    return NULL;
}

从上述代码可以看出,对应新注册到input core中的输入设备或者事件处理器,都会遍历input_dev_list或者input_handler_list进行匹配,具体的匹配规则是在input_match_device()函数中实现的,主要的依据是vendor,product,version,另外还有能够产生的事件是否事件处理器都能够处理。
如果输入设备和事件处理器匹配,则调用事件处理器的connect()进行进一步的处理,下面以evdev事件处理器为例进行说明:

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

这个函数包含的信息量比较大,首先通过input_register_handle()将输入设备和事件处理器管理起来(通过struct input_handle这个结构联系起来的),其次向用户空间注册了一个字符设备(/dev/input/eventx)

int input_register_handle(struct input_handle *handle)
{
    struct input_handler *handler = handle->handler;
    struct input_dev *dev = handle->dev;
    int 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);//将该handle加到该输入设备的handle list 列表中

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

对应evdev来说,start()方法没有实现。
下面来看看用户按下一个按键后,整个经历的过程是什么样子的。
输入设备驱动层不是这里的重点,略去不讲,从设备驱动报告输入事件给input core开始讲起:

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 = input_get_disposition(dev, type, code, &value); //决定输入事件应该分发去哪里

    if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)
        add_input_randomness(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;
    }
}

input_get_disposition()决定输入事件应该分发去哪里,这里我们只关心INPUT_PASS_TO_HANDLERS,继续往下追:

static void input_pass_values(struct input_dev *dev,                                                                                          
                  struct input_value *vals, unsigned int count)
{
    struct input_handle *handle;
    struct input_value *v;
    handle = rcu_dereference(dev->grab);
    if (handle) {
        count = input_to_handler(handle, vals, count);
    } else {
        list_for_each_entry_rcu(handle, &dev->h_list, d_node)
            if (handle->open) {
                count = input_to_handler(handle, vals, count);
                if (!count)
                    break;
            }
    }
}

上面代码中的grab是什么,这里暂且跳过这条线,后面再分析。另外一条线,一个输入事件可能有多个事件处理器,注意到handle->open没有,只有当事件处理器打开了,才会将事件分发给该事件处理器,那么handle->open是在哪里设置的呢,通过搜索很容易找到:

static int evdev_open_device(struct evdev *evdev)
{
    int retval;

    retval = mutex_lock_interruptible(&evdev->mutex);
    if (retval)
        return retval;

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

也就是说只有/dev/input/eventx 被用户空间程序打开了,该事件处理器才会被打开,触发的事件才会分发给该事件处理器。
ok,继续往下分析:

static unsigned int input_to_handler(struct input_handle *handle,
            struct input_value *vals, unsigned int count)
{
	struct input_handler *handler = handle->handler;  //得到事件处理器
	...
    if (handler->events)
        handler->events(handle, vals, count);
    else if (handler->event)
        for (v = vals; v != vals + count; v++) 
            handler->event(handle, v->type, v->code, v->value);
}

events()一次可以处理多个事件,这里简单化假设只包含一个事件:

/*
 * Pass incoming event to all connected clients.
 */
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);
}
/*
 * 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 ev_time[EV_CLK_MAX];

    ev_time[EV_CLK_MONO] = ktime_get();
    ev_time[EV_CLK_REAL] = ktime_mono_to_real(ev_time[EV_CLK_MONO]);
    ev_time[EV_CLK_BOOT] = ktime_mono_to_any(ev_time[EV_CLK_MONO],
                         TK_OFFS_BOOT);

    rcu_read_lock();

    client = rcu_dereference(evdev->grab);

    if (client)
        evdev_pass_values(client, vals, count, ev_time);
    else
        list_for_each_entry_rcu(client, &evdev->client_list, node)
            evdev_pass_values(client, vals, count, ev_time);

    rcu_read_unlock();
}

注意观察这里的client_list,会将该事件分发给所有的client,那么这里的client指的是什么呢?
注意观察evdev_open()函数,这里只提取跟client相关的代码:

static int evdev_open(struct inode *inode, struct file *file)
{
	struct evdev_client *client;
	client = kzalloc(size, GFP_KERNEL | __GFP_NOWARN);
	client->evdev = evdev;
	evdev_attach_client(evdev, client);
	
	file->private_data = 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);
} 

不用过多解释了吧,代码一目了然,每open一次分配一个client,并将该client链接到client_list中,然后在文件指针的private_data 中跟client对应起来,后面通过该文件指针读取的都是该client下面的事件列表。
也就是说/dev/input/eventx 可以被多次打开,并且相互之间不会干扰,这也就是为什么linux可以通过/dev/input/eventx监控键盘的输入了,并且对应其他读取键盘的应用完全不受影响。

下面来讲讲上面忽略掉的grab是个什么东西,通过搜索不难找到:


static int evdev_grab(struct evdev *evdev, struct evdev_client *client)
{
    int error;

    if (evdev->grab)
        return -EBUSY;

    error = input_grab_device(&evdev->handle);
    if (error)
        return error;

    rcu_assign_pointer(evdev->grab, client);                                                                                                  

    return 0;
}
static long evdev_do_ioctl(struct file *file, unsigned int cmd,
               void __user *p, int compat_mode)
{
	...
    case EVIOCGRAB:
        if (p)
            return evdev_grab(evdev, client);
        else
            return evdev_ungrab(evdev, client);
	...
}

上述的代码应该不难理解,也就是说用户程序可以打开/dev/input/eventx后通过ioctl(fd, EVIOCGRAB)设置为独占键盘输入,该特性在输入密码前设置为独占键盘输入,密码输入完成后取消独占,这样密码就没办法被监控到了,对于密码安全特别重要。

下面对键盘输入重点分析一下

//./drivers/tty/vt/keyboard.c
int __init kbd_init(void)
{
    int i;
    int error;

    for (i = 0; i < MAX_NR_CONSOLES; i++) {
        kbd_table[i].ledflagstate = kbd_defleds();
        kbd_table[i].default_ledflagstate = kbd_defleds();
        kbd_table[i].ledmode = LED_SHOW_FLAGS;
        kbd_table[i].lockstate = KBD_DEFLOCK;
        kbd_table[i].slockstate = 0; 
        kbd_table[i].modeflags = KBD_DEFMODE;
        kbd_table[i].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
    }    

    kbd_init_leds();

    error = input_register_handler(&kbd_handler);
    if (error)
        return error;

    tasklet_enable(&keyboard_tasklet);
    tasklet_schedule(&keyboard_tasklet);

    return 0;
}
static struct input_handler kbd_handler = {                                                                                                   
    .event      = kbd_event,
    .match      = kbd_match,
    .connect    = kbd_connect,
    .disconnect = kbd_disconnect,
    .start      = kbd_start,
    .name       = "kbd",
    .id_table   = kbd_ids,
};

对于键盘输入,在开启了VT的情况下,注册了kbd_handler专门来处理键盘的事件处理器:
在这里插入图片描述
下面来简要分析一下,按键来了之后如何处理:

static void kbd_event(struct input_handle *handle, unsigned int event_type,                                    
              unsigned int event_code, int value)                                                              
{                                                                                                              
	...                                                                                                        
    if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))                                  
        kbd_rawcode(value);                                                                                    
    if (event_type == EV_KEY)                                                                                  
        kbd_keycode(event_code, value, HW_RAW(handle->dev));                                                                                  
	...                                                                              
} 
static void kbd_keycode(unsigned int keycode, int down, int hw_raw)                                                                           
{
	struct vc_data *vc = vc_cons[fg_console].d;
	struct tty_struct *tty;
	    tty = vc->port.tty;

    if (tty && (!tty->driver_data)) {
        /* No driver data? Strange. Okay we fix it then. */
        tty->driver_data = vc;
    }
    ...
    if (rep &&
        (!vc_kbd_mode(kbd, VC_REPEAT) ||
         (tty && !L_ECHO(tty) && tty_chars_in_buffer(tty)))) {
        /*
         * Don't repeat a key if the input buffers are not empty and the
         * characters get aren't echoed locally. This makes key repeat
         * usable with slow applications and under heavy loads.
         */
        return;
    }
    ...
}

因为虚拟终端有多个,比如/dev/tty1-n, 所有会将按键分发给处于focus的虚拟终端,由fg_console来记录那个虚拟终端处于focus,即前台终端。
将输入通过tty_chars_in_buffer(tty)放入,这样就可以读取focus的/dev/ttyx 来读取键盘输入了。

参考资料:
http://www.embeddedlinux.org.cn/essentiallinuxdevicedrivers/final/ch07lev1sec2.html
https://www.cnblogs.com/cute/archive/2011/08/30/2159305.html
https://www.jianshu.com/p/e9cfae59e3df

发布了83 篇原创文章 · 获赞 24 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/whuzm08/article/details/104019229