输入子系统
为了实现按键、触摸屏、鼠标等输入型设备的驱动程序设计,linux推荐使用的方法是input输入子系统。输入型设备都可以利用input接口函数来实现设备驱动。
体系结构
输入子系统由驱动层,**输入子系统核心层(input core)和事件处理层(event handler)三部分组成。**一个输入事件,如鼠标移动,键盘按键按下,通过 Driver -> InputCore -> Event handler -> userspace 的顺序到达用户空间的应用程序。
- 驱动层:
将底层的硬件输入转化为统一事件型式,向输入核心(Input core)汇报。 - 输入核心层:
为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在**/PROC**下产生相应的设备信息。 - 事件处理层:
主要作用是和用户空间交互,我们知道linux在用户空间将所有设备当成文件来处理,在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,而在输入子系统中,这些工作都是由事件处理层完成的。
设备描述
在linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和Eventhandler 最终到达用户空间。
设备注册、注销
- 注册输入设备的函数为:
int input_register_device(struct input_dev *dev)
- 注销输入设备的函数为:
void input_unregister_device(struct input_dev *dev)
驱动实现
事件支持
设备驱动通过 set_bit() 告诉 input 子系统它支持哪些事件,哪些按键。例:
set_bit(EV_KEY,button_dev.evbit)
struct input_dev 中有两个成员:
evbit:事件类型
keybit:按键类型
- 事件类型
EV_RST : Reset
EV_REL: 相对坐标
EV_MSC: 其他
EV_SND: 声音
EV_FF: 力反馈
EV_KEY: 按键
EV_ABS: 绝对坐标
EV_LED: LED
EV_REP: Repeat
当事件为 EV_KEY 时,还需指明按键类型:
BTN_LEFT: 鼠标左键
BTN_RIGHT: 鼠标右键
BTN_MIDDLE: 鼠标中键
BTN_0: 数字0键
BTN_1: 数字1键
报告事件
用于报告 EV_KEY,EV_REL 和 EV_ABS 事件的函数分别为:
- 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)
参数: - code:
事件的代码。如果事件类型是 EV_KEY,则代码为设备的键盘代码。例如键盘上的按键代码值为0~127,鼠标按键代码为:0x110----0x116,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_MODDLE)为鼠标中键,其他代码参考 /include/linux/input.h - value:
事件的值。如果事件的类型是 EV_KEY,当按键按下时值为1,松开值为0
报告结束
input_sync()用于告诉 input core:此次报告已经结束
实例:
在触摸屏设备驱动中,一次点击的整个报告过程如下:
input_report_abs(input_dev,ABS_X,x); //x坐标
input_report_abs(input_dev,ABS_Y,y); //y坐标
input_report_abs(input_dev,ABS_PRESSURE,1); //x坐标
input_sync(input_dev); //同步
实例分析
//在按键中断中报告事件
static void button_interrupt(int irq,void *dummy,struct pt_regs *fp)
{
input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));
input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1));
input_sync(&button_dev);
}
static int __init button_init(void)
{
//申请中断
if(request_irq(BUTTON_IRQ,button_interrupt,0,"button",NULL))
return -EBUSY;
set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件
set_bit(BTN_0,button_dev.keybit); //设置支持两个键
set_bit(BTN_1,button_dev.keybit); //设置支持两个键
input_register_device(&button_dev); //注册input设备
}
应用程序改动
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <linux/input.h>
int main(void)
{
int buttons_fd;
int key_value, i = 0, count;
struct input_event ev_key;
buttons_fd = open("/dev/event0",O_RDWR);
if(buttons_fd < 0)
{
perror("open device buttons");
exit(1);
}
for(;;)
{
count = read(buttons_fd,&ev_key,sizeof(struct input_event));
for(i = 0; i < (int)count/sizeof(struct input_event); i++)
{
if(EV_KEY == ev_key.type)
printf("type:%d,code:%d,value:%d\n",ev_key.type,ev_key.code,ev_key.value);
}
if(EV_SYN == ev_key.type)
printf("syn event\n\n");
}
close(buttons_fd);
return 0;
}