come from : https://ww w.cnblogs.com/lifexy/p/7542989.html Good

在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架自动创建设备节点linux中断poll机制异步通知同步互斥/非阻塞定时器去抖动

其中驱动框架如下:

1)写file\_operations结构体的成员函数: .open()、.read()、.write()


2)在入口函数里通过register\_chrdev()创建驱动名,生成主设备号,赋入file\_operations结构体

3)在出口函数里通过unregister\_chrdev() 卸载驱动

若有多个不同的驱动程序时,应用程序就要打开多个不同的驱动设备,由于是自己写肯定会很清楚,如果给别人来使用时是不是很麻烦?

所以需要使用输入子系统, 使应用程序无需打开多个不同的驱动设备便能实现

          • -

1.输入子系统简介

同样的输入子系统也需要输入驱动的框架,好来辨认应用程序要打开的是哪个输入驱动

比如: 鼠标、键盘、游戏手柄等等这些都属于输入设备;这些输入设备的驱动都是通过输入子系统来实现的(当然,这些设备也依赖于usb子系统)

这些输入设备都各有不同,那么输入子系统也就只能实现他们的共性,差异性则由设备驱动来实现。差异性又体现在哪里?

最直观的就表现在这些设备功能上的不同了。对于我们写驱动的人来说在设备驱动中就只要使用输入子系统提供的工具(也就是函数)来完成这些“差异”就行了,其他的则是输入子系统的工作。这个思想不仅存在于输入子系统,其他子系统也是一样(比如:usb子系统、video子系统等)

所以我们先来分析下输入子系统input.c的代码,然后怎么来使用输入子系统(在内核中以input来形容输入子系统)

2.打开input.c,位于内核deivers/input

有以下这么两段:

<pre class="has">

subsys_initcall(input_init); //修饰入口函数

module_exit(input_exit); //修饰出口函数


  
  
  
  
  
  
  
  
**显然输入子系统是作为一个模块存在,我们先来分析下input\_int()入口函数**

  
  
  
  
  
  
  
  
  
  
```html
 1 static int __init input_init(void)
 2 {
 3        int err;
 4        err = class_register(&input_class);   //(1)注册类,放在/sys/class
 5        if (err) {
 6               printk(KERN_ERR "input: unable to register input_dev class\n");
 7               return err;
 8        }
 9  
10        err = input_proc_init();    //在/proc下面建立相关的文件
11        if (err)
12               goto fail1;
13 
14        err = register_chrdev(INPUT_MAJOR, "input", &input_fops); //(2)注册驱动
15        if (err) {
16               printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR);
17               goto fail2;
18        }
19 
20  
21 
22        return 0;
23 
24  
25 
26  fail2:     input_proc_exit();
27 
28  fail1:     class_unregister(&input_class);
29 
30        return err;
31 
32 }
```

  
  
  
  
  
  
  
  
  
  
  
  
  
(1)**上面第4行”err = class\_register(&input\_class);”**是在/sys/class 里创建一个 input类, input\_class变量如下图:

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125656056-1309527087.png)

  
  
  
  
  
如下图,我们启动内核,再启动一个input子系统的驱动后,也可以看到创建了个"input"类 :

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918190208915-1448414250.png)

  
  
  
  
  
为什么这里代码只创建类,没有使用class\_device\_create()函数在类下面创建驱动设备?

  
  
  
  
  
在下面第8小结会详细讲到,这里简单描述:当注册input子系统的驱动后,才会有驱动设备,此时这里的代码是没有驱动的

  
  
  
  
  
  
  
  
  
  
(2)**上面第14行**通过register\_chrdev创建驱动设备,其中变量INPUT\_MAJOR =13,所以创建了一个主设备为13的"input"设备。

  
  
  
  
  
然后我们来看看它的操作结构体input\_fops,如下图:

  
  
  
  
  
![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125709915-1132827718.png)

  
  
  
  
  
只有一个.open函数,比如当我们挂载一个新的input驱动,则内核便会调用该.open函数,接下来分析该.open函数

  
  
  
  
  
**3 然后进入input\_open\_file函数(drivers/input/input.c)**

  
  
  
  
  
  
  
  
  
  
