编译系统 :ubuntu 16.04
内 核 :linux-2.6.22.6
硬件平台 :jz2240
交叉编译器:arm-linux-gcc 3.4.5
Linux系统必须具备处理外部事件的能力,首先得有完善的输入设备驱动,典型的输入设备如:按键,键盘,触摸屏,鼠标等都是常用的输入设备。但是每种设备都有其独特的特性。为此,linux系统中设计了Input输入子系统,专门用于处理输入事件。Input输入子系统对输入设备驱动进行分层处理,一共可分为3层,分别为核心层(input core)、设备驱动层(deices drivers)和事件处理层(event handler)。其中,设备驱动层提供对硬件各种操作以及将硬件对用户的输入访问转换为标准输入事件。核心层把设备驱动层上报的事件提交给事件处理层。而事件处理层负责对标准的输入事件进行统一处理,然后与用户空间的应用程序进行交互。由于input输入子系统的分层思想,使得设备驱动专注于硬件控制,以及上报事件。三者关系如图1所示
设备驱动层 核心层 事件处理层 用户空间
图1 input输入子系统
2 input输入子系统关键结构体
因输入事件的多样性,使得input输入子系统必须对输入事件类型进行高度抽象以便接收不同类型的事件。因此,内核使用数据结构input_event来描述,原型如下:
struct input_event {
struct timeval time;
_u16 type;
_u16 code;
_u32 value;
} ;
其中,time表示一个由秒和微秒组成的时间;type表示设备类型;code表示事件类型;value表示事件值。
在input输入子系统驱动中,每一个设备都需要用一个数据结构struct input_dev来进行描述,原型如下:
struct input_dev {
const char *name;
struct input_id id;
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
、
、
、
int (*open)(struct input_dev *dev);
int (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev,struct file *file);
int (*event)(struct input_dev *dev,unsigned int type,unsigned int code,int value);
struct list_head h_list;
struct list_head node;
} ;
其中,name 表示设备名;id表示设备信息,包括厂商号,产品号,版本号等;evbit表示支持的事件的位图;keybit表示支持的按键的位图;relbit表示支持的相对坐标的位图;open、close、flush、event等回调函数表示支持的事件操作;h_list表示handle链表,node表示事件处理链表上的设备节点。
3 input输入子系统详细剖析
drivers/input目录下input.c 文件,在input_init()初始化函数中,调用了class_register()函数用于注册一个类,然后调用register_chrdev()函数注册一个设备,同时将操作函数集合input_fops与主设备号INPUT_MAJOR绑定,重点关注操作函数集合input_fops,其中仅仅有open操作函数。但是并没有创建一个和用户空间应用程序交互的设备节点。绝大多数输入设备都将匹配evdev事件处理程序。driver/input目录下evdev.c文件,evdev_init()初始化函数中调用input_register_handler()函数并将一个struct input_handler类型的变量evdev_ handler作为参数传入。input_register_handler()函数具体在input.c中实现,内部功能如下:
input_register_handler(struct input_handler *handler)
{
input_table[handler->minor >> 5] = handler;//根据传入参数handler中成员变量 minior(次设备号)在全局数组input_table中找到对应的位置,把handler存入数组。
list_add_tail(&handler->node, &input_handler_list);//把handler中成员变量node(设备节点) 挂载到事件处理链表。
list_for_each_entry(dev, &input_dev_list, node);//在input_dev_list(设备驱动链表)中依次遍 历,然后在input_attach_handler()函数中进行匹配
input_attach_handler(dev, handler);//根据id进行匹配,如果匹配成功则调用handler中 connect()函数把设备驱动和事件处理方法进行关联。
}
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,
};
在evdev_connect()函数中实现了设备驱动和事件处理方法相关联,内部具体实现如下:
evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) {
struct evdev *evdev; //定义一个变量用于把存储设备和事件
evdev->handle.dev = dev;//evdev中成员handle的成员dev记录设备
evdev->handle.handler = handler;//evdev中成员handle的成员handler记录事件处理方法
class_device_create(); //在/dev目录下创建设备节点,以提供和用户空间的应用程序进行交互
}
在设备驱动程序中,要想使用input输入子系统,需要先定义一个struct input_dev结构类型的变量用于描述一个设备,然后通过宏set_bit()设置设备支持哪些事件类型,以及事件类型中的哪些具体事件。当设备驱动进行注册时,首先调用input_register_device()函数,此函数的功能是把该设备挂载到设备驱动链表中。具体实现如下:
input_register_device(struct input_dev *dev)
{
list_add_tail(&dev->node, &input_dev_list);//把当前设备挂载到设备驱动链表
list_for_each_entry(handler, &input_handler_list, node); //在input_handler_list(事件处理链表)中依次遍历,然后在input_attach_handler()函数中进行匹配
input_attach_handler(dev, handler);//根据id进行匹配,如果匹配成功则调用handler中 connect()函数把设备驱动和事件处理方法进行关联。
}
因此,如上分析可知,无论是先注册设备驱动程序还是先注册事件处理方法,都会在对方的链表中根据id进行匹配,如果匹配成功则进行关联然后创建设备节点用于与用户空间的应用程序进行交互。当有设备有输入时会调用函数input_event(),进行上报事件,上报的事件在input核心层中进行判断处理,然后再提交给事件处理方法具体处理。
用户空间的应用程序想要与input输入子系统进行交互,必须先对设备节点使用open操作,随即将对input_fops结构体中的成员open()函数进行回调,即执行input_open_file()函数,具体实现如下:
input_open_file(struct inode *inode, struct file *file)
{
struct input_handler *handler = input_table[iminor(inode) >> 5];//会根据次设备号取出全局数组中的事件处理方法
new_fops = fops_get(handler->fops);//函数得到事件处理方法中的函数操作集合
new_fops->open(inode, file);//间接的打开事件处理方法中的open()函数
}