原先用的字符设备驱动框架都是自己定义fops结构体,自己去实现里面的各个成员函数,然后测试程序里面打开一个设备文件open("/dev/xxx")就能通过内核调用到驱动程序里的open函数。但这个做法的前提是写应用程序的人知道有个名字叫xxx的文件才能去打开他,没有把应用和驱动很好地分开。
输入子系统可以看作是驱动和应用的中间层,在这个中间层中流通的是“事件”,当USB设备插入时,驱动向输入子系统上报一个热插拔事件,一个按键按下时,驱动输入子系统上报一个按键事件,再由子系统处理之后再告诉应用 程序。
在总线设备驱动模型里说到,驱动程序分描述硬件的部分和软件实现的部分,其中软件实现的部分很少修改。当硬件资源改动时,只需修改描述硬件的代码,软件实现的代码不用改动就能实现同样的功能。
在输入子系统里是同样的思路:
输入设备被总结成一个input_dev结构体(硬件相关),处理函数被总结成一个input_handler结构体(软件相关)。类似总线设备驱动模型,输入子系统核心层会维护两条链表:input_dev_list和input_handler_list,当注册input_dev或input_handler结构体时,就会对应地加入这两条链表,并且判断注册的硬件(或软件)能否被相应的软件(或硬件)支持。
输入子系统里的input_dev结构体描述的是一个设备“能够产生哪些事件”,而总线设备驱动模型里的platform_device描述的是一个设备用到了哪些硬件资源(比如寄存器、io引脚),所以platform_device是更底层的概念,这也符合输入子系统作为中间层的概念。为了能让输入子系统知道设备“能够产生哪些事件”,或者说“将会产生哪些事件”,我们需要自己分配、设置、注册一个input_dev结构体,而input_handler结构体由输入子系统自个实现,处理事件的函数在input_handler结构体里面注册(不用自己实现),可以说input_handler更关心input_dev产生的事件是什么,而不是设备本身,底层的设备可能千变万化,但产生的事件种类是有限的,因此input_handler中的处理函数是“较稳定的代码”。因为稳定,不需要修改,所以被放在输入子系统里面变成内核的一部分。
举例,从应用层往下看:
应用程序怎么读按键?
先用read函数,这时不再读自己实现的某个/dev/xxx设备文件,而是读通用的文件(尝试获取相应的事件),如果有事件就会被读走,直接返回;如果是用阻塞方式读并且没有事件,程序会陷入休眠。
当中断产生时,在设备的中断服务程序里确定事件是什么,然后上报事件,最终调用相应的input_handler中的event处理函数唤醒应用程序,完成整个读过程。这个过程和不用输入子系统,由自己实现的驱动框架是一样的,只是我们不需要再用copy_to_user函数把数据传到用户空间,只需要上报事件。