Linux输入子系统就是一个基于分层模式的系统,其基本的层次分解如下图所示。
在图中我们可以发现输入子系统主要包括三个部分设备驱动层(input driver)、核心层(input core)和输入事件驱动层。输入子系统的划分使得输入设备的驱动程序设计越来越简单,但是其中的思想采用我们学习的重点和难点。
Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。下面分析input输入子系统的结构,以及功能实现。
1. Input子系统是分层结构的,总共分为三层: 硬件驱动层,子系统核心层,事件处理层。
(1)、其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。
(2)、子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。
(3)、事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。
2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value),Input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是 硬件驱动层-->子系统核心-->事件处理层-->用户空间。
在驱动程序设计中,我们对于设备的驱动设计主要集中在设备驱动层的实现,但是这与之前的设备驱动开发存在较大的差别,主要是因为设备驱动不再是编写基本操作的实现过程,也就是不在是对struct file_operations 这个结构体对象的填充和实现。在输入设备驱动中的主要实现包括下面几个过程:
1、分配一个输入设备对象。并完成响应结构体元素的填充,主要包括支持的事件类型和事件代号等。
分配对象的函数:
struct input_dev *input_allocate_device(void);
释放对象函数:
void input_free_device(struct input_dev *dev);
设置支持的事件类型和事件代码:
通常采用set_bit函数实现:
设置支持的事件类型(支持按键事件)
set_bit(EV_KEY,input_dev->evbit);
设置支持的事件代码(支持按键1)
set_bit(KEY_1, input_dev->keybit);
2、完成输入设备对象的注册,将设备对象注册到输入子系统当中去,当然也有对应的释放函数。
注册设备到内核:
int input_register_device(struct input_dev *dev);
注销设备:
void input_unregister_device(struct input_dev *dev);
3、向核心层(input core)汇报事件的发生以及传输事件类型和事件代码等。这一部分通常是采用中断的方法实现,在中断中向上一层次(Input Core)传送发生事件的事件类型、事件代号以及事件对应的值等。但是上报的内容结构体都是基于一个固定结构体的
struct input_event,在用户空间也可以采用这个结构体实现对事件的访问。
structinput_event {
/*事件发生的时间*/
structtimeval time;
/*事件类型*/
__u16type;
/*事件代号*/
__u16code;
/*事件对应的值*/
__s32value;
};
基本的事件类型包括如下:
#define EV_SYN 0x00
/*常用的事件类型*/
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
支持的事件代号:
...
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
#define KEY_MINUS 12
#define KEY_EQUAL 13
#define KEY_BACKSPACE 14
…
主要的汇报函数如下:
/*汇报键值函数*/
void input_report_key(struct input_dev *dev, unsignedint code, int value);
/*汇报相对坐标值函数*/
void input_report_rel(struct input_dev *dev, unsignedint code, int value);
/*汇报绝对坐标值函数*/
void input_report_abs(struct input_dev *dev, unsignedint code, int value);
也可以采用更加一般的函数汇报,上面的三个函数是通过下面这个函数实现的。
void input_event(struct input_dev *dev, unsigned inttype, unsigned int code, int value);
完成上面的三个部分,一个输入设备的驱动程序也就完成了。但是其中的具体实现还需要阅读相关的源码。特别是设备的操作是如何通过输入设备实现等基本的操作是我们应该去关注的,设备的具体操作函数实质上已经因为一些共性被设计成了通用的接口,在输入子系统内部已经实现。
TQ2440中的按键主要是采用外部中断的形式实现,所以我在实验过程中也采用了外部中断的模式直接对按键进行操作。
具体的程序如下所示:驱动代码:
测试代码:
测试效果:
从效果上来看,代码基本上实现了按键的识别,但是该驱动程序的问题是按键并不能实现消抖操作。