【驱动】input子系统整体流程全面分析(触摸屏驱动为例)【转】
转自:http://www.cnblogs.com/lcw/p/3294356.html
input输入子系统整体流程
input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层。
在开头部分会从设备驱动层做为线索,分析输入子系统和事件处理层是如何配合的,最后从用户角度出发,从“/dev/input/*”接口如何使用输入子系统提供的服务。
既然需要详细分析,有一个这样的流程图能够帮助我们在被绕进代码的过程中,找到出口,你能够知道你现在位于代码框架的什么位置,不会忘记正在分析的代码的“身份”。
设备驱动层注册到input子系统
以S3C2440触摸屏的驱动代码代例,初始化函数定义了struct input_dev input结构体,它用于描述一个输入子系统设备。
任何驱动设备如果想标明自己是输入设备,都应该通过初始化这样的结构体,并且调用input_allocate_device()函数进行注册。
了解这一过程,先看一下struct input_dev结构体的内容:
![](https://common.cnblogs.com/images/copycode.gif)
1 struct input_dev {
2
3 void *private; //输入设备私有指针,一般指向用于描述设备驱动层的设备结构
4
5 const char *name; //提供给用户的输入设备的名称
6 const char *phys; //提供给编程者的设备节点的名称
7 const char *uniq; //指定唯一的ID号,就像MAC地址一样
8 struct input_id id; //输入设备标识ID,用于和事件处理层进行匹配
9
10 unsigned long evbit[NBITS(EV_MAX)]; //位图,记录设备支持的事件类型
11 unsigned long keybit[NBITS(KEY_MAX)]; //位图,记录设备支持的按键类型
12 unsigned long relbit[NBITS(REL_MAX)]; //位图,记录设备支持的相对坐标
13 unsigned long absbit[NBITS(ABS_MAX)]; //位图,记录设备支持的绝对坐标
14 unsigned long mscbit[NBITS(MSC_MAX)]; //位图,记录设备支持的其他功能
15 unsigned long ledbit[NBITS(LED_MAX)]; //位图,记录设备支持的指示灯
16 unsigned long sndbit[NBITS(SND_MAX)]; //位图,记录设备支持的声音或警报
17 unsigned long ffbit[NBITS(FF_MAX)]; //位图,记录设备支持的作用力功能
18 unsigned long swbit[NBITS(SW_MAX)]; //位图,记录设备支持的开关功能
19
20 unsigned int keycodemax; //设备支持的最大按键值个数
21 unsigned int keycodesize; //每个按键的字节大小
22 void *keycode; //指向按键池,即指向按键值数组首地址
23 int (*setkeycode)(struct input_dev *dev, int scancode, int keycode); //修改按键值
24 int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode); //获取按键值
25
26 struct ff_device *ff; //用于强制更新输入设备的部分内容
27
28 unsigned int repeat_key; //重复按键的键值
29 struct timer_list timer; //设置当有连击时的延时定时器
30
31 int state; //设备状态
32
33 int sync; //同步事件完成标识,为1说明事件同步完成
34
35 int abs[ABS_MAX + 1]; //记录坐标的值
36 int rep[REP_MAX + 1]; //记录重复按键的参数值
37
38 unsigned long key[NBITS(KEY_MAX)]; //位图,按键的状态
39 unsigned long led[NBITS(LED_MAX)]; //位图,led的状态
40 unsigned long snd[NBITS(SND_MAX)]; //位图,声音的状态
41 unsigned long sw[NBITS(SW_MAX)]; //位图,开关的状态
42
43 int absmax[ABS_MAX + 1]; //位图,记录坐标的最大值
44 int absmin[ABS_MAX + 1]; //位图,记录坐标的最小值
45 int absfuzz[ABS_MAX + 1]; //位图,记录坐标的分辨率
46 int absflat[ABS_MAX + 1]; //位图,记录坐标的基准值
47
48 int (*open)(struct input_dev *dev); //输入设备打开函数
49 void (*close)(struct input_dev *dev); //输入设备关闭函数
50 int (*flush)(struct input_dev *dev, struct file *file); //输入设备断开后刷新函数
51 int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); //事件处理
52
53 struct input_handle *grab; //类似私有指针,可以直接访问到事件处理接口event
54
55 struct mutex mutex; //用于open、close函数的连续访问互斥
56 unsigned int users; //设备使用计数
57
58 struct class_device cdev; //输入设备的类信息
59 union { //设备结构体
60 struct device *parent;
61 } dev;
62
63 struct list_head h_list; //handle链表
64 struct list_head node; //input_dev链表
65 };
![](https://common.cnblogs.com/images/copycode.gif)
就这样赤裸裸的看上面的结构体,会觉得摸不着头脑,但是有一点是确定的,我们在写输入设备驱动时会定义这样一个输入设备结构体,并调用input_allocate_device()函数,这个函数的功能是为新添加的输入设备分配内存,如果成功,将返回input_dev *的指针结构,因此在写驱动的时候应该接受返回值,作为驱动层获得了一个新的输入设备操作的接口。
那么input_allocate_device()函数做了什么呢?打开函数看一下(input.c中实现):
![](https://common.cnblogs.com/images/copycode.gif)
1 struct input_dev *input_allocate_device(void)
2 {
3 struct input_dev *dev;
4
5 //动态申请内存,使用GFP_KERNEL方式,注意GFP_KERNEL可能导致睡眠,不能在中断中调用这个函数
6 dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);
7 //分配成功执行的代码,进行成员的默认填充
8 if (dev) {
9 dev->cdev.class = &input_class; //支持热插拔的结构体
10 dev->cdev.groups = input_dev_attr_groups; //描述设备的硬件信息和支持的事件类型
11 class_device_initialize(&dev->cdev); //类设备初始化,添加进input类设备模型中
12 mutex_init(&dev->mutex); //初始化互斥锁
13 INIT_LIST_HEAD(&dev->h_list); //初始化handle链表
14 INIT_LIST_HEAD(&dev->node); //初始化输入设备链表
15
16 }
17 }
![](https://common.cnblogs.com/images/copycode.gif)
通过input_allocate_device()函数,我们设备驱动现在持有的input_dev里面就被赋予了input的“形象”,但是还需要我们去充实一下“内在”,因此,设备驱动程序,还需要为自己的设备增加自己的特性,才能创造独有的设备“形象”。
![](https://common.cnblogs.com/images/copycode.gif)
1 struct input_dev *input_dev = input_allocate_device();
2 input_dev->name = "s3c2410 Touchscreen";
3 input_dev->phys = "s3c2410ts/input0";
4 input_dev->id.bustype = BUS_HOST;
5 input_dev->id.vendor = 0x0001;
6 input_dev->id.product = 0x0002;
7 input_dev->id.version = 0x0100;
8 input_dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
9 input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
10 input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
11 input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
12 input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN, PRESSURE_MAX, 0, 0);
![](https://common.cnblogs.com/images/copycode.gif)
这部分完成了输入设备的初始化工作。但是这仅是初始化自己的“特点”,还需要通知输入子系统有这样一个新设备诞生了,这就需要调用输入子系统的注册函数input_register_device(input_dev)来完成。
input_register_device()用于注册一个输入设备。那么注册过程是怎样的呢?这是一个重点,在下面的代码中进行注释分析:
![](https://common.cnblogs.com/images/copycode.gif)
1 int input_register_device(struct input_dev *dev)
2 {
3 /* 用于记录输入设备名称的索引值 */
4 static atomic_t input_no = ATOMIC_INIT(0);
5 /* 输入事件的处理接口指针,用于和设备的事件类型进行匹配 */
6 struct input_handler *handler;
7 const char *path;
8 int error;
9
10 /* 默认所有的输入设备都支持EV_SYN同步事件 */
11 set_bit(EV_SYN, dev->evbit);
12
13 /*
14 * 如果设备驱动没有指定重复按键(连击),系统默认提供以下的支持
15 * 其中init_timer为连击产生的定时器,时间到调用input_repeat_key函数
16 * 上报,REP_DELAY用于设置重复按键的键值,REP_PERIOD设置延时时间
17 */
18 init_timer(&dev->timer);
19 if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
20 dev->timer.data = (long) dev;
21 dev->timer.function = input_repeat_key;
22 dev->rep[REP_DELAY] = 250;
23 dev->rep[REP_PERIOD] = 33;
24 }
25
26 /* 如果设备驱动没有设置自己的获取键值的函数,系统默认 */
27 if (!dev->getkeycode)
28 dev->getkeycode = input_default_getkeycode;
29
30 /* 如果设备驱动没有指定按键重置函数,系统默认 */
31 if (!dev->setkeycode)
32 dev->setkeycode = input_default_setkeycode;
33
34 /* 重要,把设备挂到全局的input子系统设备链表input_dev_list上 */
35 list_add_tail(&dev->node, &input_dev_list);
36
37 /* 动态获取input设备的ID号,名称为input*,其中后面的“*”动态获得,唯一的 */
38 snprintf(dev->cdev.class_id, sizeof(dev->cdev.class_id),
39 "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
40
41 /* 如果这个值没有设置,系统把输入设备挂入设备链表 */
42 if (!dev->cdev.dev)
43 dev->cdev.dev = dev->dev.parent;
44
45 /* 在/sys目录下创建设备目录和文件 */
46 error = class_device_add(&dev->cdev);
47 if (error)
48 return error;
49
50 /* 获取并打印设备的绝对路径名称 */
51 path = kobject_get_path(&dev->cdev.kobj, GFP_KERNEL);
52 printk(KERN_INFO "input: %s as %s\n",
53 dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
54 kfree(path);
55
56 /* 核心重点,input设备在增加到input_dev_list链表上之后,会查找
57 * input_handler_list事件处理链表上的handler进行匹配,这里的匹配
58 * 方式与设备模型的device和driver匹配过程很相似,所有的input
59 * 都挂在input_dev_list上,所有类型的事件都挂在input_handler_list
60 * 上,进行“匹配相亲”*/
61 list_for_each_entry(handler, &input_handler_list, node)
62 input_attach_handler(dev, handler);
63
64 input_wakeup_procfs_readers();
65
66 return 0;
67 }
![](https://common.cnblogs.com/images/copycode.gif)
上面的代码主要的功能有以下几个功能,也是设备驱动注册为输入设备委托内核做的事情:
- 进一步初始化输入设备,例如连击事件;
- 注册输入设备到input类中;
- 把输入设备挂到输入设备链表input_dev_list中;
- 查找并匹配输入设备对应的事件处理层,通过input_handler_list链表
我们需要再分析下这个匹配的过程,但是需要注意的是下面分析的代码是我们暂时无法分析的,因为那样会使得情况变得更加复杂,当我们从应用层往下分析的时候一切都会明白。input_attach_handler匹配过程如下:
![](https://common.cnblogs.com/images/copycode.gif)
1 const struct input_device_id *id;
2 int error;
3
4 /* 如果handler的blacklist被赋值了并且则优先匹配 */
5 if (handler->blacklist && input_match_device(handler->blacklist, dev))
6 return -ENODEV;
7
8 /* 否则利用handler->id_table和dev进行匹配,后面讲述匹配什么和过程 */
9 id = input_match_device(handler->id_table, dev);
10 if (!id)
11 return -ENODEV;
12
13 /* 这是一根“红线”,虽然你可能觉的是黑色的,但不可否认,他们真的匹配上了
14 * 调用handler->connnect函数进行匹配,匹配详细过程后面讲述
15 */
16 error = handler->connect(handler, dev, id);
17 if (error && error != -ENODEV)
18 printk(KERN_ERR
19 "input: failed to attach handler %s to device %s, "
20 "error: %d\n",
21 handler->name, kobject_name(&dev->cdev.kobj), error);
22
23 return error;
![](https://common.cnblogs.com/images/copycode.gif)
先来看下input_match_device()函数,看一下这个匹配的条件是什么,如何匹配的过程是怎样的,匹配的结果会是什么?
![](https://common.cnblogs.com/images/copycode.gif)
1 /* 事件处理层中的对应flags如果设置或者driver_info被设置则进行匹配 */
2 for (; id->flags || id->driver_info; id++) {
3 /* 以下通过flags中设置的位来匹配设备的总线类型、经销商、生产ID和版本ID
4 如果没有匹配上将进行MATCH_BIT匹配 */
5 if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
6 if (id->bustype != dev->id.bustype)
7 continue;
8
9 if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
10 if (id->vendor != dev->id.vendor)
11 continue;
12
13 if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
14 if (id->product != dev->id.product)
15 continue;
16
17 if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
18 if (id->version != dev->id.version)
19 continue;
20
21 /* MATCH_BIT用于匹配设备驱动中是否设置了这些为,MATCH_BIT的宏
22 * 被定义在input.c中,我们在设备驱动中设置的事件类型会与事件链表中的
23 * 所有事件类型进行比较,匹配成功了将返回id,证明真的很合适,否则NULL
24 */
25 MATCH_BIT(evbit, EV_MAX);
26 MATCH_BIT(keybit, KEY_MAX);
27 MATCH_BIT(relbit, REL_MAX);
28 MATCH_BIT(absbit, ABS_MAX);
29 MATCH_BIT(mscbit, MSC_MAX);
30 MATCH_BIT(ledbit, LED_MAX);
31 MATCH_BIT(sndbit, SND_MAX);
32 MATCH_BIT(ffbit, FF_MAX);
33 MATCH_BIT(swbit, SW_MAX);
34
35 return id;
36 }
37
38 return NULL;
![](https://common.cnblogs.com/images/copycode.gif)
既然证明是合适的,接下来就应该登记注册,并公证了。还记得handler->connect(handler, dev, id)函数吧。
当input_match_device()找到最合适的事件处理层驱动时,便执行handler->connect函数进行公证了,看下面这部分代码(假如说找到了evdev类型的驱动,在input/evdev.c中):
![](https://common.cnblogs.com/images/copycode.gif)
1 struct evdev *evdev;
2 struct class_device *cdev;
3 dev_t devt;
4 int minor;
5 int error;
6
7 /* EVDEV_MINORS为32,代表共能容纳32个evdev事件层设备,下面代码在找到空的地方,用于保存evdev事件层的数据,即上面定义的evdev */
8 for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);
9 /* 这说明内核已经没办法再分配这种类型的设备了 */
10 if (minor == EVDEV_MINORS) {
11 printk(KERN_ERR "evdev: no more free evdev devices\n");
12 return -ENFILE;
13 }
14 /* 开始给evdev事件层驱动分配空间了 */
15 evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);
16 if (!evdev)
17 return -ENOMEM;
18
19 /* 初始化client_list列表和evdev_wait队列,后面介绍 */
20 INIT_LIST_HEAD(&evdev->client_list);
21 init_waitqueue_head(&evdev->wait);
22
23 /* 初始化evdev结构体,其中handle为输入设备和事件处理的关联接口 */
24 evdev->exist = 1;
25 evdev->minor = minor;
26 evdev->handle.dev = dev;
27 evdev->handle.name = evdev->name;
28 evdev->handle.handler = handler;
29 evdev->handle.private = evdev;
30 sprintf(evdev->name, "event%d", minor);
31
32 /* 重要,上层访问时通过次设备号找到事件处理的接口 */
33 evdev_table[minor] = evdev;
34
35 /* evdev事件设备的此设备号的基准值INPUT_MAJOR, EVDEV_MINOR_BASE */
36 devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor),
37
38 /* 创建用户事件驱动层设备访问接口/dev/input/event* */
39 cdev = class_device_create(&input_class, &dev->cdev, devt,
40 dev->cdev.dev, evdev->name);
41 if (IS_ERR(cdev)) {
42 error = PTR_ERR(cdev);
43 goto err_free_evdev;
44 }
45
46 /* 提供/sys目录的用户空间接口 */
47 error = sysfs_create_link(&input_class.subsys.kobj,
48 &cdev->kobj, evdev->name);
49 if (error)
50 goto err_cdev_destroy;
51
52 /* input_dev设备驱动和handler事件处理层的关联,由handle完成 */
53 error = input_register_handle(&evdev->handle);
![](https://common.cnblogs.com/images/copycode.gif)
通过上述代码的执行,最终,输入设备在input_register_handle()的关联下与已经匹配上的handler结合
![](https://common.cnblogs.com/images/copycode.gif)
1 struct input_handler *handler = handle->handler;
2 /* 将d_node链接到输入设备的h_list,h_node链接到事件层的h_list链表上
3 * 因此,在handle中是输入设备和事件层的关联结构体,通过输入设备可以
4 * 找到对应的事件处理层接口,通过事件处理层也可找到匹配的输入设备
5 */
6 list_add_tail(&handle->d_node, &handle->dev->h_list);
7 list_add_tail(&handle->h_node, &handler->h_list);
8
9 /* 如果start函数有定义则调用,但是evdev结构体中并未初始化这个函数 */
10 if (handler->start)
11 handler->start(handle);
![](https://common.cnblogs.com/images/copycode.gif)
以上是输入设备驱动注册的全过程,牵涉的代码比较多,需要从宏观上理顺。
纵观整个过程:
输入设备驱动最终的目的就是能够与事件处理层的事件驱动相互匹配,但是在drivers/input目录下有evdev.c事件驱动、mousedev.c事件驱动、joydev.c事件驱动等等,我们的输入设备产生的事件应该最终上报给谁,然后让事件驱动再去处理呢?
知道了这么个原因再看上面代码就会明白,其实evdev.c、mousedev.c等根据硬件输入设备的处理方式的不同抽象出了不同的事件处理接口帮助上层去调用,而我们写的设备驱动程序只不过是完成了硬件寄存器中数据的读写,但提交给用户的事件必须是经过事件处理层的封装和同步才能够完成的,事件处理层提供给用户一个统一的界面来操作。
由于以上的这些原因,才有了上述代码的关联过程,看一下整个关联注册的过程:
通过上图我们可以看到input输入设备匹配关联的关键过程以及涉及到的关键函数和数据。
以上主要是从input设备驱动程序的角度去看输入子系统的注册过程和三层之间的关联。
下面将从应用层的角度分析事件的接受过程和处理过程以及三层之间是如何配合处理输入事件的。
从应用层的角度出发看input子系统
以上部分已经借助input子系统把input设备驱动层与事件驱动层进行了关联,以s3c2440_ts.c(输入设备层驱动)和evdev.c(事件处理层驱动)为例,来分析这一过程。
由于s3c2440_ts.c中上报的事件类型为按键、绝对值坐标,而evdev事件驱动程序是全匹配的,因此早在s3c2440_ts.c注册的过程中,就会创建设备节点/dev/input/event0(假设内核中没有其他的event类型的输入设备,这里就是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文件:
![](https://common.cnblogs.com/images/copycode.gif)
1 /* 输入设备初始化函数 */
2 static int __init input_init(void)
3 {
4 class_register(&input_class);
5 input_proc_init();
6 register_chrdev(INPUT_MAJOR,"input", &input_fops);
7 }
![](https://common.cnblogs.com/images/copycode.gif)
可以看到,输入设备初始化的过程首先建立了input类,初始化input在proc下的节点,然后注册input设备,设备名称为input,操作接口是input_fops,主设备号是INPUT_MAJOR=13。
由以上可知,只要是主设备号为13的设备驱动程序,都是用input_fops接口,即当event0设备使用open函数打开时,会调用到input_fops接口中的open驱动函数,这个结构体的初始化为:
1 static const struct file_operations input_fops = {
2 .owner = THIS_MODULE,
3 .open = input_open_file,
4 };
可以看到,只实现了一个open功能字段,再看input_open_file的实现:
![](https://common.cnblogs.com/images/copycode.gif)
1 static int input_open_file(struct inode *inode, struct file *file)
2 {
3 struct input_handler *handler =input_table[iminor(inode) >> 5];
4 const struct file_operations *old_fops,*new_fops = NULL;
5 if (!handler || !(new_fops =fops_get(handler->fops)))
6 return -ENODEV;
7 old_fops = file->f_op;
8 file->f_op = new_fops;
9 new_fops->open(inode, file);
10 }
![](https://common.cnblogs.com/images/copycode.gif)
以上代码的功能为找到对应事件驱动层的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查看它的注册过程:
![](https://common.cnblogs.com/images/copycode.gif)
1 static struct input_handler evdev_handler = {
2 .event = evdev_event, //事件处理
3 .connect = evdev_connect, //设备连接
4 .disconnect = evdev_disconnect, //注销连接
5 .fops = &evdev_fops, //驱动功能接口
6 .minor = EVDEV_MINOR_BASE, //evdev的值为64
7 .name = "evdev", //设备名称
8 .id_table = evdev_ids, //用于匹配设备驱动的数组
9 };
10
11 static int __init evdev_init(void)
12 {
13 return input_register_handler(&evdev_handler); //evdev设备驱动注册
14 }
![](https://common.cnblogs.com/images/copycode.gif)
由以上的内容可以知道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表中的内容是如何填充的,还需要查看这个注册的过程:
![](https://common.cnblogs.com/images/copycode.gif)
1 int input_register_handler(struct input_handler *handler)
2 {
3 ……
4 input_table[handler->minor>> 5] = handler;
5 ……
6 }
![](https://common.cnblogs.com/images/copycode.gif)
当然这个注册过程并不是只有这么一句话,看到这条语句,相信应该知道什么意思了。
在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实现如下:
![](https://common.cnblogs.com/images/copycode.gif)
1 /*evdev字符设备驱动接口 */
2 static const struct file_operations evdev_fops = {
3 .owner = THIS_MODULE,
4 .read = evdev_read,
5 .write = evdev_write,
6 .poll = evdev_poll,
7 .open = evdev_open,
8 .release = evdev_release,
9 .unlocked_ioctl = evdev_ioctl,
10 #ifdef CONFIG_COMPAT
11 .compat_ioctl = evdev_ioctl_compat,
12 #endif
13 .fasync = evdev_fasync,
14 .flush = evdev_flush
15 };
16 /*evdev设备open函数的实现过程 */
17 static int evdev_open(struct inode *inode, struct file *file)
18 {
19 struct evdev_client *client;
20 struct evdev *evdev;
21 /* 如果是event0,对于evdev设备来说,次设备号当然是0 */
22 int i = iminor(inode) - EVDEV_MINOR_BASE;
23 int error;
24 /* 如果大于32,说明超出了evdev能够容纳的最大输入设备个数 */
25 if (i >= EVDEV_MINORS)
26 return -ENODEV;
27 /* 由于evdev中能容纳32个输入设备,因此通过设备号event0中的0定位到是要处理的是哪一个输入设备,evdev_table中的内容在输入设备驱动注册时通过evdev_connect填充 */
28 evdev = evdev_table[i];
29 /* 判断是否设备接口存在,evdev_exist也是在evdev_connect填充为1 */
30 if (!evdev || !evdev->exist)
31 return -ENODEV;
32 /* 存在则分配evdev中的client来处理event* */
33 client = kzalloc(sizeof(struct evdev_client),GFP_KERNEL);
34 if (!client)
35 return -ENOMEM;
36
37 /* 把event*中的接口指向evdev_table中对应项 */
38 client->evdev = evdev;
39 /* 把client->node链接到evdev子集中 */
40 list_add_tail(&client->node,&evdev->client_list);
41 /* 如果open是第一个打开,则会执行input_open_device*/
42 if (!evdev->open++ &&evdev->exist) {
43 error =input_open_device(&evdev->handle);
44 if (error) {
45 list_del(&client->node);
46 kfree(client);
47 return error;
48 }
49 }
50 /* 将file私有指针指向client*/
51 file->private_data = client;
52 return 0;
53 }
54 //由上的代码可以看出,最终是要执行input_open_device去执行设备驱动程序中的代码,然而我们在定义设备驱动的时候并没有给input_dev中的open字段填充内容,因此可以看到input_open_device函数的执行过程:
55 if(!dev->users++ && dev->open)
56 err = dev->open(dev);
57
58 if (err)
59 handle->open--;
![](https://common.cnblogs.com/images/copycode.gif)
上面截取了片段,并没有执行到open函数,open进行自减操作,表示没有调用过open,这个值主要是为了close中判断open为0时释放资源使用。
不仅如此,我们在触摸屏驱动中也没有定义read、write,那当触摸屏上报事件时,是如何处理的呢?
我们需要先到触摸屏驱动程序中找到上报事件的函数再做进一步分析。
输入设备上报事件的处理过程
触摸屏驱动程序上报事件的函数为:
1 input_report_abs(dev,ABS_X, s3c2440_ts->tc.xp);
2 input_report_abs(dev,ABS_Y, s3c2440_ts->tc.yp);
3 input_report_abs(dev,ABS_PRESSURE, s3c2440_ts->tc.pressure);
4 input_report_key(dev,BTN_TOUCH, s3c2440_ts->pendown);
5 input_sync(dev);
然而他们其实是input_event函数的封装,调用的都是input_event函数,这一函数在input.c中实现如下:
![](https://common.cnblogs.com/images/copycode.gif)
1 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, intvalue)
2 {
3 struct input_handle *handle;
4
5 if (type > EV_MAX || !test_bit(type,dev->evbit))
6 return;
7 switch (type) {
8 case EV_SYN:
9 switch (code) {
10 case SYN_CONFIG:
11 if(dev->event)
12 dev->event(dev,type, code, value);
13 break;
14 case SYN_REPORT:
15 if(dev->sync)
16 return;
17 dev->sync= 1;
18 break;
19 }
20 break;
21 case EV_KEY:
22 case EV_SW:
23 case EV_ABS:
24 case EV_REL:
25 case EV_MSC:
26 case EV_LED:
27 case EV_SND:
28 case EV_REP:
29 case EV_FF:
30 }
31
32 if (type != EV_SYN)
33 dev->sync = 0;
34
35 if (dev->grab)
36 dev->grab->handler->event(dev->grab,type, code, value);
37 else
38 list_for_each_entry(handle,&dev->h_list, d_node)
39 if (handle->open)
40 handle->handler->event(handle,type, code, value);
41 }
![](https://common.cnblogs.com/images/copycode.gif)
代码被做了精简,其中就是在匹配上报的事件,并根据事件的类型调用驱动程序中相应的函数来完成,但是由于我们并没有定义过这些函数,因此执行最后的handle_handler_event函数,由事件处理层evdev_event函数来完成事件的保存工作,具体过程如下:
![](https://common.cnblogs.com/images/copycode.gif)
1 list_for_each_entry(client,&evdev->client_list, node) {
2 client->buffer[client->head].type= type;
3 client->buffer[client->head].code= code;
4 client->buffer[client->head].value= value;
5 client->head= (client->head + 1) & (EVDEV_BUFFER_SIZE - 1);
6 }
![](https://common.cnblogs.com/images/copycode.gif)
这里列举了关键代码,即上报的事件被保存到了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进行读去和写入。
通过设备节点读取输入事件
还是以触摸屏驱动程序和evdev事件处理层驱动来分析:
![](https://common.cnblogs.com/images/copycode.gif)
1 static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t*ppos)
2 {
3 struct evdev_client *client =file->private_data;
4 struct evdev *evdev = client->evdev;
5 int retval;
6
7 /* 判断用户给的count是否能够容纳事件数据的大小*/
8 if (count < evdev_event_size())
9 return -EINVAL;
10
11 /* 如果数据不为空并且设备存在并且是阻塞访问方式才能继续执行 */
12 if (client->head == client->tail&& evdev->exist && (file->f_flags & O_NONBLOCK))
13 return -EAGAIN;
14
15 /* 如果数据为空,设置进程等待底层驱动层上报事件到client->buffer中 */
16 retval =wait_event_interruptible(evdev->wait,
17 client->head != client->tail|| !evdev->exist);
18 if (retval)
19 return retval;
20
21 if (!evdev->exist)
22 return -ENODEV;
23
24 /* 循环读取数据 */
25 while (client->head != client->tail&& retval + evdev_event_size() <= count) {
26
27 struct input_event *event =(struct input_event *) client->buffer + client->tail;
28
29 if (evdev_event_to_user(buffer +retval, event))
30 return -EFAULT;
31
32 client->tail = (client->tail+ 1) & (EVDEV_BUFFER_SIZE - 1);
33 retval += evdev_event_size();
34 }
35
36 return retval;
37 }
![](https://common.cnblogs.com/images/copycode.gif)
这里如果没有数据,进程会睡眠,那由谁来唤醒呢?细心的话可以发现,当设备驱动层调用input_event上报事件调用相应的event函数进行事件写入时,是会唤醒阻塞等待的进程的。
通过设备节点写入输入事件
写入过程:
![](https://common.cnblogs.com/images/copycode.gif)
1 static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count,loff_t *ppos)
2 {
3 /* 循环写入,调用input_inject_event函数 */
4 while (retval < count) {
5
6 if (evdev_event_from_user(buffer +retval, &event))
7 return -EFAULT;
8 input_inject_event(&evdev->handle,event.type, event.code, event.value);
9 retval += evdev_event_size();
10 }
11
12 return retval;
13 }
![](https://common.cnblogs.com/images/copycode.gif)
上述代码中的event是input_event数组,包含了事件的类型、键值,通过input_inject_event把数据写入循环数组client->buffer中,input_inject_event调用的是input_event函数。
总结
对input子系统的整个过程做了分析,并从两个角度进行考虑.
对于写输入设备驱动程序的来说,需要掌握的是设备应该上报事件的类型,这样才能匹配到对应的事件层驱动帮助你保存对应的数据.
而对于设备上层开发者来说,应该先使用cat /proc/bus/input/devices查看你操作的设备类型和处理接口,以帮助你更好的对设备操作。
参考
http://blog.csdn.net/ielife
【驱动】input子系统整体流程全面分析(触摸屏驱动为例)【转】的更多相关文章
- linux input输入子系统分析《四》:input子系统整体流程全面分析
1 input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...
- ARM Linux 驱动Input子系统之按键驱动测试
上一篇已经谈过,在现内核的中引入设备树之后对于内核驱动的编写,主要集中在硬件接口的配置上了即xxxx.dts文件的编写. 在自己的开发板上移植按键驱动: 1.根据开发板的原理图 确定按键的硬件接口为: ...
- 21、根据(应用程序)虚拟驱动vivi的使用过程彻底分析摄像头驱动(有ioctrl分析)
videobuf2-core.h中的vb2_buffer,记录了v4l2_buffer ,驱动可以对vb2_buffer的v4l2_buffer进行操控, vb2_buffer是v4l2框架层的代码, ...
- input子系统分析
------------------------------------------ 本文系本站原创,欢迎转载! 转载请注明出处:http://ericxiao.cublog.cn/ -------- ...
- Linux input子系统分析
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见.同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分 ...
- 【Linux高级驱动】input子系统框架
[1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网络搜索 2) 自己想办法跟内核代码! 2.1 定位此驱动是属于哪种类 ...
- 【Linux高级驱动】input子系统框架【转】
转自:http://www.cnblogs.com/lcw/p/3802617.html [1.input子系统框架(drivers\input)] 如何得出某个驱动所遵循的框架? 1) 通过网 ...
- input子系统驱动
input子系统驱动 框架分析 核心层 文件为:/drivers/input/input.c: 首先找到入口函数为**static int __init input_init(void)**,在该函数 ...
- Linux input子系统编程、分析与模板
输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系 ...
随机推荐
- 2017-2018 第一学期201623班《程序设计与数据结构》-第2&3周作业问题总结
一.作业内容 第二周作业 http://www.cnblogs.com/rocedu/p/7484252.html#WEEK02 第三周作业 作业一定按教学进程中的模板提交 本周学习任务 点评结对同学 ...
- JavaScript学习笔记之JavaScript调用C#编写的COM组件
1.新建一个C#类库项目:MyCom: 2.修改 Properties 目录下的 AssemblyInfo.cs(程序集文件) 中的 ComVisible 属性为 true: 3.项目->属性- ...
- Game over 作业
终于有一篇不拼代码拼码字的作业了,哈哈哈..... 从寒假到这次结束,经历的博客及编码作业的过程 前面七次作业做个分类: 通往博客园和C++的第一步. 知识点:让我们对C++做一个预习,在学C++前有 ...
- springmvc关于redisCluster的使用及配置
首先附上maven仓库jar包的下载地址:https://repo.spring.io/webapp/#/artifacts/browse/tree/General/libs-release-loca ...
- 正则的replace函数传参使用
<script> var str = "a1ba2b"; var reg = /a.b/g; str = str.replace(reg,function(a,b){ ...
- Spring IOC AOP的原理 如果让你自己设计IOC,AOP如何处理(百度)
百度的面试官问,如果让你自己设计一个IOC,和AOP,如何设计, 我把IOC的过程答出来了,但是明显不对, (1) IOC 利用了反射,自己有个id,classtype,hashmap,所有的功能都在 ...
- Spring之c3p0连接池配置和使用
1.导入包:c3p0和mchange包 2.代码实现方式: package helloworld.pools; import com.mchange.v2.c3p0.ComboPooledDataSo ...
- scheme 教程 #lang racket
scheme 教程 #lang racket 来源 https://blog.csdn.net/yemeishenme/article/details/51471037 原文: https://le ...
- 【BZOJ1452】[JSOI2009]Count(树状数组)
[BZOJ1452][JSOI2009]Count(树状数组) 题面 BZOJ 洛谷 题解 数据范围这么小?不是对于每个颜色开一个什么东西记一下就好了吗. 然而我不会二维树状数组? 不存在的,凭借多年 ...
- 洛谷P3957 跳房子
普及组的题.....填坑来了. 当年的我一眼二分+DP,现在都佩服起自己来了...... 然后我们就写个二分,在check里面写单调队列优化DP即可. 然后就A了...... #include < ...