linux input输入子系统分析《四》:input子系统整体流程全面分析

来源:互联网 发布:nba2konline隐藏数据 编辑:程序博客网 时间:2024/05/18 20:10

主要讲述本人在学习Linux内核input子系统的全部过程,如有分析不当,多谢指正。以下方式均可联系,文章欢迎转载,保留联系信息,以便交流。

邮箱:eabi010@gmail.com

主页:www.ielife.cn(爱嵌论坛——嵌入式技术学习交流)

博客:blog.csdn.net/ielife


1      input输入子系统整体流程

本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层。由于上节代码讲解了设备驱动层的写法,因此在开头部分会从设备驱动层做为线索,分析输入子系统和事件处理层是如何配合的,最后从用户角度出发,从“/dev/input/*”接口如何使用输入子系统提供的服务。

既然需要详细分析,有一个这样的流程图能够帮助我们在被绕进代码的过程中,找到出口,你能够知道你现在位于代码框架的什么位置,不会忘记正在分析的代码的“身份”。其实在初识输入子系统中已经贴出了这张图,我们再把它拿出来参考一下吧,见下图6。


图6  linux输入子系统事件处理机制

1.1    设备驱动层注册到input子系统

在上一节分析了S3C2440触摸屏驱动的代码,初始化函数定义了struct input_devinput结构体,它用于描述一个输入子系统设备,任何驱动设备如果想标明自己是输入设备,都应该通过初始化这样的结构体,并且调用input_allocate_device()函数进行注册。

了解这一过程,需要先看一下structinput_dev结构体的内容:

struct input_dev {void *private;//输入设备私有指针,一般指向用于描述设备驱动层的设备结构const char *name;//提供给用户的输入设备的名称const char *phys;//提供给编程者的设备节点的名称const char *uniq;//指定唯一的ID号,就像MAC地址一样struct input_id id;//输入设备标识ID,用于和事件处理层进行匹配unsigned long evbit[NBITS(EV_MAX)];//位图,记录设备支持的事件类型unsigned long keybit[NBITS(KEY_MAX)];//位图,记录设备支持的按键类型unsigned long relbit[NBITS(REL_MAX)];//位图,记录设备支持的相对坐标unsigned long absbit[NBITS(ABS_MAX)];//位图,记录设备支持的绝对坐标unsigned long mscbit[NBITS(MSC_MAX)];//位图,记录设备支持的其他功能unsigned long ledbit[NBITS(LED_MAX)];//位图,记录设备支持的指示灯unsigned long sndbit[NBITS(SND_MAX)];//位图,记录设备支持的声音或警报unsigned long ffbit[NBITS(FF_MAX)];//位图,记录设备支持的作用力功能unsigned long swbit[NBITS(SW_MAX)];//位图,记录设备支持的开关功能unsigned int keycodemax;//设备支持的最大按键值个数unsigned int keycodesize;//每个按键的字节大小void *keycode;//指向按键池,即指向按键值数组首地址int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);//修改按键值int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);//获取按键值struct ff_device *ff;//用于强制更新输入设备的部分内容unsigned int repeat_key;//重复按键的键值struct timer_list timer;//设置当有连击时的延时定时器int state;//设备状态int sync;//同步事件完成标识,为1说明事件同步完成int abs[ABS_MAX + 1];//记录坐标的值int rep[REP_MAX + 1];//记录重复按键的参数值unsigned long key[NBITS(KEY_MAX)];//位图,按键的状态unsigned long led[NBITS(LED_MAX)];//位图,led的状态unsigned long snd[NBITS(SND_MAX)];//位图,声音的状态unsigned long sw[NBITS(SW_MAX)];//位图,开关的状态int absmax[ABS_MAX + 1];//位图,记录坐标的最大值int absmin[ABS_MAX + 1];//位图,记录坐标的最小值int absfuzz[ABS_MAX + 1];//位图,记录坐标的分辨率int absflat[ABS_MAX + 1];//位图,记录坐标的基准值int (*open)(struct input_dev *dev);//输入设备打开函数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 *grab;//类似私有指针,可以直接访问到事件处理接口eventstruct mutex mutex;//用于open、close函数的连续访问互斥unsigned int users;//设备使用计数struct class_device cdev;//输入设备的类信息union {//设备结构体struct device *parent;} dev;struct list_headh_list;//handle链表struct list_headnode;//input_dev链表};

也许就这样赤裸裸的看上面的结构体,会觉得摸不着头脑,但是有一点是确定的,我们在写输入设备驱动时会定义这样一个输入设备结构体,并调用input_allocate_device()函数,这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回input_dev *的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口。

那么input_allocate_device()函数做了什么呢?打开函数看一下(input.c中实现):

struct input_dev *input_allocate_device(void){struct input_dev *dev;//动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);//分配成功执行的代码,进行成员的默认填充if (dev) {dev->cdev.class = &input_class;//支持热插拔的结构体dev->cdev.groups = input_dev_attr_groups; //描述设备的硬件信息和支持的事件类型class_device_initialize(&dev->cdev);//类设备初始化,添加进input类设备模型中mutex_init(&dev->mutex);//初始化互斥锁INIT_LIST_HEAD(&dev->h_list);//初始化handle链表INIT_LIST_HEAD(&dev->node);//初始化输入设备链表}}

通过input_allocate_device()函数,我们设备驱动现在持有的input_dev里面就被赋予了input的“形象”,但是还需要我们去充实一下“内在”,因此,设备驱动程序,还需要为自己的设备增加自己的特性,才能创造独有的设备“形象”。拿上一节的代码举例如下:

struct input_dev *input_dev = input_allocate_device();input_dev->name = "s3c2410 Touchscreen";input_dev->phys = "s3c2410ts/input0";input_dev->id.bustype = BUS_HOST;input_dev->id.vendor = 0x0001;input_dev->id.product = 0x0002;input_dev->id.version = 0x0100;input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0);

以上的内容在前一节触摸屏驱动的初始化代码中也有分析,完成了输入设备的初始化工作。但是这仅是初始化自己的“特点”,还需要通知输入子系统有这样一个新设备诞生了,这就需要调用输入子系统的注册函数input_register_device(input_dev)来完成。

input_register_device()用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,我们在下面的代码中进行注释分析:

int input_register_device(struct input_dev *dev){/* 用于记录输入设备名称的索引值 */static atomic_t input_no = ATOMIC_INIT(0);/* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */struct input_handler *handler;const char *path;int error;/* 默认所有的输入设备都支持EV_SYN同步事件 */set_bit(EV_SYN, dev->evbit);/* * 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持 * 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数 * 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间 */init_timer(&dev->timer);if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {dev->timer.data = (long) dev;dev->timer.function = input_repeat_key;dev->rep[REP_DELAY] = 250;dev->rep[REP_PERIOD] = 33;}/* 如果设备驱动没有设置自己的获取键值的函数,系统默认 */if (!dev->getkeycode)dev->getkeycode = input_default_getkeycode;/* 如果设备驱动没有指定按键重置函数,系统默认 */if (!dev->setkeycode)dev->setkeycode = input_default_setkeycode;/* 重要,把设备挂到全局的input子系统设备链表input_dev_list上 */list_add_tail(&dev->node, &input_dev_list);/* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id), "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);/* 如果这个值没有设置,系统把输入设备挂入设备链表 */if (!dev->cdev.dev)dev->cdev.dev = dev->dev.parent;/* 在/sys目录下创建设备目录和文件 */error = class_device_add(&dev->cdev);if (error)return error;/* 获取并打印设备的绝对路径名称 */path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);printk(KERN_INFO "input: %s as %s\n",dev->name ? dev->name : "Unspecified device", path ? path : "N/A");kfree(path);/* 核心重点,input设备在增加到input_dev_list链表上之后,会查找 * input_handler_list事件处理链表上的handler进行匹配,这里的匹配 * 方式与设备模型的device和driver匹配过程很相似,所有的input * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list * 上,进行“匹配相亲”*/list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);input_wakeup_procfs_readers();return 0;}

