********************************LoongEmbedded********************************
作者:LoongEmbedded(kandi)
时间:2011.12.19
类别:WINCE驱动开发
********************************LoongEmbedded********************************
1. keyboard驱动概述
1.1 键盘驱动功能概述
Keyboard驱动的主要的功能是从keyboard硬件输入转换为keyboard事件并且发送给GWES,根据这些的keyboard事件产生合适的Unicode字符,keyboard驱动的架构图1所示
图1 keyboard驱动的架构
如图1所示,Keyboard驱动分为layout manager(布局管理器)、current input language和device layouts(设备布局),其中WINCE默认支持的键盘(设备)布局有PS/2(Personal System 2)和Matrix(矩阵式)键盘布局,PS/2键盘布局是针对PS/2 8042-compatible keyboard controller设计的,Matrix键盘布局是针对矩阵式键盘来设计的,比如S3C6410内置了keypad接口用于外接矩阵式键盘。这样的设计思想便于开发任何的键盘布局,如果有不同于上面这两种布局,就需要自己添加了。键盘布局是指特定键盘的按键安排(key
arrangement),例如包括按键数量和按键的配置(比如不同的键盘布局,同一个按键会对应不同的功能)。一些私有的键盘使用自己的布局,并且很多键盘允许用户根据个人的偏爱来建立按键到字符的映射。
一些键盘驱动必须处理一些按键产生的多个按键(virtual keys,可以理解为虚拟码),可以不需要像桌面工作机一样把所有的物理按键都布置在键盘上,这对于小型硬件平台很有用。所以一些按键就有多种或、和可以修改的功能。键盘驱动根据特定物理按键和修饰按键(modifier key,比如SHIFT和ALT)的状态来产生虚拟按键。
通常,我们需要把键盘驱动作为一个分层驱动来实现,分为上层或是MDD层,这层映射扫描码成虚拟键码和产生于虚拟键码关联的字符数据,接着打包键盘消息并放入到系统范围(system-wide)的消息队列中,这样GWES就可以从消息队列中取出并且分发给相应的应用程序来处理和显示。另一层是低层或是PDD层,负责从键盘获取扫描码。
因为键盘驱动依赖于语言,扫描码映到虚拟键码和虚拟键码到unicode字符的映射都只依赖于语言的键盘布局,故不同于其他设备驱动。PFN_KEYBD_DRIVER_VKEY_TO_UNICODE指针类型函KeybdDriverVKeyToUnicode数根据虚拟键的状态来产生正确的unicode字符,此函数只依赖于指定语言的键盘布局(the keyboard layout for the language),这两种转换基于转换表。并且如果有必要,我们可以创建自己的键盘映射或者基于已经存在的键盘映射来定制。
1.2 键盘驱动的加载及初始化
在系统启动的过程中GWES加载键盘驱动,当GWES开始后,它从注册表键HKEY_LOCAL_MACHINEHardwareDeviceMapKEYBDDrivername下的子键“Drivername”中取得键盘驱动的dll名字,我这里是smdk6410_keypad.dll,但如果在此入口下没有找到dll,GWES就采用默认的Keybddr.dll。接着就加载此dll并 核实所有需要的入口函数是否存在,比如对于smdk6410_keypad.dll,会核实此dll对应的def文件中导出的接口函数是否存在,这些导出函数如图2所示
图2 smdk6410_keypad.dll的导出接口函数
GWES然后调用PFN_KEYBD_DRIVER_INITIALIZE函数指针类型的KeybdDriverInitializeEx函数初始化layout管理器和键盘驱动支持的PDD,并且传递键盘事件回调函数进来。在此函数中,键盘驱动在本地保存一份GWES的键盘事件回调函数的副本以及初始化硬件和IST来处理中断。当按键中断产生的时候,键盘驱动负责转换硬件扫描码为虚拟键码,并且通过键盘事件回调函数KeybdEventCallback或者keybd_event函数来传递扫描码和虚拟键码给GWES。后来,GWES从队列中取出键盘事件并回调PFN_KEYBD_DRIVER_VKEY_TO_UNICODE指针函数类型的KeybdDriverVKeyToUnicode函数来分析指定的按键事件和把虚拟键码映射为相应的unicode字符,然后GWES发送虚拟键码和字符给相应的应用。另外,为了支持可听见的稽声(audible
key click,我的理解是可听见的按键延时声),键盘驱动必须为虚拟键码增加KEYBD_DEVICE_SILENT or KEYBD_DEVICE_SILENT_REPEAT或者传递KEYEVENTF_SILENT给keybd_event函数。
1.3 获取和设置键盘的信息
GWES通过键盘驱动PFN_KEYBD_DRIVER_GET_INFO函数指针类型的KeybdDriverGetInfo函数和PFN_KEYBD_DRIVER_SET_MODE函数指针类型的KeybdDriverSetMode函数来获取和设置有关键盘的信息。当主输入线程(main input thread)处理KeybdEventCallback函数传递回来的关联键盘的事件的时候,此线程调用KeybdDriverGetInfo函数来获取键盘驱动支持的虚拟键码到unicode数据的转换,也为键盘驱动所用的虚拟码状态数据和任何附加的数据分配所需要的内存。
1.4 键盘驱动必需和可选的功能
当键盘的任何按键有效按下时,键盘驱动必须调用键盘事件回调函数KeybdEventCallback或者keybd_event函数来通知GWES,下面列举的键盘驱动可选的功能:
1) 更新键盘驱动来配合layout管理器一起工作。
2) 实现MapVirtualKey(uCode,0)支持把虚拟键码映射为XT扫描码来使能远程桌面协议RDP(Remote Desktop Protocol)。
3) 实现MapVirtualKey(uCode,3)支持把AT扫描码映射为虚拟键码来使能USB键盘。
4) 使能USB键盘LED。
1.5
2. layout管理器
2.1 layout管理器的功能
layout管理器管理设备布局和输入语言这两部分,下面是layout管理器处理扫描码的步骤:
1) PDD接收到一个扫描码。
2) PDD发送扫描码给layout管理器。
3) Layout管理器根据键盘来调用ScanCodeToVKey函数转换扫描码为虚拟键码,并发送对应的事件和当前的设备布局。
4) Layout管理器根据键盘重新映射扫描码,并发送对应的事件和当前的设备布局。
5) Layout管理器处理自动重复性的功能(the auto-repeat functionality),所有的键盘共享相同的自动重复功能。
6) Layout管理器调用keybd_event函数发送单个或者多个事件。
在PDD获取到指定事件并且layout管理器在处理此事件的时候,如果输入区域(input locale)发生改变,键盘事件被映射为新设备布局。当layout管理器收到一个转换虚拟键码为unicode数据的请求,它使用仅限于当前输入语言的辅助状态和辅助表 (modifier state and tables),在这之前,layout管理器执行ALT+数字小键盘逻辑(numeric keypad logic),可通过同时按下ALT键和键入数字键区上的符号数字值来产生随意的unicode字符。
Layout管理器保存每个PDD的信息在一个通过键盘标识符来索引的数组中,此数组在SMDK6410SRCDRIVERSKEYBDPddlist下面定义,如下所示
PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[] = {
PS2_NOP_Entry,
Matrix_Entry,
NULL
};
而键盘标识符是在layout管理器在初始化PDD的时候为每个PDD指定的,layout管理器保存一系列的入口点、有效的设备布局和每个PDD当前的设备布局。上面的数组表示此键盘驱动支持两种设备布局,分别为PS2和矩阵式键盘。
当输入区域(input locale)改变的时候,layout管理器改变每个PDD的设备布局为匹配新输入区域的设备布局。它也转变之前的输入语言为当前输入区域的低位字描述的输入语言,这里的低位字是指language identifier。
Layout管理器支持多个键盘布局,可在运行时切换键盘布局、打包多个设备布局和输入语言到运行时镜像中(run-time image),也可在运行时增加新的键盘布局。比如,在运行时,我们可以从德语键盘布局切换到荷兰语键盘布局。
2.2 Layout管理器定义的专门术语
1) Device layout
设备布局,描述硬件具体信息,包含扫描码到虚拟键码的转换和虚拟键码的重新映射功能。
2) Input language
输入语言,主要实现虚拟键码到unicode字符的通用性(generic mapping)映射,这其中考虑了SHIFT按键的虚拟键码到unicode字符的映射,同一个键盘,对于中文版的windows xp操作系统,按下组合键SHIFT+4在联想的主机上输出符号是¥,而英文版的windows xp操作系统输出$,这是因为中文版的input lanugae部分修改通过修改虚拟键码到unicode字符的映射表来完成这个不同的显示的。这部分也包含虚拟键码到扫描码的映射(见1.4节的描述),因为此功能是为支持RDP而用于映射为XT扫描码服务的。
输入语言部分和设备布局要相互对应起来(The input language and the device layout correspond to one another),每个键盘类型,例如一个PS/2键盘或矩阵键盘,有一个当前设备布局匹配全局共享的当前输入语言。
3) Input locale
输入区域,解析带有输入法的输入语言(Pairing of an input language with an input method),比如对于标准美国101个按键的键盘的输入区域识别码(input locale identifier)是00000409,其中低位字0409是语言识别码(language identifier),表示美式英语,高位字0000是类型识别符(type identifier),这里是指键盘类型,比如有QWERTY、DVORAK、MALT键盘。另外Dvorak键盘的输入区域识别码是00010409。
一个输入区域和输入区域句柄不同(An input locale and an input locale handle differ),一旦输入区域被装载,layout管理器产生一个可被用作键盘API的句柄给输入区域(HKL)。
3. input language
Input Language的入口函数名字是:IL_{layout序号},见图2。不像设备布局,输入语言
和具体的硬件无关,InputLang.h描述辅助键和虚拟键码转换为unicode字符的复杂关系(complex relationship),layout管理器映射当前辅助键(例如SHIFT、CTRL按键等等)为虚拟键码的一个索引,并把此索引传递到unicode表中,接着,layout管理器搜索适用于虚拟键码的unicode表,并使用辅助键的索引来确定它的unicode值。
typedef struct _INPUT_LANGUAGE {
DWORD dwSize;
DWORD dwType;
DWORD dwSubType;
// Modifier keys
const MODIFIERS *pCharModifiers;
// Optional shift key table
const VK_TO_SHIFT *pVkToShiftState;
// Optional toggle key table
const VK_TO_SHIFT *pVkToToggledState;
// Virtual key to Unicode
const VK_TO_WCHAR_TABLE *pVkToWcharTable;//ptr to tbl of ptrs to tbl
// Dead keys
const DEADKEY *pDeadKey;
// Virtual key to XT scan code
const VKEY_TO_SCANCODE *pVkToScanCodeTable;
// Locale-specific special processing
DWORD fLocaleFlags;
// Ligatures
BYTE nLgMax; // Maximum ligature table characters
BYTE cbLgEntry; // Count of bytes in each ligature table row
const LIGATURE1 *pLigature; // Pointer to ligature table
BYTE bcFnKeys;
} INPUT_LANGUAGE, *PINPUT_LANGUAGE;
4. device layout
设备布局是和具体硬件相关的键盘信息,它包含扫描码到虚拟键码的转换和虚拟键码的重新映射,例如,korean(韩语,朝鲜语)PS/2键盘比korean矩阵键盘有更多的设备布局。
因为USB人机界面设备(HID)键盘驱动转换USB键盘扫描码为AT扫描码,所以没有HID设备布局。HID驱动通过关联了PDD层的设备布局,并结合扫描码调用MapVirtualKey函数来获取相应的虚拟键码,这一连串动作只需要在设备布局中执行,以便扫描码到虚拟键码转换的定位。接着HID键盘驱动用虚拟键码和扫描码来调用keybd_event函数。
多种设备布局可以和一种输入语言一致,比如,标准美国101键键盘的设备布局和一个美国Dvorak键盘的设备布局都使用美式英语作为输入语言。
布局管理器执行扫描码到虚拟键码的映射,DeviceLayout.h文件描述了相关的数据结果,布局管理器在扫描码转换为虚拟键码后重新映射。微软提供了一个数字键盘映射库来静态链接到设备布局的重新映射电路,这样我们可以不需要开发数字键盘共有的代码。数字键盘库在NUMLOCK关闭的时候,执行比如是VK_NUMPADD0到VK_INSERT的映射。
WINCE默认支持美国英语、日本、韩语和朝鲜语的键盘布局,如果要支持其他语言的键盘布局,我们必须从windows xp布局的DLL中获取语言,并且创建一个对应的新键盘布局,比如我们要获取russian(俄语)的键盘布局和输入语言的源代码文件及注册表信息,首先通过PC机上的windows xp操作系统下面的注册表信息
[HKEY_LOCAL_MACHINESYSTEMControlSet001ControlKeyboard Layouts]来获取russian
的dll名称及输入区域识别码(input locale identifier),见下图:
图3 当前windows xp系统的键盘布局
然后可以使用键盘布局产生工具kbdgen.exe,在PB中输入下面的内容,输出如下:
图4 kbdgen工具产生俄语的相关转换表
另外在PB工程的release目录下生成输入语言源文件russianIL.cpp、设备布局源文件russianDL.cpp和这个布局有可能用到的注册表样例入口文件russian.reg。对于接下来的启动动作参考help文档。
设备布局的数据结构体如下
// Remapping function typedefs
typedef UINT (*PFN_KEYBD_REMAP)(
const KEYBD_EVENT *pKbdEvents,
UINT cKbdEvents,
KEYBD_EVENT *pRmpKbdEvents,
UINT cMaxRmpKbdEvents
);
typedef struct tagDEVICE_LAYOUT {
DWORD dwSize;
WORD wPddMask; // Matches the device layout with its PDD
// Scan code to virtual key
ScanCodeToVKeyData **rgpscvk;
UINT cpscvk;
// Remapping functions
PFN_KEYBD_REMAP pfnRemapKey;
} DEVICE_LAYOUT, *PDEVICE_LAYOUT;
typedef BOOL (*PFN_DEVICE_LAYOUT_ENTRY)(PDEVICE_LAYOUT pDeviceLayout);
这里需要注意的是,布局管理器通过支持的设备布局中根据PDD数据结构体成员wPddMask来找到匹配的设备布局。wPddMask成员是可以correlate一个设备布局到多个PDD类型,比如一个AT扫描码设备布局可以correlate为PS/2 PDD和一个stub PDD。
5. 键盘驱动的工作流程
5.1 初始化流程
根据1.2节的描述可知,GWES加载键盘驱动的时候,调用laymgr.cpp下的KeybdDriverInitializeEx函数初始化layout管理器和键盘驱动支持的PDD,下面就来学习这个函数。
5.1.1 创建化临界区、事件和线程
初始化访问input locale信息和发送事件回调函数的临界区对象、创建按键事件发送开始和处理结束的事件、创建按键事件处理线程及按键通知线程。
图5 创建化临界区、事件和线程
5.1.2 初始化PDD数据结构、为PDD链表分配空间和初始化每个PDD
图6 为PDD分配内存空间和调用PDD入口函数
1) 获取支持的PDD个数,也即layout manager支持的device layout个数。
PDD的入口是全局数组g_rgpfnPddEntries,在PDD中定义,MDD中使用,其定义及初始化如下:
PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[] = {
PS2_NOP_Entry,//在COMMONOAKDRIVERSKEYBDNOPPDD
opkb.cpp下定义
Matrix_Entry,//在PDD中定义
NULL
};
由此可知此键盘驱动支持的PDD为两个,我们接下来是针对矩阵键盘来学习的,所以后面重点学习Matrix_Entry这个PDD,先来看PFN_KEYBD_PDD_ENTRY的定义,如下:
typedef BOOL (*PFN_KEYBD_PDD_ENTRY)(UINT uiPddId,
PFN_KEYBD_EVENT pfnKeybdEvent, PKEYBD_PDD *ppKeybdPdd);
可知PFN_KEYBD_PDD_ENTRY为一个函数指针,下面来看其参数及相应数据结构。
⑴uiPddId
uiPddId表示PDD的索引值,可以知道Matrix_Entry对应的索引值为1。
⑵ pfnKeybdEvent
pfnKeybdEvent是指向PFN_KEYBD_EVENT函数指针的指针,根据后面的代码可知此参数正是传递KeybdEventCallback给PDD层的全局函数指针v_pfnKeybdEvent的,此指针类型定义如下:
typedef void (*PFN_KEYBD_EVENT)(UINT uiPddId, UINT32 uiScanCode,
BOOL fKeyDown);
此函数指针指向的函数的第一个参数表示PDP的索引值,第二个参数表示键盘的扫描码,第三个参数表示按键的状态,按下或松开。
⑶ ppKeybdPdd
ppKeybdPdd是一个指向KEYBD_PDD结构体类型的指针,此结构体定义如下:
typedef struct tagKEYBD_PDD {
WORD wPddMask; // Matches the keyboard layout with its PDD
LPCTSTR pszName; // Used to identify PDD to user
PFN_KEYBD_PDD_POWER_HANDLER pfnPowerHandler;
PFN_KEYBD_PDD_TOGGLE_LIGHTS pfnToggleLights;
} KEYBD_PDD, *PKEYBD_PDD;
wPddMask
layout管理器通过此成员来从自身支持的多个键盘布局中匹配出对应的键盘布局。
pszName
为用户标示PDD的显示名称,需要验证,比如下图所示:
图7 PDD显示给用户的标识符
pfnPowerHandler
指向PFN_KEYBD_PDD_POWER_HANDLER函数指针类型的指针,最终会传递PDD层的Matrix_PowerHandler函数来实现。
pfnToggleLights
指向PFN_KEYBD_PDD_TOGGLE_LIGHTS函数指针类型的指针,最终会传递PDD层的Matrix_ToggleLights函数来实现。
结合g_rgpfnPddEntries的初始化,可知
2) layout管理器从堆中为其支持的每个PDD分配和结构体KEYBD_PDD_INFO大小一样的空间。
3) 初始化每个PDD相关的函数指针或者结构体指针
图8 矩阵键盘MDD调用PDD的入口函数
结合图6和图8,可知在第二个for循环的时候,会调用到矩阵键盘PDD的入口函数Matrix_Entry,并且把此PDD的ID和按键事件回调函数KeybdEventCallback传递到PDD中,而由PDD的Matrix_Entry来初始化第三个输出参数为MatrixPdd,这样可使MDD中的全局变量g_pPdds的成员指针pKeybdPdd指向MatrixPdd,下面我们来看Matrix_Entry函数。
图9 Matrix_Entry函数
4) 指示当前的PDD是否有效,也即layout manager是否支持此PDD。
5.1.3 获取默认的input method并调用input language的入口函数
图10获取默认的input method并调用input language的入口函数
1) 获取默认的input method
KeybdDriverInitializeEx函数接着调用GetDefaultInputMethodName的函数体,下面来看此函数的函数体。
图11 获取默认的input method
2) 获取上面中输入法对应的input language,并且调用其入口函数,比如IL_00000409。
下面来看SetInputLanguage函数如何根据GetDefaultInputMethodName函数获取的默认输入法的名字,比如“e0010804”来获取对应的input language的,下面是SetInputLanguage的函数体,依次介绍
1) 获取输入法对应的input language的入口函数。
图12 SetInputLanguage函数体第一部分
2) 调用input lanuage的入口函数来获取此input language的全局变量InputLanguage的值,并验证是否有效。
图13 SetInputLanguage函数体第二部分
在此我们来看一下描述input language的全局变量InputLanguage及其类型如下:
图14 全局变量InputLanguage及其类型
3) 初始化描述input language信息的iliNew,并赋值给g_ili,这样layout manager就能获取到inputlanguage的信息了。
图15 SetInputLanguage函数体第三部分
⑴GenerateModifierToShiftTable函数
图16 GenerateModifierToShiftTable函数体
结合图17可更好理解此函数的作用
图17 g_rgVkToShiftState等全局数组的内容
5.1.4 初始化每个PDD的device layout
图18 初始化device layout
下面学习SetDeviceLayout函数体,此函数根据传递进来的hkl来设置特定PDD的device layout。
1) SetDeviceLayout函数体第一部分
图19 SetDeviceLayout函数体第一部分
下面介绍此部分调用的一些函数
⑴SelectDeviceLayout函数
图20 SelectDeviceLayout函数体
图21 SelectDeviceLayout函数通过此注册表信息获取device layout的入口
这样比如可以知道szValueName指向"PS2_AT",szValueData指向"smdk6410_keypad.dll",下面看DeviceLayoutMatchesPDD函数的实现部分:
图22 DeviceLayoutMatchesPDD函数体
上面会调用到PDD的Matrix(),此函数会把描述device layout信息的静态全局变量dlMatrixEngUs传递给MDD,内容如下:
static DEVICE_LAYOUT dlMatrixEngUs =
{
sizeof(DEVICE_LAYOUT),
MATRIX_PDD,
rgscvkMatrixEngUSTables,
dim(rgscvkMatrixEngUSTables),
MatrixUsRemapVKey,
};
其中rgscvkMatrixEngUSTables就包含了扫描码到虚拟键码的转换表,这个表示需要PDD层来根据开发的键盘布局来调整。
2) SetDeviceLayout函数体第二部分
图23 SetDeviceLayout函数体第二部分
到此KeybdDriverInitializeEx函数介绍完成了。