本文共 6633 字,大约阅读时间需要 22 分钟。
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见。同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分析很有意义。
一、input子系统知识点
完整的input子系统分析包括以下几方面:
1) 软件层次
2) 输入子系统分层(input_handler,input_core, input_device)
3) 输入设备(TS)驱动开发
4) evdev handler分析
5) input设备模型视图(sysfs)和运行映象(procfs)
6) tslib分析
7) 应用框架的事件处理分析
笔者一向主张学习嵌入式应尽可能培养具备分析Linux软件架构的大局观。本文将从需求的角度去分析Linux输入所涉及的应用和内核层的相关模块所承担的角色和完成的功能,让开发人员能够对Linux驱动的所涉及的整个软件层次有清晰的认识。其他知识点将在后文进行阐述,敬请关注。
二、软件层次分析
Linux输入所涉及的软件层次如下图:
基于Linux内核的应用框架常见于Android和QT。由于Android4.2对触屏驱动的支持发生过重大的变化(将tslib完成的任务抛给驱动层来完成),所以我们以较为简单的QT框架来说明Linux输入的调用过程。假设有以下基于QT的通讯录应用场景,我们重点分析查询这个button控件的输入响应过程。
1.APP即通讯录应用,输入姓名拼音首字母,然后点击查询,输出结果(姓名和电话号码)。APP在QT creator可视化开发环境上使用所见即所得的方式拖入Button控件作为“查询”按钮。APP不需要关注按钮的显示,也不需要关心用户通过何种方式按下按钮,只需要做的是对按钮的单击事件进行响应,即通过姓名首字母来对姓名数据库进行查询并输出到结构框中。
2.QT应用框架需要完成APP开发所不需要关注的事情,即对输入事件进行封装处理、分发事件到目标控件、完成目标控件的状态变化和图像显示,当然还包括窗口的管理。上述应用图示是一个LCD屏显示页面,LCD的坐标点在左上角,如下图:
对于QT应用框架来说,用户点击了屏幕,QT的事件处理会收到一个坐标事件,即相对LCD坐标原点的触点坐标(x,y)。QT会对该坐标进行分析,在当前焦点窗口系统中检测该坐标落到哪个控件的显示范围。明显,“查询”按键在窗口中有坐标范围,由左上角和右下角两个坐标点确定其范围。如果判断用户触控的坐标落到该范围,QT即会分发一个单击事件给“查询”按键控件,最终QT通过回调的方式调用APP开发中编写的查询逻辑函数,并输出到结果框中。
3.Tslib,从名称来看就可明确其是触摸屏TS场景的一个中间库。其主要完成触控坐标消息的去抖、滤波和校准等功能。其最核心的功能是从触摸屏坐标系到LCD显示屏坐标系的线性转换。电子产品的触摸屏一般包括触摸屏和显示屏两个部分,触摸屏在显示屏之上,有电阻和电容分压原理之分。在TS驱动实现中,触摸屏的坐标原点是在屏幕的左下角,即驱动向TSLIB提供的基于左下角为原点的坐标系坐标,而TSLIB需要向QT应用框架提供基于左上角为原点的坐标系统坐标,因此TSLIB需要完成坐标系的转换。如下图:
另外,触摸屏的分辨率和LCD显示屏的分辨率也有可能不一样,因此坐标的转换还要考虑分辨率的因素。
4.C library,Tslib必须通过open,read这些标准c接口访问内核层驱动,而这些接口最终都会使用syscall指令跳转到内核态。
5.VFS,open、read等接口通过syscall系统调用层最终会调用到vfs_open、vfs_read等接口。Open所带参数为输入设备文件名,如/dev/input/event1,vfs_open通过lookup在dentry链表中找到该设备文件对应的inode,进而分析出该文件是一个字符设备文件,交由字符设备驱动框架的chardev_open进行处理,最终获得输入子系统(主设备号都是13)对应的input-core层定义的file_operations,并封装到所在进程的file结构中,最后向应用层返回file对应的句柄fd,而read则是通过该file_operations进行读转发和访问操作。
6.字符设备驱动框架层。chardev_open通过设备文件对应的inode读出对应的主设备号是13,并在字符设备驱动全局链表cdev_map中找到主设备号13对应的file_operations, 即输入子系统初始化时向系统注册的input_fops。如下图:
如何访问不同的输入设备,如触摸屏和按键等等,是由input_fops的open来负责。input_fops即为input-core的组成部分。
7.输入子系统,输入子系统对linux的输入设备驱动进行了高度抽象,最终分成了三层,包括input核心层、input事件处理层和input设备驱动层。如下图:
1)所有的输入设备的主设备号都是13,其通过次设备来将输入设备进行分类,如下图:
2)对于应用层来说,其并不关心输入设备驱动层,其只关心输入事件处理层,即关注哪种类型的输入设备。
3)系统初始化时,事件处理层(input-handler)向input-core注册自己,并告知input-core其能处理底层设备驱动的类型和能力,例如event_handler事件处理(evdev.c)能够处理触摸屏和按键驱动。
4)当输入设备驱动input-device向input-core注册时,input-core会给其匹配合适的事件处理层input-handler,最终用input-handle来关联两者,并在input-handler的控制下生成用户访问的字符设备文件,如如/dev/input/event0对应的次设备号即是64。
8.input核心层,有了以上的分析,我们再继续分析input-core的input_open_file,假如设备文件名为/dev/input/event0,那次设备号即64,input_open_file通过次设备号得到对应的事件处理层event_handler,并交给该handler进行管理。
9.input事件处理层,event_handler根据次设备号可以得到该输入驱动在其管理设备数组中evdev_table的偏移(为0),进而得到对应的input_handle,最终找到关联的输入设备驱动input-device。
10.input设备驱动层,输入设备驱动层的open会进行硬件初始化等。
11.VFS层以下我们着重分析了驱动的open过程,接下来我们来分析一下触屏消息的产生和读取过程。
1)可以想象出在QT应用框架中会有一个高优先级的线程一直在等待读取触摸和按键消息。当没有消息产生时,该线程处于休眠状态。
2)当用户触屏时,触摸屏会产生一个外部硬件中断,在输入设备驱动的中断处理中会读取触屏坐标,并向其对应的input-handler汇报,而input-handler在分析该消息是否是重复消息等过程后向打开该handler的线程发送信号量进行唤醒,并将该触屏消息填入线程的消息队列。
3)QT应用框架线程会通过tslib的接口来读取该消息,最终获得LCD坐标系坐标,并进行后续处理。
备注:输入子系统分层(input_handler, input_core, input_device)、输入设备(TS)驱动开发、evdev handler分析、input设备模型视图(sysfs)和运行映象(procfs)、tslib机制分析、应用框架的事件处理分析等系列知识待后续补充。谢谢!
同时,我们还应该看到,触摸屏可能是I2C总线接口、UART接口、USB接口等等,因此触摸屏设备驱动层不仅作为一个input_device存在,还有可能作为一个I2C设备而存在等等。而I2C设备驱动又需要对I2C子系统进行分析。继续努力吧~~
Linux input分析之二:解构input_handler、input_core、input_device
输入输出是用户和产品交互的手段,因此输入驱动开发在Linux驱动开发中很常见。同时,input子系统的分层架构思想在Linux驱动设计中极具代表性和先进性,因此对Linux input子系统进行深入分析很有意义。
本文继续在《Linuxinput子系统分析之一:软件分层》的基础上继续深入研究Linux输入子系统的分层架构思想以及其实现。软件分层探讨的是输入消息从底层硬件到内核、应用层的消息传递和使用过程,而本文则是专注剖析Linux内核驱动层对输入设备的抽象分层管理和实现。
一、input子系统知识点回顾
详细请看《Linux input子系统分析之一:软件分层》一文。输入子系统对linux的输入设备驱动进行了高度抽象,最终分成了三层,包括input核心层、input事件处理层和input设备驱动层。input核心层(input-core)对input设备(input-device)和input事件处理(input-handler)进行管理并进行消息转发。如下图:
所有的输入设备的主设备号都是13,input-core通过次设备来将输入设备进行分类,如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。
二、input核心层的任务
核心层input-core完成的工作包括:
1) 直接跟字符设备驱动框架交互,字符设备驱动框架根据主设备号来进行管理,而input-core则是依赖于次设备号来进行分类管理。Input子系统的所有输入设备的主设备号都是13,其对应input-core定义的structfile_operations input_fops.驱动架构层通过主设备号13获取到input_fops,之后的处理便交给input_fops进行。
2) 提供接口供事件处理层(input-handler)和输入设备(input-device)注册,并为输入设备找到匹配的事件处理者。
3) 将input-device产生的消息(如触屏坐标和压力值)转发给input-handler,或者将input-handler的消息传递给input-device(如鼠标的闪灯命令)。
三、input子系统初始化
1. input-core初始化
--driver/input/input.c
在设备模型/sys/class目录注册设备类,在/proc/bus/input目录产生设备信息,向字符设备驱动框架注册input子系统的接口操作集合(主设备号13和input_fops)。
2.input-handler初始化
以支持触摸屏TS的event-handler为例说明。
--driver/input/evdev.c
继续展开input_register_handler接口:
--driver/input/input.c
3.input-device初始化
以触摸屏TSC2007为例,该触摸屏是I2C总线接口访问。
--driver/input/touchscreen/tsc2007.c
I2C总线的管理类似于平台总线,在注册I2C设备驱动接口i2c_add_driver中也会匹配其管理的I2C设备链表元素,匹配成功后即会调用i2c_driver的probe接口。有关总线、设备和驱动的关系请参看《从需求的角度去理解Linux:总线、设备和驱动》。
继续跟踪tsc2007_probe之前先看看input-device的数据结构:
继续跟踪tsc2007_probe:
继续展开input_register_device接口:
--driver/input/input.c
4.input-core关联匹配input-device和input-handler
在input_register_handler和input_register_device最后都会使用input_attach_handler接口来匹配输入设备和对应的事件处理者。
继续跟踪evdev_connect:
--driver/input/evdev.c
Struct evdev evdev_table代表evdev_handler所管理的底层input-device(通过input-handle管理)和应用层已打开该设备的进程、同步的相关结构和消息队列(evdev_client记录)。
input-handle关联input-device和input-handler一目了然。
所以input_register_handle的接口很容易想到是通过input-handle通过自身的d-node和h-node关联到input-device和input-handler实例中。这样通过input-handler可以快速找到input-device,通过input-device也可以快速找到input-handler。
至于evdev_install_chrdev即是将一个evdev实例记录到evdev_table数组,宣告其存在。
至此,我们可以得到以下evdev-handler管理下的示意图:
四、应用open过程
假设触摸屏驱动在注册输入设备过程中生成/dev/input/event0设备文件。我们来跟踪打开这个设备的过程。
Open(“/dev/input/event0”)
1.vfs_open打开该设备文件,读出文件的inode内容,得到主设备号13和次设备号64.
2.chardev_open 字符设备驱动框架的open根据主设备号13得到输入子系统的input_fops操作集。
3.input_fops->open, 即input_open_file
4.继续跟踪input-handler层的evdev-open,至此evdev不仅关联了底层具体的input-device,而且记录了应用层进程打开该设备的信息。之后input-device产生的消息可以传递到evdev的client中的消息队列。便于上层读取。
5. input-device层的open。
实际上,tsc2007驱动并没有定义input_dev的open接口。
五、触屏消息传递过程
1. open获得的fd句柄对应的file_operations是evdev_handler的evdev_fops。因此read接口最终会调用到evdev_fops的read接口,即evdev_read。接下来我们来跟踪这个接口的实现过程。我们先看看struct evdev的成员evdev_client的定义,其即是代表打开该输入设备的进程相关的数据结构。
2. evdev_read
3. 假设消息队列为空时,则上层进程将会睡眠,直到被唤醒再进行消息读取。谁来唤醒它呢?由底层input-device的硬件中断发起,最终将触屏消息送达该消息队列后即会发出唤醒信号。tsc2007_probe中注册的外部硬件中断服务函数即是发起者。
来看看该中断服务函数tsc2007_irq:
ts-work即是tsc2007_work:
跟踪input_report_abs接口:
继续跟进evdev_event:
即会唤醒执行在evdev_read中等待读取消息的进程,继续下面的执行过程,从client的buffer中取出消息,并通过copy_to_user返回给应用程序。
有一点需要注意,每次触屏消息产生后,在tsc2007_work中要input-report-abs报告x坐标,y坐标和压力值,最后再通过input-sync接口发出同步事件,向上层应用发出异步通知进行读取。
怎样,应该是全网络讲述Linux input子系统最详尽和最深入的分析了吧!
敬请关注微信公众号:嵌入式企鹅圈,百分百原创,嵌入式Linux和物联网开发技术经验,资深嵌入式软件架构师撰文。