我对linux驱动 输入子系统的理解

前言:学习驱动有一段时间了,之前所学习的驱动都是驱动最原始的写法,也移植过很多驱动,然而并没有对内核自带的驱动进行深入的了解,于是出于好奇,就认真的学习了一下内核自带的驱动,我发现之前所学习的驱动都是将file_operations结构体直接定义在驱动定义的C代码里的,当然还有它里面的一些函数操作,可是我看内核自带的驱动代码的时候我发现里面是用另外一种机制来实现这些的,那便是子系统!初次接触到子系统的我可谓一脸懵逼啊!脑海里浮现一大堆问号,比如:子系统是个什么鬼?,它是用来干嘛的?以及它在内核中它是怎么定义的?出于这些问题,为了搞明白它是怎么一回事儿,我并决定一探究竟!于是选择内核自带的按键驱动进行了具体的分析与学习!

     1.什么是子系统?

        内核是操作系统的核心。Linux内核提供很多基本功能,如虚拟内存、多任务、共享库、需求加载、共享写时拷贝(Copy-On-Write)以及网络功能等。增加各种不同功能导致内核代码不断增加。 
          Linux内核把不同功能分成不同的子系统的方法,通过一种整体的结构把各种功能集合在一起,提高了工作效率。同时还提供动态加载模块的方式,为动态修改内核功能提供了灵活性。

    2.linux 驱动子系统

         linux内核中自带了很多的驱动子系统,其中比较典型的有:input子系统,led子系统,framebuffer子系统(LCD),I2c子系统等,这些子系统它是通过一层一层的函数传递封装,它实现了设备驱动的注册,以及定义了file-operations结构体里面的各种函数操作等,不需要在单独的设备驱动代码中进行注册,定义,直接调用相应的的子系统即可,

      3.linux input子系统

      以前学了单独的按键设备驱动以及led驱动,实际上,在linux中实现这些设备驱动,有一种更为推荐的方法,就是input输入子系统。平常我们的按键,触摸屏,鼠标等输入型设备都可以利用input接口(这个接口是什么呢?可以看我对按键驱动分析就一目了然)来简化驱动程序并实现设备驱动。Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互.

         我对input 子系统的理解是:其实input 子系统是对linux输入设备驱动进行了高度抽象最终分成了三层:包括input设备驱动层,核心层,事件处理层,也就是说我们之前移植的按键,usb,触摸屏驱动代码也只是子系统的一部分,起初我们自己编写的那些驱动代码都是分散的,按键是按键,led是led,都没有对这些不同类别的输入设备驱动统一起来,也就是说input 子系统这种机制的出现,最大的优点我觉得就是为内核大大的简化了驱动程序!!!这个input子系统最神秘之处就是它的核心层部分,这个核心层做了什么呢?它在内核中是怎么定义的?对于这两个问题只有深入理解了input.c这个代码之后,并豁然开朗!!!当然我有分析,继续看下文...

下面我们来好好学习一下这神秘的input子系统吧!

   4.input子系统原理

         linux输入子系统的体系结构可以分为三个层面,分别为:硬件驱动层子系统核心层、事件处理层 ,意思就是每个层次只是负责单独的一个功能,无需参与其他的功能,有点类似函数的封装, 三个层面具体的功能如下:

         硬件驱动层:其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
    子系统核心层:子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
       事件处理层:事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

 关于linux输入子系统的框架结构如下图所示:

我对linux驱动 输入子系统的理解_第1张图片

****************************************************************************************************************************************************************************************

分析:由上图所展现的内容就是linux输入子系统的分层结构。

/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

*****************************************************************************************************************************************************************************************


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

我对linux驱动 输入子系统的理解_第2张图片

******************************************************************************************************************************************************************************

分析:由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

作为输入设备的驱动开发者,需要做以下几步:

1. 在驱动加载模块中,设置你的input设备支持的事件类型。(事件类型有:EV_SYN     0x00    同步事件;EV_KEY     0x01    按键事件;EV_LED     0x11    按键/设备灯等)

2. 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)

3.将输入设备注册到输入子系统中

*****************************************************************************************************************************************************************************************

5.内核自带的按键驱动分析

****************************************************************************************************************************************************************************

其实在linux内核中实现按键功能,主要的涉及到两方面的内容:linux的中断处理输入子系统

