八、输入子系统

前面几章我们写的按键驱动程序虽然已经足够完善,但是这个驱动只有知道/dev/key设备节点和write()格式的人才能使用,不具有适应性

故本节引入标准的输入子系统,来编写通用的输入类设备。输入子系统是对所有的标准输入类设备的统一的管理系统,使用这个模型可以跨平台的处理所有的输入类设备

一、输入子系统分层

输入子系统将一个输入设备的输入过程分成了设备驱动(input device driver)和事件驱动(input event driver)两层。设备驱动负责从底层硬件采集数据,事件驱动负责给用户程序提供接口。通过分层设计,将不同的设备统一到几种驱动接口上。同一种事件驱动可以用来处理多个同类设备;同一个设备也可以和多种事件驱动相衔接。而事件驱动和设备驱动则由输入核心层进行连接,匹配。分层结构如下图:

输入子系统核心层定义在drivers/input/input.c中

由于输入子系统也是字符设备驱动程序,因此它一定也会有创建类、注册字符设备的过程,而且会有file_operations等结构体,我们可以从此进行分析

二、input.c分析

在input_init()函数中所做的和按键驱动程序中所做的大致相同,如创建类、注册名为input的字符设备

static int __init input_init(void)
{
    ...
    err = class_register(&input_class);
    ...
    err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
    ...
}

其file_operations结构体定义如下:

static const struct file_operations input_fops = {
    .owner = THIS_MODULE,
    .open = input_open_file,
    .llseek = noop_llseek,
};

当我们应用程序open()时,会调用file_operations input_fops对应的open()函数

static int input_open_file(struct inode *inode, struct file *file)
{
        ...
    handler = input_table[iminor(inode) >> 5];
    if (handler)
        new_fops = fops_get(handler->fops);
        ...
    old_fops = file->f_op;
    file->f_op = new_fops;

    err = new_fops->open(inode, file);
        ...
}

由上述代码可知:

1. input_table[]根据次设备号存储handler

2. open()函数使用新的fops(设备驱动中的fops)代替了旧的fops,这个操作也就解释了为什么file_operations结构体中没有读写函数

3. open()函数在替换之后,调用了新的fops的open()函数

handler是我们之前没有分析的,它定义为:

static struct input_handler *input_table[8];

至于数组大小为什么是8,这是因为目前常用的handler只有三种:evdev,mousedev,joydev。而且evdev是通用的handler,定义8个应该够用了

handler结构体定义为:

/**
 * struct input_handler - implements one of interfaces for input devices
 * ...
 */
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;
};

根据注释信息,handler应该就是输入事件驱动程序的结构体

在input_handler结构体中使用了input_handle结构体,其定义如下:

/**
 * struct input_handle - links input device with an input handler
 * ...
 */
struct input_handle {
    void *private;

    int open;
    const char *name;

    struct input_dev *dev;
    struct input_handler *handler;

    struct list_head    d_node;
    struct list_head    h_node;
};

根据注释信息,input_handle用于连接input_dev和input_handler,三者关系在下面分析

分析完了input.c文件,我们来一一分析input_handler、input_handle以及两者的连接过程

三、input_dev

input_dev使用方法遵循:分配、设置、注册

分配:

struct input_dev *input_allocate_device(void)

设置(首先设置事件类,然后设置具体事件):

void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{
    switch (type) {
    case EV_KEY:    // 按键事件类,可指定按键如KEY_1、KEY_Q、KEY_ENTER等
        __set_bit(code, dev->keybit);
        break;

    case EV_REL:    // 相对位移事件类,可指定相对位移如REL_X、REL_Y等
        __set_bit(code, dev->relbit);
        break;

    case EV_ABS:    // 绝对位移事件类,可指定绝对位移如ABS_X、ABS_Y等
        __set_bit(code, dev->absbit);
        break;

    ...
    }

    __set_bit(type, dev->evbit);
}

注册:

int input_register_device(struct input_dev *dev)
{
...
    /* 通用的同步事件 */
    __set_bit(EV_SYN, dev->evbit);

...
    /* 注册的设备名字为input0, 1, 2, ... */
    dev_set_name(&dev->dev, "input%ld",
             (unsigned long) atomic_inc_return(&input_no) - 1);

    /* 添加device */
    error = device_add(&dev->dev);
...
    /* 把dev结构放到链表里面 */
    list_add_tail(&dev->node, &input_dev_list);

    /* 对每一个input_handler都调用input_attach_handler()函数 */
    list_for_each_entry(handler, &input_handler_list, node)
        /* 匹配dev和handler */
        input_attach_handler(dev, handler);
...
}

