学完第一期基础驱动入门后,接着开始讲input子系统。看着视频,心里有个大概对输入子系统的认识,但是说不出来。百度一下,摘抄吧。
https://blog.csdn.net/zouleideboke/article/details/70650001
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子系统最神秘之处就是它的核心层部分,这个核心层做了什么呢?它在内核中是怎么定义的?
2.linux输入子系统原理
linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。
设备驱动层:主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。
核心层: 为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
事件处理层:事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
对于linux输入子系统的框架结构图:
分析:/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。
事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。
linux输入子系统的事件处理机制:
分析:由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。
作为输入设备的驱动开发者,需要做以下几步:
1. 在驱动加载模块中,设置你的input设备支持的事件类型。(事件类型有:EV_SYN 0x00 同步事件;
EV_KEY 0x01 按键事件;
EV_LED 0x11 按键/设备灯等)
2. 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等)
3.将输入设备注册到输入子系统中
3.input 核心层
input核心层位于/drivers/input/input.c
3.1 入口和出口
1 static const struct file_operations input_fops = { 2 .owner = THIS_MODULE, 3 .open = input_open_file, 4 }; 5 static int __init input_init(void) 6 { 7 int err; 8 err = class_register(&input_class);//注册input类,这里就是生成了sys/class/input目录,在该目录下会看到驱动链接文件 9 if (err) { 10 printk(KERN_ERR "input: unable to register input_dev class "); 11 return err; 12 } 13 err = input_proc_init();//在proc目录下建立input子系统目录及交互文件,即/proc/bus/input目录下的devices文件和handlers文件 14 if (err) 15 goto fail1; 16 17 err = register_chrdev(INPUT_MAJOR, "input", &input_fops);//注册一个主设备号为INPUT_MAJOR(13),次设备号为0~255,文件操作符为input_fops的字符设备 18 if (err) { 19 printk(KERN_ERR "input: unable to register char major %d", INPUT_MAJOR); 20 goto fail2; 21 } 22 return 0; 23 fail2: input_proc_exit(); 24 fail1: class_unregister(&input_class); 25 return err; 26 } 27 static void __exit input_exit(void) 28 { 29 input_proc_exit(); 30 unregister_chrdev(INPUT_MAJOR, "input"); 31 class_unregister(&input_class); 32 } 33 subsys_initcall(input_init); 34 module_exit(input_exit);
这个初始化函数中,有一个注册字符设备函数register_chrdev(第17行),它用到了input_fops,这个结构体为struct file_operations,但这里他只有一个input_open_file——open成员。
3.2 input_open_file函数
1 static int input_open_file(struct inode *inode, struct file *file) 2 { 3 struct input_handler *handler = input_table[iminor(inode) >> 5];//根据所打开的驱动的次设备号inode,查找对应input_handler 4 const struct file_operations *old_fops, *new_fops = NULL; 5 int err; 6 /* No load-on-demand here? */ 7 if (!handler || !(new_fops = fops_get(handler->fops)))//new_fops赋值为该驱动的file_operations 8 return -ENODEV; ...
17 old_fops = file->f_op; 18 file->f_op = new_fops;//所打开的文件的f_op等于新的new_fops 19 err = new_fops->open(inode, file);//打开新的驱动 ...25 return err; 26 }
分析:根据inode,在input_table数组中查找的对应的input_handler,然后从中提取出.fops成员复制给new_fops,并调用其成员下的.open函数。old_fops是为了防止出错做备份用的。
3.3 input_table的分析,input_table由谁构造?
首先input_table在Input.c中定义。
1 static struct input_handler *input_table[8];
2 构造
Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....的入口函数
input_register_handler //向上注册
->input_table[handler->minor >> 5] = handler;
//以Evdev.c为例 static int __init evdev_init(void) { return input_register_handler(&evdev_handler); } static struct input_handler evdev_handler = { .event = evdev_event, .connect = evdev_connect, .disconnect = evdev_disconnect, .fops = &evdev_fops,//这个结构体里有各种读写操作函数 .minor = EVDEV_MINOR_BASE,//次设备号 宏定义64,handler->minor >> 5后,就是2 所以它在第三项 .name = "evdev", .id_table = evdev_ids,//用于比较是否要打开的设备是否匹配 }; int input_register_handler(struct input_handler *handler) { ... if (handler->fops != NULL) { if (input_table[handler->minor >> 5]) return -EBUSY; input_table[handler->minor >> 5] = handler;//这里evdev_handler添加到input_table[2]中 } ... return 0; }
分析:input_table由input_register_handler函数构造。
input_register_handler
input_table[handler->minor >> 5] = handler;//放入数组
list_add_tail(&handler->node, &input_handler_list);//放入链表
list_for_each_entry(dev, &input_dev_list, node)//对于每个input_dev,调用input_attach_handler
input_attach_handler(dev, handler);//根据input_handler的id_table判断是否支持这个input_dev
以上Evbug.c/Evdev.c/Joydev.c/Keyboard.c/Mousedev.c....向input.c注册handler,为输入设备实现一个接口。
查看evdev_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,//表示这个接口能够支持那些输入设备,如果能够支持就调用.connect函数, };
3.4注册输入设备——input_register_device
input_register_device
list_add_tail(&dev->node, &input_dev_list);//放入链表
list_for_each_entry(handler, &input_handler_list, node)//对于每一个input_handler,都调用input_attach_handler函数
input_attach_handler(dev, handler);//根据input_handler的id_table判断是否支持这个input_dev
3.5input_attach_handler函数作用
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler) { ... id = input_match_device(handler->id_table, dev);//根据id_table和dev判断是否匹配 if (!id) return -ENODEV; error = handler->connect(handler, dev, id);//如果匹配,执行.conect函数 ... return error; }
总结:注册input_dev或input_handler时,会两两比较,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能够支持,则调用input_handler的connect函数建立连接。
3.6怎么建立连接
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.c为例
evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); //分配一个input_handle
evdev->handle.dev = dev;//指向input_dev
evdev->handle.name = evdev->name;
evdev->handle.private = evdev;
error = input_register_handle(&evdev->handle);//注册