```
```html
 1 static int input_open_file(struct inode *inode, struct file *file)
 2  {
 3      struct input_handler *handler = input_table[iminor(inode) >> 5]; // (1)
 4      const struct file_operations *old_fops, *new_fops = NULL;
 5      int err;
 6 
 7      if (!handler || !(new_fops = fops_get(handler->fops)))  //(2)
 8           return -ENODEV; 
 9 
10     if (!new_fops->open) {
11            fops_put(new_fops);
12            return -ENODEV;
13     }
14 
15     old_fops = file->f_op;
16     file->f_op = new_fops;     //(3)
17 
18     err = new_fops->open(inode, file);   //(4)
19     if (err) {
20           fops_put(file->f_op);
21            file->f_op = fops_get(old_fops);
22    }
23 
24    fops_put(old_fops);
25 
26     return err;
27 }
```

  
  
  
  
  
  
  
  
  
  
  
  
  
**(1)第3行中**,其中iminor (inode)函数调用了MINOR(inode->i\_rdev);读取子设备号,然后将子设备除以32,找到新挂载的input驱动的数组号,然后放在input\_handler 驱动处理函数handler中

  
  
  
  
  
**(2)第7行中**,若handler有值,说明挂载有这个驱动,就将handler结构体里的成员file\_operations \* fops赋到新的file\_operations \*old\_fops里面

  
  
  
  
  
**(3)第16行中**, 再将新的file\_operations \*old\_fops赋到file-> file\_operations \*f\_op里, 此时input子系统的file\_operations就等于新挂载的input驱动的file\_operations结构体,实现一个偷天换日的效果.

  
  
  
  
  
**(4)第18行中**,然后调用新挂载的input驱动的\*old\_fops里面的成员.open函数

  
  
  
  
  
**4.上面代码的input\_table\[\]数组在初始时是没有值的,**

  
  
  
  
  
**所以我们来看看input\_table数组里面的数据又是在哪个函数里被赋值**

  
  
  
  
  
在input.c函数(drivers/input/input.c)中搜索input\_table,找到它在input\_register\_handler()函数中被赋值,代码如下:

  
  
  
  
  
  
  
  
  
  
```
```html
1 int input_register_handler(struct input_handler *handler)
2 {
3 ... ...
4 input_table[handler->minor >> 5] = handler;   //input_table[]被赋值
5 ... ...
6 list_add_tail(&handler->node, &input_handler_list); //然后将这个input_handler放到input_handler_list链表中  
7 ... ...
8 }
```

  
  
  
  
  
  
  
  
就是将驱动处理程序input\_handler注册到input\_table\[\]中,然后放在input\_handler\_list链表中,后面会讲这个链表

  
  
  
  
  
**5继续来搜索input\_register\_handler,看看这个函数被谁来调用**

  
  
  
  
  
如下图所示,有evdev.c(事件设备),tsdev.c(触摸屏设备),joydev.c(joystick操作杆设备),keyboard.c(键盘设备),mousedev.c(鼠标设备) 这5个内核自带的设备处理函数注册到input子系统中

  
  
  
  
  
![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125734228-444718177.png)

  
  
  
  
  
我们以evdev.c为例,它在evdev\_ini()函数中注册:

  
  
  
  
  
```
```html
static int __init evdev_init(void)
{
       return input_register_handler(&evdev_handler);  //注册
}
```

  
  
  
  
  
  
  
  
**6我们来看看这个evdev\_handler变量是什么结构体,:**

  
  
  
  
  
  
  
  
  
  
```
```html
1 static struct input_handler evdev_handler = {
2        .event =  evdev_event,    
3        .connect =      evdev_connect,  //(4)
4        .disconnect = evdev_disconnect,
5        .fops =           &evdev_fops,    //(1)
6        .minor =  EVDEV_MINOR_BASE, //(2)
7        .name =         "evdev",
8        .id_table =      evdev_ids, //(3)
9 };
```

  
  
  
  
  
  
  
  
就是我们之前看的input\_handler驱动处理结构体

  
  
  
  
  
(1) **第5行中.fops:**文件操作结构体,其中evdev\_fops函数就是自己的写的操作函数,然后赋到.fops中

  
  
  
  
  
(2)**第6行中 .minor:**用来存放次设备号

  
  
  
  
  
其中EVDEV\_MINOR\_BASE=64, 然后调用input\_register\_handler(&evdev\_handler)后,由于EVDEV\_MINOR\_BASE/32=2,所以存到input\_table\[2\]中

  
  
  
  
  
 所以当open打开这个input设备,就会进入 input\_open\_file()函数,执行evdev\_handler-> evdev\_fops -> .open函数,如下图所示:

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125751978-2110731647.png)

  
  
  
  
  
  
  
  
  
  
