============================================
作者:yuanlulu
http://blog.csdn.net/yuanlulu
版权没有,但是转载请保留此段声明
============================================
2.4 mouse_handler的实现
在内核的driver/input/mousedev.c中内核已经实现了一个专门处理鼠标事件的mousedev_handler。这个handler占用的次设备号是32~63。虽然有32个次设备号可用,但是只有前31个可被用户所用,系统保留了最后一个,所以最多连接31个鼠标设备。
mousedev_handler对应dev/input/mouseX(X是数字)和dev/input/mice。其中前者是具体设备的对应节点,mice是所有鼠标共享的,次设备号63就是被mice占用的。每个鼠标设备的事件都可以从mice和各自的设备节点读到。没有特殊原因,用户应该总是打开mice,因为即使没有的鼠标设备也不会打开失败,这对于热拔插的USB鼠标很有用。
2.4.1 支持的设备种类
mousedev_handler支持多种类鼠标设备,它的匹配规则如程序清单 2.7<!--[if gte mso 9]><![endif]-->所示。
程序清单 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程序清单/* ARABIC /s 1 <![endif]-->7<!--[if supportFields]><![endif]--> mousedev_ids
/* driver/input/mousedev.c */
static const struct input_device_id mousedev_ids[] = {
{
.flags= INPUT_DEVICE_ID_MATCH_EVBIT | <!--[if supportFields]> = 1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->
INPUT_DEVICE_ID_MATCH_KEYBIT|
INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit= { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
.keybit= { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
.relbit= { BIT_MASK(REL_X) | BIT_MASK(REL_Y) },
}, /* A mouse like device, at least onebutton,
two relative axes */
{
.flags= INPUT_DEVICE_ID_MATCH_EVBIT | <!--[if supportFields]> = 2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->
INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit= { BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) },
.relbit= { BIT_MASK(REL_WHEEL) },
}, /* A separate scrollwheel */
{
.flags= INPUT_DEVICE_ID_MATCH_EVBIT | <!--[if supportFields]> = 3 /* GB2 <![endif]-->⑶<!--[if supportFields]><![endif]-->
INPUT_DEVICE_ID_MATCH_KEYBIT|
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit= { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
.keybit= { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) },
.absbit= { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
}, /* A tablet like device, at least touchdetection,
two absolute axes */
{
.flags= INPUT_DEVICE_ID_MATCH_EVBIT | <!--[if supportFields]> = 4 /* GB2 <![endif]-->⑷<!--[if supportFields]><![endif]-->
INPUT_DEVICE_ID_MATCH_KEYBIT|
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit = { BIT_MASK(EV_KEY) |BIT_MASK(EV_ABS) },
.keybit= { [BIT_WORD(BTN_TOOL_FINGER)] =
BIT_MASK(BTN_TOOL_FINGER)},
.absbit= { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) |
BIT_MASK(ABS_PRESSURE)|
BIT_MASK(ABS_TOOL_WIDTH)},
}, /* A touchpad */
{
.flags= INPUT_DEVICE_ID_MATCH_EVBIT | <!--[if supportFields]> = 5 /* GB2 <![endif]-->⑸<!--[if supportFields]><![endif]-->
INPUT_DEVICE_ID_MATCH_KEYBIT|
INPUT_DEVICE_ID_MATCH_ABSBIT,
.evbit= { BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS) },
.keybit= { [BIT_WORD(BTN_LEFT)] = BIT_MASK(BTN_LEFT) },
.absbit= { BIT_MASK(ABS_X) | BIT_MASK(ABS_Y) },
}, /* Mouse-like device with absolute X and Ybut ordinary
clicks, like hp ILO2 High Performance mouse*/
{ }, /* Terminating entry */
};
各种设备的特点如下:
<!--[if supportFields]> =1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->支持按键和相对坐标。可以匹配只有相对坐标和按键的设备。
<!--[if supportFields]> =2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->支持按键和滚轮。可以匹配拥有一个滚轮和有若干按键的设备。
<!--[if supportFields]> =3 /* GB2 <![endif]-->⑶<!--[if supportFields]><![endif]-->支持按键和绝对坐标。可以匹配的设备至少支持触按和两个绝对坐标,比如触摸板。
<!--[if supportFields]> =4 /* GB2 <![endif]-->⑷<!--[if supportFields]><![endif]-->支持按键和绝对坐标。可以匹配支持触摸、两个绝对坐标轴、按压和多点触摸的设备。
<!--[if supportFields]> =5 /* GB2 <![endif]-->⑸<!--[if supportFields]><![endif]-->支持按键和绝对坐标。可以匹配支持左键、两个绝对坐标轴的设备。
以上匹配规则只是能够匹配的设备的子集,能够支持几乎所有的类鼠标设备。mousedev_handler将事件分为两大类进行处理,一类是鼠标可以产生的,另一类就触摸板产生的(点击击触摸板和在触摸板上移动)。而从用户空间看来,mousedev_handler的行为就是一只鼠标,无法分辨有没有触摸板。
2.4.2 鼠标的通信协议
用户程序跟dev/input/mice通信就仿佛同一只真正的PS2鼠标通信,读取到的数据包模拟了ps2鼠标的数据格式。当然,mousedev_handler并没有实现所有的功能。
现在的鼠标往往支持不同的通信协议,分别是标准PS2、MicrosoftIntellimouse和Intellimouse Extensions,其中后面两个分别是前一个的扩展。它们的数据包的格式也各不相同。
读取标准PS2鼠标得到的数据格式如<!--[if supportFields]> REF _Ref282440623 /h <![endif]-->图 2.1<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
图 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 图 /* ARABIC /s 1 <![endif]-->1<!--[if supportFields]><![endif]--> 标准PS2数据包格式
由上图可以看出,标准PS2鼠标仅仅支持三个按键和两个方向的坐标,不支持滚轮。MicrosoftIntellimouse加入了对滚轮的支持,数据包变为4个字节。如<!--[if supportFields]> REF _Ref282441142 /h <![endif]-->图 2.2<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
图 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 图 /* ARABIC /s 1 <![endif]-->2<!--[if supportFields]><![endif]--> Microsoft Intellimouse数据包格式
但是现在的鼠标上往往有一些其他的按键可用,为了进一步拓展鼠标的功能,又发展出了Intellimouse Extensions。如<!--[if supportFields]> REF _Ref282441517 /h <![endif]-->图 2.3<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
图 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 图 /* ARABIC /s 1 <![endif]-->3<!--[if supportFields]><![endif]--> Intellimouse Extensions数据包格式
Intellimouse Extensions可以支持的鼠标最多允许多达五个按键。
2.4.3 mousedev_handler初始化
mousedev_handler的初始化非常简单,如程序清单 2.8<!--[if gte mso 9]><![endif]-->所示。
程序清单 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程序清单 /* ARABIC /s 1 <![endif]-->8<!--[if supportFields]><![endif]--> mousedev_handler初始化函数
/* driver/input/mousedev.c */
static int __init mousedev_init(void)
{
interror;
mousedev_mix= mousedev_create(NULL, &mousedev_handler, MOUSEDEV_MIX); <!--[if supportFields]> = 1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->
if(IS_ERR(mousedev_mix))
returnPTR_ERR(mousedev_mix);
error =input_register_handler(&mousedev_handler); <!--[if supportFields]> = 2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->
if(error) {
mousedev_destroy(mousedev_mix);
returnerror;
}
printk(KERN_INFO"mice: PS/2 mouse device common for all mice/n");
return0;
}
这个函数做了两件事情:
<!--[if supportFields]> =1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->首先创建一个mousedev结构体并用全局指针mousedev_mix指向它。mousedev_create第一个参数为“NULL”意味着这个mousedev不和任何input_dev相关,它对应我们上文提到的/dev/input/mice。
<!--[if supportFields]> =2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->将mousedev_handler注册进输入子系统。
2.4.4 用户打开鼠标设备
用户空间可以打开鼠标设备的设备节点。
打开设备节点时,首先创建一个mousedev_client并添加到对应的mousedev的链表当中。mousedev_client中包含一个大小为16的环形fifo缓冲,缓冲可以存储16个同步时传入的数据包(这个数据包的格式不是PS2数据包,只能被mousedev识别),每个数据包只能被读一次,未能及时读取的数据包将被直接覆盖并不会有任何提示。同时mousedev_client还包含一个大小为6字节的数据缓冲ps2[6],每次用户读取数据时,最旧的数据包被组织成合适的格式(参考<!--[if supportFields]> REF _Ref282450235 /r/h <![endif]-->2.4.2<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->节的内容)存储在ps2中传给用户。
每个设备可以被多个线程同时打开,每个线程都能得到相同的数据。对同一个设备而言,每打开一次就会新建一个client。
2.4.5 用户写入PS2命令
鼠标的设备节点mouseX和mice支持用户空间写入符合PS2标准的命令。利用这些命令可以实现对鼠标的控制和信息的读取。
打开鼠标设备之后使用标准PS2协议,打开同一设备的不同线程之间的协议互不影响。如果想使用MicrosoftIntellimouse协议进行通信,需要连续写入{0xf3, 200, 0xf3, 100, 0xf3, 80 }这几个字节,任何时候写入都可以;同样写入{ 0xf3, 200, 0xf3, 200, 0xf3, 80 }将会使用Intellimouse Extensions协议。用户读取的鼠标移动和按键的信息是通过以上三种协议的数据包格式传给用户空间的。
下面要介绍的命令,会首先填充1字节的0xfa(ACK)到ps2[]头部,接下来才存放有意义的数据到ps2[]。
写入0xeb将会使对应的mousedev_client的一个数据包的数据转化为对应的格式存储在该client的ps2[]中,第一个字节是0xfa(ACK)。
写入0xf2将会把当前协议对应的ID存储在ps2[1]当中,标准PS2对应0,Microsoft Intellimouse协议对应0x3,IntellimouseExtensions协议对应0x4。
写入0xe9将会紧挨着0xfa(ACK)存放三个字节的内容,包含缩放比例、分辨率、采样速率等信息(具体请参考PS2协议)。
写入0xff将会复位client的设置,返回标准PS2的通信模式。
上面介绍的命令的执行结果放在对应的client的缓冲之中,写入命令之后调用read函数就可以返回结果。需要强调的是如果写入上面没有提到的命令或无意义的数据,则仅仅能够读到ACK字节,用户无法通过往mouseX或者mice写入数据模拟鼠标移动,但是可以通过往鼠标设备对应的eventX节点中写入数据模拟鼠标移动。
2.4.6 用户读取鼠标设备的数据
用户空间打开一个鼠标设备节点之后,可以调用read函数读取数据。如果是非阻塞调用,没有数据时会阻塞,否则立即返回。读取一次,最多能够读取的数据长度是client中ps2[]拥有的有效数据长度,短于有效数据长度将会只读取前面的部分,剩下的数据作废。
如果之前写入了命令,那么紧接着调用read函数将会读取执行的结果,其中第一字节是0xfa(ACK)。如果之前没有写入命令,就会将client中现有的最旧的数据包转化为当前协议的格式返回给用户。读取长度长于一个数据包则只返回一个数据包的长度,短于数据包的长度则只返回靠前的部分。如果没有可读的数据包,则阻塞或者直接返回。
2.4.7 建立连接
当一个鼠标类input_dev和mousedev_handler匹配之后会建立连接,连接的过程会创建一个mousedev结构体,其中的handle成员连接input_dev和mousedev_handler。
2.4.8 消息处理
mousedev_handler处理消息的函数如所示。
程序清单<!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程序清单 /* ARABIC /s 1 <![endif]-->9<!--[if supportFields]><![endif]--> mousedev_event
static void mousedev_event(struct input_handle*handle,
unsigned int type, unsigned int code, intvalue)
{
structmousedev *mousedev = handle->private;
switch(type) {
caseEV_ABS:
/*Ignore joysticks */
if(test_bit(BTN_TRIGGER, handle->dev->keybit))
return;
if(test_bit(BTN_TOOL_FINGER, handle->dev->keybit))
mousedev_touchpad_event(handle->dev, <!--[if supportFields]> = 1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->
mousedev,code, value);
else
mousedev_abs_event(handle->dev,mousedev, code, value); <!--[if supportFields]> = 2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->
break;
caseEV_REL:
mousedev_rel_event(mousedev,code, value); <!--[if supportFields]> = 3 /* GB2 <![endif]-->⑶<!--[if supportFields]><![endif]-->
break;
caseEV_KEY:
if(value != 2) {
if(code == BTN_TOUCH &&
test_bit(BTN_TOOL_FINGER,handle->dev->keybit))
mousedev_touchpad_touch(mousedev,value); <!--[if supportFields]> = 4 /* GB2 <![endif]-->⑷<!--[if supportFields]><![endif]-->
else
mousedev_key_event(mousedev,code, value); <!--[if supportFields]> = 5 /* GB2 <![endif]-->⑸<!--[if supportFields]><![endif]-->
}
break;
caseEV_SYN:
if(code == SYN_REPORT) {
if(mousedev->touch) {
mousedev->pkt_count++;
fx(0)= fx(1);
fy(0)= fy(1);
}
mousedev_notify_readers(mousedev,&mousedev->packet); <!--[if supportFields]> = 6 /* GB2 <![endif]-->⑹<!--[if supportFields]><![endif]-->
mousedev_notify_readers(mousedev_mix,&mousedev->packet); <!--[if supportFields]> = 7 /* GB2 <![endif]-->⑺<!--[if supportFields]><![endif]-->
mousedev->packet.dx= mousedev->packet.dy = mousedev->packet.dz = 0;
mousedev->packet.abs_event= 0;
}
break;
}
}
各句的解释如下:
<!--[if supportFields]> =1 /* GB2 <![endif]-->⑴<!--[if supportFields]><![endif]-->如果是触摸板类设备,则调用mousedev_touchpad_event。
<!--[if supportFields]> =2 /* GB2 <![endif]-->⑵<!--[if supportFields]><![endif]-->否则调用mousedev_abs_event作为一般的绝对坐标轴事件。
<!--[if supportFields]> =3 /* GB2 <![endif]-->⑶<!--[if supportFields]><![endif]-->处理相对坐标轴事件。
<!--[if supportFields]> =4 /* GB2 <![endif]-->⑷<!--[if supportFields]><![endif]-->如果是触摸板类设备,则作为触摸板的点击处理,调用mousedev_touchpad_touch。
<!--[if supportFields]> =5 /* GB2 <![endif]-->⑸<!--[if supportFields]><![endif]-->否则mousedev_key_event作为一般鼠标的按键处理。
<!--[if supportFields]> =6 /* GB2 <![endif]-->⑹<!--[if supportFields]><![endif]-->接收到同步事件则将同步消息发送给对应的mousedev,将目前的设备状态(按键和坐标的状态)打包传入每个client的缓冲区。
<!--[if supportFields]> =7 /* GB2 <![endif]-->⑺<!--[if supportFields]><![endif]-->无论哪个设备同步,同时将当前设备的鼠标状态同步给mousedev_mix,这样所有打开/dev/input/mice 的线程就能收到数据了。
这里我们看到在鼠标类设备中同步事件的重要性。如果一直不进行同步的话,用户空间就会一直读不到有效的数据。每同步一次,只要自上次同步以来有了相对位置的改变或者有按键和点击触摸板的事件发生就会生成一个新的数据包放在client的缓冲当中。
之所以要区分触摸设备和非触摸设备,是因为触摸类设备发送的是绝对坐标,需要将每次的绝对坐标和之前的状态比较转换为相对坐标的值。而鼠标发送的绝对事件会被当做是对光标的重定位,直接把光标移到相应的位置。
2.4.9 设备驱动应该如何编写
上面已经基本分析了mousedev_handler的实现。下面总结如何编写鼠标类设备的设备驱动。
1. 鼠标设备驱动的编写
这里假设我们有一个光电鼠标,除了左键、右键、中键还有两个扩展键。支持持滚轮朝两个方向滑动。假设我们已经定义了一个struct input_dev变量mice_dev,其他的初始化同<!--[if supportFields]> REF _Ref282006937 /r/h <![endif]-->1.2<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->节的内容类似,下面只写不同的部分。如所<!--[if supportFields]> REF _Ref282503946 /h <![endif]-->程序清单 2.10<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->示。
程序清单 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程序清单 /* ARABIC /s 1 <![endif]-->10<!--[if supportFields]><![endif]--> 鼠标设备驱动实例
static unsigned int mice_keycode[] = {BTN_LEFT, BTN_RIGHT,BTN_MIDDLE, BTN_SIDE, BTN_EXTRA};
mice_dev.keycode = mice_keycode;
mice_dev.keycodesize = sizeof(unsigned int);
mice_dev.keycodemax = ARRAY_SIZE(mice_keycode);
set_bit(EV_KEY, mice_dev.evbit); /*使设备支持按键事件 */
set_bit(EV_REL, mice_dev.evbit); /*使设备支持相对坐标事件 */
set_bit(BTN_LEFT, mice_dev.keybit); /*依次使设备支持五个按键 */
set_bit(BTN_RIGHT, mice_dev.keybit);
set_bit(BTN_MIDDLE, mice_dev.keybit);
set_bit(BTN_SIDE, mice_dev.keybit);
set_bit(BTN_EXTRA, mice_dev.keybit);
set_bit(REL_X, mice_dev.relbit); /*支持X轴相对坐标 */
set_bit(REL_Y, mice_dev.relbit); /*支持Y轴相对坐标 */
set_bit(REL_WHEEL, mice_dev.relbit); /*支持滚轮的相对坐标 */
报告事件发生的时候调用input_report_key或者input_report_rel函数。其它的注意事项参考1.2<!--[if gte mso 9]><![endif]-->节。每次报告了事件之后都要及时调用input_sync进行同步。
2. 触摸板设备驱动的编写
现在我们有一个触摸板,点触之后会感应到点触和点触的位置。能够计算出点击的绝对坐标。下面仅仅示范需要特殊注意的地方。同样假设已经声明了一个struct input_dev变量tp_dev,初始化过程如<!--[if supportFields]> REF _Ref282505717 /h <![endif]-->程序清单 2.11<!--[if gte mso 9]><![endif]--><!--[if supportFields]><![endif]-->所示。
程序清单 <!--[if supportFields]> STYLEREF 1 /s <![endif]-->2<!--[if supportFields]><![endif]-->.<!--[if supportFields]> SEQ 程序清单 /* ARABIC /s 1 <![endif]-->11<!--[if supportFields]><![endif]--> 触摸板设备驱动
unsigned int tp_keycode[] = {BTN_TOUCH, BTN_TOOL_FINGER};
tp_dev.keycode = tp_keycode;
tp_dev.keysize = sizeof(unsigned int);
tp_dev.keymax = ARRAY_SIZE(tp_keycode);
set_bit(EV_KEY, mice_dev.evbit); /*使设备支持按键事件 */
set_bit(EV_ABS, mice_dev.evbit); /*使设备支持绝对坐标事件 */
set_bit(BTN_TOUCH, tp_dev.keybit); /*使设备支持两个按键 */
set_bit(BTN_TOOL_FINGER, tp_dev.keybit); /*这个设置仅仅表明输入设备种类*/
input_set_abs_params(&tp_dev, ABS_X, 0, 100, 4, 0); /* 设置坐标轴的信息 */
input_set_abs_params(&tp_dev, ABS_Y, 0, 100, 4, 0);
设置坐标轴的信息的用法可以参考1.3.2<!--[if gte mso 9]><![endif]-->节。
点击的事件应该用input_report_key( &tp_dev, BTN_TOUCH, 1)的形式。并不需要报告BTN_TOOL_FINGER按键,事实上它不对应任何按键,仅仅让handler确定是什么类别的输入工具。BTN_TOOL_FINGER代表“手指(触摸输入)”,相应的还有BTN_TOOL_PEN等其它类别的输入设备。目前mousedev_handler仅仅支持BTN_TOOL_FINGER。