上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情:

  • 进一步初始化输入设备,例如连击事件;
  • 注册输入设备到input类中;
  • 把输入设备挂到输入设备链表input_dev_list中;
  • 查找并匹配输入设备对应的事件处理层,通过input_handler_list链表

我们需要再分析下这个匹配的过程,“相亲”这种事情还是很有意思的,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:

const struct input_device_id *id;int error;/* 如果handler的blacklist被赋值了并且则优先匹配 */if (handler->blacklist && input_match_device(handler->blacklist, dev))return -ENODEV;/* 否则利用handler->id_table和dev进行匹配,后面讲述匹配什么和过程 */id = input_match_device(handler->id_table, dev);if (!id)return -ENODEV;/* 这是一根“红线”,虽然你可能觉的是黑色的,但不可否认,他们真的匹配上了  * 调用handler->connnect函数进行匹配,匹配详细过程后面讲述 */error = handler->connect(handler, dev, id);if (error && error != -ENODEV)printk(KERN_ERR"input: failed to attach handler %s to device %s, ""error: %d\n",handler->name, kobject_name(&dev->cdev.kobj), error);return error;

我们先来看下input_match_device()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么?

/* 事件处理层中的对应flags如果设置或者driver_info被设置则进行匹配 */for (; id->flags || id->driver_info; id++) {/* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID   如果没有匹配上将进行MATCH_BIT匹配 */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;/* MATCH_BIT用于匹配设备驱动中是否设置了这些为,MATCH_BIT的宏* 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的* 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL*/MATCH_BIT(evbit,  EV_MAX);MATCH_BIT(keybit, KEY_MAX);MATCH_BIT(relbit, REL_MAX);MATCH_BIT(absbit, ABS_MAX);MATCH_BIT(mscbit, MSC_MAX);MATCH_BIT(ledbit, LED_MAX);MATCH_BIT(sndbit, SND_MAX);MATCH_BIT(ffbit,  FF_MAX);MATCH_BIT(swbit,  SW_MAX);return id;}return NULL;

