8.1 Linux的input子系统的驱动框架
说起输入设备,想必大家并不陌生,按键、鼠标、键盘、触摸屏等一系列需要用户“动手”产生信息并且将生成的信息交由处理器完成处理的设备都可以称之为输入设备。而在Linux的内核驱动的实现上也将这些设备进行了专门的分类,即input设备,也叫做输入子系统。对于这些设备,Linux中有其对应的一套驱动模型。
说到这里读者可能产生了疑问,在Linux中不是仅仅将设备分成了字符设备、块设备和网络设备吗?的确是这样,input设备或input子系统和以上3中设备并不是并行的关系,只是将有特定属性的一类设备采用了另一种划分模式而已。那么,读者可能又有疑问了,对于一个字符设备而言,如只需要实现其驱动必须实现结构体file_operations中的接口函数,然后在应用程序中使用open、write、read等函数来调用就可以了,那么对于input子系统而言呢?在这里必须重点指出,input子系统已经完成了字符驱动的文件操作接口,所以编写input类设备驱动的核心工作是完成input系统留出的接口。input子系统的框架如图所示:
由此可以看出,输入子系统由input驱动层(input driver)、input核心层(input core)和事件处理层(event handler)3部分组成。一个输入事件,如触摸屏按下、按键按下、鼠标移动等动作信息传输路劲:input设备驱动(input driver)->input核心层(input core)->事件处理层(event handler)->用户空间(userspace)传递到用户空间的应用程序。
8.2 input驱动层
input驱动的编写一般只需要修改这一层,驱动核心层和事件处理层一般无需作代码修改。具体方法如下:
(1)输入事件驱动层和输入核心层不需要动,只需要编写设备驱动层(input驱动层)
(2)设备驱动层编写的接口和调用模式已定义好,驱动工程师的核心工作量是对具体输入设备硬件的操作和性能调优。
(3)input子系统不算复杂,学习时要注意“标准模式”四个字。
8.3 驱动核心层
虽然input驱动编写一般无需修改这一层的代码但是这里还是作一下介绍。驱动核心层的最关键信息集中在两个文件即input.h和input.c中。打开目录include/linux/input.h可以看到input_dev结构体的定义,这个结构体是input设备在linux内核中的模拟,即里面记录了一个input设备的所有信息。
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet;
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
int rep[REP_CNT];
struct input_mt *mt;
struct input_absinfo *absinfo;
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
int (*open)(struct input_dev *dev);
void (*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 input_handle __rcu *grab;
spinlock_t event_lock;
struct mutex mutex;
unsigned int users;
bool going_away;
struct device dev;
struct list_head h_list;
struct list_head node;
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
bool devres_managed;
};
由于在实际应用中需要对驱动支持的事件进行设置,因此对unsigned long evbit[BITS_TO_LONGS(EV_CNT)];成员进行补充说明。该成员就是用于设置支持事件的,而所支持的事件种类定义如下:
#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 //LED灯事件
#define EV_SND 0x12 //声音事件
#define EV_REP 0x14 //重复按键事件
#define EV_FF 0x15 //受力事件
#define EV_PWR 0x16 //电源事件
#define EV_FF_STATUS 0x17 //受力状态事件
在input.h文件中还能看到input设备的分配、注册、注销函数,函数结构如下:
int __must_check input_register_device(struct input_dev *);
void input_unregister_device(struct input_dev *);
struct input_dev __must_check *devm_input_allocate_device(struct device *);
其它重要函数:
void input_set_abs_params(struct input_dev *dev, unsigned int axis,
int min, int max, int fuzz, int flat);//设个函数是设定相应的位来支持某一类的绝对值坐标的
static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{//发送按键事件时向子系统报告
input_event(dev, EV_KEY, code, !!value);
}
static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value)
{//发送相对坐标事件时向子系统报告
input_event(dev, EV_REL, code, value);
}
static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value)
{//发送绝对坐标事件时向子系统报告
input_event(dev, EV_ABS, code, value);
}
8.4 input子系统事件处理层
虽然input驱动编写一般无需修改这一层的代码但是这里还是作一下介绍。事件处理层主要功能是完成相应的input设备所传递事件的处理功能。对于事件处理层的分析,可以从两个基本结构即input_handler和input_handle开始,然后沿着input字符设备的注册和打开过程一直进入到drivers/input/evdev.c文件中的evdev_fops结构结束。接下来就按照这条主线首先分析两个重要的结构体。input_handler和input_handle结构体在include/linux/input.h头文件可以找到。
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
void (*events)(struct input_handle *handle,
const struct input_value *vals, unsigned int count);
bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
bool (*match)(struct input_handler *handler, struct input_dev *dev);
int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
bool legacy_minors;
int minor;
const char *name;
const struct input_device_id *id_table;
struct list_head h_list;
struct list_head node;
};
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
从名称就可以看出,input_handler是用来处理input设备的一个结构体,每一个input设备在注册时都会遍历input_handler_list链表上的每一个input_handler,去寻找与之对应的那个input_handler;同理,每一个input_handler在注册时,也会去input_dev_list上寻找那个属于它的input_dev。
input_handle并不难理解,它就是input_dev和input_handler的黏合剂,通过input_handle的联系,input_dev和input_handler就能够产生对应关系。
以下将要分析input设备的打开和注册过程用以了解事件处理层的工作过程。在drivers/input/input.c中可以看到input字符设备的注册过程为
static int __init input_init(void)
{
int err;
........
err = class_register(&input_class);
err = input_proc_init(); //注册file_operations
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
........
}
input设备注册中又调用了inptu_fops结构体。从drivers/input/input.c文件中可以看到其定义:
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
};
在注册过程中会完成input_dev和input_handler的匹配过程,在完成匹配后就会调用相应input_handler的connect。以input字符设备为例,就会调用evdev_handler,evdev_handler位于drivers/input/evdev.c中读者可以自行查阅。
注册完input字符设备后,接着看设备是如何打开的。之前已经提到,input子系统已经将file_operations的接口函数完成,但针对不同类型的input设备,对应的file_operations中的实现函数也是不一样的。这个不一样是由evdev_handler中的evdev_fops方法决定,其位于drivers\input\evdev.c。
static const struct file_operations evdev_fops = {
.owner = THIS_MODULE,
.read = evdev_read,
.write = evdev_write,
.poll = evdev_poll,
.open = evdev_open,
.release = evdev_release,
.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = evdev_ioctl_compat,
#endif
.fasync = evdev_fasync,
.flush = evdev_flush,
.llseek = no_llseek,
};
input信息如何传递给GUI的呢?通过事件传递,事件驱动型GUI框架,如QT、VC等。
8.5.input设备应用层编程实践1
8.5.1、确定设备文件名
(1)应用层操作驱动有2条路:/dev目录下的设备文件,/sys目录下的属性文件
(2)input子系统用的/dev目录下的设备文件,具体一般是 /dev/input/eventn
(3)用cat命令来确认某个设备文件名对应哪个具体设备。我在自己的ubuntu中实测的键盘是event1,而鼠标是event3.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>
#define DEVICE_KEY "/dev/input/event1"
#define DEVICE_MOUSE "/dev/input/event3"
int main(void)
{
int fd = -1, ret = -1;
struct input_event ev;
// 第1步:打开设备文件
fd = open(DEVICE_KEY, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
// 第2步:读取一个event事件包
memset(&