linux的input子系统框架源码剖析

整个输入子系统的框架主要分为三个部分,如下图所示:

下面是具体的阐述:

首先从核心文件drivers/input/input.c中的入口函数分析:
input_init:
    // 向系统中注册设备,fops使用input_fops
    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    
下面我们着重看一下该文件中定义的file_operations结构体:
static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
};
可见这个结构体定义非常简单,只有一个open函数,要想实现对输入
设备的一系列操作,该fops显然不可能完成,那么系统又是怎样处理
的呢?

猜测:我们猜测系统可能是在open函数中做了进一步的处理,比如说
重新绑定了一个file_operations结构体。

下面我们来看open函数:
input_open_file:
    // 由设备的次设备号在input_table中获取一个处理句柄handler
    handler = input_table[iminor(inode) >> 5];
    // 获取新的fops
    if (handler)
        new_fops = fops_get(handler->fops);   
    // 重新绑定新的fops
    file->f_op = new_fops;
上面这段代码验证了我们的猜想,旧的fops只是一个引子,具体做事的
是handler中定义的fops。那么这个handler的作用是什么呢?由名字可
以猜测是一个句柄,帮着处理事情的,可以看成是一个中间工具。
handler结构体的详细描述:
struct input_handler {
    void *private;
    void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
    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);

    const struct file_operations *fops;
    int minor;
    const char *name;

    const struct input_device_id *id_table;

    struct list_head    h_list;
    struct list_head    node;
};
这个结构体里面成员的具体作用我们先放着,后面用到了再说。
思考:问题来了,这个input_table中的handler是在哪里定义并填充到
这个数组中来的呢?

下面我们就要看input_register_handler函数了:
input_register_handler:
    // 将handler加入到input_table数组中
    input_table[handler->minor >> 5] = handler
    // 将handler加入到input_handler_list链表中
    list_add_tail(&handler->node, &input_handler_list);
    // 遍历input_dev_list链表,找到里面的每一个设备
    list_for_each_entry(dev, &input_dev_list, node)
        // 将该handler与其中的某个设备进行配对
        input_attach_handler(dev, handler);
        

思考:上面我们讲到了handler与device的配对,handler结构体已经讲解
device设备又是哪里来的呢?
device设备主要是通过input_register_device函数注册进来的:
input_register_device:
    // 将设备加入到input_dev_list链表中
    list_add_tail(&dev->node, &input_dev_list);
    // 遍历input_handler_list链表得到每一个handler
    list_for_each_entry(handler, &input_handler_list, node)
        // 将handler与device进行配对
        input_attach_handler(dev, handler);
        

思考:上面两个注册函数非常对称,都用到了匹配,这个配对是怎样
实现的呢?答案在input_attach_handler函数中    
input_attach_handler:
    id = input_match_device(handler, dev);
        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 (!handler->match || handler->match(handler, dev))
                return id;
        }
    // 将handler与device进行连接,印证了我们最开始讲解的那副三角关系图
    error = handler->connect(handler, dev, id);
上面的匹配过程也很简单,主要用到了handler结构体中的id_table成员,
id_table中有很多id选项,将其中的id与device结构体的id一一对比,
只要有一个id对的上,就表明该handler可以配对该设备,并返回id。
上面两个地方均进行匹配的好处:
不管是先注册的设备,还是先注册的handler,都会找到一个handler与
一个device进行配对,这个实现很巧妙。

上面将handler与device进行了连接connect,调用的是操作句柄handler
的connect函数,下面以一个具体的handler分析一下这个实现过程:
以drivers/input/evdev.c中的evdev_handler为例:
evdev_connect:
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.handler = handler;    
    error = input_register_handle(&evdev->handle);
上面代码主要是构造了一个evdev结构体,这个结构体有一个关键的
成员handle,这个是将handler与device联系起来的关键,这个handle
对象同时指向了device与handler,也就是说通过handle对象既可以
找到handler也可以找到device
下面我们来看看input_register_handle函数做了什么:
input_register_handle:
    // 将handle对象加入到dev->h_list链表中
    list_add_rcu(&handle->d_node, &dev->h_list)
    // 将handle对象加入到handler->h_list链表中
    list_add_tail_rcu(&handle->h_node, &handler->h_list)
上面代码的目的很明确:这样做了之后,我们既可以通过handler找到
对应的device,也可以通过device找到对应的handler,这样handler
与device之间就建立了双向连接。


有了上面的三角关系,下面我们具体看驱动框架怎样读按键:
read:
    evdev_read
        // 判断读取的字节数是否正确
        if (count < input_event_size())
            return -EINVAL;
        // 如果数据为空,且为非阻塞读取,则直接返回
        if (client->head == client->tail && evdev->exist &&
            (file->f_flags & O_NONBLOCK))
            return -EAGAIN;
        // 如果为阻塞读取,而此时无数据,则进入休眠
        retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);
上面代码我们并不陌生,这是驱动中读函数惯用的处理方法,这里既然有个
休眠,那么在哪里进行唤醒呢?
唤醒是在evdev_event函数中:
evdev_event:
    if (client)
        evdev_pass_event(client, &event);
            -->client->buffer[client->head++] = *event;
                client->head &= EVDEV_BUFFER_SIZE - 1;
    else
        list_f
        or_each_entry_rcu(client, &evdev->client_list, node)
            evdev_pass_event(client, &event);

    wake_up_interruptible(&evdev->wait)
由上面的代码我们不难猜测出这是一个事件的处理函数,当有事件发生时
我们填充数据,并唤醒进程。

上面的定义的事件处理函数是纯软件层面的东西,那么由谁来调用这个
事件处理函数呢?在什么时候调用呢?其实这个不难猜测,我们在与
硬件有关的device代码中一旦获取到输入数据,就表示有事件发生,
我们就调用这个事件处理函数。

以drivers/input/keyboard/gpio_keys.c为例验证一下我们的想法:
gpio_keys_report_event:
    input_event(input, type, button->code, !!state)
        input_handle_event(dev, type, code, value)
            // 如果dev->event设备中定义了event处理函数,就调用此事件函数
            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);
                    // 由设备找到handle
                    handle = rcu_dereference(dev->grab);
                    // 如果存在对应的handle,通过handle就能找到handler,调用其事件处理函数
                    if (handle)
                        handle->handler->event(handle, type, code, value);
上面代码涉及到的由device找到handle,由handle找到handler,这就是
为什么前面要将device与handler建立connect的原因,怎样建立connect
前面已经解释过了。

总结:
怎么写符合输入子系统框架的驱动程序?
1. 分配一个input_dev结构体
2. 设置
3. 注册
4. 硬件相关的代码,比如在中断服务程序里上报事件

猜你喜欢

转载自blog.csdn.net/Wenlong_L/article/details/81590134