既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧,当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说找到了evdev类型的驱动,在input/evdev.c中):

struct evdev *evdev;struct class_device *cdev;dev_t devt;int minor;int error;/* EVDEV_MINORS为32,代表共能容纳32个evdev事件层设备,下面代码在找到空的地方,用于保存evdev事件层的数据,即上面定义的evdev */for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);/* 这说明内核已经没办法再分配这种类型的设备了 */if (minor == EVDEV_MINORS) {printk(KERN_ERR "evdev: no more free evdev devices\n");return -ENFILE;}/* 开始给evdev事件层驱动分配空间了 */evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);if (!evdev)return -ENOMEM;/* 初始化client_list列表和evdev_wait队列,后面介绍 */INIT_LIST_HEAD(&evdev->client_list);init_waitqueue_head(&evdev->wait);/* 初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */evdev->exist = 1;evdev->minor = minor;evdev->handle.dev = dev;evdev->handle.name = evdev->name;evdev->handle.handler = handler;evdev->handle.private = evdev;sprintf(evdev->name, "event%d", minor);/* 重要,上层访问时通过次设备号找到事件处理的接口 */evdev_table[minor] = evdev;/* evdev事件设备的此设备号的基准值INPUT_MAJOR, EVDEV_MINOR_BASE */devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),/* 创建用户事件驱动层设备访问接口/dev/input/event* */cdev = class_device_create(&input_class, &dev->cdev, devt,   dev->cdev.dev, evdev->name);if (IS_ERR(cdev)) {error = PTR_ERR(cdev);goto err_free_evdev;}/* 提供/sys目录的用户空间接口 */error = sysfs_create_link(&input_class.subsys.kobj,  &cdev->kobj, evdev->name);if (error)goto err_cdev_destroy;/* input_dev设备驱动和handler事件处理层的关联,由handle完成 */error = input_register_handle(&evdev->handle);

通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合,代码如下:

struct input_handler *handler = handle->handler;/* 将d_node链接到输入设备的h_list,h_node链接到事件层的h_list链表上* 因此,在handle中是输入设备和事件层的关联结构体,通过输入设备可以* 找到对应的事件处理层接口,通过事件处理层也可找到匹配的输入设备*/list_add_tail(&handle->d_node, &handle->dev->h_list);list_add_tail(&handle->h_node, &handler->h_list);/* 如果start函数有定义则调用,但是evdev结构体中并未初始化这个函数 */if (handler->start)handler->start(handle);

以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。纵观整个过程,输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。

由于以上的这些原因,才有了上述代码的关联过程,通过下图7,看一下整个关联注册的过程:


图7  input_dev与handler关联图

通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。这一节主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联,下一节将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。

1.2    从应用层的角度出发看input子系统

