wince串口之MDD分析
(作者:wogoyixikexie@gliet)
以前曾经看过一阵子串口,只了解一点皮毛,现在在5.0 2440BSP上增加串口遇到阻碍,现在不得不仔细研究一下。MDD是驱动的最上层,应用程序等都是和它打交道的,所以从它开始看才合理。
首先看Com_Init这个函数吧
- // ****************************************************************
- //
- // @doc EXTERNAL
- // @func HANDLE | COM_INIT | Serial device initialization.
- //
- //@parm ULONG | Identifier | Port identifier. The device loader
- //passes in the registry key that contains information(参数是注册表的键值)
- // about the active device.
- //
- //@remark This routine is called at device load time in order
- // to perform any initialization. Typically the init
- // routine does as little as possible, postponing memory
- // allocation and device power-on to Open time.
- //
- // @rdesc Returns a pointer to the serial head which is passed into
- // the COM_OPEN and COM_DEINIT entry points as a device handle.
- //
- 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);
- 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);
- 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.
- 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));
- //其实下面执行的是SerInit函数(由一个表完成了初始化的)
- 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);//返回结构体句柄,给设备管理器
- }
com_init这个函数参数是注册表的键值,并返回pSerialHead指向的结构体的指针。该函数最重要的是
pSerialHead指向的结构体,现在来看看
- // @struct HW_INDEP_INFO | Hardware Independent Serial Driver Head Information.
- typedef struct __HW_INDEP_INFO {
- CRITICAL_SECTION TransmitCritSec1; // @field Protects tx action
- CRITICAL_SECTION ReceiveCritSec1; // @field Protects rx action
- PHWOBJ pHWObj; // @field Represents PDD object.
- PVOID pHWHead; // @field Device context for PDD.
- HANDLE hSerialEvent; // @field Serial event, both rx and tx
- HANDLE hReadEvent; // @field Serial event, both rx and tx
- HANDLE hKillDispatchThread; // @field Synchonize thread end
- HANDLE hTransmitEvent; // @field transmit event, both rx and tx
- HANDLE pDispatchThread;// @field ReceiveThread
- ULONG Priority256; // @field CeThreadPriority of Dispatch Thread.
- ULONG DroppedBytesMDD;// @field Record of bytes dropped by MDD.
- ULONG DroppedBytesPDD;// @field Record of bytes dropped by PDD.
- ULONG RxBytes; // @field Record of total bytes received.
- ULONG TxBytes; // @field Record of total bytes transmitted.
- ULONG TxBytesPending; // @field Record of total bytes awaiting transmit.
- ULONG TxBytesSent; // @field Record of bytes sent in one transmission
- DCB DCB; // @field DCB (see Win32 Documentation.
- COMMTIMEOUTS CommTimeouts; // @field Time control field.
- DWORD OpenCnt; // @field Protects use of this port
- DWORD KillRxThread:1; // @field Flag to terminate SerialDispatch thread.
- DWORD XFlow:1; // @field True if Xon/Xoff flow ctrl.
- DWORD StopXmit:1; // @field Stop transmission flag.
- DWORD SentXoff:1; // @field True if XOFF sent.
- DWORD DtrFlow:1; // @field True if currently DTRFlowed
- DWORD RtsFlow:1; // @field True if currently RTSFlowed
- DWORD fAbortRead:1; // @field Used for PURGE
- DWORD fAbortTransmit:1;// @field Used for PURGE
- DWORD Reserved:24; // @field remaining bits.
- ULONG fEventMask; // @field Sum of event mask for all opens
- RX_BUFFER_INFO RxBufferInfo; // @field rx buffer info.
- TX_BUFFER_INFO TxBufferInfo; // @field tx buffer info.
- LIST_ENTRY OpenList; // @field Head of linked list of OPEN_INFOs
- CRITICAL_SECTION OpenCS; // @field Protects Open Linked List + ref counts
- PHW_OPEN_INFO pAccessOwner; // @field Points to whichever open has acess permissions
- } HW_INDEP_INFO, *PHW_INDEP_INFO;
现在来逐一分析这个结构体的成员。
————————————————
PHWOBJ pHWObj; // @field Represents PDD object. 原型在Serhw.h
- typedef struct __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;
- } HWOBJ, *PHWOBJ;
在com_init函数中有pSerialHead->pHWObj = GetSerialObject( DevIndex );GetSerialObject函数参数其实是注册表中的
DeviceArrayIndex的值,这相当于串口的编号。现在来看看GetSerialObject的原型
- // GetSerialObj : The purpose of this function is to allow multiple PDDs to be
- // linked with a single MDD creating a multiport driver. In such a driver, the
- // MDD must be able to determine the correct vtbl and associated parameters for
- // each PDD. Immediately prior to calling HWInit, the MDD calls GetSerialObject
- // to get the correct function pointers and parameters.
- //
- 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.
- //BindFlags 表示在哪里启动串口驱动的IST线程
- 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
- //IoVTbl这个数组很重要,在中间层起承上启下的作用,
- // Now return this structure to the MDD.
- return (pSerObj);
- }
GetSerialObject函数获得了串口的编号,以及负责和中间层联系。在4.2下增加串口是要修改这个函数的,现在
放在微软的源代码下是否不妥当?难道5.0另有安排?
pSerObj->pFuncTbl = (HW_VTBL *) &IoVTbl; // Return pointer to appropriate functions 这个很关键
- 不知道怎么回事,为什么要实现那么多的层,这个IoVTbl初始化HW_VTBL 指向的结构体,何必多次一举,为什么还要改变个名字,用原来的名字不是更直观吗??不会是我C语言还没有达到水准吧?
- const
- HW_VTBL IoVTbl = {
- SerInit,
- SerPostInit,
- SerDeinit,
- SerOpen,
- SerClose,
- SerGetInterruptType,
- SerRxIntr,
- SerTxIntrEx,
- SerModemIntr,
- SerLineIntr,
- SerGetRxBufferSize,
- SerPowerOff,
- SerPowerOn,
- SerClearDTR,
- SerSetDTR,
- SerClearRTS,
- SerSetRTS,
- SerEnableIR,
- SerDisableIR,
- SerClearBreak,
- SerSetBreak,
- SerXmitComChar,
- SerGetStatus,
- SerReset,
- SerGetModemStatus,
- SerGetCommProperties,
- SerPurgeComm,
- SerSetDCB,
- SerSetCommTimeouts,
- SerIoctl
- };
- 上面的函数表其实是实现了下面结构体内的函数(结构体赋值,相当巧妙),这个函数表只是实现,但是调用的却是下面的函数,刚开始还真反映不过来。为了分层,太TMD了,
- typedef struct __HW_VTBL {
- PVOID (*HWInit)(ULONG Identifier, PVOID pMDDContext);
- ULONG (*HWDeinit)(PVOID pHead);
- BOOL (*HWOpen)(PVOID pHead);
- ULONG (*HWClose)(PVOID pHead);
- ULONG (*HWGetBytes)(PVOID pHead, PUCHAR pTarget, PULONG pBytes);
- PVOID (*HWGetRxStart)(PVOID pHead);
- INTERRUPT_TYPE (*HWGetIntrType)(PVOID pHead);
- VOID (*HWOtherIntrHandler)(PVOID pHead);
- VOID (*HWLineIntrHandler)(PVOID pHead);
- ULONG (*HWGetRxBufferSize)(PVOID pHead);
- VOID (*HWTxIntrHandler)(PVOID pHead);
- ULONG (*HWPutBytes)(PVOID pHead, PUCHAR pSrc, ULONG NumBytes, PULONG pBytesSent);
- BOOL (*HWPowerOff)(PVOID pHead);
- BOOL (*HWPowerOn)(PVOID pHead);
- VOID (*HWClearDTR)(PVOID pHead);
- VOID (*HWSetDTR)(PVOID pHead);
- VOID (*HWClearRTS)(PVOID pHead);
- VOID (*HWSetRTS)(PVOID pHead);
- BOOL (*HWEnableIR)(PVOID pHead, ULONG BaudRate);
- BOOL (*HWDisableIR)(PVOID pHead);
- VOID (*HWClearBreak)(PVOID pHead);
- VOID (*HWSetBreak)(PVOID pHead);
- BOOL (*HWXmitComChar)(PVOID pHead, UCHAR ComChar);
- ULONG (*HWGetStatus)(PVOID pHead, LPCOMSTAT lpStat);
- VOID (*HWReset)(PVOID pHead);
- VOID (*HWGetModemStatus)(PVOID pHead, PULONG pModemStatus);
- VOID (*HWGetCommProperties)(PVOID pHead, LPCOMMPROP pCommProp);
- VOID (*HWPurgeComm)(PVOID pHead, DWORD fdwAction);
- BOOL (*HWSetDCB)(PVOID pHead, LPDCB pDCB);
- BOOL (*HWSetCommTimeouts)(PVOID pHead, LPCOMMTIMEOUTS lpCommTO);
- BOOL (*HWIoctl)(PVOID pHead, DWORD dwCode,PBYTE pBufIn,DWORD dwLenIn,
- PBYTE pBufOut,DWORD dwLenOut,PDWORD pdwActualOut);
- } HW_VTBL, *PHW_VTBL;
现在来看看在com_init中调用的HWInit——从后面的分析可以知道,其实调用的是SerInit函数
//--------------------------------------------------------------------------------------------------------
//这个函数在MDD和PDD之间按照微软的命名SerXXX,应该叫做服务层
// Converting C Style to C++ Serial Object.
/*
@doc OEM
@func PVOID | SerInit | Initializes device identified by argument.
* This routine sets information controlled by the user
* such as Line control and baud rate. It can also initialize events and
* interrupts, thereby indirectly managing initializing hardware buffers.
* Exported only to driver, called only once per process.
*
@rdesc The return value is a PVOID to be passed back into the HW
dependent layer when HW functions are called.
*/
PVOID
SerInit(
ULONG Identifier, // @parm Device identifier.
PVOID pMddHead, // @parm First argument to mdd callbacks.
PHWOBJ pHWObj // @parm Pointer to our own HW OBJ for this device
)
{
DEBUGMSG (ZONE_CLOSE,(TEXT("+SerInit, 0x%X/r/n"), Identifier));
CSerialPDD * pSerialPDD = NULL;
if (pHWObj) {
DWORD dwIndex= pHWObj->dwIntID;
pHWObj->dwIntID = 0;
pSerialPDD = CreateSerialObject((LPTSTR)Identifier,pMddHead, pHWObj,dwIndex);
}
if (pSerialPDD==NULL) {
ASSERT(FALSE);
LocalFree(pHWObj);
}
DEBUGMSG (ZONE_CLOSE,(TEXT("-SerInit, 0x%X/r/n"), pSerialPDD));
return pSerialPDD;
}
————————————————————————————————————————
//要增加串口的话,就要修改这个函数——增加几个case即可,哈哈,不用修改GetSerialObject了
//这个函数在BSP下的
CSerialPDD * CreateSerialObject(LPTSTR lpActivePath, PVOID pMdd,PHWOBJ pHwObj, DWORD DeviceArrayIndex)
{
CSerialPDD * pSerialPDD = NULL;
switch (DeviceArrayIndex) {
case 0:
pSerialPDD = new CPdd2440Serial1(lpActivePath,pMdd, pHwObj);
break;
case 1:
pSerialPDD = new CPdd2440Serial2(lpActivePath,pMdd, pHwObj);
break;
}
if (pSerialPDD && !pSerialPDD->Init()) {
delete pSerialPDD;
pSerialPDD = NULL;
}
return pSerialPDD;
}
其实其他的串口接口函数com_Read等等,起实现方法都和com_init一致,都是调用上面结构体的函数实现了,
使用了从上到下的设计方法。在这里就不再分析了。现在开始看PDD层。
————————————————————————————————————————————————
转载请标明:作者wogoyixikexie@gliet.桂林电子科技大学一系科协,原文地址:http://blog.csdn.net/gooogleman——如有错误,希望能够留言指出;如果你有更加好的方法,也请在博客后面留言,我会感激你的批评和分享。