(3)**第8行中.id\_table :** 表示能支持哪些输入设备,比如某个驱动设备的input\_dev->的id和某个input\_handler的id\_table相匹配,就会调用.connect连接函数,如下图

  
  
  
  
  
(4)**第3行中.connect:**连接函数,将设备input\_dev和某个input\_handler建立连接,如下图

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125805775-128197571.png)

  
  
  
  
  
**7我们先来看看上图的input\_register\_device()函数,如何创建驱动设备的**

  
  
  
  
  
搜索input\_register\_device,发现内核自己就已经注册了很多驱动设备

  
  
  
  
  
**7.1然后进入input\_register\_device()函数,代码如下:**

  
  
  
  
  
  
  
  
  
  
```
```html
1 int input_register_device(struct input_dev *dev)   //*dev:要注册的驱动设备
2 {
3  ... ...
4        list_add_tail(&dev->node, &input_dev_list);   //(1)放入链表中
5  ... ...
6        list_for_each_entry(handler, &input_handler_list, node)  //(2)
7        input_attach_handler(dev, handler); 
8  ... ...
9 }
```

  
  
  
  
  
  
  
  
  
  
  
  
  
**(1)第4行中,**将要注册的input\_dev驱动设备放在input\_dev\_list链表中

  
  
  
  
  
**(2)第6行中,**其中input\_handler\_list在前面讲过,就是存放每个input\_handle驱动处理结构体,

  
  
  
  
  
然后list\_for\_each\_entry()函数会将每个input\_handle从链表中取出,放到handler中

  
  
  
  
  
最后会调用input\_attach\_handler()函数,将每个input\_handle的id\_table进行判断,若两者支持便进行连接。

  
  
  
  
  
**7.2然后我们在回过头来看注册input\_handler的input\_register\_handler()函数,如下图所示**

  
  
  
  
  
![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125828931-1140237004.png)

  
  
  
  
  
所以,不管新添加input\_dev还是input\_handler,都会进入input\_attach\_handler()判断两者id是否有支持, 若两者支持便进行连接。

  
  
  
  
  
**7.3我们来看看input\_attach\_handler()如何实现匹配两者id的:**

  
  
  
  
  
  
  
  
  
  
```
```html
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)
{
... ...
id = input_match_device(handler->id_table, dev);  //匹配两者

if (!id)                                     //若不匹配,return退出
return -ENODEV; 

error = handler->connect(handler, dev, id);  //调用input_handler ->connect函数建立连接
... ...

}
```

  
  
  
  
  
  
  
  
  
  
  
  
  
若两者匹配成功,就会自动进入input\_handler 的connect函数建立连接

  
  
  
  
  
**8我们还是以evdev.c(事件驱动) 的evdev\_handler->connect函数**

  
  
  
  
  
**来分析****是怎样建立连接的,如下图:**

  
  
  
  
  
![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125845790-1398162479.png)

  
  
  
  
  
**8.1 evdev\_handler的.connect函数是evdev\_connect(),代码如下:**

  
  
  
  
  
  
  
  
  
  
```
```html
 1 static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id)     
 2 {
 3 ... ... 
 4 for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); //查找驱动设备的子设备号
 5     if (minor == EVDEV_MINORS) {  // EVDEV_MINORS=32,所以该事件下的驱动设备最多存32个,
 6         printk(KERN_ERR "evdev: no more free evdev devices\n");
 7         return -ENFILE;                //没找到驱动设备
 8     }
 9  ... ...
10  evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);   //分配一个input_handle全局结构体(没有r)
11  ... ...
12  evdev->handle.dev = dev;              //指向参数input_dev驱动设备
13 evdev->handle.name = evdev->name;
14 evdev->handle.handler = handler;    //指向参数 input_handler驱动处理结构体
15 evdev->handle.private = evdev;
16 sprintf(evdev->name, "event%d", minor);    //(1)保存驱动设备名字, event%d
17 ... ...
18 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),  //(2) 将主设备号和次设备号转换成dev_t类型
19 cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); 
                                                           // (3)在input类下创建驱动设备
