S3C6410 KeyPad驱动(上)
********************************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_MACHINE\Hardware\DeviceMap\KEYBD\Drivername下的子鍵“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的信息在一個通過鍵盤標識符來索引的數組中,此數組在SMDK6410\SRC\DRIVERS\KEYBD\Pddlist下面定義,如下所示
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_MACHINE\SYSTEM\ControlSet001\Control\Keyboard 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,//在COMMON\OAK\DRIVERS\KEYBD\NOP\PDD\nopkb.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函數介紹完成了。
總結
以上是生活随笔為你收集整理的S3C6410 KeyPad驱动(上)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: S3C6410的SPI控制器
- 下一篇: S3C6410 KeyPad驱动(下)