0 Replies Latest reply on Aug 23, 2011 7:52 PM by wensenyuan

    [资料转发分享]WINCE4.2键盘驱动流程分析

    Brown Belt

      键盘驱动有点繁杂,可以配合以下资料查阅:

      1. Platform Build自带的帮助文件.
      2. 阅读源代码:

      2.1  C:\WINCE420\Public\common\oak\drivers\keybd

      2.2  C:\WINCE420\Platform\smdk2410\drivers\keybd

      1. 网上的一些相关资料

       

      初步查阅后可以知道,键盘驱动是分层(Layered Driver)的,上面的第2.1可以理解为MDD层,2.2是我们要实现的PDD层。

       

      看下面这段(注:网上摘的):

      一个简单的过程描述:输入系统(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的概念

       


      布局管理器处理扫描码的步骤:

      PDD接受到一个扫描码; 扫描码被送到布局管理器; 布局管理器依据当前设备的布局和事件将其转换成虚拟键值; 布局管理器依据当前设备的布局和事件将重新映射; 布局管理器设置自动重复功能,所有的键盘都将共享相同的自动重复设置;

      布局管理器调用函数keybd_event发送一个或多个事件。
      PDD ----Platform Dependent Driver
      PDD是下层的,负责从硬件拿到扫描码(上层的是MDD,负责将扫描码转换成字符).键盘PDD是键盘驱动中与设备相关的一部分代码。键盘PDD包括初始化和电源函数。可以使用公共的ist,也可以包括自己的。当GWES初始化键盘驱动时,它初始化每一个PDD. 每个键盘PDD有一个函数返回关于该PDD的描述和函数指针。当布局管理器初始化这个PDD,键盘驱动传递PDD一个唯一的标示符。有时,多种设备能使用同样的PDD,比如2个独立的PS/2 控制器。每个PDD和布局管理器在同样的DLL里。不可以在运行时加一个PDD(但可以不同的PDD之间切换!)

       

       

      按照顺序看一下这个KeybdDriverInitializeEx()函数,它在我的电脑中位于C:\Wince420\public\common\oak\drivers\keybd\Laymgr\laymgr.cpp文件,这个函数功能主要是:

      1.创建几个事件,注意下面两个:   
      g_hevBeginEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      g_hevEndEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
      然后创建两个线程:
          g_hEventThread = CreateThread(NULL, 0, KeybdEventThreadProc, NULL, 0, NULL);
      注意这个线程函数KeybdEventThreadProc,功能是等待上面创建的g_hevBeginEvent事件,然后调用ScanCodeToVKey得到虚拟键值(这个函数是调用我们的PDD实现的DEVICE_LAYOUT,这是什么呢,可以简单理解为扫描码到虚拟键值的对应数组:ScanCodeToVKeyTable它位于C:\Wince420\platform\smdk2410\drivers\keybd\matrix_0409\s3c2410.cpp),ScanCodeToVKey调用后,再调用SendRemappedEvent(这个函数是再次map虚拟键值->CallDLRemapFn->MatrixUsRemapVKey(这个函数位置同ScanCodeToVKeyTable数组的文件),使得我们有机会再次映射一些功能键Fn,为什么这么做?往往Matrix键盘按键不会太多,大多功能复用!)然后执行keybd_event()API函数,KeybdEventThreadProc的功能分析完成。执行时机是g_hevBeginEvent事件被触发,记住了,下面会分析到。

      2.KeybdDriverInitializeEx接下来初始化每个键盘驱动PDD,PDD数组位于c:\wince420\platform\smdk2410\drivers\keybd\pddlist\pddlist.cpp:
      PFN_KEYBD_PDD_ENTRY g_rgpfnPddEntries[] = {
          PS2_NOP_Entry,
          Matrix_Entry,
          NULL
      };


      我们关心的是Matrix_Entry,这是个函数,位于C:\wince420\platform\smdk2410\drivers\keybd\kbdcommon\kbd.cpp
      BOOL WINAPI Matrix_Entry(
          UINT uiPddId,
          PFN_KEYBD_EVENT pfnKeybdEvent,
          PKEYBD_PDD *ppKeybdPdd
          )
      这个函数功能就是初始目标平台硬件了,具体可以查看源代码,请注意这里的形参pfnKeybdEvent,它很重要,以致于要保存到全局变量,供之后的PDD其它函数调用。下面还会提到,记住这个全局变量是: v_pfnKeybdEvent.由于KeybdDriverInitializeEx是以KeybdEventCallback函数为参数调用这个Matrix_Entry的,所以v_pfnKeybdEvent = KeybdEventCallback.

      1. 之后KeybdDriverInitializeEx函数设置输入语言与Device Layout,这里可以理解为设置键盘布局,键盘布局不同,同一个Virtual Key代表的字符不同。比如我们常用的0409语言布键盘数字8的上档键是”*”,而0411日本数字8的上档键是”(”,下面会讲到如何更改键盘布局。

       

      好了,下面我们换一个角度来看键盘驱动流程,从键盘中断开始:
      在初始化的时候调用Matrix_Entry函数,在这个函数内部初始化硬件信息:
      v_pp2k = new Ps2Keybd;
          if (v_pp2k->Initialize()) {
              v_pp2k ->IsrThreadStart();
          }
          else {
              ERRORMSG(1,(TEXT("Could not initialize ps2 keyboard.\r\n")));
              delete v_pp2k;
              v_pp2k = NULL;
          }
            if (!KeybdDriverInitializeAddresses()) {
                 goto leave;
            }
            v_pp2k->KeybdPowerOn();
      ….
      这些函数在kbd.cpp与s3c2410kbd.cpp中,初始化硬件管脚、中断等。进入IsrThreadStart->KeybdIstLoop等待键盘中断。这里的KeybdIstLoop函数参数,是个结构体,注意最后两个域:
      keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
      keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;
      KeybdIstLoop位于C:\wince420\public\common\oak\drivers\keybd\ist\keybdist.cpp中,就是等待键盘中断,然后依次调用上面的KeybdPdd_GetEventEx2与v_pfnKeybdEvent,由上面分析可知v_pfnKeybdEvent是指向KeybdEventCallback的函数指针.

      KeybdPdd_GetEventEx2函数是从硬件获取键盘扫描码后转换成ScanCodeToVKeyTable数组的下标.所以,这里要做的工作即是将扫描码与虚拟键对应起来,注意这里ScanCodeToVKeyTable数组里的值是虚拟键,但不是字符,比如你可以将一个扫描码与’A’对应,但不能与’a’对应。

      keybdEventCallbac()函数设置g_hevBeginEvent事件,结合上面的分析KeybdEventThreadProc()函数等待这个事件后,进行扫描码到虚拟键值的转换,最后执行keybd_event()API函数。这里就回到了上面分析的内容了。

       

       

      我的分析到这里就暂时结束了,事实上还有流程没有跟完,键盘驱动尚未结束,后续的大致是根据键盘状态与键盘布局将虚拟键转成字符。如果你的键盘很简单,只有一些数字或常用字母,需要更改的不多,如ScanCodeToVKeyTable与获取扫描码的对应,如果你的键盘功能复用与0409也就是我们常用的键盘不同,那你可以增加一个自已的键盘布局或是更改0409,这些文件位于c:\wince420\public\common\oak\drivers\keybd\InputLangs\0409目录下。其实也就是些数组,不难。至于如何更改/增加键盘中断,在kernel目录下的cfw.c与armint.c中,如果连这个都不会,那你就不要写驱动了。

       

      我写的一个是Matrix驱动,通过spi协议与键盘板上的cpld通讯,这些在KeybdPdd_GetEventEx2中获取扫描码的时候实现,看一下这个函数:
      static UINT KeybdPdd_GetEventEx2(UINT uiPddId, UINT32 rguiScanCode[16], BOOL rgfKeyUp[16])
      参数rguiScanCode数组内容即是扫描码对应ScanCodeToVKeyTable的数组下标,rgfKeyUp是键的状态(按下或松开),所以最好是在按键按下或松开时都能触发中断,否则若只有一个按下状态,系统会认为此键一下按下。参数是数组,所以可以是多个按键同时按下,函数返回值即是按下或松开按键的个数。这里也可以模拟按错,比如只要大写字母,可以在有按键按下时,在rguiScanCode数组中先填入VK_SHIFT对应的ScanCodeToVKeyTable数组下标值,再填入虚拟键值。当然,松开按键时,也要两个同时松开,最好按相返的顺序填值。