本文通过对WinCE 5.0下的鼠标键盘驱动分析,对WinCE驱动程序设计进行了分析。欢迎大家对不对的地方指出。
写一个驱动程序的第一件事就是读硬件的规范文档。所以首先必须了解硬件才能写好驱动。鼠标和键盘现在已经成为现代PC机上不可缺少的组成部分了。有兴趣可以看下他们的发展历史:
目前现在大概有下面3类键盘:
l USB keyboard 被Macintosh 和IBM兼容机所支持的最新键盘。在WinCE下,这种设备(键盘和鼠标)属于HID设备(Human Interface Device).由于涉及到USB驱动,所以在本文暂时,准备在USB驱动中详细补充(其中的中断处理算法和ps/2基本一致,只是接口上发生变化)。
l IBM/compatible keyboards 现代PC机支持的AT Keyboard 和PS/2 keyboard.。本文重点讲解WinCE 5.0下PS/2键盘驱动。
l ADB keyboards 老式的苹果机上使用,本文不关注。
8042和8048
又有历史来了,一开始,IBM首先使用Intel 8048微控制器作为键盘的编码器,使用Intel 8042微控制器作为键盘控制器。所以呢,现在这个标准都被大家所接受了。基本上键盘设备的控制器就这2种了。
其中,8048是键盘上的编码器,8042是在主机上的键盘控制器(目前,基本上都已经被集成到了芯片组里了)。位置就如下图所示:
8042的工作模式
l 依赖于主板的不同键盘控制器可以工作于两个模式之一AT 兼容模式或PS/2 兼容模式
l 如果主板支持PS/2 鼠标就工作在后一种模式下。在这种情况下8042 的作用是键盘控制器和鼠标控制器
l 键盘控制器根据键盘端口的连线情况自动检测它应该工作在何种模式下
PS/2键盘
l Scan code, Make Codes, Break Codes, and Typematic Repeat
l
0409 是United States 101 keyboard 标准
鼠标
l The standard PS/2 mouse supports the following inputs:
X (right/left) movement, Y (up/down) movement, left button, middle button, and right button.
l The mouse reads these inputs at a regular frequency and updates various counters and flags to reflect movement and button states.
l Use the same protocol as the PS/2 (AT) keyboard.
l PS/2 鼠标和键盘履行一种双向同步串行协议.
l 计算机启动时检测和初始化
微软的代码的路径:C:/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/KEYBD
这里先说下,OEM商如何根据自己的硬件来写自己的鼠标键盘驱动。在D:/WINCE500/PUBLIC/COMMON/OAK/DRIVERS目录下的驱动代码都是微软提供给OEM商的。以EMULATOR 为例,这个OEM商也提供了他们自己写的程序在这里:D:/WINCE500/PLATFORM/EMULATOR/SRC/DRIVERS/KBDMOUSE。这里,微软其实给了OEM商2种选择:1 符合微软标准的硬件,ok,你可以选择使用微软提供的驱动(当然,也可以不用);2 自己做的硬件,当然只能自己写驱动(但可以一部分调用微软提供的库)。
所以,对于EMULATOR的鼠标键盘驱动来说,实际上由下面这些源文件构成:
0409 是United States 101 keyboard 标准
OK.首先,让我们来看看D:/WINCE500/PUBLIC/COMMON/OAK/DRIVERS/KEYBD目录。
这些目录的各自作用是:
l DEVICELAYOUTS: 键盘布局,有几种国际标准。0409是美国101键标准
l DLL: 产生AT扫描码和空的驱动库,这些库在sysgen阶段被连接。
l HIDIOCTL: 没有布局管理器时,使用该驱动
l INPUTLANGS 输入语言,与上面的DEVICELAYOUTS一致
l IST 中断服务线程
l LAYMGR 布局管理器
l NOP 当硬件平台没有键盘控制器时,Stub keyboard PDD
l PS2_8042 PS2_8042标准的键盘控制器驱动
l TEST 一个简单的测试程序
l Keybd 一个默认的键盘驱动注册表项
l Laymgr 一个默认的键盘布局注册表项
一个简单的过程描述:
输入系统(GWES)在启动时装在键盘驱动。首先,从HKEY_LOCAL_MACHINE/Hardware/DeviceMap/KEYBD/Drivername 注册表项获得dll名,如果没有,则用默认的名字:Keybddr.dll。然后就是装载dll, 并且确定函数进入点是否存在。然后输入系统调用函数KeybdDriverInitialize来一次性初始化驱动。在这个函数里,驱动在本地保存了一份输入系统回调函数的副本以及初始化硬件和IST来处理中断。当一个中断信号来的时候,键盘驱动负责把硬件扫描码转换为虚拟键值。然后虚拟键值会再发送给输入系统。输入系统从队列中取出按键事件,然后返回到驱动程序的函数KeybdDriverVKeyToUnicode中。驱动程序根据分析特定的键事件和虚拟键的状态产生相应的字符。输入系统把虚拟键值和字符发送给合适的程序。
Layout Manager
WinCE下的驱动从层次这角度大概可以分2种:monolithic driver 和layered driver。其实2者的区别正如他们字面意义一样:monolithic driver单一驱动,不分层,没有MDD和PDD之分;layered driver具有层次架构,一般都有分为MDD和PDD。这里,鼠标键盘驱动就是layered driver。
这里有一个Layout Manager的概念
l 布局管理器处理扫描码的步骤:
- PDD接受到一个扫描码;
- 扫描码被送到布局管理器;
- 布局管理器依据当前设备的布局和事件将其转换成虚拟键值;
- 布局管理器依据当前设备的布局和事件将重新映射;
- 布局管理器设置自动重复功能,所有的键盘都将共享相同的自动重复设置;
- 布局管理器调用函数keybd_event发送一个或多个事件。
PDD ----Platform Dependent Driver
l PDD是下层的,负责从硬件拿到扫描码(上层的是MDD,负责将扫描码转换成字符).键盘PDD是键盘驱动中与设备相关的一部分代码。键盘PDD包括初始化和电源函数。可以使用公共的ist,也可以包括自己的。当GWES初始化键盘驱动时,它初始化每一个PDD. 每个键盘PDD有一个函数返回关于该PDD的描述和函数指针。当布局管理器初始化这个PDD,键盘驱动传递PDD一个唯一的标示符。有时,多种设备能使用同样的PDD,比如2个独立的PS/2 控制器。
l 每个PDD和布局管理器在同样的DLL里。不可以在运行时加一个PDD(但可以不同的PDD之间切换!)
Driver Code----Kbdmouse.cpp
实现了KEYBD_PDD结构中的2个函数(函数指针)PS2_EMUL_PowerHandler和PS2_EMUL_ToggleLights
入口函数:
PS2_EMUL_Entry()
{
*ppKeybdPdd = &PS28042Pdd; /设定键盘控制器是PS2 8042键盘控制器
v_pp2p = new Ps2Port;
/ We always assume that there is a keyboard.
v_pp2k = new Ps2Keybd; /NEW一个键盘
v_pp2k -> Initialize(v_pp2p) /初始化键盘
v_pp2k -> IsrThreadStart(); /键盘中断处理线程启动
if ( v_pp2p -> bMouseFound() ) /如果有鼠标连着的话
{
v_pp2m = new Ps2Mouse; /NEW一个鼠标
v_pp2m -> Initialize(v_pp2p) /初始化鼠标
v_pp2m -> IsrThreadStart(); /鼠标中断处理线程启动
}
}
Driver Code----ps2mouse.cpp
实现了鼠标中断处理线程,以及获取鼠标数据(x,y,滚轮)的函数。
获取鼠标的绝对位置GetHostMousePosition()
中断服务线程实现:
IsrThreadStart ->Ps2MouseIsrThread->IsrThreadProc
IsrThreadProc()
{
/查注册表,检查中断是否能使系统从休眠中醒来. KernelIoControl
。。。。。。
m_pp2p -> MouseInterruptEnable();
for ( ; ; )
{
wait_for_interrupt:
if(WaitForSingleObject(m_hevInterrupt, (cBytes == 0 ? INFINITE : IN_PACKET_TIMEOUT)) == WAIT_TIMEOUT)
{
/确定鼠标事件和位置,READ_PORT_UCHAR
。。。。。。
goto wait_for_interrupt;
}
}
Driver Code---- Ps2keybd.cpp
函数调用方向:IsrThreadStart ->Ps2KeybdIsrThread->IsrThreadProc
IsrThreadProc()
{
/查注册表,检查中断是否能使系统从休眠中醒来
dwStatus = RegOpenKeyEx();
if (m_pp2p->WillWake()) {
/ Ask the OAL to enable our interrupt to wake the system from suspend.
KernelIoContro();
}
m_pp2p -> KeybdInterruptEnable();
KeybdIstLoop () / KeybdIst.lib
}
Driver Code---- Keybdist.cpp
KeybdIstLoop()
{
wait_for_keybd_interrupt:
if (WaitForSingleObject(pKeybdIst->hevInterrupt, INFINITE) == WAIT_OBJECT_0)
{
....
}
goto wait_for_keybd_interrupt;
ErrorShow……
}
Source file
TARGETNAME=KbdXscXTMsEngUs1
DEFFILE=$(_COMMONOAKROOT)/INC/kbdmouse.def
TARGETTYPE=DYNLINK
DLLENTRY=DllMain
TARGETLIBS= /
$(_COMMONSDKROOT)/lib/$(_CPUINDPATH)/coredll.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/ceddk.lib /
$(_TARGETPLATROOT)/lib/$(_CPUINDPATH)/drvlib.lib
SOURCELIBS=/
$(_TARGETPLATROOT)/lib/$(_CPUINDPATH)/KbdmsCommon.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/KeybdIst.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/PS2_AT_00000409.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/layoutmanager.lib /
$(_TARGETPLATROOT)/lib/$(_CPUINDPATH)/kbdpddlist.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/InputLang_0409.lib /
$(_COMMONOAKROOT)/lib/$(_CPUINDPATH)/numpadrmp.lib