https://www.cnblogs.com/lifexy/p/7542989.html
输入子系统概念介绍
在以前的按键程序上,有个缺点,没办法用在被人的现成的应用程序上,比如QT等应用程序。别的应用程序不会打开/dev/buttons,有可能打开现成的设备/dev/tty。也有可能直接调用scanf(),就可以获取按键的输入。
以前写的应用程序,只有你知道怎么用,但是其他人不知道怎么用。
写出一个通用的驱动程序,让已经现成的应用程序无缝的移植到你的单板上。(无缝就是不需要修改别人的应用程序)
使用现成的驱动。在内核里面现成的驱动程序,把自己需要的东西融合进去。(现成的驱动程序就是输入input子系统)
以前的字符设备驱动程序步骤:
1、确定主设备号major;
2、构造一个file_operations结构体(open函数、read函数、write函数);
3、告诉内核,register_chrdev,注册字符设备驱动程序;
4、入口函数调用注册函数;
5、有入口函数,必然有出口函数;
一、输入(input)子系统框架:(现成的框架,系统已做好,现成的框架也有上面的几个步骤)
打开核心层input.c(中转作用),位于drivers\input目录下
subsys_initcall(input_init); module_exit(input_exit);
入口函数input_init,出口函数input_exit。
二、显然输入子系统是作为一个模块存在,我们先来分析一下input_init()入口函数
static int __init input_init(void) { int err; err = class_register(&input_class); if (err) { printk(KERN_ERR "input: unable to register input_dev class\n"); return err; } err = input_proc_init(); if (err) goto fail1; err = register_chrdev(INPUT_MAJOR, "input", &input_fops); if (err) { printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); goto fail2; } return 0; fail2: input_proc_exit(); fail1: class_unregister(&input_class); return err; }
(1)上面第15行,register_chrdev(INPUT_MAJOR, "input", &input_fops);,以前注册设备需要自己写的,现在内核代码里面有了。注册了主设备号为13。看一下input_fops(file_operantions)结构体:
为什么只有一个open函数,不是要读按键吗?
显然open函数做了某些工作。
三、然后进入input_open_file函数里面:
static int input_open_file(struct inode *inode, struct file *file) { struct input_handler *handler = input_table[iminor(inode) >> 5]; //(1) const struct file_operations *old_fops, *new_fops = NULL; int err; if (!handler || !(new_fops = fops_get(handler->fops))) //(2) return -ENODEV; if (!new_fops->open) { fops_put(new_fops); return -ENODEV; } old_fops = file->f_op; file->f_op = new_fops; //(3) err = new_fops->open(inode, file); //(4) if (err) { fops_put(file->f_op); file->f_op = fops_get(old_fops); } fops_put(old_fops); return err; } static const struct file_operations input_fops = { .owner = THIS_MODULE, .open = input_open_file, };
(1)第三行中, iminor(inode)调用了MINOR(inode->i_rdev)来获取次设备号,将次设备号处理32。input_handler输入处理器(处理句柄),handler=input_table。input_table数组,根据次设备号所打开的文件赋值给handler。
(2)第七行中,input_handler有个file_operations结构体,fops_get那个file_operations结构体。
(3)第十五行中,现在所打开的文件f_op 等于 新的f_op(file_operantions结构体)。
(4)第十七行中,调用新的f_op的open函数。
以后应用程序app读read时,最终会调用到file->f_op->read。
input_table数组由谁来构造?
四、input_register_handler会构造input_table数组:
int input_register_handler(struct input_handler *handler) { struct input_dev *dev; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) return -EBUSY; input_table[handler->minor >> 5] = handler; //(1) } list_add_tail(&handler->node, &input_handler_list); list_for_each_entry(dev, &input_dev_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); return 0; }
五、搜索一下input_register_handler函数会被谁调用:
我们可以从下图可以知道,evdev.c(事件设备),joydev.c(游戏句柄设备),keyboard.c(键盘设备),mousedev.c(鼠标设备),tsdev.c(触摸屏设备)。
以evdev.c为例子,它在evdev_exit()注册:
static void __exit evdev_exit(void) { input_unregister_handler(&evdev_handler); //注册 }
六、再来看一下edev_handler结构体:(纯软件)
static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops, .minor = EVDEV_MINOR_BASE, .name = "evdev", .id_table = evdev_ids, };
(1)第五中.fops:file_oprations结构体。
(2)第六行中.minor:其中,EVDEV_MINOR_BASE=64,次设备是64。然后调用input_register_handler注册,相当于EVDEV_MINOR_BASE/32=2,所以放在数组的第二项EVDEV_MINOR_BASE[2]。
所以open时,就会调用input_open_file,执行evdev_handler->edev_fops->.open
(3)第八行中.id_table:表示handler能够支持哪一些输入设备。hadler和devices进行比较,handler处理器(软件)能否支持device(设备),如果能够支持设备,则调用.connect函数。
(4)第三行中.connect:连接函数,将input_device和input_handler建立连接。
七、再来看一下input_register_device函数:
int input_register_device(struct input_dev *dev) { ... ... list_add_tail(&dev->node, &input_dev_list); //(1)放入链表 ... ... list_for_each_entry(handler, &input_handler_list, node) //(2) input_attach_handler(dev, handler); //(2) ... ... return 0; }
(1)第4行中,将input_devid结构体,放入input_dev_list链表里面。
(2)第6行中,对于每一个input_handler,都调用input_attach_handler,input_attach_handler根据input_handler的id_table判断能否支持那个input_dev(输入设备)。
八、然后再回过头看input_handler的input_register_handler()函数:
int input_register_handler(struct input_handler *handler) { ... ... input_table[handler->minor >> 5] = handler; //(1) ... ... list_add_tail(&handler->node, &input_handler_list); //(2) list_for_each_entry(dev, &input_dev_list, node) //(3) input_attach_handler(dev, handler); ... ... return 0; }
(1)第4行中,首先将input_device放入一个数组里面。
(2)第6行中,再讲input_device放到一个链表input_handler_list里面。
(3)第8行中,对于每一个input_device,都调用input_attach_handler,input_attach_handler根据input_handler的id_table判断能否支持那个input_dev(输入设备)。
所以,不管是先添加input_handler还是input_dev,调用input_attach_handler(),判断两者是否匹配、支持。
九、我们来看一下input_attach_handler()函数:
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { ... ... id = input_match_device(handler->id_table, dev); //根据handler的id_table和输入设备dev进行比较 if (!id) //匹配失败 return -ENODEV; error = handler->connect(handler, dev, id); //如果匹配调用handler的connect函数 ... ... return error; }
根据handler的id_table和输入设备dev进行比较,是否匹配。如果匹配,调用hadler里面的connect函数建立连接。
注册input_dev或者input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立“连接”。
十、举个列子(evdev.c事件驱动):evdev_handler->connect函数
10.1 来分析怎么样建立连接:
10.2 evdev_handler的.connect()函数是evdev_connect():
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id) { ... ... 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 = kzalloc(sizeof(struct evdev), GFP_KERNEL); //(1)分配一个input_handle结构体(没有r) ... ... evdev->handle.dev = dev; //(2)设置input_handle,指向左边的input_dev结构体 evdev->handle.name = evdev->name; evdev->handle.handler = handler; //(2)指向右边的input_handler结构体 evdev->handle.private = evdev; sprintf(evdev->name, "event%d", minor); ... ... devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor), cdev = class_device_create(&input_class, &dev->cdev, devt,dev->cdev.dev, evdev->name); ... ... error = input_register_handle(&evdev->handle); //(3)注册handle ... ... }
(1)第10行中,分配一个input_handle结构体(没有r)。
(2)第12行中,设置input_handle,dev指向左边的input_dev结构体,.handler指向右边的input_handler结构体。
(3)第21行中,注册handle。
10.3 最终进入input_register_handle()函数注册handle
int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; list_add_tail(&handle->d_node, &handle->dev->h_list); //(1) list_add_tail(&handle->h_node, &handler->h_list); //(2) if (handler->start) handler->start(handle); return 0; }
(1)第五行中,把传进来的handle放入一个输入设备dev的链表h_list里面。
(2)第六行中,把handle放入右边handler的链表h_list里面。
连接的时候构造一个input_handle,里面.dev指向input_device,里面.handler指向input_handler。
input_device的.h_list指向input_handle,input_handler的h_.list指向input_handle。
可以从input_device输入设备,通过.h_list找到input_handle,从里面的.handler找到右边的handler处理者。
也可从input_handler,通过.h_list找到input_handle,从里面的.dev找到左边的能支持的设备dev。
总结一下:
1、分配一个input_handle结构体
2、input_handle.dev = input_dev //指向左边的input_dev
input_handle.handler = input_handler //指向右边的 input_handler
3、注册:
input_handler->h_list = &input_handle;
input_dev->h_list = &input_handle;
十一、怎么读取按键evdev_read(时间驱动)?
static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { ... ... if (count < evdev_event_size()) return -EINVAL; if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//无数据,并且是非阻塞方式打开,则立刻返回 return -EAGAIN; retval = wait_event_interruptible(evdev->wait, //否则休眠 client->head != client->tail || !evdev->exist); ... ... }
十二、read函数进入休眠,被谁唤醒呢?evdev_event事件处理函数
static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { ... ... wake_up_interruptible(&evdev->wait);//事件发生,唤醒 }
十三、分下一下,evdev_event被谁调用?gpio_keys_isr(在drivers\input\keyboard\gpio_keys.c文件)
在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数。
static irqreturn_t gpio_keys_isr(int irq, void *dev_id) { ... ... input_event(input, type, button->code, !!state); ///上报事件 input_sync(input); ... ... }
通过input_event调用.event事件函数:
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { struct input_handle *handle; list_for_each_entry(handle, &dev->h_list, d_node)//对链表的每一项handle if (handle->open)//如果handle已经打开 handle->handler->event(handle, type, code, value); }