各种不同的输入设备(如按键,鼠标,触摸屏等)都是字符设备。这些设备文件操作接口等基本是通用的,只是他们底部的硬件操作方式有所不同。Linux内核中采用了一种分层的思想,将这些上层相同的东西分类出来单独开发了一个输入子系统(input subsystem)。输入子系统已经完成了字符驱动的文件操作接口,所以编写驱动的核心工作是完成输入系统留出的接口,在很大程度上简化输入设备的驱动开发。
Linux的输入子系统从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。输入子系统的框架如下图所示:
设备驱动层而言,实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。核心层而言为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。事件处理层而言是用户编程的接口(设备节点),并处理驱动层提交的数据处理。用户可通过操作设备文件层的文件来对不同的输入设备进行硬件操作。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler(mousedev.c中定义)来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。
在进行输入设备驱动开发时可参照下面的流程:
1、 我们要向系统申请一个输入设备
struct input_dev *myInputDev;
myInputDev = input_allocate_device();
2、 对申请到的输入设备执行一些初始化操作,其中最主要的是配置输入设备支持的事件类型:
myInputDev ->evbit[0] = BIT_MASK(EV_KEY);
myInputDev ->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
//也可以采用下面的方法
set_bit(EV_KEY, myInputDev evbit);
set_bit(BTN_0, myInputDev.keybit);
其中evbit和keybit成员分别代表设备产生的事件类型和上报的按键值。事件类型和键值得定义在input.h文件中:
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //按键事件
#define EV_REL 0x02 //相对坐标(鼠标)
#define EV_ABS 0x03 //绝对坐标(TP)
#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)
3、 输入设备如按键、TP等,当产生动作时通常是采用中断的方式上报动作的。输入设备的驱动程序也是在中断处理函数中上报输入事件的,所以我们还需为我们的输入设备注册一个中断:
request_irq(BUTTON_IRQ, myInputInterrupt, 0, "button", NULL)
输入设备子系统所提供的上报事件的主要接口函数有:
/*上报指定类型的事件*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
/*上报按键事件*/
void input_report_key(struct input_dev *dev, unsigned int code, int value);
/*上报相对坐标事件*/
void input_report_rel(struct input_dev *dev, unsigned int code, int value);
/*上报绝对坐标事件*/
void input_report_abs(struct input_dev *dev, unsigned int code, int value);
/*报告同步事件*/
void input_sync(struct input_dev *dev);
一个简单的中断处理函数可以像下面这样:
myInputInterrupt (int irq, void *dummy)
{
input_report_key(myInputDev, BTN_0, btnState); /*btnState按键状态0/1*/
input_sync(myInputDev);
return IRQ_HANDLED;
}
input_sync(myInputDev)的意思是在这之前报的多个消息是一个消息组。例如,用户在报完X坐标后,又报一个Y坐标,之后再报一个input_sync。上层就可以知道前面报的两个事件属于一组,会将两者结合起来形成一个(X,Y)的坐标。
4、注册输入设备:
ret = input_register_device(myInputDev);
通过调用input_register_device将我们的输入设备加我系统的输入设备链表中。input_register_device可能会休眠,所以不要在中断或spinlock上下文中调用它。
5、资源回收
当我们使用完申请的系统资源后,一定要记得释放。前面申请了一个输入设备并在内核中进行了注册,我们还申请了一个中断。这些资源的释放工作可以放在模块的清除函数中进行:
input_unregister_device(myInputDev);
free_irq(BUTTON_IRQ, myInputInterrupt);
还有一点要注意的是,如果前面注册输入设备的时候失败了的话,就应该调用下面的接口来释放掉之前申请到的输入设备:
input_free_device(myInputDev);
到此,输入设备驱动开发的主要流程都已介绍完了。Linux的输入子系统还有很多的内容,我也在学习的过程当中,随着后面学习的深入到时候再进一步同大家一起探讨吧!