/*========================================================================== * * Copyright (C) 1996-1998 Microsoft Corporation. All Rights Reserved. * * File: comport.c * Content: Routines for COM port I/O *@@BEGIN_MSINTERNAL * History: * Date By Reason * ==== == ====== * 4/10/96 kipo created it * 4/12/96 kipo use GlobalAllocPtr to create memory * 4/15/96 kipo added msinternal * 5/22/96 kipo added support for RTSDTR flow control * 6/10/96 kipo added modem support * 6/22/96 kipo added support for EnumConnectionData(); added methods * to NewComPort(). * 7/13/96 kipo added GetComPortAddress() * 8/15/96 kipo added CRC * 8/16/96 kipo loop on WriteFile to send large buffers * 8/19/96 kipo update thread interface * 1/06/97 kipo updated for objects * 2/18/97 kipo allow multiple instances of service provider * 4/08/97 kipo added support for separate modem and serial baud rates * 5/23/97 kipo added support return status codes * 11/24/97 kipo better error messages * 1/30/98 kipo added hTerminateThreadEvent to fix bugs #15220 & #15228 *@@END_MSINTERNAL ***************************************************************************/ #include #include #include "comport.h" #include "dpf.h" #include "macros.h" // constants #define READTIMEOUT 5000 // ms to wait before read times out #define WRITETIMEOUT 5000 // ms to wait before write times out #define WRITETOTALTIMEOUT 5000 // total ms to wait before write times out #define IOBUFFERSIZE 4096 // size of read/write buffers in bytes // prototypes static HRESULT SetupComPort(LPDPCOMPORT globals, HANDLE hCom); static HRESULT ShutdownComPort(LPDPCOMPORT globals); static DWORD ReadComPort(LPDPCOMPORT globals, LPVOID lpvBuffer, DWORD nMaxLength); static DWORD WriteComPort(LPDPCOMPORT globals, LPVOID lpvBuffer, DWORD dwBytesToWrite, BOOLEAN bQueueOnReenter); static HRESULT GetComPortBaudRate(LPDPCOMPORT globals, LPDWORD lpdwBaudRate); static HANDLE GetComPortHandle(LPDPCOMPORT globals); static DWORD WINAPI IOThread(LPVOID lpvParam1); /* * NewComPort * * Creates a com port object of the given size. The readRoutine is called whenever * a byte is received in the input thread. */ HRESULT NewComPort(DWORD dwObjectSize, LPDIRECTPLAYSP lpDPlay, LPREADROUTINE lpReadRoutine, LPDPCOMPORT *lplpObject) { LPDPCOMPORT globals; DWORD dwError; // allocate space for base object and our globals globals =(LPDPCOMPORT) SP_MemAlloc(dwObjectSize); if (globals == NULL) { dwError = GetLastError(); return (HRESULT_FROM_WIN32(dwError)); } // store read routine pointer and IDirectPlaySP pointer globals->lpReadRoutine = lpReadRoutine; globals->lpDPlay = lpDPlay; // fill in base methods globals->Dispose = NULL; globals->Connect = NULL; globals->Disconnect = NULL; globals->Setup = SetupComPort; globals->Shutdown = ShutdownComPort; globals->Read = ReadComPort; globals->Write = WriteComPort; globals->GetBaudRate = GetComPortBaudRate; globals->GetHandle = GetComPortHandle; globals->GetAddress = NULL; globals->GetAddressChoices = NULL; // return base object *lplpObject = globals; return (DP_OK); } /* * SetupComPort * * Sets up the COM port for overlapped I/O with a read thread. */ static HRESULT SetupComPort(LPDPCOMPORT globals, HANDLE hCom) { COMMTIMEOUTS timoutInfo; DWORD dwError; // store com port handle globals->hCom = hCom; // wake up read thread when a byte arrives SetCommMask(globals->hCom, EV_RXCHAR); // setup read/write buffer for I/O SetupComm(globals->hCom, IOBUFFERSIZE, IOBUFFERSIZE); // set time outs timoutInfo.ReadIntervalTimeout = MAXDWORD; timoutInfo.ReadTotalTimeoutMultiplier = 0; timoutInfo.ReadTotalTimeoutConstant = 0; timoutInfo.WriteTotalTimeoutMultiplier = 0; timoutInfo.WriteTotalTimeoutConstant = WRITETOTALTIMEOUT; if (!SetCommTimeouts(globals->hCom, &timoutInfo)) goto Failure; // create I/O event used for overlapped read ZeroMemory(&globals->readOverlapped, sizeof(OVERLAPPED)); globals->readOverlapped.hEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ); // no name if (globals->readOverlapped.hEvent == NULL) goto Failure; // create I/O event used for overlapped write ZeroMemory(&globals->writeOverlapped, sizeof(OVERLAPPED)); globals->writeOverlapped.hEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ); // no name if (globals->writeOverlapped.hEvent == NULL) goto Failure; // create event used to signal I/O thread to exit globals->hTerminateThreadEvent = CreateEvent( NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ); // no name if (globals->hTerminateThreadEvent == NULL) goto Failure; // Init vars for pending queue InitializeCriticalSection(&globals->csWriting); InitBilink(&globals->PendingSends); globals->bWriting=FALSE; // create read thread globals->hIOThread = CreateThread( NULL, // default security 0, // default stack size IOThread, // pointer to thread routine globals, // argument for thread 0, // start it right away &globals->IOThreadID); if (globals->hIOThread == NULL) goto Failure; // adjust thread priority to be higher than normal or the serial port will // back up and the game will slow down or lose messages. SetThreadPriority(globals->hIOThread, THREAD_PRIORITY_ABOVE_NORMAL); ResumeThread(globals->hIOThread); // assert DTR EscapeCommFunction(globals->hCom, SETDTR); return (DP_OK); Failure: dwError = GetLastError(); ShutdownComPort(globals); return (HRESULT_FROM_WIN32(dwError)); } /* * ShutdownComPort * * Stop's all I/O on COM port and releases allocated resources. */ static HRESULT ShutdownComPort(LPDPCOMPORT globals) { if (globals->hIOThread) { // the thread will wake up if we disable event notifications using // SetCommMask. Need to set the hTerminateThread event before doing // this so the thread will know to exit SetEvent(globals->hTerminateThreadEvent); SetCommMask(globals->hCom, 0); WaitForSingleObject(globals->hIOThread, INFINITE); CloseHandle (globals->hIOThread); globals->hIOThread = NULL; // purge any outstanding reads/writes EscapeCommFunction(globals->hCom, CLRDTR); PurgeComm(globals->hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ); } if (globals->hTerminateThreadEvent) { CloseHandle(globals->hTerminateThreadEvent); globals->hTerminateThreadEvent = NULL; } if (globals->readOverlapped.hEvent) { CloseHandle(globals->readOverlapped.hEvent); globals->readOverlapped.hEvent = NULL; } if (globals->writeOverlapped.hEvent) { CloseHandle(globals->writeOverlapped.hEvent); globals->writeOverlapped.hEvent = NULL; } // the com port is shut down globals->hCom = NULL; // Free resources for pending queue DeleteCriticalSection(&globals->csWriting); return (DP_OK); } /* * ReadComPort * * Read bytes from COM port. Will block until all bytes have been read. */ static DWORD ReadComPort(LPDPCOMPORT globals, LPVOID lpvBuffer, DWORD nMaxLength) { COMSTAT ComStat; DWORD dwErrorFlags, dwLength, dwError; ClearCommError(globals->hCom, &dwErrorFlags, &ComStat); dwLength = min(nMaxLength, ComStat.cbInQue); if (dwLength == 0) return (0); if (ReadFile(globals->hCom, lpvBuffer, dwLength, &dwLength, &globals->readOverlapped)) return (dwLength); // deal with error dwError = GetLastError(); if (dwError != ERROR_IO_PENDING) { DPF(0, "Error reading from com port: 0x%8X", dwError); return (0); } // wait for this transmission to complete if (WaitForSingleObject(globals->readOverlapped.hEvent, READTIMEOUT) != WAIT_OBJECT_0) { DPF(0, "Timed out reading com port after waiting %d ms", READTIMEOUT); return (0); } GetOverlappedResult(globals->hCom, &globals->readOverlapped, &dwLength, FALSE); globals->readOverlapped.Offset += dwLength; return (dwLength); } /* * WriteComPort * * Write bytes to COM port. Will block until all bytes have been written. */ static DWORD WriteComPort(LPDPCOMPORT globals, LPVOID lpvBuffer, DWORD dwBytesToWrite, BOOLEAN bQueueOnReenter) { DWORD dwLength; DWORD dwBytesWritten; LPBYTE lpData; DWORD dwError; EnterCriticalSection(&globals->csWriting); if(!globals->bWriting || !bQueueOnReenter){ globals->bWriting=TRUE; LeaveCriticalSection(&globals->csWriting); lpData = lpvBuffer; dwBytesWritten = 0; while (dwBytesWritten < dwBytesToWrite) { dwLength = dwBytesToWrite - dwBytesWritten; if (WriteFile(globals->hCom, lpData, dwLength, &dwLength, &globals->writeOverlapped)) { dwBytesWritten += dwLength; globals->bWriting = FALSE; return (dwBytesWritten); } dwError = GetLastError(); if (dwError != ERROR_IO_PENDING) { DPF(0, "Error writing to com port: 0x%8X", dwError); globals->bWriting = FALSE; return (dwBytesWritten); } // wait for this transmission to complete if (WaitForSingleObject(globals->writeOverlapped.hEvent, WRITETIMEOUT) != WAIT_OBJECT_0) { DPF(0, "Timed out writing to com port after waiting %d ms", WRITETIMEOUT); globals->bWriting = FALSE; return (dwBytesWritten); } if (GetOverlappedResult(globals->hCom, &globals->writeOverlapped, &dwLength, TRUE) == 0) { dwError = GetLastError(); DPF(0, "Error writing to com port: 0x%8X", dwError); /* // a-josbor: this probably should return, but I'm unwilling to make the change so close to ship... globals->bWriting = FALSE; return (dwBytesWritten); */ } globals->writeOverlapped.Offset += dwLength; lpData += dwLength; dwBytesWritten += dwLength; } if(bQueueOnReenter){ // don't drain queue recurrsively. // Drain any pending sends. EnterCriticalSection(&globals->csWriting); while(!EMPTY_BILINK(&globals->PendingSends)){ LPPENDING_SEND lpPendingSend; lpPendingSend=CONTAINING_RECORD(globals->PendingSends.next,PENDING_SEND,Bilink); Delete(&lpPendingSend->Bilink); LeaveCriticalSection(&globals->csWriting); WriteComPort(globals,lpPendingSend->Data,lpPendingSend->dwBytesToWrite,FALSE); SP_MemFree(lpPendingSend); EnterCriticalSection(&globals->csWriting); } globals->bWriting=FALSE; LeaveCriticalSection(&globals->csWriting); } } else { LPPENDING_SEND lpPendingSend; // we are in the middle of writing, so copy this to the pending queue and it will get // sent after the current write. lpPendingSend = (LPPENDING_SEND) SP_MemAlloc(dwBytesToWrite+sizeof(PENDING_SEND)); if(lpPendingSend){ memcpy(lpPendingSend->Data,lpvBuffer,dwBytesToWrite); lpPendingSend->dwBytesToWrite=dwBytesToWrite; InsertBefore(&lpPendingSend->Bilink, &globals->PendingSends); } LeaveCriticalSection(&globals->csWriting); dwBytesWritten=dwBytesToWrite; } return (dwBytesWritten); } /* * GetComPortBaudRate * * Get baud rate of com port. */ static HRESULT GetComPortBaudRate(LPDPCOMPORT globals, LPDWORD lpdwBaudRate) { DCB dcb; DWORD dwError; ZeroMemory(&dcb, sizeof(DCB)); dcb.DCBlength = sizeof(DCB); if (!GetCommState(globals->hCom, &dcb)) goto Failure; *lpdwBaudRate = dcb.BaudRate; return (DP_OK); Failure: dwError = GetLastError(); return (HRESULT_FROM_WIN32(dwError)); } /* * GetComPortHandle * * Get handle of com port. */ static HANDLE GetComPortHandle(LPDPCOMPORT globals) { return (globals->hCom); } /* * IOThread * * Thread to wait for events from COM port. Will call the read routine if an byte * is received. */ DWORD WINAPI IOThread(LPVOID lpvParam1) { LPDPCOMPORT globals = (LPDPCOMPORT) lpvParam1; DWORD dwTransfer, dwEvtMask; OVERLAPPED os; HANDLE events[3]; DWORD dwResult; // create I/O event used for overlapped read ZeroMemory(&os, sizeof(OVERLAPPED)); os.hEvent = CreateEvent(NULL, // no security TRUE, // explicit reset req FALSE, // initial event reset NULL ); // no name if (os.hEvent == NULL) goto CreateEventFailed; if (!SetCommMask(globals->hCom, EV_RXCHAR)) goto SetComMaskFailed; // events to use when waiting for overlapped I/O to complete events[0] = globals->hTerminateThreadEvent; events[1] = os.hEvent; events[2] = (HANDLE) -1; // work around Win95 bugs in WaitForMultipleObjects // spin until this event is signaled during Close. while (WaitForSingleObject(globals->hTerminateThreadEvent, 0) == WAIT_TIMEOUT) { dwEvtMask = 0; // wait for COM port event if (!WaitCommEvent(globals->hCom, &dwEvtMask, &os)) { if (GetLastError() == ERROR_IO_PENDING) { // wait for overlapped I/O to complete or the terminating event // to be set. This lets us terminate this thread even if the I/O // never completes, which fixes a bug on NT 4.0 dwResult = WaitForMultipleObjects(2, events, FALSE, INFINITE); // terminating event was set if (dwResult == WAIT_OBJECT_0) { break; // exit the thread } // I/O completed else if (dwResult == (WAIT_OBJECT_0 + 1)) { GetOverlappedResult(globals->hCom, &os, &dwTransfer, TRUE); os.Offset += dwTransfer; } } } // was a read event if (dwEvtMask & EV_RXCHAR) { if (globals->lpReadRoutine) globals->lpReadRoutine(globals->lpDPlay); // call read routine } } SetComMaskFailed: CloseHandle(os.hEvent); CreateEventFailed: ExitThread(0); return (0); } /* Name : "CRC-32" Width : 32 Poly : 04C11DB7 Init : FFFFFFFF RefIn : True RefOut : True XorOut : FFFFFFFF Check : CBF43926 This is supposedly what Ethernet uses */ #if 0 #define WIDTH 32 #define POLY 0x04C11DB7 #define INITVALUE 0xFFFFFFFF #define REFIN TRUE #define XOROUT 0xFFFFFFFF #define CHECK 0xCBF43926 #define WIDMASK 0xFFFFFFFF // value is (2^WIDTH)-1 #endif /* Name : "CRC-16" Width : 16 Poly : 8005 Init : 0000 RefIn : True RefOut : True XorOut : 0000 Check : BB3D */ #if 1 #define WIDTH 16 #define POLY 0x8005 #define INITVALUE 0 #define REFIN TRUE #define XOROUT 0 #define CHECK 0xBB3D #define WIDMASK 0x0000FFFF // value is (2^WIDTH)-1 #endif #define BITMASK(X) (1L << (X)) DWORD crc_normal(LPBYTE blk_adr, DWORD blk_len, DWORD crctable[]) { DWORD crc = INITVALUE; while (blk_len--) crc = crctable[((crc>>24) ^ *blk_adr++) & 0xFFL] ^ (crc << 8); return (crc ^ XOROUT); } DWORD crc_reflected(LPBYTE blk_adr, DWORD blk_len, DWORD crctable[]) { DWORD crc = INITVALUE; while (blk_len--) crc = crctable[(crc ^ *blk_adr++) & 0xFFL] ^ (crc >> 8); return (crc ^ XOROUT); } DWORD reflect(DWORD v, int b) /* Returns the value v with the bottom b [0,32] bits reflected. */ /* Example: reflect(0x3e23L,3) == 0x3e26 */ { int i; DWORD t = v; for (i = 0; i < b; i++) { if (t & 1L) v |= BITMASK((b-1)-i); else v &= ~BITMASK((b-1)-i); t >>= 1; } return v; } DWORD cm_tab (int index) { int i; DWORD r; DWORD topbit = (DWORD) BITMASK(WIDTH-1); DWORD inbyte = (DWORD) index; if (REFIN) inbyte = reflect(inbyte, 8); r = inbyte << (WIDTH-8); for (i = 0; i < 8; i++) { if (r & topbit) r = (r << 1) ^ POLY; else r <<= 1; } if (REFIN) r = reflect(r, WIDTH); return (r & WIDMASK); } void generate_table(DWORD dwTable[]) { int i; for (i = 0; i < 256; i++) { dwTable[i] = cm_tab(i); } } // todo - make this a static table DWORD gCRCTable[256]; BOOL gTableCreated = FALSE; DWORD GenerateCRC(LPVOID pBuffer, DWORD dwBufferSize) { if (!gTableCreated) { generate_table(gCRCTable); gTableCreated = TRUE; } return (crc_reflected(pBuffer, dwBufferSize, gCRCTable)); }