Linux中断分为两个部分:前半部后半部。前半部是实际响应中断的函数,需要用request_irq注册;后半部是由前半部调度,并在一个更安全的时间来执行的函数,该函数就是具体负责执行中断的内容。前半部不允许被其他中断干扰,因此它的内容短小,而执行后半部时可以响应其他中断。这种机制的好处是可以迅速的响应中断。

输入子系统有三个核心结构体input_devinput_handleinput_handler。input_dev表示一个输入设备,包含输入设备的一些相关信息;input_handler表示对输入事件的具体处理,它为输入设备的功能实现了一个接口;input_handle是用来连接输入设备和输入事件。输入子系统主要的任务就是把这三个结构体连接在一起。

      参考网上很多人总结的博客,大部分人分析linux input子系统都是从硬件驱动层,核心层,事件处理层这三个层面来分析的,

感觉他们分析的蛮好,那么我也按这个套路来学习一下按键驱动子系统!!!我将从底层向用户层的方向来分析这个input 子系统!

*****************************************************************************************************************************************************************************

1.设备驱动层

在驱动层,Linux提供了一个通用的基于GPIO的按键驱动程序文件——gpio_keys.c(它是在drivers/input/keyboard目录下),

我们的按键驱动也是调用该文件的。

[zoulei@CentOS linux-3.0]$ vim drivers/input/keyboard/gpio_keys.c

static int __init gpio_keys_init(void)/*按键的初始化*/
{
       return platform_driver_register(&gpio_keys_device_driver);//注册驱动

}

/*gpio_keys_device_driver的内容为*/
static struct platform_drivergpio_keys_device_driver = {
       .probe            = gpio_keys_probe,

       .remove          = __devexit_p(gpio_keys_remove),

       .driver            = {
              .name      = "gpio-keys",                            //与设备名一致,即与mach-smdk.c文件中的平台设备中.name一致
              .owner    = THIS_MODULE,
              .pm  = &gpio_keys_pm_ops,
              .of_match_table= gpio_keys_of_match,
       }
};




在这个gpio_keys.c文件中我觉得最为重要且最值得注意的就是里面的probe函数,它与我们之前自己编写的代码最大的不同之处就是里面出现了input结构体,话不多说请看源代码!