20 
21 ... ...
22 error = input_register_handle(&evdev->handle); //(4)注册这个input_handle结构体
23 
24 ... ...
25 }
```

  
  
  
  
  
  
  
  
(1) 第16行中,是在保存驱动设备名字,名为event%d, 比如下图(键盘驱动)event1: 因为没有设置子设备号,默认从小到大排列,其中event0是表示这个input子系统,所以这个键盘驱动名字就是event1

  
  
  
  
  
(2)第18行中,是在保存驱动设备的主次设备号,其中主设备号INPUT\_MAJOR=13,因为EVDEV\_MINOR\_BASE=64,所以此设备号=64+驱动程序本事子设备号, 比如下图(键盘驱动)event1: 主次设备号就是13,65

  
  
  
  
  
(3)在之前在2小结里就分析了input\_class类结构,所以第19行中,会在/sys/class/input类下创建驱动设备event%d,比如下图(键盘驱动)event1:

  
  
  
  
  
![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918190616478-260239851.png)

  
  
  
  
  
(4)最终会进入input\_register\_handle()函数来注册,代码在下面

  
  
  
  
  
8.2 **input\_register\_handle()函数如下:**

  
  
  
  
  
  
  
  
  
  
```
```html
 1 int input_register_handle(struct input_handle *handle)
 2 {
 3       struct input_handler *handler = handle->handler; //handler= input_handler驱动处理结构体 
 4 
 5       list_add_tail(&handle->d_node, &handle->dev->h_list); //(1)
 6       list_add_tail(&handle->h_node, &handler->h_list);    // (2)
 7  
 8       if (handler->start)
 9              handler->start(handle);
10       return 0;
11 }
```

  
  
  
  
  
  
  
  
  
  
  
  
  
**(1)在第5行中,** 因为handle->dev指向input\_dev驱动设备,所以就是将handle->d\_node放入到input\_dev驱动设备的h\_list链表中,

  
  
  
  
  
即input\_dev驱动设备的h\_list链表就指向handle->d\_node

  
  
  
  
  
**(2) 在第6行中,** 同样, input\_handler驱动处理结构体的h\_list也指向了handle->h\_node

  
  
  
  
  
最终如下图所示:

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125909525-684596795.png)

  
  
  
  
  
两者的.h\_list都指向了同一个handle结构体,然后通过.h\_list 来找到handle的成员.dev和handler,便能找到对方,便建立了连接

  
  
  
  
  
**9建立了连接后,又如何读取evdev.c(事件驱动) 的evdev\_handler->.fops->.read函数?**

  
  
  
  
  
事件驱动的.read函数是evdev\_read()函数,我们来分析下:

  
  
  
  
  
  
  
  
  
  
```
```html
static ssize_t evdev_read(struct file *file, char __user *      buffer, size_t count, loff_t *ppos)
{
 ... ...
/*判断应用层要读取的数据是否正确*/
if (count < evdev_event_size())
return -EINVAL;

/*在非阻塞操作情况下,若client->head == client->tail|| evdev->exist时(没有数据),则return返回*/
 if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))
return -EAGAIN;
 
/*若client->head == client->tail|| evdev->exist时(没有数据),等待中断进入睡眠状态  */
  retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

  ... ...           //上传数据

}
```

  
  
  
  
  
  
  
  
  
  
  
  
  
**10若read函数进入了休眠状态,又是谁来唤醒?**

  
  
  
  
  
我们搜索这个evdev->wait这个等待队列变量,找到evdev\_event函数里唤醒:

  
  
  
  
  
```
```html
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)
{
... ...
 wake_up_interruptible(&evdev->wait);   //有事件触发,便唤醒等待中断
}
```

  
  
  
  
  
  
  
  
其中evdev\_event()是evdev.c(事件驱动) 的evdev\_handler->.event成员,如下图所示:

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125924681-419081807.png)

  
  
  
  
  
当有事件发生了,比如对于按键驱动,当有按键按下时,就会进入.event函数中处理事件

  
  
  
  
  
**11分析下,是谁调用evdev\_event()这个.event事件驱动函数**

  
  
  
  
  
应该就是之前分析的input\_dev那层调用的

  
  
  
  
  
我们来看看内核 gpio\_keys\_isr()函数代码例子就知道了 (driver/input/keyboard/gpio\_key.c)

  
  
  
  
  
  
  
  
  
  
