PS:第一篇技术博客,轻拍。。
题记:
今天把WinCE的整个串口驱动看了一遍,硬件是S5PV210的开发板,从MDD到PDD,有些收获,怕忘了,就写下来。由于个人文笔不好,可能会有些乱。假如有不对的地方,欢迎指正。
MDD层微软已经写好了,源码位于\WINCE600\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\COM_MDD2,主要实现了标准流驱动的接口:
COM_Init
COM_Deinit
COM_PreDeinit
COM_Open
COM_Close
COM_PreClose
COM_Read
COM_Write
COM_Seek
COM_PowerDown
COM_PowerUp
COM_IOControl
这些接口都跟硬件无关。
这里只分析一个COM_Init,只要理清楚了一个,再看其他的就简单多了。系统开机时,通过COM_Init加载驱动,注册串口,并对硬件进行一定的初始化。要注册和初始化哪个串口(S5PV210上芯片上有4个)是通过读取注册表来得知的,这个值被定义为DevIndex。然后调用GetSerialObject( DevIndex )进行函数映射,然后就可以进行硬件的初始化了:
pHWHead = pSerialHead->pHWObj->pFuncTbl->HWInit(Identifier, pSerialHead, pSerialHead->pHWObj);
接着完成其他的初始化任务(这里只说明了一小部分COM_Init的源码)。
那么那个HWInit函数到底是什么呢?这里就说到了上面提到的函数映射了。实现方式如下:
extern "C" PHWOBJ
GetSerialObject(
DWORD DeviceArrayIndex
)
{
PHWOBJ pSerObj;
// Unlike many other serial samples, we do not have a statically allocated
// array of HWObjs. Instead, we allocate a new HWObj for each instance
// of the driver. The MDD will always call GetSerialObj/HWInit/HWDeinit in
// that order, so we can do the alloc here and do any subsequent free in
// HWDeInit.
// Allocate space for the HWOBJ.
pSerObj=(PHWOBJ)LocalAlloc( LPTR ,sizeof(HWOBJ) );
if ( !pSerObj )
return (NULL);
// Fill in the HWObj structure that we just allocated.
pSerObj->BindFlags = THREAD_IN_PDD; // PDD create thread when device is first attached.
pSerObj->dwIntID = DeviceArrayIndex; // Only it is useful when set set THREAD_AT_MDD. We use this to transfer DeviceArrayIndex
pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl; // Return pointer to appropriate functions
// Now return this structure to the MDD.
return (pSerObj);
}
pFuncTbl是一个HW_VTBL类型的指针,它里面的函数被映射到IoVTbl中对应的函数。两者的定义如表所示
HW_VTBL | IoVTbl |
HWInit HWPostInit HWDeinit HWOpen HWClose HWGetIntrType HWRxIntrHandler HWTxIntrHandler HWModemIntrHandler HWLineIntrHandler HWGetRxBufferSize HWPowerOff HWPowerOn HWClearDTR HWSetDTR HWClearRTS HWSetRTS HWEnableIR HWDisableIR HWClearBreak HWSetBreak HWXmitComChar HWGetStatus HWPreDeini HWGetModemStatus HWGetCommProperties HWPurgeComm HWSetDCB HWSetCommTimeouts HWIoctl | SerInit, SerPostInit, SerDeinit, SerOpen, SerClose, SerGetInterruptType, SerRxIntr, SerTxIntrEx, SerModemIntr, SerLineIntr, SerGetRxBufferSize, SerPowerOff, SerPowerOn, SerClearDTR, SerSetDTR, SerClearRTS, SerSetRTS, SerEnableIR, SerDisableIR, SerClearBreak, SerSetBreak, SerXmitComChar, SerGetStatus, PreDeinit, SerGetModemStatus, SerGetCommProperties, SerPurgeComm, SerSetDCB, SerSetCommTimeouts, SerIoctl |
而IoVTbl中的函数则最终调用到硬件。下面是SerSetBreak的函数体:
VOID
SerSetBreak(
PVOID pHead // @parm PVOID returned by HWinit.
)
{
DEBUGMSG (ZONE_FUNCTION, (TEXT("+SL_SetBreak, 0x%X\r\n"), pHead));
if (pHead)
((CSerialPDD *)pHead)->SetBreak(TRUE);
DEBUGMSG (ZONE_FUNCTION, (TEXT("-SL_SetBreak, 0x%X\r\n"), pHead));
}
为了方便说明,这里先解释一下串口驱动用到的几个类:
类CSerialPDD:由类CRegistryEdit派生而来;
类CPddUart:由类CSerialPDD和类CMiniThread派生而来;
类CPddSerial:由CPddUart派生而来,分别实现对4个串口的寄存器的配置等。
回到函数SerSetBreak,SerSetBreak中参数pHead为SerInit()返回的指针,它指向类CPddSerial。而在CPddSerial中最终实现了函数SetBreak()。
(SerInit()中定义了一个类CPddSerial,并返回一个指向它的指针。)
这样就可以基本理清系统是如何从MDD层一步步执行PDD层,再到最底层的硬件寄存器了。
另外,对于串口,系统还提供了SetCommState这样的函数来对其进行设置。这些函数是在coredll.dll中实现:
BOOL WINAPI
SetCommState(HANDLE hCommDev, LPDCB lpDCB)
{
DWORD dwBytesReturned;
return DeviceIoControl (hCommDev, IOCTL_SERIAL_SET_DCB,
(LPVOID)lpDCB, sizeof(DCB),
(LPVOID)0, 0, &dwBytesReturned, 0);
}
它通过调用IOControl来实现其目的,而在COM_IOControl中则实现了这些IOCTL。