448 static int __devinit gpio_keys_probe(struct platform_device *pdev)
449 {
450         struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
451         struct gpio_keys_drvdata *ddata;
452         struct device *dev = &pdev->dev;
453         struct input_dev *input;
454         int i, error;
455         int wakeup = 0;
456
457         ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +   //kzalloc为驱动数据ddata开辟一段内存空间,并清零,它实现了kmalloc与memset两个函数的功能
458                         pdata->nbuttons * sizeof(struct gpio_button_data),

459                         GFP_KERNEL);
460         input = input_allocate_device();//为输入设备input开辟一段内存空间
461         if (!ddata || !input) {
462                 dev_err(dev, "failed to allocate state\n");
463                 error = -ENOMEM;
464                 goto fail1;
465         }
466
467         ddata->input = input;
468         ddata->n_buttons = pdata->nbuttons;
469         ddata->enable = pdata->enable;
470         ddata->disable = pdata->disable;
471         mutex_init(&ddata->disable_lock);
472
473         platform_set_drvdata(pdev, ddata); //把驱动数据ddata以私有数据形式存放在平台总线设备pdev中,以备日后使用
474         input_set_drvdata(input, ddata);  //把驱动数据ddata以私有数据形式存放在输入设备input中,以备日后使用
475
476         input->name = pdata->name ? : pdev->name;  //为输入设备input幅值
477         input->phys = "gpio-keys/input0";
478         input->dev.parent = &pdev->dev;
479         input->open = gpio_keys_open;
480         input->close = gpio_keys_close;
481
482         input->id.bustype = BUS_HOST;
483         input->id.vendor = 0x0001;
484         input->id.product = 0x0001;
485         input->id.version = 0x0100;
486
487         /* Enable auto repeat feature of Linux input subsystem */ 
488         if (pdata->rep)
489                 __set_bit(EV_REP, input->evbit); //为输入设备input赋予自动重复特性,因为按键可以反复地按
490
491         for (i = 0; i < pdata->nbuttons; i++)
 {        //为每个按键进行初始化,及设置属性
492                 struct gpio_keys_button *button = &pdata->buttons[i];
493                 struct gpio_button_data *bdata = &ddata->data[i];
494                 unsigned int type = button->type ?: EV_KEY;
495
496                 bdata->input = input;
497                 bdata->button = button;
498
499                 error = gpio_keys_setup_key(pdev, bdata, button);// gpio_keys_setup_key为关键函数,它的作用是初始化按键,即按键去抖,以及
500                 if (error)                                      //按键中断函数的定义都是在这个函数中
501                         goto fail2;
502
503                 if (button->wakeup)
504                         wakeup = 1;
505
506                 input_set_capability(input, type, button->code);
507         }
508 /* 在指定目录下生成sys属性文件,即在执行完下面语句后,会在/sys/devices/platform/gpio-keys/目录下生成四个文件:
disabled_keys、disabled_switches、switches、keys,前两个是可读可写的,后两个为只读    */
509         error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
510         if (error) {
511                 dev_err(dev, "Unable to export keys/switches, error: %d\n",
512                         error);
513                 goto fail2;
514         }
515
516         error = input_register_device(input);//注册输入设备input,把输入设备加到输入设备链表中,并寻找合适的handler与input_handler配对
517         if (error) {
518                 dev_err(dev, "Unable to register input device, error: %d\n",
519                         error);
520                 goto fail3;
521         }
522
523         /* get current state of buttons */  //把当前的按键状态上报给系统,这个是在系统初始化,即上电的时候报告给系统的
524         for (i = 0; i < pdata->nbuttons; i++)
525                 gpio_keys_report_event(&ddata->data[i]);
526         input_sync(input);        //用于事件的同步,即告知系统设备已传递完一组完整的数据
527
528         device_init_wakeup(&pdev->dev, wakeup);
529
530         return 0;
531
532  fail3:
533         sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
534  fail2:
535         while (--i >= 0) {
536                 free_irq(gpio_to_irq(pdata->buttons[i].gpio), &ddata->data[i]);
537                 if (ddata->data[i].timer_debounce)
538                         del_timer_sync(&ddata->data[i].timer);
539                 cancel_work_sync(&ddata->data[i].work);
540                 gpio_free(pdata->buttons[i].gpio);
541         }
542
543         platform_set_drvdata(pdev, NULL);
544  fail1:
545         input_free_device(input);
546         kfree(ddata);
547
548         return error;
549 }
/*小节

           这个probe函数它是通过主要函数gpio_keys_setup_key,实现了按键去抖,按键中断功能,并没有想当初我们自己编写的按键驱动代码那样,直接将按键去抖函数,和按键中断函数定义在按键驱动代码别处而是封装在了gpio_keys_setup_key函数中,简化了函数代码!另外probe函数中调用了 input_set_capability函数,它标识输入设备对哪些事件感兴趣,因为是按键,所以感兴趣的事件一定是EV_KEY,这个函数就是子系统处理机制中,我们事先要进行设置input设备支持的事件类型,它是定义在input.c文件中的。

        这个中断函数要注意,该中断函数即为中断机制的前半部,它主要思想是在按键去抖间隔时间内不执行中断后半部分内容,只有在经过去抖间隔时间达到以后,才执行工作队列中的内容&bdata->work,即执行gpio_keys_gpio_work_func函数。中断后半部函数gpio_keys_gpio_work_func调用的是gpio_keys_gpio_report_event函数,而gpio_keys_gpio_report_event函数调用的是input_event函数,该函数的功能是把输入事件上报给系统input_event函数经过一次次传递封装,最终到达用户空间而被用户所用。    */

2.input 核心层

之前说过,这个核心层我觉得是input子系统最重要,也是最神秘的一层,接下来让我们来揭开其神秘的面纱吧!

[zoulei@CentOS linux-3.0]$ vim drivers/input/input.c

2133 static const struct file_operations input_fops = {
2134         .owner = THIS_MODULE,
2135         .open = input_open_file,
2136         .llseek = noop_llseek,
2137 };
     static int __init input_init(void)
