1. PDD层代码简单分析
PDD层的主要包含了以下2个类:CSerialPDDPowerUpCallback, CSerialPDD, 下面简单的分析这2个类的作用。
1.1 CSerialPDDPowerUpCallback
CSerialPDDPowerUpCallback 类用于串口电源上电时的处理。
在调用CSerialPDD::Init()后会创建一个CSerialPDDPowerUpCallback类型的对象
在CSerialPDD::PowerOn()函数中会调用此对象的SignalCallBack()函数, 这样RunThread就开始运行, 进而通过调用CSerialPDD::NotifyPDDInterrupt()进行后续处理, 包括调用
CeEventHasOccurred (NOTIFICATION_EVENT_RS232_DETECTED, NULL) 通知系统有串口被探测到.
以及调用SerialEventHandler()进行MDD层上的处理.
更详细的细节可以参考微软的源代码:
%WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\SERIAL\SERPDDCM\cserpdd.cpp
%WINCEROOT%\PUBLIC\COMMON\OAK\INC\cserpdd.h
2.2 CSerialPDD
CSerialPDD 是串口PDD层的关键,实现对硬件的操作,开发者开发的驱动就是继承这个类,并实现重载相关的函数来完成特定设备的PDD功能的。
这个类抽象了以下这些接口:
// Tx Function.
virtual BOOL InitXmit(BOOL bInit) = 0;
virtual void XmitInterruptHandler(PUCHAR pTxBuffer, ULONG *pBuffLen) = 0;
virtual void XmitComChar(UCHAR ComChar) = 0;
virtual BOOL EnableXmitInterrupt(BOOL bEnable)= 0;
virtual BOOL CancelXmit() = 0 ;
// Rx Function.
virtual BOOL InitReceive(BOOL bInit) = 0;
virtual ULONG ReceiveInterruptHandler(PUCHAR pRxBuffer,ULONG *pBufflen) = 0;
virtual ULONG CancelReceive() = 0;
// Modem
virtual BOOL InitModem(BOOL bInit) = 0;
virtual void ModemInterruptHandler()= 0; // This is Used to Indicate Modem Signal Changes.
virtual ULONG GetModemStatus() = 0;
virtual void SetDTR(BOOL bSet)= 0;
virtual void SetRTS(BOOL bSet)= 0;
virtual BOOL IsCTSOff()
{
return ((GetModemStatus() & MS_CTS_ON)==0) ;
};
virtual BOOL IsDSROff()
{
return ((GetModemStatus() & MS_DSR_ON)==0) ;
};
// Line Function
virtual BOOL InitLine(BOOL bInit) = 0;
virtual void LineInterruptHandler() = 0;
virtual void SetBreak(BOOL bSet) = 0 ;
virtual BOOL SetBaudRate(ULONG BaudRate,BOOL bIrModule) = 0;
virtual BOOL SetByteSize(ULONG ByteSize) = 0;
virtual BOOL SetParity(ULONG Parity)= 0;
virtual BOOL SetStopBits(ULONG StopBits)= 0;
以上这些接口都是纯虚函数, 在实现PDD层时, 必须实现这些接口.
当然不需要的功能你可以简单的用类似virtual BOOL func()
{
;
} 的形式来实现。
其他的非纯虚函数的虚函数也可以被用户重载, 以实现自己特定的功能。
我们知道PDD层的函数为如下函数:
GetSerialObject
This function returns a pointer to a HWOBJ structure. The structure contains the function pointers and parameters for the hardware interface functions of the relevant lower layer.
HWClearBreak
This function clears an RS-232 line break condition.
HWClearDTR
This function clears the Data Terminal Ready (DTR) signal.
HWClearRTS
This function clears the Request to Send (RTS) signal.
HWClose
This function closes the device initialized by the HWInit function.
HWDeinit
This function is called by the upper layer to de-initialize the hardware when a device driver is unloaded.
HWDisableIR
This function disables the infrared (IR) serial interface.
HWEnableIR
This function enables the infrared (IR) serial interface.
HWGetCommProperties
This function retrieves the current properties of the communications device.
HWGetIntrType
This function returns the current interrupt type.
HWGetModemStatus
This function retrieves the modem status.
HWGetRxBufferSize
This function returns the maximum number of bytes that the hardware buffer can hold, not including the padding, stop, and start bits.
HWGetRxStart
This function returns the start of the hardware-receive buffer.
HWGetStatus
This function specifies the hardware status API.
HWInit
This function initializes a serial device.
HWIoctl
This function executes device I/O control (IOCTL) routines.
HWLineIntrHandler
This function handles line interrupts for serial port devices.
HWModemIntrHandler
This function handles the modem interrupt. In the serial port upper layer implementation available in Microsoft Windows CE 3.0 and later, this function replaces the HWOtherIntrHandler function.
HWOpen
This function is called by the upper layer to open the serial device.
HWOtherIntrHandler
In Windows CE 3.0 and later, this function has been replaced with the new function HWModemIntrHandler.
HWPostInit
This function performs necessary operations after it initializes all data structures and prepares the serial IST to begin handling interrupts. It is called by the upper layer.
HWPowerOff
This function notifies the platform-dependent driver that the hardware platform is about to enter suspend mode. It is called by the model device driver (MDD).
HWPowerOn
This function notifies the platform-dependent driver that the hardware platform is resuming from suspend mode. It is called by the MDD.
HWPurgeComm
This function purges the communications device.
HWPutBytes
This function writes bytes to hardware. The driver calls this function.
HWReset
This function resets the hardware API.
HWRxIntrHandler
This function handles serial port interrupts.
HWSetBreak
This function sets the line break condition on the transmit line.
HWSetCommTimeouts
This function sets the communications time-out events in response to a call to the SetCommTimeouts function.
HWSetDCB
This function sets the device control block.
HWSetDTR
This function sets the Data Terminal Ready (DTR) signal.
HWSetRTS
This function sets the Request to Send (RTS) signal.
HWTxIntrHandler
This function handles the transmit interrupt for serial port devices.
HWXmitComChar
This function transmits a single character.
更具体的细节可以参考:MSDN帮助 ms-help://MS.WindowsCE.500/wceddk5/html/wce50grfserialportdriverfunctions.htm
微软用一堆SerXXXXX()的函数封装了CSerialPdd相关的操作,来完成PDD功能。
2. PDD与MDD层的交互
2.1 PDD层的函数是如何能够被MDD层所调用
1.2节介绍的那一堆SerXXXXX()函数会被记录到一个函数指针表里,真是通过这个函数指针表来实现与MDD层的交互的。此函数指针表是HW_VTBL类型的, 在微软的代码里是命名为IoVTbl的函数指针表。
MDD层通过调用 GetSerialObject(DWORD DeviceArrayIndex)函数来获取一个HWOBJ结构的指针, 这个结构包含以下成员:
ULONG BindFlags; // Flags controlling MDD behaviour. Se above.
DWORD dwIntID; // Interrupt Identifier used if THREAD_AT_INIT or THREAD_AT_OPEN
PHW_VTBL pFuncTbl;
其中pFuncTbl正是指向我们之前说到的 IoVTbl 函数指针结构的地址.
这样MDD层就可以通过->pFuncTbl.DDSIFunction()来对特定的硬件做特定的操作了.
2.2 GetSerialObject函数
这个函数的原型是:PHWOBJ GetSerialObject(DWORD DeviceArrayIndex) ,它是连接MDD和PDD层的关键函数, 也是实现多端口驱动的关键, 这个函数允许一个MDD层连接多个PDD层。
在MDD层的COM_Init函数中,驱动会通过查询DeviceArrayIndex键值, 得到我们需要打开的是普通串口或者是其他种类的串口设备。
而正是这个数值决定了GetSerialObject是返回哪个串口的PHWOBJ指针, PHWOBJ包含了pFuncTbl,这个正是前一节说到的PDD层函数指针表,通过这个函数表我们就可以实现对相应的串口设备进行操作。(串口有可能是标准的串口,也有可能是红外设备)
2.3 CreateSerialObject 函数
如果开发者选择直接继承微软CSerialPdd类来实现串口的PDD层,还要实现CreateSerialObject及DeleteSerialObject函数。
在MDD层的COM_Init函数被调用时,驱动会通过查询注册表来得到相关参数并调用PDD层的HWInit函数,HWInit函数又会调用CreateSerialObject来初始化串口设备,在关闭串口以后DeleteSerialObject函数会被调用。
3. 其他
3.1 COM_Init函数如何被调用
此函数通常是由Device.exe进程调用的, 而此函数的参数Identifier 是一个指向注册表HKEY_LOCAL_MACHINE\Drivers\Active.下的一个键值,这也是决定驱动如何操作硬件的关键。
对于使用ActivateDeviceEx函数调用的情况,请参照MSDN, http://msdn.microsoft.com/en-us/library/aa447677.aspx
3.2 相关代码段:
HANDLE COM_Init(ULONG Identifier)
{
PVOID pHWHead = NULL;
PHW_INDEP_INFO pSerialHead = NULL;
ULONG HWBufferSize;
DWORD DevIndex;
HKEY hKey;
ULONG kreserved = 0, kvaluetype;
ULONG datasize = sizeof(ULONG);
/*
* INTERNAL: this routine initializes the hardware abstraction interface
* via HWInit(). It allocates a data structure representing this
* instantiation of the device. It also creates an event and initializes
* a critical section for receiving as well as registering the logical
* interrupt dwIntID with NK via InterruptInitialize. This call
* requires that the hardware dependent portion export apis that return
* the physical address of the receive buffer and the size of that buffer.
* Finally, it creates a buffer to act as an intermediate
* buffer when receiving.
*/
DEBUGMSG (ZONE_INIT | ZONE_FUNCTION, (TEXT("+COM_Init\r\n")));
// Allocate our control structure.
pSerialHead = (PHW_INDEP_INFO)LocalAlloc(LPTR, sizeof(HW_INDEP_INFO));
// Check that LocalAlloc did stuff ok too.
if ( !pSerialHead )
{
DEBUGMSG(ZONE_INIT | ZONE_ERROR,
(TEXT("Error allocating memory for pSerialHead, COM_Init failed\n\r")));
return(NULL);
}
// Initially, open list is empty.
InitializeListHead( &pSerialHead->OpenList );
InitializeCriticalSection(&(pSerialHead->OpenCS));
pSerialHead->pAccessOwner = NULL;
pSerialHead->fEventMask = 0;
// Init CommTimeouts.
pSerialHead->CommTimeouts.ReadIntervalTimeout = READ_TIMEOUT;
pSerialHead->CommTimeouts.ReadTotalTimeoutMultiplier =
READ_TIMEOUT_MULTIPLIER;
pSerialHead->CommTimeouts.ReadTotalTimeoutConstant =
READ_TIMEOUT_CONSTANT;
pSerialHead->CommTimeouts.WriteTotalTimeoutMultiplier= 0;
pSerialHead->CommTimeouts.WriteTotalTimeoutConstant = 0;
/* Create tx and rx events and stash in global struct field. Check return.
*/
pSerialHead->hSerialEvent = CreateEvent(0,FALSE,FALSE,NULL);
pSerialHead->hKillDispatchThread = CreateEvent(0, FALSE, FALSE, NULL);
pSerialHead->hTransmitEvent = CreateEvent(0, FALSE, FALSE, NULL);
pSerialHead->hReadEvent = CreateEvent(0, FALSE, FALSE, NULL);
if ( !pSerialHead->hSerialEvent || !pSerialHead->hKillDispatchThread ||
!pSerialHead->hTransmitEvent || !pSerialHead->hReadEvent )
{
DEBUGMSG(ZONE_ERROR | ZONE_INIT,
(TEXT("Error creating event, COM_Init failed\n\r")));
LocalFree(pSerialHead);
return(NULL);
}
/* Initialize the critical sections that will guard the parts of
* the receive and transmit buffers.
*/
InitializeCriticalSection(&(pSerialHead->ReceiveCritSec1));
InitializeCriticalSection(&(pSerialHead->TransmitCritSec1));
/* Want to use the Identifier to do RegOpenKey and RegQueryValue (?)
* to get the index to be passed to GetHWObj.
* The HWObj will also have a flag denoting whether to start the
* listening thread or provide the callback.
*/
DEBUGMSG (ZONE_INIT,(TEXT("Try to open %s\r\n"), (LPCTSTR)Identifier));
hKey = OpenDeviceKey((LPCTSTR)Identifier);
// 通过调用OpenDeviceKey, 驱动可以通过Identifier找到驱动的注册表键的所在.
if ( !hKey )
{
DEBUGMSG (ZONE_INIT | ZONE_ERROR,
(TEXT("Failed to open devkeypath, COM_Init failed\r\n")));
LocalFree(pSerialHead);
return(NULL);
}
datasize = sizeof(DWORD);
// 通过查询DeviceArrayIndex键值, 我们可以知道需要打开的是普通串口或者是其他类型的串口设备.
if ( RegQueryValueEx(hKey, L"DeviceArrayIndex", NULL, &kvaluetype,
(LPBYTE)&DevIndex, &datasize) )
{
DEBUGMSG (ZONE_INIT | ZONE_ERROR,
(TEXT("Failed to get DeviceArrayIndex value, COM_Init failed\r\n")));
RegCloseKey (hKey);
LocalFree(pSerialHead);
return(NULL);
}
datasize = sizeof(DWORD);
// 通过查询Priority256键值, 我们可以知道IST的优先级, 在后面会通过这个值来改变IST的优先级.
if ( RegQueryValueEx(hKey, L"Priority256", NULL, &kvaluetype,
(LPBYTE)&pSerialHead->Priority256, &datasize) )
{
pSerialHead->Priority256 = DEFAULT_CE_THREAD_PRIORITY;
DEBUGMSG (ZONE_INIT | ZONE_WARN,
(TEXT("Failed to get Priority256 value, defaulting to %d\r\n"), pSerialHead->Priority256));
}
RegCloseKey (hKey);
DEBUGMSG (ZONE_INIT,
(TEXT("DevIndex %X\r\n"), DevIndex));
// Initialize hardware dependent data.
// GetSerialObject是连接MDD和PDD层的关键函数, 在这个函数里面决定了DevIndex是对什么设备进行操作.
// 可能是标准的串口, 也可能是红外接口
pSerialHead->pHWObj = GetSerialObject( DevIndex );
if ( !pSerialHead->pHWObj )
{
DEBUGMSG(ZONE_ERROR | ZONE_INIT,
(TEXT("Error in GetSerialObject, COM_Init failed\n\r")));
LocalFree(pSerialHead);
return(NULL);
}
DEBUGMSG (ZONE_INIT, (TEXT("About to call HWInit(%s,0x%X)\r\n"),
Identifier, pSerialHead));
// 这里调用了PDD层的HWInit
pHWHead = pSerialHead->pHWObj->pFuncTbl->HWInit(Identifier, pSerialHead, pSerialHead->pHWObj);
pSerialHead->pHWHead = pHWHead;
/* Check that HWInit did stuff ok. From here on out, call Deinit function
* when things fail.
*/
if ( !pHWHead )
{
DEBUGMSG (ZONE_INIT | ZONE_ERROR,
(TEXT("Hardware doesn't init correctly, COM_Init failed\r\n")));
COM_Deinit(pSerialHead);
return(NULL);
}
DEBUGMSG (ZONE_INIT,
(TEXT("Back from hardware init\r\n")));
// Allocate at least twice the hardware buffer size so we have headroom
HWBufferSize = 2 * pSerialHead->pHWObj->pFuncTbl->HWGetRxBufferSize(pHWHead);
// Init rx buffer and buffer length here.
pSerialHead->RxBufferInfo.Length =
HWBufferSize > RX_BUFFER_SIZE ? HWBufferSize:RX_BUFFER_SIZE;
pSerialHead->RxBufferInfo.RxCharBuffer =
LocalAlloc(LPTR, pSerialHead->RxBufferInfo.Length);
if ( !pSerialHead->RxBufferInfo.RxCharBuffer )
{
DEBUGMSG(ZONE_INIT|ZONE_ERROR,
(TEXT("Error allocating receive buffer, COM_Init failed\n\r")));
COM_Deinit(pSerialHead);
return(NULL);
}
DEBUGMSG (ZONE_INIT, (TEXT("RxHead init'ed\r\n")));
RxResetFifo(pSerialHead);
InitializeCriticalSection(&(pSerialHead->RxBufferInfo.CS));
InitializeCriticalSection(&(pSerialHead->TxBufferInfo.CS));
DEBUGMSG (ZONE_INIT, (TEXT("RxBuffer init'ed with start at %x\r\n"),
pSerialHead->RxBufferInfo.RxCharBuffer));
if ( pSerialHead->pHWObj->BindFlags & THREAD_AT_INIT )
{
// Hook the interrupt and start the associated thread.
if ( ! StartDispatchThread( pSerialHead ) )
{
// Failed on InterruptInitialize or CreateThread. Bail.
COM_Deinit(pSerialHead);
return(NULL);
}
}
// OK, now that everything is ready on our end, give the PDD
// one last chance to init interrupts, etc.
(void) pSerialHead->pHWObj->pFuncTbl->HWPostInit( pHWHead );
DEBUGMSG (ZONE_INIT | ZONE_FUNCTION, (TEXT("-COM_Init\r\n")));
return(pSerialHead);
}