以上部分已经借助input子系统把input设备驱动层与事件驱动层进行了关联,我们以前面几节所介绍的s3c2440_ts.c(输入设备层驱动)和evdev.c(事件处理层驱动)为例,来分析这一过程。

由于s3c2440_ts.c中上报的事件类型为按键、绝对值坐标,而evdev事件驱动程序是全匹配的,因此早在s3c2440_ts.c注册的过程中,就会创建设备节点/dev/input/event0(假设内核中没有其他的event类型的输入设备,这里就是event0),当然由于我的内核是2.6.22,这个节点的名字是/dev/event0。因此需要注意你的内核版本,以确定设备节点的位置,我们以“/dev/event0”为例来说明。

我们知道,应用层使用设备的第一步,是open(“/dev/event0”),因此这里event0的主设备号成为关键,因为主设备号将表明你是什么设备,我们ls -l查看/dev/event0发现:

crw-r-----1 root root 13, 64 2012-07-26 14:32 /dev/input/event0

由此可见主设备是13,输入命令cat /proc/devices查看主设备为13的是input设备,因此可以确定当我们执行open函数打开event0设备的时候,会调用input设备的open驱动函数,这个函数在input.c中,为了说明这一问题,需要从input驱动注册过程开始,还是input.c文件:

/* 输入设备初始化函数 */static int __init input_init(void){       class_register(&input_class);       input_proc_init();       register_chrdev(INPUT_MAJOR,"input", &input_fops);}

可以看到,输入设备初始化的过程首先建立了input类,初始化input在proc下的节点,然后注册input设备,设备名称为input,操作接口是input_fops,主设备号是INPUT_MAJOR=13。

由以上可知,只要是主设备号为13的设备驱动程序,都是用input_fops接口,即当event0设备使用open函数打开时,会调用到input_fops接口中的open驱动函数,这个结构体的初始化为:

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

可以看到,只实现了一个open功能字段,再看input_open_file的实现:

static int input_open_file(struct inode *inode, struct file *file){       struct input_handler *handler =input_table[iminor(inode) >> 5];       const struct file_operations *old_fops,*new_fops = NULL;       if (!handler || !(new_fops =fops_get(handler->fops)))              return -ENODEV;       old_fops = file->f_op;       file->f_op = new_fops;       new_fops->open(inode, file);}

以上代码的功能为找到对应事件驱动层的fops,即进行fops的接口转换,指向对应设备的事件处理接口。其中input_table[iminor(inode)]>>5的input_table是一个全局的input_handler类型的数组,iminor(inode)取得次设备号,并且右移5位索引input_table表中对应的位置,为什么这样做呢?这是因为这个表格中填写的就是事件处理的指针,我们待会分析,先继续查看下面的代码。if中将判断是否为空并且事件处理层中的fops有没有初始化,如果没有就不能进行接口转换,报出设备不存在的错误,如果设备存在则把input设备的f_op驱动接口指向input_table表中存在的接口,并调用其open函数。那么这个input_table里面到底存放了什么呢?我们还是拿触摸屏驱动来讲解。由于触摸屏驱动已经完成了和evdev.c事件处理层的匹配,且次设备号为64,设备名称为/dev/event0,这是我们通过分析驱动注册中获得的内容,既然input核心设备注册了,s3c2440触摸屏驱动也注册了,那会不会evdev设备也会注册了呢?答案是肯定的,要想知道input_table里面放了什么,必须要去查看evdev设备的注册过程,打开input/evdev.c查看它的注册过程:

static struct input_handler evdev_handler = {       .event =   evdev_event,                               //事件处理       .connect =      evdev_connect,                    //设备连接       .disconnect =  evdev_disconnect,                //注销连接       .fops =           &evdev_fops,                      //驱动功能接口       .minor =  EVDEV_MINOR_BASE,              //evdev的值为64       .name =          "evdev",                              //设备名称       .id_table =      evdev_ids,                           //用于匹配设备驱动的数组}; static int __init evdev_init(void){       return input_register_handler(&evdev_handler);          //evdev设备驱动注册}

由以上的内容可以知道evdev_handler也被作为一个设备来操作,但是它属于input handler事件处理设备,然而我们在evdev_handler结构体的.fops字段又发现它的驱动接口为字符设备类型,在input中,如果input_table匹配到了evdev_handler,将会把file->f_op=&evdev_fops,那么如果使用read、write等函数操作,将会调用到evdev_fops中的read、write。

为了进一步查看input_table表中的内容是如何填充的,还需要查看这个注册的过程:

int input_register_handler(struct input_handler *handler){    ……           input_table[handler->minor>> 5] = handler;    ……}

当然这个注册过程并不是只有这么一句话,看到这条语句,相信你应该知道什么意思了。在input的open函数执行之前,即我们的open代码打开之前,input_table中的字段已经被事件处理层填充了。由于evdev的次设备号在初始化的时候就设置成了64,因此这里相当于:

input_table[2]=&evdev_handler;

回到input_open_file函数查看new_fops->open(inode, file)你便知道了调用的是:

evdev_handler.evdev_fops.open(inode, file);

在分析open函数之前,解释一下为什么要右移5位,这说明一个问题,次设备号的低5位被忽略,这说明evdev的最大支持的输入设备驱动个数为2^5次方等于32个,你可能会看到你的/dev目录下面有event0、event1、event2等设备,他们的次设备号分别为64、65、66等等。但最大是64+32-1,因此input_table为这些输入设备增加的一个统一接口,通过上层打开设备时,只要次设备号在64+32-1之间的设备都会重新定位到evdev_handler中,即event*设备打开后执行的底层函数将被重新定义到evdev_handler中。

相信上面的问题已经描述清楚,如果还是不明白,你最起码应该知道的是,input设备中的open函数只是一个接口,通过次设备号才找到了真正的事件处理接口。接下来要看新的open接口的实现了,evdev_handler-> fops->open实现如下:

/*evdev字符设备驱动接口 */static const struct file_operations evdev_fops = {       .owner = THIS_MODULE,       .read =           evdev_read,       .write =   evdev_write,       .poll =            evdev_poll,       .open =          evdev_open,       .release = evdev_release,       .unlocked_ioctl = evdev_ioctl,#ifdef CONFIG_COMPAT       .compat_ioctl =      evdev_ioctl_compat,#endif       .fasync = evdev_fasync,       .flush =   evdev_flush};/*evdev设备open函数的实现过程 */static int evdev_open(struct inode *inode, struct file *file){       struct evdev_client *client;       struct evdev *evdev;       /* 如果是event0,对于evdev设备来说,次设备号当然是0 */       int i = iminor(inode) - EVDEV_MINOR_BASE;       int error;       /* 如果大于32,说明超出了evdev能够容纳的最大输入设备个数 */       if (i >= EVDEV_MINORS)              return -ENODEV;       /* 由于evdev中能容纳32个输入设备,因此通过设备号event0中的0定位到是要处理的是哪一个输入设备,evdev_table中的内容在输入设备驱动注册时通过evdev_connect填充 */       evdev = evdev_table[i];       /* 判断是否设备接口存在,evdev_exist也是在evdev_connect填充为1 */       if (!evdev || !evdev->exist)              return -ENODEV;       /* 存在则分配evdev中的client来处理event* */       client = kzalloc(sizeof(struct evdev_client),GFP_KERNEL);       if (!client)              return -ENOMEM;        /* 把event*中的接口指向evdev_table中对应项 */       client->evdev = evdev;       /* 把client->node链接到evdev子集中 */       list_add_tail(&client->node,&evdev->client_list);       /* 如果open是第一个打开,则会执行input_open_device*/       if (!evdev->open++ &&evdev->exist) {              error =input_open_device(&evdev->handle);              if (error) {                     list_del(&client->node);                     kfree(client);                     return error;              }       }       /* 将file私有指针指向client*/       file->private_data = client;       return 0;}由上的代码可以看出,最终是要执行input_open_device去执行设备驱动程序中的代码,然而我们在定义设备驱动的时候并没有给input_dev中的open字段填充内容,因此可以看到input_open_device函数的执行过程:if(!dev->users++ && dev->open)              err = dev->open(dev);        if (err)              handle->open--;

上面截取了片段,并没有执行到open函数,open进行自减操作,表示没有调用过open,这个值主要是为了close中判断open为0时释放资源使用。

不仅如此,我们在触摸屏驱动中也没有定义read、write,那当触摸屏上报事件时,是如何处理的呢?我们需要先到触摸屏驱动程序中找到上报事件的函数再做进一步分析。

1.3    输入设备上报事件的处理过程

触摸屏驱动程序上报事件的函数为:

input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp);input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp);input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure);input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown);input_sync(dev);