2140 {
2141         int err;
2142
2143         err = class_register(&input_class);//注册input类,这里就是生成了sys/class/input目录,在该目录下会看到按键驱动移植链接文件event0
2144         if (err) {
2145                 pr_err("unable to register input_dev class\n");
2146                 return err;
2147         }
2148
2149         err = input_proc_init();//在proc目录下建立input子系统目录及交互文件,即/proc/bus/input目录下的devices文件和handlers文件
2150         if (err)
2151                 goto fail1;
2152
2153         err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
2154         if (err) {     //注册一个主设备号为INPUT_MAJOR(13),次设备号为0~255,文件操作符为input_fops的字符设备
2155                 pr_err("unable to register char major %d", INPUT_MAJOR);
2156                 goto fail2;
2157         }
2158
2159         return 0;
2160
2161  fail2: input_proc_exit();
2162  fail1: class_unregister(&input_class);
2163         return err;
2164 }
        这个初始化函数中,有一个注册字符设备函数register_chrdev,这个函数有一个参数input_fops值得注意,所有主设备号13的字符设备的操作最终都会转入到input_fops中。例如event0的主设备号为13,对其的操作会落在input_fops中,这个结构体中,源代码中就只有两个函数操作成员,分别是open与llseek,这个open对应的是源代码中的 input_open_file,通过对input_open_file函数的学习,我发现input_fops只是输入子系统通用的file_operations,对于不同的输入子系统(如键盘、鼠标、触摸屏等),通过input_open_file函数会调用各自不同的file_operations。对于按键来说,它调用的是drivers/input目录下Evdev.c文件中的evdev_fops。

 struct input_dev *input_allocate_device(void)
1648 {
1649         struct input_dev *dev;
1650
1651         dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
1652         if (dev) {
1653                 dev->dev.type = &input_dev_type;//初始化设备的类型
1654                 dev->dev.class = &input_class;
1655                 device_initialize(&dev->dev);
1656                 mutex_init(&dev->mutex);   //初始化互斥锁
1657                 spin_lock_init(&dev->event_lock);  //初始化自旋锁
1658                 INIT_LIST_HEAD(&dev->h_list);   //初始化链表
1659                 INIT_LIST_HEAD(&dev->node);
1660
1661                 __module_get(THIS_MODULE);
1662         }
1663
1664         return dev;
1665 }

 该函数返回一个指向 input_dev 类型的指针,该结构体是一个输入设备结构体,包含了输入设备的一些相关信息,如