其中的input_attach_handler(dev, handler);对应input_handle,因为之前说过input_handle用于连接input_dev和input_handler

在注册完成后,若input_dev获得数据,需要向核心层上报事件,上报事件使用如下函数:

// 上报事件
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

// 上报绝对坐标
void input_report_abs(struct input_dev *dev, unsigned int code, int value)

// 上报相对坐标
void input_report_rel(struct input_dev *dev, unsigned int code, int value)

// 上报键值
void input_report_key(struct input_dev *dev, unsigned int code, int value)

// 上报同步事件
void input_sync(struct input_dev *dev)

代码中后四个函数均使用input_event()函数实现,input_event()函数调用过程如下:

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

上报事件最终会调用handler->event()函数

之前说过evdev是通用的handler,在此我便以/drivers/input/evdev.c进行分析

evdev_read()会进行休眠,evdev_event()在上报事件被调用后会唤醒休眠进程,从而完成read()操作

注销:

// 注销
void input_unregister_device(struct input_dev *dev)

// 释放
void input_free_device(struct input_dev *dev)

四、input_handler

注册:

int input_register_handler(struct input_handler *handler)
{
...
    INIT_LIST_HEAD(&handler->h_list);

    /* 设置input_table */
    if (handler->fops != NULL) {
        if (input_table[handler->minor >> 5]) {
            retval = -EBUSY;
            goto out;
        }
        input_table[handler->minor >> 5] = handler;
    }

    /* 把handler放入input_handler_list */
    list_add_tail(&handler->node, &input_handler_list);

    /* 对每一个dev调用input_attach_handler匹配handler */
    list_for_each_entry(dev, &input_dev_list, node)
        input_attach_handler(dev, handler);
...
}

注销:

void input_unregister_handler(struct input_handler *handler)

五、input_dev和input_handler的连接过程

两者匹配使用的是input_attach_handler()函数:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
...
    id = input_match_device(handler, dev);
...
    error = handler->connect(handler, dev, id);
...
}

input_match_device()函数:

static const struct input_device_id *input_match_device(struct input_handler *handler,
                            struct input_dev *dev)
{
    for (id = handler->id_table; id->flags || id->driver_info; id++) {

        // 默认的匹配过程,使用handler->id_table和dev->id进行匹配

        if (!handler->match || handler->match(handler, dev))
            return id;
    }
}

在代码中,匹配成功退出调用handler的connect()函数,否则调用handler的match()函数

在此还是以evdev为例,其连接函数为evdev_connect()定义如下:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
             const struct input_device_id *id)
{
...
    /* 分配evdev */
    evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
...
    /* 设置handle */
    evdev->handle.dev = input_get_device(dev);
    evdev->handle.name = dev_name(&evdev->dev);
    evdev->handle.handler = handler;
    evdev->handle.private = evdev;
...
    /* 注册handle */
    error = input_register_handle(&evdev->handle);

    error = evdev_install_chrdev(evdev);
    error = device_add(&evdev->dev);
...
}

input_dev、input_handler和input_handle三者关系如下:

六、总结

1. input_init()初始化输入子系统

1.1 调用register_chrdev(13, "input", &input_fops);

2. open()输入子系统文件:int input_open_file()

2.1 替换替换file_oprations

2.2 执行new_fops->open()函数

3. 注册input_handler:input_register_handler()

3.1 添加handler到input_table[]数组

3.2 添加handler到input_handler_list链表

3.3 调用input_attach_handler()

 

4. 注册input_dev:input_register_device()

4.1 添加dev到input_dev链表

4.2 调用input_attach_handler()

5. 匹配:input_attach_handler()

5.1 匹配dev->id和handler->id_table

5.2 成功,调用input_handler->connect()

6. 连接:input_handler->connect()

6.1 创建input_handle,三者连接


7. event发生(如按键中断),在中断函数中上报事件:input_event()

7.1 调用input_handler->event()

猜你喜欢

转载自www.cnblogs.com/Lioker/p/10871151.html
今日推荐