然而他们其实是input_event函数的封装,调用的都是input_event函数,这一函数在input.c中实现如下:

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, intvalue){       struct input_handle *handle;        if (type > EV_MAX || !test_bit(type,dev->evbit))              return;       switch (type) {              case EV_SYN:                     switch (code) {                            case SYN_CONFIG:                                   if(dev->event)                                          dev->event(dev,type, code, value);                                   break;                            case SYN_REPORT:                                   if(dev->sync)                                          return;                                   dev->sync= 1;                                   break;                     }                     break;              case EV_KEY:              case EV_SW:              case EV_ABS:              case EV_REL:              case EV_MSC:              case EV_LED:              case EV_SND:              case EV_REP:              case EV_FF:       }        if (type != EV_SYN)              dev->sync = 0;        if (dev->grab)              dev->grab->handler->event(dev->grab,type, code, value);       else              list_for_each_entry(handle,&dev->h_list, d_node)                     if (handle->open)                            handle->handler->event(handle,type, code, value);}

代码被做了精简,其中就是在匹配上报的事件,并根据事件的类型调用驱动程序中相应的函数来完成,但是由于我们并,没有定义过这些函数,因此执行最后的handle_handler_event函数,由事件处理层evdev_event函数来完成事件的保存工作,具体过程如下:

list_for_each_entry(client,&evdev->client_list, node) {client->buffer[client->head].type= type;client->buffer[client->head].code= code;client->buffer[client->head].value= value;client->head= (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);}

这里列举了关键代码,即上报的事件被保存到了client_buffer中,其中client_buffer是一个循环缓冲区,client->head表示当前数据的位置,因此每次都写到client->head的位置,而读数据时需要到client_tail中读取。因为在open的时候,client已经被链入到了evdev->client_list中,因此通过可以通过list_for_each_entry重evdev->client_list中找到对应的client。

事件的上报都会把数据保存到client->buffer中,以便上层通过read和write进行读去和写入。

1.4    通过设备节点读取输入事件

还是以触摸屏驱动程序和evdev事件处理层驱动来分析:

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;       int retval;        /* 判断用户给的count是否能够容纳事件数据的大小*/       if (count < evdev_event_size())              return -EINVAL;        /* 如果数据不为空并且设备存在并且是阻塞访问方式才能继续执行 */       if (client->head == client->tail&& evdev->exist && (file->f_flags & O_NONBLOCK))              return -EAGAIN;        /* 如果数据为空,设置进程等待底层驱动层上报事件到client->buffer中 */       retval =wait_event_interruptible(evdev->wait,              client->head != client->tail|| !evdev->exist);       if (retval)              return retval;        if (!evdev->exist)              return -ENODEV;        /* 循环读取数据 */       while (client->head != client->tail&& retval + evdev_event_size() <= count) {               struct input_event *event =(struct input_event *) client->buffer + client->tail;               if (evdev_event_to_user(buffer +retval, event))                     return -EFAULT;               client->tail = (client->tail+ 1) & (EVDEV_BUFFER_SIZE - 1);              retval += evdev_event_size();       }        return retval;}

这里如果没有数据,进程会睡眠,那由谁来唤醒呢?细心的话可以发现,当设备驱动层调用input_event上报事件调用相应的event函数进行事件写入时,是会唤醒阻塞等待的进程的。

1.5    通过设备节点写入输入事件

写入过程:

static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count,loff_t *ppos){       /* 循环写入,调用input_inject_event函数 */       while (retval < count) {               if (evdev_event_from_user(buffer +retval, &event))                     return -EFAULT;              input_inject_event(&evdev->handle,event.type, event.code, event.value);              retval += evdev_event_size();       }        return retval;}

上述代码中的event是input_event数组,包含了事件的类型、键值,通过input_inject_event把数据写入循环数组client->buffer中,input_inject_event调用的是input_event函数。

1.6    总结

本节对input子系统的整个过程做了分析,并从两个角度进行考虑,对于写输入设备驱动程序的来说,需要掌握的是设备应该上报事件的类型,这样才能匹配到对应的事件层驱动帮助你保存对应的数据,而对于设备上层开发者来说,应该先使用cat /proc/bus/input/devices查看你操作的设备类型和处理接口,以帮助你更好的对设备操作。

原创粉丝点击