| |
硬件工程师眼中的WINCE 键盘驱动的自定义修改
最近有个项目需要弄弄WINCE,趁机玩玩。由于自己偏重于硬件,一般不喜欢自己搭程序框架 (其实是功力不够,心虚的表现),总是希望借助现有的框架,修修补补,符合自己使用就行。主要关注修改的关键点和如何修改,系统的运行机理不是我所重点研究的范畴。就像把IBM换成LENOVL,就变成了中国品牌,我只知道怎么贴标签,贴在哪里就行,而不管Thinkpad里面是怎么做的。刚刚搞定了WINCE的键盘驱动,趁着手热写下这些文字,希望能有用。
如果你也是一个像我一样的初学者,也请在现有的框架上修改。
为了避免广告嫌疑,开发板就定为SMDK2440吧软件环境PB5。
键盘驱动位于C:\WINCE500\PLATFORM\SMDK2440A\Src\Drivers\Keybd文件夹下,首先从键盘驱动的加载入口点开始。
..\Keybd\ Kbdcommon 下包含文件kbd.cpp包含有键盘驱动的入口地址,Matrix_Entry()。要想了解键盘驱动的整个加载过程,必须从Matrix_Entry()函数入手。
1. Matrix_Entry()函数的几个主要工作流程:
if (v_pp2k->Initialize())
{
v_pp2k ->IsrThreadStart();
}
Else
{
ERRORMSG(1,(TEXT("Could not initialize ps2 keyboard.\r\n")));
delete v_pp2k;
v_pp2k = NULL;
}
变量v_pp2k 是个全局变量,定义为Ps2Keybd *v_pp2k; 从名字可以看出Ps2Keybd是个类,定义了针对键盘操作的一些函数,定义在s
代码首先执行键盘初始化工作,if (v_pp2k->Initialize())中可以对硬件配置进行操作,例如采用键盘阵列,配置IO端口以及中断方式;如果采用专属芯片(SMDK2440),可以进行初始化芯片寄存器等。
初始化成功之后则启动线程v_pp2k ->IsrThreadStart();具体实现细节后文详细分析。
if (!KeybdDriverInitializeAddresses())
{
goto leave;
}
KeybdDriverInitializeAddresses()函数被调用,该函数给键盘驱动分配内存空间。为何大费周折,进行一个系统调用?里面太深层次的问题没有仔细研究,实际上就是给几个struct分配物理地址(物理地址是真实存在的,对应于SDRAM的内存中的一块区域)。
if (v_pp2k)
{
v_pp2k->KeybdPowerOn();
}
这个也好理解,给你一个上电启动的机会(是否可以和v_pp2k->Initialize()合并在一起??)
如果足够幸运,上述流程都走了一遍,此时就可以执行Matrix_Entry()函数的返回
fRet = TRUE;
leave:
DEBUGMSG(ZONE_INIT, (_T("%s: Initialization complete\r\n"), pszFname));
return fRet;
让我们理一理Matrix_Entry()到底干了什么,初始化->创建线程->分配物理空间->上电->退出。好像确实没干什么东西。那么作为一个硬件工程师,此时带给我的疑问:
1 中断怎么挂接到驱动中的;
2 何时在那个函数中读取键盘扫描码,又如何送给操作系统;
3键盘扫描码该如何与操作系统的码字对应起来。
回头看看Matrix_Entry()函数,对于上面三个问题没有一个给出答案,而我们唯一不清楚的就是IsrThreadStart()到底干了什么,不过从字面上理解就应当是启动(START)中断服务程序(ISR)线程(Thread)。好吧,让我们进入到IsrThreadStart()内部去看看他的所以然。
2. IsrThreadStart()函数分析:
BOOL Ps2Keybd::IsrThreadStart()
{
HANDLE hthrd;
DEBUGMSG(1,(TEXT("IsrThreadStart:\r\n")));
hthrd = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Ps2KeybdIsrThread,this,0,NULL);
// Since we don't need the handle, close it now.
CloseHandle(hthrd);
return TRUE;
}
真是人如其名 IsrThreadStart ()只干了一件事,创建Ps2KeybdIsrThread()线程,激动的心再次莫名的跌落。还好保留了一丝线索,让我们去看看创建的Ps2KeybdIsrThread()去干了什么吧,别忘记上面的三个问题。
3. Ps2KeybdIsrThread()分析
DWORD Ps2KeybdIsrThread(Ps2Keybd *pp2k)
{
DEBUGMSG(1,(TEXT("Ps2KeybdIsrThread:\r\n")));
pp2k->IsrThreadProc();
return 0;
}
大哥,有意思么,又是什么没干,算了不说了,去看看IsrThreadProc(),字面意思中断服务(ISR)线程(THREAD)处理(PROC)。
4. IsrThreadProc()分析
先贴码子:
BOOL Ps2Keybd::IsrThreadProc()
{
DWORD dwPriority;
DWORD dwIrq_Keybd = 0;
// look for our priority in the registry -- this routine sets it to zero if
// it can't find it.
ReadRegDWORD( TEXT("HARDWARE\\DEVICEMAP\\KEYBD"), _T("Priority256"), &dwPriority );
if(dwPriority == 0) {
dwPriority = 240; // default value is 145
}
DEBUGMSG(1, (TEXT("IsrThreadProc:\r\n")));
m_hevInterrupt = CreateEvent(NULL,FALSE,FALSE,NULL);
if (m_hevInterrupt == NULL) {
DEBUGMSG(1, (TEXT("IsrThreadProc: InterruptInitialize\r\n")));
goto leave;
}
ReadRegDWORD( TEXT("HARDWARE\\DEVICEMAP\\KEYBD"), _T("Irq"), &dwIrq_Keybd );
// Call the OAL to translate the IRQ into a SysIntr value.
//
if (!KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &dwIrq_Keybd, sizeof(DWORD), &g_dwSysIntr_Keybd, sizeof(DWORD), NULL))
{
DEBUGMSG(1, (TEXT("ERROR: Failed to obtain sysintr value for keyboard interrupt.\r\n")));
g_dwSysIntr_Keybd = SYSINTR_UNDEFINED;
goto leave;
}
if (!InterruptInitialize(g_dwSysIntr_Keybd,m_hevInterrupt,NULL,0)) {
DEBUGMSG(1, (TEXT("IsrThreadProc: KeybdInterruptEnable\r\n")));
goto leave;
}
// update the IST priority
CeSetThreadPriority(GetCurrentThread(), (int)dwPriority);
extern UINT v_uiPddId;
extern PFN_KEYBD_EVENT v_pfnKeybdEvent;
KEYBD_IST keybdIst;
keybdIst.hevInterrupt = m_hevInterrupt;
keybdIst.dwSysIntr_Keybd = g_dwSysIntr_Keybd;
keybdIst.uiPddId = v_uiPddId;
keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;
KeybdIstLoop(&keybdIst);
leave:
return 0;
}
看上去好像有货,一点点看吧。先看第一句,
ReadRegDWORD( TEXT("HARDWARE\\DEVICEMAP\\KEYBD"), _T("Priority256"), &dwPriority );
GOOGLE了一下ReadRegDWORD(),字面意思读取(READ)注册表(REG)DWORD型的数据,注册表路径"HARDWARE\\DEVICEMAP\\KEYBD",子建"Priority256",放到dwPriority里面。注册表信息在platform.reg中可以查到。
[HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\KEYBD]
"DriverName"="kbdmouse.dll"
"Irq"=dword:1
"IOBase"=dword:B1600000
"SSPBase"=dword:B1900000
好像没有"Priority256"子键啊,算了,就是一个优先级的事情不管了,先知道意思就行了。接着往下看m_hevInterrupt = CreateEvent(NULL,FALSE,FALSE,NULL);建立了一个 事件(event),从字面上看不出来是干什么的。为什么要建立这个事件呢。在嵌入式系统中,中断处理一般方式为,触发中断->中断响应->触发中断对应的事件(isrevent)->中断退出->等待isrevent的进行执行->具体的中断处理程序。这样做的目的就是为了尽量减少中断的处理时间。该时间创建应当为相同目的
ReadRegDWORD( TEXT("HARDWARE\\DEVICEMAP\\KEYBD"), _T("Irq"), &dwIrq_Keybd );
读取了一个Irq中断号到dwIrq_Keybd变量中,是不是和问题1联系上了?"Irq"=dword:1原来 中断号是1,是不是对应于物理中断?看来只能求助于SMDK2440的原理图了。看看下图1 和 图2,网络号KBDINT是U24产生的中断信号,确实与S
接着看,KernelIoControl(IOCTL_HAL_REQUEST_SYSINTR, &dwIrq_Keybd, sizeof(DWORD), &g_dwSysIntr_Keybd, sizeof(DWORD), NULL),这个函数网上又很多说法,个人感觉是申请了一个系统中断号g_dwSysIntr_Keybd,并把系统中断号与物理中断号对应起来。
InterruptInitialize(g_dwSysIntr_Keybd,m_hevInterrupt,NULL,0),把系统中断号与刚刚建立的事件挂接起来。
CeSetThreadPriority(GetCurrentThread(), (int)dwPriority);设置线程的优先级。
到此我们解决了第一个问题,总结一下,首先从注册表中读取到中断号1,这个中断号实际上与硬件结构密切相关;然而在WINCE系统下,是不能直接调用物理中断号,因此需要申请一个WINCE提供的系统中断号,并将系统中断号与物理中断号对应起来。最后建立系统中断号与用于中断事件的联系。
图1
图2