前面对于按键的驱动有了大致的了解,赋予虚拟总线的概念,注册设备、驱动,互相探测,在探测函数中分配设备号、传递硬件设备信息给自定义结构体、初始化fops结构体、注册cdev结构体,这就是一个按键类型的驱动模型:https://blog.csdn.net/qq_40215005/article/details/90384563
但是作为一个操作系统,这样的设备实在是太多了,如果能在虚拟总线的基础上再进行抽象那就好了,所以子系统的概念就这么来了,比如说网络设备,就有网络子系统,输入设备就有输入子系统,显示屏就有framebuffuer子系统。
子系统
一个子系统是一个具有特定功能的模块,那么对于输入子系统来说,所有具有输入功能的设备都应该被集成在子系统中,子系统抽象了三层模型:
- 设备驱动层:进行底层的设备初始化
- 核心层:提供上下层的函数接口将一类设备统一管理
- 事件处理层:进行不同底层事件的处理,提供不同的系统调用给不同的设备节点
从上到下依次接近用户空间
在Linux中,输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理。所以这使得我们输入设备的驱动部分不在用关心对设备文件的操作,而是要关心对各硬件寄存器的操作和提交的输入事件。这也就是为什么在看内核按键驱动源码的时候找不到fops结构体的初始化,这些动作都由于子系统的存在而不复存在了,更加精简的模块做更细致的工作而不用再担心所谓的上层文件操作了。
驱动层和核心层接口
在Linux中,Input设备用input_dev结构体描述,定义在input.h中。设备的驱动只需按照如下步骤就可实现了。
①在驱动模块加载函数中设置Input设备支持input子系统的哪些事件;
②将Input设备注册到input子系统中;
③在Input设备发生输入操作时(如:键盘被按下/抬起、触摸屏被触摸/抬起/移动、鼠标被移动/单击/抬起时等),提交所发生的事件及对应的键值/坐标等状态。
事件类型
EV_SYN 0x00 同步事件
EV_KEY 0x01 按键事件
EV_REL 0x02 相对坐标(如:鼠标移动,报告的是相对最后一次位置的偏移)
EV_ABS 0x03 绝对坐标(如:触摸屏和操作杆,报告的是绝对的坐标位置)
EV_MSC 0x04 其它
EV_LED 0x11 LED
EV_SND 0x12 声音
EV_REP 0x14 Repeat
EV_FF 0x15 力反馈
每个事件都有相应的报告函数
在include/linux/input.h中
input_report_rel 鼠标
input_report_abs 触摸
input_report_key 按键
input_sync 同步事件
…
事件发生后调用报告函数,报告函数再调用input_event()函数告知给核心层
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
所以我们在写驱动的时候只需要申请input设备,进行事件的类型注册和使用核心层的一些接口就行了,这样的子系统结构使得驱动编写简单很多
核心层和事件处理层接口
input_handler是事件层的主要数据结构,事件到来时,要对事件进行处理,由于到来时事件类型不同,所以要进行相应的connect,然后进行相应的设备节点创建和提供接口给用户空间
分析input子系统下的触摸屏驱动
有了子系统的存在,输入设备驱动不在关心设备文件的操作,而是关心对各个硬件寄存器的操作和提交的输入事件
目录driver/input/touchscreen/s3c2410_ts.c
struct s3c2410