设备支持的按键码、设备的名称、设备支持的事件等

 static void input_handle_event(struct input_dev *dev,
 217                                unsigned int type, unsigned int code, int value)
 218 {
 219         int disposition = INPUT_IGNORE_EVENT;
 221         switch (type) {
 223         case EV_SYN:
 224                 switch (code) {
 225                 case SYN_CONFIG:
 226                         disposition = INPUT_PASS_TO_ALL;
 227                         break;
 228
 229                 case SYN_REPORT:
 230                         if (!dev->sync) {
 231                                 dev->sync = true;
 232                                 disposition = INPUT_PASS_TO_HANDLERS;
 233                         }
 234                         break;
 235                 case SYN_MT_REPORT:
 236                         dev->sync = false;
 237                         disposition = INPUT_PASS_TO_HANDLERS;
 238                         break;
 239                 }
                      ......
       }

 347 void input_event(struct input_dev *dev,
 348                  unsigned int type, unsigned int code, int value)
 349 {
 350         unsigned long flags;
 352         if (is_event_supported(type, dev->evbit, EV_MAX)) {
 354                 spin_lock_irqsave(&dev->event_lock, flags);
 355                 add_input_randomness(type, code, value);
 356                 input_handle_event(dev, type, code, value);
 357                 spin_unlock_irqrestore(&dev->event_lock, flags);
 358         }
 359 }
上面我们已经分析过input_event函数的功能是上报事件给子系统,但是分析这个函数起着主要作用的是里面的input_handle_event函数,而 input_handle_event

中有一个event()函数与input_pass_event()函数有些事件是发送给设备,而不是发送给 handler 处理的。event()函数用来向输入子系统报告一个将要发送给设备的事件,例如让 LED 灯点亮事件、蜂鸣器鸣叫事件等。当事件报告给输入子系统后,就要求设备处理这个事件。这里就体现了函数的一次次传递与封装!

*****************************************************************************************************************************************************************************************

/*小节

       其实这个核心层,事件处理层,还有设备驱动层三者之间的关系就好像一个单独驱动中的device与driver的关系一样,设备驱动层定义了设备注册的接口函数input_register_handle,因为设备驱动层input device与事件处理层hander 有一个匹配的过程,匹配成功就会调用事件处理层中的connect函数,connect函数会调用input_register_handle,当然就好比device与driver匹配成功之后会调用probe函数一样,

所有的input device都挂在

input_dev_list

链上

所有的handler都挂在input_handler_list上,

所以事件层要与设备驱动层进行匹配就必须的提前注册。

      事件层与设备驱动层之间的匹配调用的是input_attach_handler-> input_match_device函数。

    总的来说,这个核心层所扮演的角色就好比一家人,一对夫妻上要照顾自己的爸妈,下要照顾自己的子女,而照顾所需要的钱就是函数接口,所以说子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口(input_register_handle),向上提供事件处理层的接口(connect)。


3.事件处理层

事件处理层文件主要是用来支持输入设备并与用户空间交互,这部分代码一般不需要我们自己去编写,因为Linux内核已经自带有一些事件处理器,可以支持大部分输入设备,比如Evdev.c、mousedev.c、joydev.c等。对按键来说,用到的是Evdev.c文件(它在drivers/input目录下)。

[zoulei@CentOS linux-3.0]$ vim drivers/input/evdev.c

 998 static int __init evdev_init(void)
 999 {
1000         return input_register_handler(&evdev_handler);//注册evdev_handler
1001 }
evdev_handler类型就是前面介绍过的三个重要结构体中的一个——input_handler

 input_handler 是输入子系统的主要数据结构,一般将其称为 handler 处理器,表示对输入事件的具体处理。

input_handler 为输入设备的功能实现了一个接口,输入事件最终传递到handler 处理器,handler 处理器根据一定的规则,

然后对事件进行处理,具体的规则将在下面详细介绍

它的定义为:

struct input_handler {
void *private; 
void (*event)(struct input_handle *handle, unsigned int type,
unsigned int code, int value);
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; //定义了一个 name, 表示 handler 的名字,显示在/proc/bus/input/handlers 目录                                                            //中。
const struct input_device_id *blacklist; //指向一个 input_device_id 表,这个表包含 handler 应该忽略的设备
struct list_head h_list;
struct list_head node;
 };

988 static struct input_handler evdev_handler = {
 989         .event          = evdev_event,//传递信息是调用,在input_pass_event
 990         .connect        = evdev_connect,//device与handler匹配时调用
 991         .disconnect     = evdev_disconnect,
 992         .fops           = &evdev_fops,
 993         .minor          = EVDEV_MINOR_BASE,
 994         .name           = "evdev",
 995         .id_table       = evdev_ids,
 996 };
其中evdev_connect主要用来连接input_dev和input_handler;evdev_event是把input事件发给所有的client。另外在该结构体内还定义了一个fops集合,它被赋值为evdev_fops的指针。evdev_fops就是在介绍核心层input.c文件时,提到的当处理按键输入子系统时,file_operations所对应的就是evdev_fops

  

input_handler注册

1923 int input_register_handler(struct input_handler *handler)
1924 {
1925         struct input_dev *dev;
1926         int retval;
1927
1928         retval = mutex_lock_interruptible(&input_mutex);
1929         if (retval)
1930                 return retval;
1931
1932         INIT_LIST_HEAD(&handler->h_list);
1933//其中的 handler->minor 表示对应 input 设备结点的次设备号。 handler->minor以右移 5 位作为索引值插入到 //input_table[ ]
1934         if (handler->fops != NULL) {
1935                 if (input_table[handler->minor >> 5]) {
1936                         retval = -EBUSY;
1937                         goto out;
1938                 }
1939                 input_table[handler->minor >> 5] = handler;
1940         }
1941
1942         list_add_tail(&handler->node, &input_handler_list);
1943
1944         list_for_each_entry(dev, &input_dev_list, node)
1945                 input_attach_handler(dev, handler);
//input_attach_handler()函数的作用是匹配 input_dev_list 链表中的 input_dev 与 handler。如果成功会将 input_dev 

//与 handler 联系起来。也就是说在注册handler和dev时都会去调用该函数。

1947 input_wakeup_procfs_readers(); 1948 1949 out: 1950 mutex_unlock(&input_mutex); 1951 return retval; 1952 }

evdev_open函数分析

 275 static int evdev_open(struct inode *inode, struct file *file)
 276 {
 277         struct evdev *evdev;
 278         struct evdev_client *client;
 279         int i = iminor(inode) - EVDEV_MINOR_BASE;//得到设备列表中的序号
 280         unsigned int bufsize;
 281         int error;
 283         if (i >= EVDEV_MINORS)
 284                 return -ENODEV;
 286         error = mutex_lock_interruptible(&evdev_table_mutex);
 287         if (error)
 288                 return error;
 289         evdev = evdev_table[i];//得到设备列表中的序号
 290         if (evdev)
 291                 get_device(&evdev->dev);  //增加计数
 292         mutex_unlock(&evdev_table_mutex);
 294         if (!evdev)
 295                 return -ENODEV;
 297         bufsize = evdev_compute_buffer_size(evdev->handle.dev);
 299         client = kzalloc(sizeof(struct evdev_client) +              //分配并初始化client结构体
 300                                 bufsize * sizeof(struct input_event),
 301                          GFP_KERNEL);
 302         if (!client) {
 303                 error = -ENOMEM;
 304                 goto err_put_evdev;
 305         }
 307         client->bufsize = bufsize;
 308         spin_lock_init(&client->buffer_lock);
 309         client->evdev = evdev;
 310         evdev_attach_client(evdev, client);
 312         error = evdev_open_device(evdev); //evdev_open_device函数的作用是打开设备,当设备是第一次被打开时,则调用input_open_device函数
 313         if (error)
 314                 goto err_free_client;
 316         file->private_data = client;
 317         nonseekable_open(inode, file);
 318

evdev_open函数负责打开一个输入设备,但是真正起到打开设备的是evdev_open_device

evdev_read分析:

383 static ssize_t evdev_read(struct file *file, char __user *buffer,//evdev_read函数负责读取设备
 384                           size_t count, loff_t *ppos)
 385 {
 386         struct evdev_client *client = file->private_data;
 387         struct evdev *evdev = client->evdev;
 388         struct input_event event;
 389         int retval;
 390
 391         if (count < input_event_size())  //对读取的数据大小进行判断
 392                 return -EINVAL;
 393
 394         if (client->packet_head == client->tail && evdev->exist &&   //如果缓存中没有数据可读而设备又存在,并且被设置为O_NONBLOCK方式,
 395             (file->f_flags & O_NONBLOCK))                            //则退出
 396                 return -EAGAIN;
 397
 398         retval = wait_event_interruptible(evdev->wait,    //等待缓存中是否有数据可读,或设备是否被移走
 399                 client->packet_head != client->tail || !evdev->exist);
 400         if (retval)
 401                 return retval;
 402
 403         if (!evdev->exist)//如果设备不存在,则退出
 404                 return -ENODEV;
 405
 406         while (retval + input_event_size() <= count &&
 407                evdev_fetch_next_event(client, &event)) {
 408
 409                 if (input_event_to_user(buffer + retval, &event))
 410                         return -EFAULT;
 411
 412                 retval += input_event_size();
 413         }
 414
 415         return retval;
 416 }

evdev_connect分析:

908 static int evdev_connect(struct input_handler *handler, struct input_dev *dev,
 909                          const struct input_device_id *id)
 910 {
 911         struct evdev *evdev;
 912         int minor;
 913         int error;
 914
 915         for (minor = 0; minor < EVDEV_MINORS; minor++)
 916                 if (!evdev_table[minor])
 917                         break;
 918
 919         if (minor == EVDEV_MINORS) {
 920                 pr_err("no more free evdev devices\n");
 921                 return -ENFILE;
 922         }
 923
 924         evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
 925         if (!evdev)
 926                 return -ENOMEM;
 927
 928         INIT_LIST_HEAD(&evdev->client_list);
 929         spin_lock_init(&evdev->client_lock);
 930         mutex_init(&evdev->mutex);
 931         init_waitqueue_head(&evdev->wait);
 932
 933         dev_set_name(&evdev->dev, "event%d", minor);
 934         evdev->exist = true;
 935         evdev->minor = minor;
 937         evdev->handle.dev = input_get_device(dev);
 938         evdev->handle.name = dev_name(&evdev->dev);
 939         evdev->handle.handler = handler;
 940         evdev->handle.private = evdev;
 942         evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
 943         evdev->dev.class = &input_class;
 944         evdev->dev.parent = &dev->dev;
 945         evdev->dev.release = evdev_free;
 946         device_initialize(&evdev->dev);

//在这段代码里主要完成 evdev封装的 device的初始化 .注意在这里 ,使它所属的类指向 input_class.这样在 /sysfs中创

//建的设备目录就会在 /sys/class/input/下面显示

948 error = input_register_handle(&evdev->handle); ...... }

 将handle挂到所对应input deviceh_list链表上.还将handle挂到对应的handlerhlist链表上.如果handler

义了start函数,将调用之到这里,我们已经看到了input device, handlerhandle是怎么关联起来的了

到这里input子系统按键驱动就分析完了。。。












你可能感兴趣的:(linux驱动学习)