• Input子系统(二)【转】


    转自:http://blog.chinaunix.net/uid-25047042-id-4192368.html

    上一篇中粗略的分析了下input_dev,input_handle,input_handler这三者之间的关系,而在实际系统当中input子系统是如何工作的呢,当然我们知道,故事肯定是围绕着它们三个发生,下面我们来看看具体的input设备的工作流程。同样以触摸屏为例。

    在触摸屏驱动中,当有触摸事件产生(手接触到触摸屏的时候),触摸屏相关IC会产生中断,在中断处理函数当中,kernel或者说tp driver会读取此次中断产生的数据(对于一个支持多点触摸的触摸屏来说就是每条触摸轨迹的坐标),driver将数据组织好,然后向input子系统report数据:

    ……

    ret = gtp_i2c_read(ts->client, buf, 2 + 8 * (touch_num - 1)); 

    memcpy(&point_data[12], &buf[2], 8 * (touch_num - 1));//从硬件读取数据

    ……

    input_x  = coor_data[pos + 1] | coor_data[pos + 2] << 8;

    input_y  = coor_data[pos + 3] | coor_data[pos + 4] << 8;

    input_w  = coor_data[pos + 5] | coor_data[pos + 6] << 8;//组织相关数据

    ……

    input_mt_slot(ts->input_dev, id);

    input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, id);

    input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);

    input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);

    input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, w);

    input_report_abs(ts->input_dev, ABS_MT_WIDTH_MAJOR, w);//向input子系统report数据

    input_sync(ts->input_dev);//同步相关数据

    ……

    Input子系统对于不同类型的事件有不同的处理方法,以多点触摸绝对坐标为例:

    input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);

    ts->input_dev是report数据的这个设备,在上一篇input设备初始化有提过,ABS_MT_POSITION_X表明report的这个事件是一个多点触摸的X轴绝对坐标事件,x就是从硬件中读取的某个触摸点的X轴也就是这次report的值了。

    static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)

    {

    input_event(dev, EV_ABS, code, value);

    }

    Input子系统用input_report_abs()封装了对绝对坐标类型事件,在input.h中我们可以看到,input子系统还封装了其他很多种类型事件接口,它们中间都调用了input_event接口。

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

    /*

    首先会判断该input设备是否具有report type类型事件的能力,只有设备支持report type类型事件,才会向input子系统report相关数据。而支持该种类型事件是在设备的初始化的时候做的,上一篇中也有提过,也就是:

    ts->input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) ;

    在设备的probe当中会设置设备支持的处理的事件类型,在这里设备是支持处理坐标类型事件的能力的。

    */

    spin_lock_irqsave(&dev->event_lock, flags);

    add_input_randomness(type, code, value);

    /*

    将事件数据做为一个随机数产生熵,因为触摸事件可等同视为一个随机事件,人手触摸屏幕点是随机的,没有什么规定一定要总去触摸屏上某个位置

    */

    input_handle_event(dev, type, code, value);//处理这次input事件

    spin_unlock_irqrestore(&dev->event_lock, flags);

    }

    }

    static void input_handle_event(struct input_dev *dev,

           unsigned int type, unsigned int code, int value)

    /*

    在input_handle_event()当中,根据事件类型的不同,分别进行不同的处理,下面只选取了EV_ABS类型事件列出。

    */

    {

    int disposition = INPUT_IGNORE_EVENT;

    switch (type) {

    ……

    case EV_ABS:

    if (is_event_supported(code, dev->absbit, ABS_MAX))

    /*

    这里同样还会做出一个判断,因为坐标类型事件同样分为很多种类型的坐标事件,我们这里要处理的是多点触摸的绝对坐标类型事件,如果设备不支持处理这种事件的话,那也不会向input子系统report事件。对于该类型事件是否支持的设置同样放在了设备初始化的时候:

    input_set_abs_params(ts->input_dev, ABS_MT_POSITION_X, 0, ts->abs_x_max, 0, 0);

    这里同样设置了该设备report的最大的x轴绝对坐标值。

    */

    disposition = input_handle_abs_event(dev, code, &value);

    /*

    对于绝对坐标类型事件在很多时候是会非常频繁的产生,像触摸屏它的报点率一般会有50-60HZ,那么它产生的数据相对来说是非常大的,对于连续的重复的坐标,input子系统会进行过滤,只处理一次,以及可以过滤由于硬件noise产生的误数据。

    */

    break;

    ……

    if (disposition != INPUT_IGNORE_EVENT && type != EV_SYN)

    dev->sync = false;

    if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)

    dev->event(dev, type, code, value);

    if (disposition & INPUT_PASS_TO_HANDLERS)

    input_pass_event(dev, type, code, value);//事件处理成功,将事件传递给input_handler处理。

    }

    通过以上一系列接口的传递和处理,input event终于被传递给了input_handler。

    static void input_pass_event(struct input_dev *dev,

         unsigned int type, unsigned int code, int value)

    {

    struct input_handler *handler;

    struct input_handle *handle;

    rcu_read_lock();

    handle = rcu_dereference(dev->grab);

    if (handle)

    handle->handler->event(handle, type, code, value);

    else {

    bool filtered = false;

    list_for_each_entry_rcu(handle, &dev->h_list, d_node) {

    /*

    在这里是不是看到了很眼熟的东西,在上一篇当中我们有说过,在注册Input_device的时候,会将input_handle通过d_node保存在input_dev的h_list上,在这里就是通过d_node从input_dev的h_list获取其对应的input_handle。

    */

    if (!handle->open)

    continue;

    handler = handle->handler;//通过handle获取到对应的input_handler。

    if (!handler->filter) {

    if (filtered)

    break;

    handler->event(handle, type, code, value);//input_handler处理input事件。

    } else if (handler->filter(handle, type, code, value))

    filtered = true;

    }

    }

    rcu_read_unlock();

    }

    到这里,我们就要进入input_handler了,input_handler调用其event接口对数据进行处理。

    Input_handler

    上一篇有说过,input_handler是数据的消耗者,负责kernel设备数据与上层应用的交接。Linux内核为我们提供了一个默认的input_handler,它支持处理所有的input设备,它就是evdev。相关代码在evdev.c当中。

    在evdev.c当中为我们定义了一个input_handler:

    static struct input_handler evdev_handler = {

    .event = evdev_event,

    .connect = evdev_connect,

    .disconnect = evdev_disconnect,

    .fops = &evdev_fops,

    .minor = EVDEV_MINOR_BASE,

    .name = "evdev",

    .id_table = evdev_ids,

    };

    其中event是事件处理接口,当input设备传递数据过来的时候,event负责处理。

    Connect在上一篇有粗略分析过,当注册一个input_device或者input_handler的时候,它就会被调用,关联对应的input_handler或者input_device。

    Disconnect断开input_device和input_handler时被调用。

    Fops是input_handler提供的文件操作接口集合。在linux系统当中,所有的设备对上层来说都是文件,而这里定义了应用对设备文件所有的接口。

    Minor是input_handler处理的设备的次设备号的起始设备号。

    Name,很简单,该input_handler的name。

    Id_table,定义了该input_handler支持处理的设备id,evdev支持所有类型的input_device设备类型。

    Evdev.c模块加载的时候会注册当input_handler:

    static int __init evdev_init(void)

    {

    return input_register_handler(&evdev_handler);

    }

    以下是注册input_handler内核的动作:

    int input_register_handler(struct input_handler *handler)

    {

    struct input_dev *dev;

    int retval;

    retval = mutex_lock_interruptible(&input_mutex);

    if (retval)

    return retval;

    INIT_LIST_HEAD(&handler->h_list);

    if (handler->fops != NULL) {

    if (input_table[handler->minor >> 5]) {

    retval = -EBUSY;

    goto out;

    }

    input_table[handler->minor >> 5] = handler;

    }

    list_add_tail(&handler->node, &input_handler_list);

    list_for_each_entry(dev, &input_dev_list, node) //在注册input_device的时候会将input_device挂载在input_dev_list链表上。

    input_attach_handler(dev, handler);//关联input_dev和input_handler

    input_wakeup_procfs_readers();

     out:

    mutex_unlock(&input_mutex);

    return retval;

    }

    这里可以看到,当注册input_handler的时候,会关联内核当前已经存在的input_device,因此不用担心有注册在input_handler注册之前的input_device没有关联相关input_handler。

    这里还需要介绍另外两个重要的数据结构:

    struct evdev {

    int open;

    int minor;//次设备号

    struct input_handle handle;//对应input_device关联的input_handle

    wait_queue_head_t wait;//等待队列头

    struct evdev_client __rcu *grab;

    struct list_head client_list;

    spinlock_t client_lock; /* protects client_list */

    struct mutex mutex;

    struct device dev;

    bool exist;

    };

    Evdev是input_device eventn设备的定义,eventn设备是Input_device的一个子设备,在linux内核系统当中,/dev/input/目录下每个input_device都对应着一个eventn(n=0,1,2……)文件,而input_handler所处理的就是这个evdev设备。

    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;

    char name[28];

    struct fasync_struct *fasync;

    struct evdev *evdev;//指向的evdev,该evdev_client处理的eventn对象

    struct list_head node;

    unsigned int bufsize;

    struct input_event buffer[];//保存input_device传递过来的数据的buffer

    };

    Evdev_client是evdev提供处理数据的一个客户端,当有多个应用进程打开同一个eventn设备文件的时候,内核为每个应用进程提供一个event_client客户端,该event_client为对应进程负责数据处理传输。

    我们知道在注册input_device的时候会匹配evdev,然后关联input_device和input_handler,即调用evdev的connect接口evdev_connect:

    static int evdev_connect(struct input_handler *handler, struct input_dev *dev,

     const struct input_device_id *id)

    {

    struct evdev *evdev;

    int minor;

    int error;

    for (minor = 0; minor < EVDEV_MINORS; minor++)

    if (!evdev_table[minor])

    break;

    /*

    首先进行判断evdev_table数组是否还有剩余空间留给新的evdev,evdev_table数组当中的每一个元素都代表着一个evdev设备,系统最多只能存在32个evdev设备

    */

    if (minor == EVDEV_MINORS) {

    pr_err("no more free evdev devices ");

    return -ENFILE;

    }

    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

    if (!evdev)

    return -ENOMEM;

    INIT_LIST_HEAD(&evdev->client_list);

    spin_lock_init(&evdev->client_lock);

    mutex_init(&evdev->mutex);

    init_waitqueue_head(&evdev->wait);

    dev_set_name(&evdev->dev, "event%d", minor);//通过次设备号设置设备文件名,可在/dev/input/目录下查看

    evdev->exist = true;//设置evdev存在标志

    evdev->minor = minor;//从evdev_table空闲空间中选取一个做为该evdev的次设备号。

    evdev->handle.dev = input_get_device(dev);

    evdev->handle.name = dev_name(&evdev->dev);

    evdev->handle.handler = handler;

    evdev->handle.private = evdev;//初始化evdev的input_handle结构

    evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);//通过input_device主设备号和evdev次设备号组合该设备的设备号

    evdev->dev.class = &input_class;

    evdev->dev.parent = &dev->dev;//将evdev设备父设备设置为input_device

    evdev->dev.release = evdev_free;

    device_initialize(&evdev->dev);

    error = input_register_handle(&evdev->handle);

    if (error)

    goto err_free_evdev;

    error = evdev_install_chrdev(evdev);

    /*

    static int evdev_install_chrdev(struct evdev *evdev)

    {

    evdev_table[evdev->minor] = evdev;//将该evdev保存在evdev_table数组中,minor作为数组下标,因此,evdev_table的第minor个元素被占用了。

    return 0;

    }

    */

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

    return error;

    }

    因此,当input_device和input_handler关联起来之后,便会在/dev/input/目录下生成一个evdev设备文件eventn(n=0,1,2,3……)。

    而当上层应用需要获取input_device数据的时候就会打开eventn设备文件,就会调用static int evdev_open(struct inode *inode, struct file *file)

    {

    struct evdev *evdev;

    struct evdev_client *client;

    int i = iminor(inode) - EVDEV_MINOR_BASE;//获取该设备文件的次设备号

    unsigned int bufsize;

    int error;

    if (i >= EVDEV_MINORS)

    return -ENODEV;

    error = mutex_lock_interruptible(&evdev_table_mutex);

    if (error)

    return error;

    evdev = evdev_table[i];//通过次设备号取得其evdev结构

    if (evdev)

    get_device(&evdev->dev);

    mutex_unlock(&evdev_table_mutex);

    if (!evdev)

    return -ENODEV;

    bufsize = evdev_compute_buffer_size(evdev->handle.dev);

    client = kzalloc(sizeof(struct evdev_client) +

    bufsize * sizeof(struct input_event),

     GFP_KERNEL);//分配一个evdev_client客户端

    if (!client) {

    error = -ENOMEM;

    goto err_put_evdev;

    }

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

    wake_lock_init(&client->wake_lock, WAKE_LOCK_SUSPEND, client->name);

    client->evdev = evdev;//初始化evdev_client相关结构

    evdev_attach_client(evdev, client);//关联event_client和evdev,将event_client保存在evdev的client_list链表上。

    error = evdev_open_device(evdev);//调用input_device open接口来做一些初始化工作。

    if (error)

    goto err_free_client;

    file->private_data = client;//将evdev_client保存在file的私有域。

    nonseekable_open(inode, file);

    return 0;

     err_free_client:

    evdev_detach_client(evdev, client);

    wake_lock_destroy(&client->wake_lock);

    kfree(client);

     err_put_evdev:

    put_device(&evdev->dev);

    return error;

    }

    以上准备工作做好了,再回到前面的input_device的数据被传递到input_handler,input_handler调用其event接口对数据处理,我们来看看evdev的event接口evdev_event:

    static void evdev_event(struct input_handle *handle,

    unsigned int type, unsigned int code, int value)

    {

    struct evdev *evdev = handle->private;//通过handle获取该input_device对应的evdev

    struct evdev_client *client;

    struct input_event event;

    struct timespec ts;

    ktime_get_ts(&ts);

    event.time.tv_sec = ts.tv_sec;

    event.time.tv_usec = ts.tv_nsec / NSEC_PER_USEC;

    event.type = type;

    event.code = code;

    event.value = value;

    /*

    Evdev用一个input_event结构将传递过来的事件类型type,事件编码code,事件值value以及该事件产生的时间保存起来

    */

    rcu_read_lock();

    client = rcu_dereference(evdev->grab);

    if (client)

    evdev_pass_event(client, &event);

    else{

    list_for_each_entry_rcu(client, &evdev->client_list, node)

    /*

    遍历evdev的client_list链表,将事件event传递给每一个evdev客户端去处理,这样所有打开该eventn设备文件的进程都将得到响应。

    */

    evdev_pass_event(client, &event);

    }

    rcu_read_unlock();

    if (type == EV_SYN && code == SYN_REPORT)

    /*

    接受到同步信号之后也就是input_sync(ts->input_dev)调用之后,系统唤醒等待队列,所有阻塞在该等待队列的进程得到响应,告诉它们现在有数据可以读取了。

    进程通过poll系统调用,等待数据,直到input_device将数据传递过来且通过上面的evdev_pass_event将数据传递到第一个evdev客户端处理完之后:

    static unsigned int evdev_poll(struct file *file, poll_table *wait)

    {

    struct evdev_client *client = file->private_data;

    struct evdev *evdev = client->evdev;

    unsigned int mask;

    poll_wait(file, &evdev->wait, wait);//进程阻塞,直到evdev_pass_event处理完之后被唤醒

    mask = evdev->exist ? POLLOUT | POLLWRNORM : POLLHUP | POLLERR;

    if (client->packet_head != client->tail)

    mask |= POLLIN | POLLRDNORM;

    return mask;

    }

    */

    wake_up_interruptible(&evdev->wait);//唤醒阻塞进程,进程返回后上层应用就能意识到数据已经准备好了,可以读取了。

    }

    static void evdev_pass_event(struct evdev_client *client,

         struct input_event *event)

    {

    /* Interrupts are disabled, just acquire the lock. */

    spin_lock(&client->buffer_lock);

    wake_lock_timeout(&client->wake_lock, 5 * HZ);

    client->buffer[client->head++] = *event;//将event保存在evdev_client的buffer当中

    client->head &= client->bufsize - 1;

    if (unlikely(client->head == client->tail)) {

    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;

    }

    if (event->type == EV_SYN && event->code == SYN_REPORT) {

    client->packet_head = client->head;

    kill_fasync(&client->fasync, SIGIO, POLL_IN);//接收到input_device的同步信号之后evdev_client发送同步信号。

    }

    spin_unlock(&client->buffer_lock);

    }

    return retval;

    }

    下面看看数据是怎么被读取的:

    static ssize_t evdev_read(struct file *file, char __user *buffer,

      size_t count, loff_t *ppos)

    {

    struct evdev_client *client = file->private_data;

    struct evdev *evdev = client->evdev;

    struct input_event event;

    int retval;

    if (count < input_event_size())//如果要读取的数据量少于一个input_event的字长,说明读取参数有误

    return -EINVAL;

    if (client->packet_head == client->tail && evdev->exist &&

        (file->f_flags & O_NONBLOCK))//如果客户端buffer没有input_event且evdev有效且以非阻塞方式读取数据,刚返回重新读取错误

    return -EAGAIN;

    retval = wait_event_interruptible(evdev->wait,

    client->packet_head != client->tail || !evdev->exist);//客户端evdev_client buffer有数据或者evdev无效,进程继续执行

    if (retval)

    return retval;

    if (!evdev->exist)

    return -ENODEV;

    while (retval + input_event_size() <= count &&

           evdev_fetch_next_event(client, &event)) {

    if (input_event_to_user(buffer + retval, &event))//每次向用户空间copy一个input_event,直到evdev_client buffer中所有input_event copy完毕

    return -EFAULT;

    retval += input_event_size();

    }

    return retval;

    }

    根据对evdev的分析,我们可知,当input_device report数据的时候,数据首先被组织成一个input_event事件,并记录该事件的详细时间,然后input_event会被传递给每一个打开该设备文件eventn的evdev_client客户端的buffer中保存,当input_device report type为EV_SYN且code为SYN_ERPORT数据的时候,说明在input_dev该次已全部完成数据的report,唤醒阻塞进程(应用进程通过调用poll()或者select()系统调用阻塞在evdev的evdev_poll()当中,检测是否存在数据可以读取),开始在evdev_read()中将各自event_client 的buffer中的input_event copy到用户空间,这样用户空间就能获取到input_device从硬件读取然后传递给input_handler的数据了。详细的上层应用是怎么读取input设备数据流程,且听下回分解。

  • 相关阅读:
    关于.NET2.0下的脱机文件App_Offline.htm文件
    VS2005中安装AJAX指南
    Ajax中“Sys未定义”错误的解决方法汇总
    GridView控件模板列中的按钮单击时,在RowDataBound事件中获取该行行号
    用户控件中使用User.Identity
    同一个页面中的不同Button分别验证某一部分输入控件
    hdu Tempter of the Bone
    acm steps chapter1总结
    ORACLE中的TOPN查询(TOPN分析),分页查询
    MySQL中如何实现select top n
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/10783643.html
Copyright © 2020-2023  润新知