```
```html
static irqreturn_t gpio_keys_isr(int irq, void *dev_id)
{
 /*获取按键值,赋到state里*/
 ... ...

/*上报事件*/
input_event(input, type, button->code, !!state);  
input_sync(input);                        //同步信号通知,表示事件发送完毕
}
```

  
  
  
  
  
  
  
  
  
  
  
  
  
显然就是通过input\_event()来调用.event事件函数,我们来看看:

  
  
  
  
  
  
  
  
  
  
```
```html
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
... ...

/* 通过input_dev ->h_list链表找到input_handle驱动处理结构体*/
list_for_each_entry(handle, &dev->h_list, d_node)    
if (handle->open)  //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体
    handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数 

}
```

  
  
  
  
  
  
  
  
  
  
  
  
  
若之前驱动input\_dev和处理input\_handler已经通过input\_handler 的.connect函数建立起了连接,那么就调用evdev\_event()的.event事件函数,如下图所示:

  
  
  
  
  
 ![12.Linux之输入子系统分析(详解)教程](https://www.icode9.com/i/i/?n=17&i=blog/1182576/201709/1182576-20170918125942290-1009610213.png)

  
  
  
  
  
**12本节总结分析:**

  
  
  
  
  
**1.****注册输入子系统****,****进入****put\_init():**

  
  
  
  
  
1)创建主设备号为13的"input"字符设备

  
  
  
  
  
```
```html
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
```

  
  
  
  
  
  
  
  
**2.open打开****驱动****,****进入****input\_open\_file():**

  
  
  
  
  
1)更新设备的file\_oprations

  
  
  
  
  
```
```html
file->f_op=fops_get(handler->fops);
```

  
  
  
  
  
  
  
  
2)执行file\_oprations->open函数

  
  
  
  
  
```
```html
err = new_fops->open(inode, file);
```

  
  
  
  
  
  
  
  
**3.****注册****input\_handler,****进入****input\_register\_handler():**

  
  
  
  
  
1)添加到input\_table\[\]处理数组中

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

  
  
  
  
  
  
  
  
2)添加到input\_handler\_list链表中

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

  
  
  
  
  
  
  
  
3)判断input\_dev的id,是否有支持这个驱动的设备

  
  
  
  
  
```
```html
 list_for_each_entry(dev, &input_dev_list, node)   //遍历查找input_dev_list链表里所有input_dev

 input_attach_handler(dev, handler);             //判断两者id,若两者支持便进行连接。
```

  
  
  
**4.****注册****input\_dev****,进入****input\_register\_device():**

  
  
  
  
  
1)放在input\_dev\_list链表中

  
  
  
  
  
```
```html
list_add_tail(&dev->node, &input_dev_list);
```

  
  
  
  
  
  
  
  
2)判断input\_handler的id,是否有支持这个设备的驱动

  
  
  
  
  
```
```html
list_for_each_entry(handler, &input_handler_list, node)  //遍历查找input_handler_list链表里所有input_handler
input_attach_handler(dev, handler);                      //判断两者id,若两者支持便进行连接。
```

  
  
  
  
  
  
  
  
**5.****判断****input\_handler****和****input\_dev****的****id,****进入****input\_attach\_handler():**

  
  
  
  
  
1)匹配两者id,

  
  
  
  
  
```
```html
input_match_device(handler->id_table, dev);        //匹配input_handler和dev的id,不成功退出函数
```

  
  
  
  
  
  
  
  
2)匹配成功调用input\_handler ->connect

  
  
  
  
  
```
```html
handler->connect(handler, dev, id);              //建立连接
```

  
  
  
**6.****建立****input\_handler****和****input\_dev****的连接,进入****input\_handler->connect():**

  
  
  
  
  
1)创建全局结构体,通过input\_handle结构体连接双方

  
  
  
  
  
```
```html
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);    //创建两者连接的input_handle全局结构体
list_add_tail(&handle->d_node, &handle->dev->h_list); //连接input_dev->h_list
list_add_tail(&handle->h_node, &handler->h_list);    // 连接input_handle->h_list
```

  
  
  
**7.****有事件发生时****,****比如按键中断****,****在中断函数中需要进入****input\_event()****上报事件****:**

  
  
  
  
  
1)找到驱动处理结构体,然后执行input\_handler->event()

  
  
  
  
  
```
```html
list_for_each_entry(handle, &dev->h_list, d_node)     // 通过input_dev ->h_list链表找到input_handle驱动处理结构体
if (handle->open)  //如果input_handle之前open 过,那么这个就是我们的驱动处理结构体(有可能一个驱动设备在不同情况下有不同的驱动处理方式)
    handle->handler->event(handle, type, code, value); //调用evdev_event()的.event事件函数 
```

标签: Linux, dev, handle, input, list, evdev, handler

相关文章推荐

添加新评论,含*的栏目为必填