/*************************************************************************** Name : FCOM.C Comment : Functions for dealing with Windows Comm driver Revision Log Num Date Name Description --- -------- ---------- ----------------------------------------------- ***************************************************************************/ #define USE_DEBUG_CONTEXT DEBUG_CONTEXT_T30_COMM #include "prep.h" #include #include "fcomapi.h" #include "fcomint.h" #include "fdebug.h" ///RSL #include "t30gl.h" #include "glbproto.h" #include "psslog.h" #define FILE_ID FILE_ID_FCOM /***------------- Local Vars and defines ------------------***/ #define AT "AT" #define cr "\r" #define iSyncModemDialog2(pTG, s, l, w1, w2)\ iiModemDialog(pTG, s, l, 990, TRUE, 2, TRUE, (CBPSTR)w1, (CBPSTR)w2, (CBPSTR)(NULL)) #define LONG_DEADCOMMTIMEOUT 60000L #define SHORT_DEADCOMMTIMEOUT 10000L #define WAIT_FCOM_FILTER_FILLCACHE_TIMEOUT 120000 #define WAIT_FCOM_FILTER_READBUF_TIMEOUT 120000 // don't want DEADCOMMTIMEOUT to be greater than 32767, so make sure // buf sizes are always 9000 or less. maybe OK. // Our COMM timeout settings, used in call to SetCommTimeouts. These // values (expect read_interval_timeout) are the default values for // Daytona NT Beta 2, and seem to work fine.. #define READ_INTERVAL_TIMEOUT 100 #define READ_TOTAL_TIMEOUT_MULTIPLIER 0 #define READ_TOTAL_TIMEOUT_CONSTANT 0 #define WRITE_TOTAL_TIMEOUT_MULTIPLIER 0 #define WRITE_TOTAL_TIMEOUT_CONSTANT LONG_DEADCOMMTIMEOUT #define CTRL_P 0x10 #define CTRL_Q 0x11 #define CTRL_S 0x13 #define MYGETCOMMERROR_FAILED 117437834L // Forward declarations static BOOL FComFilterFillCache(PThrdGlbl pTG, UWORD cbSize, LPTO lptoRead); static BOOL CancellPendingIO(PThrdGlbl pTG, HANDLE hComm, LPOVERLAPPED lpOverlapped, LPDWORD lpCounter); BOOL FComDTR(PThrdGlbl pTG, BOOL fEnable) { DEBUG_FUNCTION_NAME(_T("FComDTR")); DebugPrintEx(DEBUG_MSG,"FComDTR = %d", fEnable); if(!GetCommState(pTG->hComm, &(pTG->Comm.dcb))) goto error; DebugPrintEx(DEBUG_MSG, "Before: %02x", pTG->Comm.dcb.fDtrControl); pTG->Comm.dcb.fDtrControl = (fEnable ? DTR_CONTROL_ENABLE : DTR_CONTROL_DISABLE); if(!SetCommState(pTG->hComm, &(pTG->Comm.dcb))) goto error; DebugPrintEx(DEBUG_MSG,"After: %02x", pTG->Comm.dcb.fDtrControl); return TRUE; error: DebugPrintEx(DEBUG_ERR, "Can't Set/Get DCB"); GetCommErrorNT(pTG); return FALSE; } BOOL FComClose(PThrdGlbl pTG) { BOOL fRet = TRUE; DEBUG_FUNCTION_NAME(_T("FComClose")); DebugPrintEx( DEBUG_MSG, "Closing Comm pTG->hComm=%d", pTG->hComm); // // handoff // if (pTG->Comm.fEnableHandoff && pTG->Comm.fDataCall) { if (pTG->hComm) { if (!CloseHandle(pTG->hComm)) { DebugPrintEx( DEBUG_ERR, "Close Handle pTG->hComm failed (ec=%d)", GetLastError()); } else { pTG->hComm = NULL; } } goto lEnd; } // We flush our internal buffer here... if (pTG->Comm.lpovrCur) { int nNumWrote; // Must be 32bits in WIN32 if (!ov_write(pTG, pTG->Comm.lpovrCur, &nNumWrote)) { // error... DebugPrintEx(DEBUG_ERR, "1st ov_write failed"); } pTG->Comm.lpovrCur=NULL; DebugPrintEx(DEBUG_MSG,"done writing mybuf."); } ov_drain(pTG, FALSE); { // Here we will restore settings to what it was when we // took over the port. Currently (9/23/94) we (a) restore the // DCB to pTG->Comm.dcbOrig and (b) If DTR was originally ON, // try to sync the modem to // the original speed by issueing "AT" -- because unimodem does // only a half-hearted attempt at synching before giving up. if(pTG->Comm.fStateChanged && (!pTG->Comm.fEnableHandoff || !pTG->Comm.fDataCall)) { if (!SetCommState(pTG->hComm, &(pTG->Comm.dcbOrig))) { DebugPrintEx( DEBUG_WRN, "Couldn't restore state. Err=0x%lx", (unsigned long) GetLastError()); } DebugPrintEx( DEBUG_MSG, "restored DCB to Baud=%d, fOutxCtsFlow=%d, " " fDtrControl=%d, fOutX=%d", pTG->Comm.dcbOrig.BaudRate, pTG->Comm.dcbOrig.fOutxCtsFlow, pTG->Comm.dcbOrig.fDtrControl, pTG->Comm.dcbOrig.fOutX); pTG->CurrentSerialSpeed = (UWORD) pTG->Comm.dcbOrig.BaudRate; if (pTG->Comm.dcbOrig.fDtrControl==DTR_CONTROL_ENABLE) { // Try to pre-sync modem at new speed before we hand // it back to TAPI. Can't call iiSyncModemDialog here because // it's defined at a higher level. We don't really care // to determine if we get an OK response anyway... if (!iSyncModemDialog2(pTG, AT cr,sizeof(AT cr)-1,"OK", "0")) { DebugPrintEx(DEBUG_ERR,"couldn't sync AT command"); } else { DebugPrintEx(DEBUG_MSG,"Sync AT command OK"); // We flush our internal buffer here... if (pTG->Comm.lpovrCur) { int nNumWrote; // Must be 32bits in WIN32 if (!ov_write(pTG, pTG->Comm.lpovrCur, &nNumWrote)) { // error... DebugPrintEx(DEBUG_ERR, "2nd ov_write failed"); } pTG->Comm.lpovrCur=NULL; DebugPrintEx(DEBUG_MSG,"done writing mybuf."); } ov_drain(pTG, FALSE); } } } pTG->Comm.fStateChanged=FALSE; pTG->Comm.fDataCall=FALSE; } if (pTG->hComm) { DebugPrintEx(DEBUG_MSG,"Closing Comm pTG->hComm=%d.", pTG->hComm); if (!CloseHandle(pTG->hComm)) { DebugPrintEx( DEBUG_ERR, "Close Handle pTG->hComm failed (ec=%d)", GetLastError()); GetCommErrorNT(pTG); fRet=FALSE; } else { pTG->hComm = NULL; } } lEnd: if (pTG->Comm.ovAux.hEvent) CloseHandle(pTG->Comm.ovAux.hEvent); _fmemset(&pTG->Comm.ovAux, 0, sizeof(pTG->Comm.ovAux)); ov_deinit(pTG); pTG->Comm.fCommOpen = FALSE; pTG->Comm.fDoOverlapped=FALSE; return fRet; } ///////////////////////////////////////////////////////////////////////////////////////////// BOOL T30ComInit ( PThrdGlbl pTG ) { DEBUG_FUNCTION_NAME(("T30ComInit")); if (pTG->fCommInitialized) { goto lSecondInit; } pTG->Comm.fDataCall=FALSE; DebugPrintEx(DEBUG_MSG,"Opening Comm Port=%x", pTG->hComm); pTG->CommCache.dwMaxSize = 4096; ClearCommCache(pTG); pTG->CommCache.fReuse = 0; DebugPrintEx( DEBUG_MSG, "OPENCOMM: bufs in=%d out=%d", COM_INBUFSIZE, COM_OUTBUFSIZE); if (BAD_HANDLE(pTG->hComm)) { DebugPrintEx(DEBUG_ERR,"OPENCOMM failed. nRet=%d", pTG->hComm); goto error2; } DebugPrintEx(DEBUG_MSG,"OPENCOMM succeeded hComm=%d", pTG->hComm); pTG->Comm.fCommOpen = TRUE; pTG->Comm.cbInSize = COM_INBUFSIZE; pTG->Comm.cbOutSize = COM_OUTBUFSIZE; // Reset Comm timeouts... { COMMTIMEOUTS cto; _fmemset(&cto, 0, sizeof(cto)); // Out of curiosity, see what they are set at currently... if (!GetCommTimeouts(pTG->hComm, &cto)) { DebugPrintEx( DEBUG_WRN, "GetCommTimeouts fails for handle=0x%lx", pTG->hComm); } else { DebugPrintEx( DEBUG_MSG, "GetCommTimeouts: cto={%lu, %lu, %lu, %lu, %lu}", (unsigned long) cto.ReadIntervalTimeout, (unsigned long) cto.ReadTotalTimeoutMultiplier, (unsigned long) cto.ReadTotalTimeoutConstant, (unsigned long) cto.WriteTotalTimeoutMultiplier, (unsigned long) cto.WriteTotalTimeoutConstant); } cto.ReadIntervalTimeout = READ_INTERVAL_TIMEOUT; cto.ReadTotalTimeoutMultiplier = READ_TOTAL_TIMEOUT_MULTIPLIER; cto.ReadTotalTimeoutConstant = READ_TOTAL_TIMEOUT_CONSTANT; cto.WriteTotalTimeoutMultiplier = WRITE_TOTAL_TIMEOUT_MULTIPLIER; cto.WriteTotalTimeoutConstant = WRITE_TOTAL_TIMEOUT_CONSTANT; if (!SetCommTimeouts(pTG->hComm, &cto)) { DebugPrintEx( DEBUG_WRN, "SetCommTimeouts fails for handle=0x%lx", pTG->hComm); } } pTG->Comm.fCommOpen = TRUE; pTG->Comm.cbInSize = COM_INBUFSIZE; pTG->Comm.cbOutSize = COM_OUTBUFSIZE; _fmemset(&(pTG->Comm.comstat), 0, sizeof(COMSTAT)); if(!GetCommState(pTG->hComm, &(pTG->Comm.dcb))) goto error2; pTG->Comm.dcbOrig = pTG->Comm.dcb; // structure copy. pTG->Comm.fStateChanged=TRUE; lSecondInit: // Use of 2400/ 8N1 and 19200 8N1 is not actually specified // in Class1, but seems to be adhered to be universal convention // watch out for modems that break this! if (pTG->SerialSpeedInit) { pTG->Comm.dcb.BaudRate = pTG->SerialSpeedInit; } else { pTG->Comm.dcb.BaudRate = 57600; // default } pTG->CurrentSerialSpeed = (UWORD) pTG->Comm.dcb.BaudRate; pTG->Comm.dcb.ByteSize = 8; pTG->Comm.dcb.Parity = NOPARITY; pTG->Comm.dcb.StopBits = ONESTOPBIT; pTG->Comm.dcb.fBinary = 1; pTG->Comm.dcb.fParity = 0; /************************************ Pins assignments, & Usage Protective Gnd -- 1 Transmit TxD (DTE to DCE) 3 2 Recv RxD (DCE to DTE) 2 3 RTS (Recv Ready--DTE to DCE) 7 4 CTS (TransReady--DCE to DTE) 8 5 DSR (DCE to DTE) 6 6 signal ground 5 7 CD (DCE to DTR) 1 8 DTR (DTE to DCE) 4 20 RI (DCE to DTE) 9 22 Many 9-pin adaptors & cables use only 6 pins, 2,3,4,5, and 7. We need to worry about this because some modems actively use CTS, ie. pin 8. We don't care about RI and CD (Unless a really weird modem uses CD for flow control). We ignore DSR, but some (not so weird, but not so common either) modems use DSR for flow control. Thought :: Doesn't generate DSR. Seems to tie CD and CTS together DOVE :: Generates only CTS. But the Appletalk-9pin cable only passes 1-5 and pin 8. GVC :: CTS, DSR and CD ************************************/ // CTS -- dunno. There is some evidence that the // modem actually uses it for flow control if (pTG->fEnableHardwareFlowControl) { pTG->Comm.dcb.fOutxCtsFlow = 1; // Using it hangs the output sometimes... } else { pTG->Comm.dcb.fOutxCtsFlow = 0; } // Try ignoring it and see if it works? pTG->Comm.dcb.fOutxDsrFlow = 0; // Never use this?? pTG->Comm.dcb.fRtsControl = RTS_CONTROL_ENABLE; // Current code seems to leave this ON pTG->Comm.dcb.fDtrControl = DTR_CONTROL_DISABLE; pTG->Comm.dcb.fErrorChar = 0; pTG->Comm.dcb.ErrorChar = 0; // Can't change this cause SetCommState() resets hardware. pTG->Comm.dcb.EvtChar = ETX; // set this when we set an EventWait pTG->Comm.dcb.fOutX = 0; // Has to be OFF during HDLC recv phase pTG->Comm.dcb.fInX = 0; // Will this do any good?? // Using flow-control on input is only a good // idea if the modem has a largish buffer pTG->Comm.dcb.fNull = 0; pTG->Comm.dcb.XonChar = CTRL_Q; pTG->Comm.dcb.XoffChar = CTRL_S; pTG->Comm.dcb.XonLim = 100; // Need to set this when BufSize is set pTG->Comm.dcb.XoffLim = 50; // Set this when BufSize is set // actually we *never* use XON/XOFF in recv, so don't worry about this // right now. (Later, when we have smart modems with large buffers, & // we are worried about our ISR buffer filling up before our windows // process gets run, we can use this). Some tuning will be reqd. pTG->Comm.dcb.EofChar = 0; pTG->Comm.dcb.fAbortOnError = 0; // RSL don't fail if minor problems if(!SetCommState(pTG->hComm, &(pTG->Comm.dcb))) goto error2; if (pTG->fCommInitialized) { return TRUE; } pTG->Comm.fStateChanged=TRUE; if (!SetCommMask(pTG->hComm, 0)) // all events off { DebugPrintEx(DEBUG_ERR, "SetCommMask failed (ec=%d)",GetLastError()); } pTG->Comm.lpovrCur=NULL; _fmemset(&pTG->Comm.ovAux,0, sizeof(pTG->Comm.ovAux)); pTG->Comm.ovAux.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (pTG->Comm.ovAux.hEvent==NULL) { DebugPrintEx(DEBUG_ERR, "FComOpen: couldn't create event"); goto error2; } if (!ov_init(pTG)) { CloseHandle(pTG->Comm.ovAux.hEvent); pTG->Comm.ovAux.hEvent=0; goto error2; } return TRUE; error2: DebugPrintEx(DEBUG_ERR, "FComOpen failed"); GetCommErrorNT(pTG); if (pTG->Comm.fCommOpen) { FComClose(pTG); } return FALSE; } BOOL FComSetBaudRate(PThrdGlbl pTG, UWORD uwBaudRate) { DEBUG_FUNCTION_NAME(("FComSetBaudRate")); DebugPrintEx(DEBUG_MSG,"Setting BAUDRATE=%d",uwBaudRate); if(!GetCommState( pTG->hComm, &(pTG->Comm.dcb))) goto error; pTG->Comm.dcb.BaudRate = uwBaudRate; pTG->CurrentSerialSpeed = uwBaudRate; if(!SetCommState( pTG->hComm, &(pTG->Comm.dcb))) goto error; return TRUE; error: DebugPrintEx(DEBUG_ERR, "Set Baud Rate --- Can't Get/Set DCB"); GetCommErrorNT(pTG); return FALSE; } BOOL FComInXOFFHold(PThrdGlbl pTG) { DEBUG_FUNCTION_NAME(_T("FComInXOFFHold")); GetCommErrorNT(pTG); if(pTG->Comm.comstat.fXoffHold) { DebugPrintEx(DEBUG_MSG,"In XOFF hold"); return TRUE; } else { return FALSE; } } BOOL FComXon(PThrdGlbl pTG, BOOL fEnable) { DEBUG_FUNCTION_NAME(_T("FComXon")); if (pTG->fEnableHardwareFlowControl) { DebugPrintEx( DEBUG_MSG, "FComXon = %d IGNORED : h/w flow control", fEnable); return TRUE; } DebugPrintEx(DEBUG_MSG,"FComXon = %d",fEnable); // enables/disables flow control // returns TRUE on success, false on failure if(!GetCommState( pTG->hComm, &(pTG->Comm.dcb))) goto error; DebugPrintEx(DEBUG_MSG,"FaxXon Before: %02x", pTG->Comm.dcb.fOutX); pTG->Comm.dcb.fOutX = fEnable; if(!SetCommState(pTG->hComm, &(pTG->Comm.dcb))) goto error; DebugPrintEx(DEBUG_MSG,"After: %02x",pTG->Comm.dcb.fOutX); return TRUE; error: DebugPrintEx(DEBUG_ERR,"Can't Set/Get DCB"); GetCommErrorNT(pTG); return FALSE; } // queue=0 --> receiving queue // queue=1 --> transmitting queue void FComFlushQueue(PThrdGlbl pTG, int queue) { int nRet; DWORD lRet; DEBUG_FUNCTION_NAME(_T("FComFlushQueue")); DebugPrintEx(DEBUG_MSG, "FlushQueue = %d", queue); if (queue == 1) { DebugPrintEx(DEBUG_MSG,"ClearCommCache"); ClearCommCache(pTG); } nRet = PurgeComm(pTG->hComm, (queue ? PURGE_RXCLEAR : PURGE_TXCLEAR)); if(!nRet) { DebugPrintEx(DEBUG_ERR,"FlushComm failed (ec=%d)", GetLastError()); GetCommErrorNT(pTG); // Throwing away errors that happen here. // No good reason for it! } if(queue == 1) { FComInFilterInit(pTG); } else // (queue == 0) { // Let's dump any stuff we may have in *our* buffer. if (pTG->Comm.lpovrCur && pTG->Comm.lpovrCur->dwcb) { DebugPrintEx( DEBUG_WRN, "Clearing NonNULL pTG->Comm.lpovrCur->dwcb=%lx", (unsigned long) pTG->Comm.lpovrCur->dwcb); pTG->Comm.lpovrCur->dwcb=0; ov_unget(pTG, pTG->Comm.lpovrCur); pTG->Comm.lpovrCur=NULL; } // Lets "drain" -- should always return immediately, because // we have just purged the output comm buffers. if (pTG->Comm.fovInited) { DebugPrintEx(DEBUG_MSG," before ov_drain"); ov_drain(pTG, FALSE); DebugPrintEx(DEBUG_MSG," after ov_drain"); } // just incase it got stuck due to a mistaken XOFF lRet = EscapeCommFunction(pTG->hComm, SETXON); if(!lRet) { // Returns the comm error value CE!! DebugPrintEx(DEBUG_MSG,"EscapeCommFunc(SETXON) returned %d", lRet); GetCommErrorNT(pTG); } } } /*************************************************************************** Name : FComDrain(BOOL fLongTO, BOOL fDrainComm) Purpose : Drain internal buffers. If fDrainComm, wait for Comm ISR Output buffer to drain. Returns when buffer is drained or if no progress is made for DRAINTIMEOUT millisecs. (What about XOFFed sections? Need to set Drain timeout high enough) Parameters: Returns : TRUE on success (buffer drained) FALSE on failure (error or timeout) Revision Log Num Date Name Description --- -------- ---------- ----------------------------------------------- 101 06/03/92 arulm Created it in a new incarnation ***************************************************************************/ // This timeout has to be low at time and high at others. We want it low // so we don't spend too much time trying to talk to a non-existent modem // during Init/Setup. However in PhaseC, when the timer expires all we // do is abort and kill everything. So it serves no purpose to make it // too low. With the Hayes ESP FIFO card long stretches can elapse without // any visible "progress", so we fail with that card because we think // "no progress" is being made // So....make it short for init/install // but not too short. Some cmds (e.g. AT&F take a long time) // Used to be 800ms & seemed to work then, so leave it at that #define SHORT_DRAINTIMEOUT 800 // So....make it long for PhaseC // 4secs should be about long enough #define LONG_DRAINTIMEOUT 4000 BOOL FComDrain(PThrdGlbl pTG, BOOL fLongTO, BOOL fDrainComm) { WORD wTimer = 0; UWORD cbPrevOut = 0xFFFF; BOOL fStuckOnce=FALSE; BOOL fRet=FALSE; DEBUG_FUNCTION_NAME(_T("FComDrain")); // We flush our internal buffer here... if (pTG->Comm.lpovrCur) { int nNumWrote; // Must be 32bits in WIN32 if (!ov_write(pTG, pTG->Comm.lpovrCur, &nNumWrote)) goto done; pTG->Comm.lpovrCur=NULL; DebugPrintEx(DEBUG_MSG,"done writing mybuf."); } if (!fDrainComm) { fRet=TRUE; goto done; } // +++ Here we drain all our overlapped events.. // If we setup the system comm timeouts properly, we // don't need to do anything else, except for the XOFF/XON // stuff... fRet = ov_drain(pTG, fLongTO); goto done; done: return fRet; //+++ was (cbOut == 0); } /*************************************************************************** Name : FComDirectWrite(, lpb, cb) Purpose : Write cb bytes starting from lpb to pTG->Comm. If Comm buffer is full, set up notifications and timers and wait until space is available. Returns when all bytes have been written to the Comm buffer or if no progress is made for WRITETIMEOUT millisecs. (What about XOFFed sections? Need to set timeout high enough) Parameters: , lpb, cb Returns : Number of bytes written, i.e. cb on success and Comm.lpovrCur) { pTG->Comm.lpovrCur = ov_get(pTG); if (!pTG->Comm.lpovrCur) goto error; } dwcbCopy = OVBUFSIZE-pTG->Comm.lpovrCur->dwcb; if (dwcbCopy>cbLeft) { dwcbCopy = cbLeft; } // Copy as much as we can to the overlapped buffer... _fmemcpy(pTG->Comm.lpovrCur->rgby+pTG->Comm.lpovrCur->dwcb, lpb, dwcbCopy); cbLeft -= dwcbCopy; pTG->Comm.lpovrCur->dwcb += dwcbCopy; lpb += dwcbCopy; // Let's always update comstat here... GetCommErrorNT(pTG); DebugPrintEx( DEBUG_MSG, "OutQ has %d fDoOverlapped=%d", pTG->Comm.comstat.cbOutQue, pTG->Comm.fDoOverlapped); // We write to comm if our buffer is full or the comm buffer is // empty or if we're not in overlapped mode... if ( !pTG->Comm.fDoOverlapped || pTG->Comm.lpovrCur->dwcb>=OVBUFSIZE || !pTG->Comm.comstat.cbOutQue) { BOOL fRet = ov_write(pTG, pTG->Comm.lpovrCur, &dwcbWrote); pTG->Comm.lpovrCur=NULL; if (!fRet) { goto error; } } } // while (cbLeft) return cb; error: return 0; } /*************************************************************************** Name : FComFilterReadLine(, lpb, cbSize, pto) Purpose : Reads upto cbSize bytes from Comm into memory starting from lpb. If Comm buffer is empty, set up notifications and timers and wait until characters are available. Filters out DLE characters. i.e DLE-DLE is reduced to a single DLE, DLE ETX is left intact and DLE-X is deleted. Returns success (+ve bytes count) when CR-LF has been encountered, and returns failure (-ve bytes count). when either (a) cbSize bytes have been read (i.e. buffer is full) or (b) PTO times out or an error is encountered. It is critical that this function never returns a timeout, as long as data is still pouring/trickling in. This implies two things (a) FIRST get all that is in the InQue (not more than a line, though), THEN check the timeout. (b) Ensure that at least 1 char-arrival-time at the slowest Comm speed passes between the function entry point and the last time we check for a byte, or between two consecutive checks for a byte, before we return a timeout. Therefor conditions to return a timeout are Macro timeout over and inter-char timeout over. In theory the slowest speed we need to worry about is 2400, because that's the slowest we run the Comm at, but be paranoid and assume the modem sends the chars at the same speed that they come in the wire, so slowest is now 300. 1 char-arrival-time is now 1000 / (300/8) == 26.67ms. If pto expires, returns error, i.e. -ve of the number of bytes read. Returns : Number of bytes read, i.e. cb on success and -ve of number of bytes read on timeout. 0 is a timeout error with no bytes read. Revision Log Num Date Name Description --- -------- ---------- ----------------------------------------------- 101 06/03/92 arulm Created it ***************************************************************************/ // totally arbitrary #define READLINETIMEOUT 50 #define ONECHARTIME (30 * 2) // see above *2 to be safe // void WINAPI OutputDebugStr(LPSTR); // char szJunk[200]; // Read a line of size no more than cbSize to lpb // #undef USE_DEBUG_CONTEXT #define USE_DEBUG_CONTEXT DEBUG_CONTEXT_T30_CLASS1 SWORD FComFilterReadLine(PThrdGlbl pTG, LPB lpb, UWORD cbSize, LPTO lptoRead) { WORD wTimer = 0; UWORD cbIn = 0, cbGot = 0; LPB lpbNext; BOOL fPrevDLE = 0; SWORD i, beg; TO to; DWORD dwLoopCount = 0; DEBUG_FUNCTION_NAME(_T("FComFilterReadLine")); DebugPrintEx( DEBUG_MSG, "lpb=0x%08lx cb=%d timeout=%lu", lpb, cbSize, lptoRead->ulTimeout); cbSize--; // make room for terminal NULL lpbNext = lpb; // we write the NULL to *lpbNext, so init this NOW! cbGot = 0; // return value (even err return) is cbGot. Init NOW!! fPrevDLE=0; pTG->fLineTooLongWasIgnored = FALSE; // // check the cache first. // Maybe the cache contains data if ( ! pTG->CommCache.dwCurrentSize) { DebugPrintEx(DEBUG_MSG,"Cache is empty. Resetting comm cache."); ClearCommCache(pTG); // Try to fill the cache if (!FComFilterFillCache(pTG, cbSize, lptoRead)) { DebugPrintEx(DEBUG_ERR,"FillCache failed"); goto error; } } while (1) { if ( ! pTG->CommCache.dwCurrentSize) { DebugPrintEx(DEBUG_ERR, "Cache is empty after FillCache"); goto error; } DebugPrintEx( DEBUG_MSG, "Cache: size=%d, offset=%d", pTG->CommCache.dwCurrentSize, pTG->CommCache.dwOffset); lpbNext = pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset; if (pTG->CommCache.dwCurrentSize >= 3) { DebugPrintEx( DEBUG_MSG, "0=%x 1=%x 2=%x 3=%x 4=%x 5=%x 6=%x 7=%x 8=%x /" " %d=%x, %d=%x, %d=%x", *lpbNext, *(lpbNext+1), *(lpbNext+2), *(lpbNext+3), *(lpbNext+4), *(lpbNext+5), *(lpbNext+6), *(lpbNext+7), *(lpbNext+8), pTG->CommCache.dwCurrentSize-3, *(lpbNext+ pTG->CommCache.dwCurrentSize-3), pTG->CommCache.dwCurrentSize-2, *(lpbNext+ pTG->CommCache.dwCurrentSize-2), pTG->CommCache.dwCurrentSize-1, *(lpbNext+ pTG->CommCache.dwCurrentSize-1) ); } else { DebugPrintEx(DEBUG_MSG,"1=%x 2=%x", *lpbNext, *(lpbNext+1) ); } for (i=0, beg=0; i< (SWORD) pTG->CommCache.dwCurrentSize; i++) { if (i > 0 ) { // check from the second char in the buffer for CR + LF. if ( ( *(pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset + i - 1) == CR ) && ( *(pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset + i) == LF ) ) { if ( i - beg >= cbSize) { // line too long. try next one. DebugPrintEx( DEBUG_ERR, "Line len=%d is longer than bufsize=%d " " Found in cache pos=%d, CacheSize=%d, Offset=%d", i-beg, cbSize, i+1, pTG->CommCache.dwCurrentSize, pTG->CommCache.dwOffset); beg = i + 1; pTG->fLineTooLongWasIgnored = TRUE; continue; } // found the line. CopyMemory (lpb, (pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset + beg), (i - beg + 1) ); pTG->CommCache.dwOffset += (i+1); pTG->CommCache.dwCurrentSize -= (i+1); *(lpb+i-beg+1) = '\0'; // Make sure that the line is null terminated DebugPrintEx( DEBUG_MSG, "Found in cache pos=%d, CacheSize=%d, Offset=%d", i+1, pTG->CommCache.dwCurrentSize, pTG->CommCache.dwOffset); // return how much bytes in the line return ( i-beg+1 ); } } } // we get here if we didn't find CrLf in Cache DebugPrintEx(DEBUG_MSG,"Cache wasn't empty but we didn't find CrLf"); // if cache too big (and we have not found anything anyway) --> clean it if (pTG->CommCache.dwCurrentSize >= cbSize) { DebugPrintEx(DEBUG_MSG, "ClearCommCache"); ClearCommCache(pTG); } else if ( ! pTG->CommCache.dwCurrentSize) { DebugPrintEx(DEBUG_MSG,"Cache is empty. Resetting comm cache."); ClearCommCache(pTG); } // If the modem returned part of a line, the rest of the line should already be // in the serial buffer. In this case, read again with a short timeout (500ms). // However, some modems can get into a state where they give random data forever. // So... give the modem only one "second chance". if (dwLoopCount && (!checkTimeOut(pTG, lptoRead))) { DebugPrintEx(DEBUG_ERR,"Total Timeout passed"); goto error; } dwLoopCount++; to.ulStart = 0; to.ulTimeout = 0; to.ulEnd = 500; if ( ! FComFilterFillCache(pTG, cbSize, &to/*lptoRead*/) ) { DebugPrintEx(DEBUG_ERR, "FillCache failed"); goto error; } } error: ClearCommCache(pTG); return (0); } // Read from the comm port. // The input is written to 'the end' of pTG->CommCache.lpBuffer buffer. // returns TRUE on success, FALSE - otherwise. BOOL FComFilterFillCache(PThrdGlbl pTG, UWORD cbSize, LPTO lptoRead) { WORD wTimer = 0; UWORD cbGot = 0, cbAvail = 0; DWORD cbRequested = 0; char lpBuffer[4096]; // ATTENTION: We do overlapped read into the stack!! LPB lpbNext; int nNumRead; // _must_ be 32 bits in Win32!! LPOVERLAPPED lpOverlapped; COMMTIMEOUTS cto; DWORD dwLastErr; DWORD dwTimeoutRead; DWORD dwTimeoutOrig; DWORD dwTickCountOrig; char *pSrc; char *pDest; DWORD i, j; DWORD dwErr; COMSTAT ErrStat; DWORD NumHandles=2; HANDLE HandlesArray[2]; DWORD WaitResult; DEBUG_FUNCTION_NAME(_T("FComFilterFillCache")); HandlesArray[1] = pTG->AbortReqEvent; dwTickCountOrig = GetTickCount(); dwTimeoutOrig = (DWORD) (lptoRead->ulEnd - lptoRead->ulStart); dwTimeoutRead = dwTimeoutOrig; lpbNext = lpBuffer; DebugPrintEx( DEBUG_MSG, "cb=%d to=%d", cbSize, dwTimeoutRead); // we want to request the read such that we will be back // no much later than dwTimeOut either with the requested // amount of data or without it. cbRequested = cbSize; do { // use COMMTIMEOUTS to detect there are no more data cto.ReadIntervalTimeout = 50; // 30 ms is during negotiation frames; del(ff, 2ndchar> = 54 ms with USR 28.8 cto.ReadTotalTimeoutMultiplier = 0; cto.ReadTotalTimeoutConstant = dwTimeoutRead; // RSL may want to set first time ONLY*/ cto.WriteTotalTimeoutMultiplier = WRITE_TOTAL_TIMEOUT_MULTIPLIER; cto.WriteTotalTimeoutConstant = WRITE_TOTAL_TIMEOUT_CONSTANT; if (!SetCommTimeouts(pTG->hComm, &cto)) { DebugPrintEx( DEBUG_ERR, "SetCommTimeouts fails for handle %lx , le=%x", pTG->hComm, GetLastError()); } lpOverlapped = &pTG->Comm.ovAux; (lpOverlapped)->Internal = 0; (lpOverlapped)->InternalHigh = 0; (lpOverlapped)->Offset = 0; (lpOverlapped)->OffsetHigh = 0; if ((lpOverlapped)->hEvent) { if (!ResetEvent((lpOverlapped)->hEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } nNumRead = 0; DebugPrintEx( DEBUG_MSG, "Before ReadFile Req=%d", cbRequested); if (! ReadFile( pTG->hComm, lpbNext, cbRequested, &nNumRead, lpOverlapped) ) { if ( (dwLastErr = GetLastError() ) == ERROR_IO_PENDING) { // // We want to be able to un-block ONCE only from waiting on I/O when the AbortReqEvent is signaled. // if (pTG->fAbortRequested) { if (pTG->fOkToResetAbortReqEvent && (!pTG->fAbortReqEventWasReset)) { DebugPrintEx(DEBUG_MSG,"RESETTING AbortReqEvent"); pTG->fAbortReqEventWasReset = TRUE; if (!ResetEvent(pTG->AbortReqEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } pTG->fUnblockIO = TRUE; } HandlesArray[0] = pTG->Comm.ovAux.hEvent; // Remeber that: HandlesArray[1] = pTG->AbortReqEvent; if (pTG->fUnblockIO) { NumHandles = 1; // We don't want to be disturb by an abort } else { NumHandles = 2; } if (pTG->fStallAbortRequest) { // this is used to complete a whole IO operation (presumably a short one) // when this flag is set, the IO won't be disturbed by the abort event // this flag should NOT be set for long periods of time since abort // is disabled while it is set. DebugPrintEx(DEBUG_MSG,"StallAbortRequest, do not abort here..."); NumHandles = 1; // We don't want to be disturb by an abort pTG->fStallAbortRequest = FALSE; } DebugPrintEx(DEBUG_MSG,"Waiting for %d Event(s)",NumHandles); WaitResult = WaitForMultipleObjects(NumHandles, HandlesArray, FALSE, WAIT_FCOM_FILTER_FILLCACHE_TIMEOUT); DebugPrintEx(DEBUG_MSG,"WaitForMultipleObjects returned %d",WaitResult); if (WaitResult == WAIT_TIMEOUT) { DebugPrintEx(DEBUG_ERR, "WaitForMultipleObjects TIMEOUT"); goto error; } if (WaitResult == WAIT_FAILED) { DebugPrintEx( DEBUG_ERR, "WaitForMultipleObjects FAILED le=%lx NumHandles=%d", GetLastError(), NumHandles); goto error; } if ( (NumHandles == 2) && (WaitResult == WAIT_OBJECT_0 + 1) ) { // There was an abort by the user and that there are still pending reads // Lets cancell the pending I/O operations, and then wait for the overlapped results pTG->fUnblockIO = TRUE; DebugPrintEx(DEBUG_MSG,"ABORTed"); goto error; } // The IO operation was complete. Lets try to get the overlapped result. if ( ! GetOverlappedResult ( pTG->hComm, lpOverlapped, &nNumRead, TRUE) ) { DebugPrintEx(DEBUG_ERR, "GetOverlappedResult le=%x", GetLastError()); if (! ClearCommError( pTG->hComm, &dwErr, &ErrStat) ) { DebugPrintEx(DEBUG_ERR, "ClearCommError le=%x", GetLastError()); } else { DebugPrintEx( DEBUG_ERR, "ClearCommError dwErr=%x ErrSTAT: Cts=%d Dsr=%d " " Rls=%d XoffHold=%d XoffSent=%d fEof=%d Txim=%d " " In=%d Out=%d", dwErr, ErrStat.fCtsHold, ErrStat.fDsrHold, ErrStat.fRlsdHold, ErrStat.fXoffHold, ErrStat.fXoffSent, ErrStat.fEof, ErrStat.fTxim, ErrStat.cbInQue, ErrStat.cbOutQue); } goto errorWithoutCancel; } } else { DebugPrintEx(DEBUG_ERR, "ReadFile"); // We will do cancell pending IO, should we? goto errorWithoutCancel; } } else { DebugPrintEx(DEBUG_WRN,"ReadFile returned w/o WAIT"); } DebugPrintEx( DEBUG_MSG, "After ReadFile Req=%d Ret=%d", cbRequested, nNumRead); // How much bytes we actually have read cbAvail = (UWORD)nNumRead; if (!cbAvail) { // With USB modems, ReadFile sometimes returns after 60ms with 0 bytes // read regardless of supplied timeout. So, if we got 0 bytes, check // whether timeout has passed, and if not - issue another ReadFile. DWORD dwTimePassed = GetTickCount() - dwTickCountOrig; // Allow for 20ms inaccuracy in TickCount if (dwTimePassed+20 >= dwTimeoutOrig) { DebugPrintEx(DEBUG_ERR, "0 read, to=%d, passed=%d", dwTimeoutOrig, dwTimePassed); goto errorWithoutCancel; } dwTimeoutRead = dwTimeoutOrig - dwTimePassed; DebugPrintEx( DEBUG_WRN, "0 read, to=%d, passed=%d, re-reading with to=%d", dwTimeoutOrig, dwTimePassed, dwTimeoutRead); } } while (cbAvail==0); // filter DLE stuff pSrc = lpbNext; pDest = pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset+ pTG->CommCache.dwCurrentSize; for (i=0, j=0; iCommCache.dwCurrentSize += j; return TRUE; error: if (!CancellPendingIO(pTG , pTG->hComm , lpOverlapped , (LPDWORD) &nNumRead)) { DebugPrintEx(DEBUG_ERR, "failed when call to CancellPendingIO"); } errorWithoutCancel: return FALSE; } /*************************************************************************** Name : FComDirectReadBuf(, lpb, cbSize, lpto, pfEOF) Purpose : Reads upto cbSize bytes from Comm into memory starting from lpb. If Comm buffer is empty, set up notifications and timers and wait until characters are available. Returns when success (+ve byte count) either (a) cbSize bytes have been read or (b) DLE-ETX has been encountered (in which case *pfEOF is set to TRUE). Does no filtering. Reads the Comm buffer in large quanta. If lpto expires, returns error, i.e. -ve of the number of bytes read. Returns : Number of bytes read, i.e. cb on success and -ve of number of bytes read on timeout. 0 is a timeout error with no bytes read. Revision Log Num Date Name Description --- -------- ---------- ----------------------------------------------- 101 06/03/92 arulm Created it ***************************************************************************/ // +++ #define READBUFQUANTUM (pTG->Comm.cbInSize / 8) // totally arbitrary // +++ #define READBUFTIMEOUT 200 // *lpswEOF is 1 on Class1 EOF, 0 on non-EOF, -1 on Class2 EOF, -2 on error -3 on timeout UWORD FComFilterReadBuf ( PThrdGlbl pTG, LPB lpb, UWORD cbSize, LPTO lptoRead, BOOL fClass2, LPSWORD lpswEOF ) { WORD wTimer = 0; UWORD cbGot = 0, cbAvail = 0; UWORD cbUsed = 0; DWORD cbRequested = 0; LPB lpbNext; int nNumRead = 0; // _must_ be 32 bits in Win32!! LPOVERLAPPED lpOverlapped; COMMTIMEOUTS cto; DWORD dwLastErr; DWORD dwTimeoutRead; DWORD cbFromCache = 0; DWORD dwErr; COMSTAT ErrStat; DWORD NumHandles=2; HANDLE HandlesArray[2]; DWORD WaitResult; DEBUG_FUNCTION_NAME(_T("FComFilterReadBuf")); HandlesArray[1] = pTG->AbortReqEvent; dwTimeoutRead = (DWORD) (lptoRead->ulEnd - lptoRead->ulStart); DebugPrintEx( DEBUG_MSG, "lpb=0x%08lx cbSize=%d to=%d", lpb, cbSize, dwTimeoutRead); // Dont want to take ^Q/^S from modem to // be XON/XOFF in the receive data phase!! *lpswEOF=0; // Leave TWO spaces at start to make sure Out pointer will // never get ahead of the In pointer in StripBuf, even // if the last byte of prev block was DLE & first byte // of this one is SUB (i.e need to insert two DLEs in // output). // Save a byte at end for the NULL terminator (Why? Dunno...) lpb += 2; cbSize -= 3; cbRequested = cbSize; for(lpbNext=lpb;;) { DebugPrintEx( DEBUG_MSG, "cbSize=%d cbGot=%d cbAvail=%d", cbSize, cbGot, cbAvail); if((cbSize - cbGot) < cbAvail) { cbAvail = cbSize - cbGot; } if( (!cbGot) && !checkTimeOut(pTG, lptoRead) ) { // No chars available *and* lptoRead expired DebugPrintEx( DEBUG_ERR, "ReadLn:Timeout %ld-toRd=%ld start=%ld", GetTickCount(), lptoRead->ulTimeout, lptoRead->ulStart); *lpswEOF = -3; goto done; } // check Comm cache first (AT+FRH leftovers) if ( pTG->CommCache.fReuse && pTG->CommCache.dwCurrentSize ) { DebugPrintEx( DEBUG_MSG, "CommCache will REUSE %d offset=%d 0=%x 1=%x", pTG->CommCache.dwCurrentSize, pTG->CommCache.dwOffset, *(pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset), *(pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset+1) ); if ( pTG->CommCache.dwCurrentSize >= cbRequested) { CopyMemory (lpbNext, pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset, cbRequested); pTG->CommCache.dwOffset += cbRequested; pTG->CommCache.dwCurrentSize -= cbRequested; cbAvail = (UWORD) cbRequested; cbRequested = 0; DebugPrintEx(DEBUG_MSG,"CommCache still left; no need to read"); goto l_merge; } else { cbFromCache = pTG->CommCache.dwCurrentSize; CopyMemory (lpbNext, pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset, cbFromCache); ClearCommCache(pTG); cbRequested -= cbFromCache; DebugPrintEx(DEBUG_MSG,"CommCache used all %d",cbFromCache); } } // use COMMTIMEOUTS to detect there are no more data cto.ReadIntervalTimeout = 20; // 0 RSL make 15 later cto.ReadTotalTimeoutMultiplier = 0; cto.ReadTotalTimeoutConstant = dwTimeoutRead; // RSL may want to set first time ONLY cto.WriteTotalTimeoutMultiplier = WRITE_TOTAL_TIMEOUT_MULTIPLIER; cto.WriteTotalTimeoutConstant = WRITE_TOTAL_TIMEOUT_CONSTANT; if (!SetCommTimeouts(pTG->hComm, &cto)) { DebugPrintEx( DEBUG_ERR, "SetCommTimeouts fails for handle %lx , le=%x", pTG->hComm, GetLastError()); } lpOverlapped = &pTG->Comm.ovAux; (lpOverlapped)->Internal = (lpOverlapped)->InternalHigh = (lpOverlapped)->Offset = \ (lpOverlapped)->OffsetHigh = 0; if ((lpOverlapped)->hEvent) { if(!ResetEvent((lpOverlapped)->hEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } nNumRead = 0; DebugPrintEx(DEBUG_MSG,"Before ReadFile Req=%d",cbRequested); if (! ReadFile( pTG->hComm, lpbNext+cbFromCache, cbRequested, &nNumRead, &pTG->Comm.ovAux) ) { if ( (dwLastErr = GetLastError() ) == ERROR_IO_PENDING) { // We want to be able to un-block ONCE only from waiting on I/O when the AbortReqEvent is signaled. // if (pTG->fAbortRequested) { if (pTG->fOkToResetAbortReqEvent && (!pTG->fAbortReqEventWasReset)) { DebugPrintEx(DEBUG_MSG,"RESETTING AbortReqEvent"); pTG->fAbortReqEventWasReset = TRUE; if (!ResetEvent(pTG->AbortReqEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } pTG->fUnblockIO = TRUE; *lpswEOF = -2; goto error; } HandlesArray[0] = pTG->Comm.ovAux.hEvent; HandlesArray[1] = pTG->AbortReqEvent; if (pTG->fUnblockIO) { NumHandles = 1; } else { NumHandles = 2; } WaitResult = WaitForMultipleObjects(NumHandles, HandlesArray, FALSE, WAIT_FCOM_FILTER_READBUF_TIMEOUT); if (WaitResult == WAIT_TIMEOUT) { DebugPrintEx(DEBUG_ERR, "WaitForMultipleObjects TIMEOUT"); *lpswEOF = -3; goto error; } if (WaitResult == WAIT_FAILED) { DebugPrintEx( DEBUG_ERR, "WaitForMultipleObjects FAILED le=%lx", GetLastError()); *lpswEOF = -3; goto error; } if ( (NumHandles == 2) && (WaitResult == WAIT_OBJECT_0 + 1) ) { // We have an abort and also there is pending IO ReadFile. pTG->fUnblockIO = TRUE; DebugPrintEx(DEBUG_MSG,"ABORTed"); *lpswEOF = -2; goto error; } if ( ! GetOverlappedResult ( pTG->hComm, &pTG->Comm.ovAux, &nNumRead, TRUE) ) { DebugPrintEx(DEBUG_ERR, "GetOverlappedResult le=%x", GetLastError()); if (! ClearCommError( pTG->hComm, &dwErr, &ErrStat) ) { DebugPrintEx(DEBUG_ERR, "ClearCommError le=%x", GetLastError()); } else { DebugPrintEx( DEBUG_WRN, "ClearCommError dwErr=%x ErrSTAT: Cts=%d " "Dsr=%d Rls=%d XoffHold=%d XoffSent=%d " "fEof=%d Txim=%d In=%d Out=%d", dwErr, ErrStat.fCtsHold, ErrStat.fDsrHold, ErrStat.fRlsdHold, ErrStat.fXoffHold, ErrStat.fXoffSent, ErrStat.fEof, ErrStat.fTxim, ErrStat.cbInQue, ErrStat.cbOutQue); } *lpswEOF = -3; goto done; } } else { DebugPrintEx(DEBUG_ERR, "ReadFile le=%x", dwLastErr); *lpswEOF = -3; goto done; } } else { DebugPrintEx(DEBUG_WRN,"ReadFile returned w/o WAIT"); } DebugPrintEx( DEBUG_MSG, "After ReadFile Req=%d Ret=%d", cbRequested, nNumRead); cbAvail = (UWORD) (nNumRead + cbFromCache); l_merge: if (!cbAvail) { DebugPrintEx(DEBUG_MSG,"cbAvail = %d --> continue", cbAvail); continue; } // else we just drop through // try to catch COMM read problems DebugPrintEx( DEBUG_MSG, "Just read %d bytes, from cache =%d, " "log [%x .. %x], 1st=%x last=%x", nNumRead, cbFromCache, pTG->CommLogOffset, (pTG->CommLogOffset+cbAvail), *lpbNext, *(lpbNext+cbAvail-1) ); pTG->CommLogOffset += cbAvail; // Strip the redunant chars. The return value is the number of chars we got. cbAvail = FComStripBuf(pTG, lpbNext-2, lpbNext, cbAvail, fClass2, lpswEOF, &cbUsed); // If the requested buffer size is small, and the buffer includes some // chars, FComStripBuf could return 0. In this case, reset cbRequested, so // that we read the next characters correctly. if (cbAvail==0) { cbRequested = cbSize - cbGot; } if (fClass2) { // for class 2 FComFilterReadBuf should keep cache for FComFilterReadLine if ((*lpswEOF)==-1) { // We got EOF, we should keep the extra data we got for FComFilterReadLine // cbUsed is the number of input bytes consumed by FComStripBuf, including dle-etx // any data after cbUsed bytes should go to ComCache INT iExtraChars = nNumRead - cbUsed; if (iExtraChars>0) { DebugPrintEx(DEBUG_MSG,"There are %ld chars after EOF",iExtraChars); CopyMemory (pTG->CommCache.lpBuffer,lpbNext+cbUsed, iExtraChars); pTG->CommCache.dwOffset = 0; pTG->CommCache.dwCurrentSize = iExtraChars; } else { DebugPrintEx(DEBUG_MSG,"No extra data after EOF"); } } } DebugPrintEx(DEBUG_MSG,"After FComStripBuf cbAvail=%ld",cbAvail); cbGot += cbAvail; lpbNext += cbAvail; // RSL 970123. Dont wanna loop if got anything. if ( (*lpswEOF != 0) || (cbGot > 0) ) { // some eof or full buf goto done; } } *lpswEOF = -2; goto done; error: if (!CancellPendingIO(pTG , pTG->hComm , lpOverlapped , (LPDWORD) &nNumRead)) { DebugPrintEx(DEBUG_ERR, "failed when call to CancellPendingIO"); } // fall through to done done: // DebugPrintEx(DEBUG_MSG,"exit: cbGot=%d swEOF=%d", cbGot, *lpswEOF); return cbGot; } #undef USE_DEBUG_CONTEXT #define USE_DEBUG_CONTEXT DEBUG_CONTEXT_T30_COMM BOOL FComGetOneChar ( PThrdGlbl pTG, UWORD ch ) { BYTE rgbRead[10]; // must be 3 or more. 10 for safety // ATTENTION: We do overlapped read into the stack!! TO toCtrlQ; int nNumRead; // _must_ be 32 bits in WIN32 LPOVERLAPPED lpOverlapped; DWORD dwErr; COMSTAT ErrStat; DWORD NumHandles=2; HANDLE HandlesArray[2]; DWORD WaitResult; DWORD dwLastErr; SWORD i; DEBUG_FUNCTION_NAME(("FComGetOneChar")); HandlesArray[1] = pTG->AbortReqEvent; // // check the cache first. // if ( ! pTG->CommCache.dwCurrentSize) { DebugPrintEx(DEBUG_MSG, "Cache is empty. Resetting comm cache."); ClearCommCache(pTG); } else { // The cache is not empty, lets look for ch in the cache for (i=0; i< (SWORD) pTG->CommCache.dwCurrentSize; i++) { if ( *(pTG->CommCache.lpBuffer + pTG->CommCache.dwOffset + i) == ch) { // found in cache DebugPrintEx( DEBUG_MSG, "Found XON in cache pos=%d total=%d", i, pTG->CommCache.dwCurrentSize); pTG->CommCache.dwOffset += (i+1); pTG->CommCache.dwCurrentSize -= (i+1); goto GotCtrlQ; } } DebugPrintEx( DEBUG_MSG, "Cache wasn't empty. Didn't find XON. Resetting comm cache."); ClearCommCache(pTG); } // Send nothing - look for cntl-Q (XON) after connect startTimeOut(pTG, &toCtrlQ, 1000); do { lpOverlapped = &pTG->Comm.ovAux; (lpOverlapped)->Internal = 0; (lpOverlapped)->InternalHigh = 0; (lpOverlapped)->Offset = 0; (lpOverlapped)->OffsetHigh = 0; if ((lpOverlapped)->hEvent) { if (!ResetEvent((lpOverlapped)->hEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } nNumRead = 0; DebugPrintEx(DEBUG_MSG, "Before ReadFile Req=1"); if (! ReadFile( pTG->hComm, rgbRead, 1, &nNumRead, lpOverlapped) ) { if ( (dwLastErr = GetLastError() ) == ERROR_IO_PENDING) { // We want to be able to un-block ONCE only from waiting on I/O when the AbortReqEvent is signaled. // if (pTG->fAbortRequested) { if (pTG->fOkToResetAbortReqEvent && (!pTG->fAbortReqEventWasReset)) { DebugPrintEx(DEBUG_MSG,"RESETTING AbortReqEvent"); pTG->fAbortReqEventWasReset = TRUE; if (!ResetEvent(pTG->AbortReqEvent)) { DebugPrintEx( DEBUG_ERR, "ResetEvent failed (ec=%d)", GetLastError()); } } pTG->fUnblockIO = TRUE; goto error; } HandlesArray[0] = pTG->Comm.ovAux.hEvent; HandlesArray[1] = pTG->AbortReqEvent; if (pTG->fUnblockIO) { NumHandles = 1; } else { NumHandles = 2; } WaitResult = WaitForMultipleObjects(NumHandles, HandlesArray, FALSE, WAIT_FCOM_FILTER_READBUF_TIMEOUT); if (WaitResult == WAIT_TIMEOUT) { DebugPrintEx(DEBUG_ERR, "WaitForMultipleObjects TIMEOUT"); goto error; } if (WaitResult == WAIT_FAILED) { DebugPrintEx( DEBUG_ERR, "WaitForMultipleObjects FAILED le=%lx", GetLastError()); goto error; } if ( (NumHandles == 2) && (WaitResult == WAIT_OBJECT_0 + 1) ) { pTG->fUnblockIO = TRUE; DebugPrintEx(DEBUG_MSG,"ABORTed"); goto error; } // The IO operation was complete. Lets try to get the overlapped result. if ( ! GetOverlappedResult ( pTG->hComm, lpOverlapped, &nNumRead, TRUE) ) { DebugPrintEx(DEBUG_ERR,"GetOverlappedResult le=%x",GetLastError()); if (! ClearCommError( pTG->hComm, &dwErr, &ErrStat) ) { DebugPrintEx(DEBUG_ERR, "ClearCommError le=%x",GetLastError()); } else { DebugPrintEx( DEBUG_ERR, "ClearCommError dwErr=%x ErrSTAT: Cts=%d " "Dsr=%d Rls=%d XoffHold=%d XoffSent=%d " "fEof=%d Txim=%d In=%d Out=%d", dwErr, ErrStat.fCtsHold, ErrStat.fDsrHold, ErrStat.fRlsdHold, ErrStat.fXoffHold, ErrStat.fXoffSent, ErrStat.fEof, ErrStat.fTxim, ErrStat.cbInQue, ErrStat.cbOutQue); } goto errorWithoutCancel; } } else { // error in ReadFile (not ERROR_IO_PENDING), so there is no Pending IO operations DebugPrintEx(DEBUG_ERR, "ReadFile le=%x at",dwLastErr); goto errorWithoutCancel; } } else { DebugPrintEx(DEBUG_WRN,"ReadFile returned w/o WAIT"); } DebugPrintEx(DEBUG_MSG,"After ReadFile Req=1 Ret=%d",nNumRead); switch(nNumRead) { case 0: break; // loop until we get something case 1: if(rgbRead[0] == ch) { goto GotCtrlQ; } else { DebugPrintEx(DEBUG_ERR,"GetCntlQ: Found non ^Q char"); goto errorWithoutCancel; } default: goto errorWithoutCancel; } } while(checkTimeOut(pTG, &toCtrlQ)); goto errorWithoutCancel; GotCtrlQ: return TRUE; error: if (!CancellPendingIO(pTG , pTG->hComm , lpOverlapped , (LPDWORD) &nNumRead)) { DebugPrintEx(DEBUG_ERR, "failed when call to CancellPendingIO"); } errorWithoutCancel: return FALSE; } OVREC *ov_get(PThrdGlbl pTG) { OVREC *lpovr=NULL; DEBUG_FUNCTION_NAME(_T("ov_get")); if (!pTG->Comm.covAlloced) { // There are no OVREC in use now. lpovr = &(pTG->Comm.rgovr[0]); } else { UINT uNewLast = (pTG->Comm.uovLast+1) % NUM_OVS; DebugPrintEx( DEBUG_MSG, "iov_flush: 1st=%d, last=%d", pTG->Comm.uovFirst, pTG->Comm.uovLast); lpovr = pTG->Comm.rgovr+uNewLast; if (uNewLast == pTG->Comm.uovFirst) { if (!iov_flush(pTG, lpovr, TRUE)) { ov_unget(pTG, lpovr); lpovr=NULL; // We fail if a flush operation failed... } else { pTG->Comm.uovFirst = (pTG->Comm.uovFirst+1) % NUM_OVS; } } if (lpovr) pTG->Comm.uovLast = uNewLast; } if (lpovr && lpovr->eState!=eALLOC) { pTG->Comm.covAlloced++; lpovr->eState=eALLOC; } return lpovr; } // We have array of overllaped structures (size: NUM_OVS) // This function release given OVREC BOOL ov_unget(PThrdGlbl pTG, OVREC *lpovr) { BOOL fRet = FALSE; DEBUG_FUNCTION_NAME(("ov_unget")); DebugPrintEx(DEBUG_MSG,"lpovr=%lx",lpovr); if ( lpovr->eState!=eALLOC || !pTG->Comm.covAlloced || lpovr!=(pTG->Comm.rgovr+pTG->Comm.uovLast)) { DebugPrintEx(DEBUG_ERR, "invalid lpovr."); goto end; } if (pTG->Comm.covAlloced==1) { pTG->Comm.uovLast = pTG->Comm.uovFirst = 0; } else { pTG->Comm.uovLast = (pTG->Comm.uovLast)? (pTG->Comm.uovLast-1) : (NUM_OVS-1); } pTG->Comm.covAlloced--; lpovr->eState=eFREE; fRet = TRUE; end: return fRet; } // function: ov_write // This function writes the buffer from lpovr to the comm. In case of error or return w/o waiting, the function free // the ovrec. In case of IO_PENDING we write to *lpdwcbWrote the size of the buffer to write and return without waiting // for operation to complete // BOOL ov_write(PThrdGlbl pTG, OVREC *lpovr, LPDWORD lpdwcbWrote) { DEBUG_FUNCTION_NAME(_T("ov_write")); // Write out the buffer associated with lpovr. if (!lpovr->dwcb) // Nothing in the buffer { // Just free the overlapped structure ov_unget(pTG, lpovr); lpovr=NULL; } else { BOOL fRet; DWORD dw; OVERLAPPED *lpov = &(lpovr->ov); DWORD cbQueue; pTG->Comm.comstat.cbOutQue += lpovr->dwcb; GetCommErrorNT(pTG); cbQueue = pTG->Comm.comstat.cbOutQue; { DebugPrintEx( DEBUG_MSG, "Before WriteFile lpb=%x, cb=%d lpovr=%lx", lpovr->rgby, lpovr->dwcb, lpovr); if (!(fRet = WriteFile( pTG->hComm, lpovr->rgby, lpovr->dwcb, lpdwcbWrote, lpov))) { dw=GetLastError(); } DebugPrintEx(DEBUG_MSG,"After, wrote %ld",*lpdwcbWrote); GetCommErrorNT(pTG); DebugPrintEx( DEBUG_MSG, "Queue before=%lu; after = %lu. n= %lu, *pn=%lu", (unsigned long) cbQueue, (unsigned long) (pTG->Comm.comstat.cbOutQue), (unsigned long) lpovr->dwcb, (unsigned long) *lpdwcbWrote); } if (fRet) { // Write operation completed DebugPrintEx(DEBUG_WRN, "WriteFile returned w/o wait"); OVL_CLEAR( lpov); lpovr->dwcb=0; ov_unget(pTG, lpovr); lpovr=NULL; } else { if (dw==ERROR_IO_PENDING) { DebugPrintEx(DEBUG_MSG,"WriteFile returned PENDING"); *lpdwcbWrote = lpovr->dwcb; // We set *pn to n on success else 0. lpovr->eState=eIO_PENDING; } else { DebugPrintEx( DEBUG_ERR, "WriteFile returns error 0x%lx", (unsigned long)dw); OVL_CLEAR(lpov); lpovr->dwcb=0; ov_unget(pTG, lpovr); lpovr=NULL; goto error; } } } return TRUE; error: return FALSE; } // This function do "iov_flush" on all the allocated OVREC, and free the OVREC for future use. BOOL ov_drain(PThrdGlbl pTG, BOOL fLongTO) { BOOL fRet = TRUE; // We want to iterate on all the OVREC that are in use. UINT u = pTG->Comm.covAlloced; DEBUG_FUNCTION_NAME(_T("ov_drain")); while(u--) { OVREC *lpovr = pTG->Comm.rgovr+pTG->Comm.uovFirst; OVERLAPPED *lpov = &(lpovr->ov); if (lpovr->eState==eIO_PENDING) { if (!iov_flush(pTG, lpovr, fLongTO)) fRet=FALSE; lpovr->eState=eFREE; pTG->Comm.covAlloced--; pTG->Comm.uovFirst = (pTG->Comm.uovFirst+1) % NUM_OVS; } else { // Only the newest (last) structure can be still in the // allocated state. DebugPrintEx(DEBUG_WRN,"called when alloc'd structure pending"); } } if (!pTG->Comm.covAlloced) { pTG->Comm.uovFirst=pTG->Comm.uovLast=0; } return fRet; } BOOL ov_init(PThrdGlbl pTG) { UINT u; OVREC *lpovr = pTG->Comm.rgovr; DEBUG_FUNCTION_NAME(_T("ov_init")); // init overlapped structures, including creating events... if (pTG->Comm.fovInited) { DebugPrintEx(DEBUG_ERR, "we're *already* inited."); ov_deinit(pTG); } for (u=0;uov); _fmemset(lpov, 0, sizeof(OVERLAPPED)); lpov->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (lpov->hEvent==NULL) { DebugPrintEx( DEBUG_ERR, "couldn't create event #%lu", (unsigned long)u); goto failure; } lpovr->eState=eFREE; lpovr->dwcb=0; } pTG->Comm.fovInited=TRUE; return TRUE; failure: while (u--) { --lpovr; CloseHandle(lpovr->ov.hEvent); lpovr->eState=eDEINIT; } return FALSE; } BOOL ov_deinit(PThrdGlbl pTG) { UINT u=NUM_OVS; OVREC *lpovr = pTG->Comm.rgovr; DEBUG_FUNCTION_NAME(("ov_deinit")); if (!pTG->Comm.fovInited) { DebugPrintEx(DEBUG_WRN,"Already deinited."); goto end; } // // if handoff ==> dont flush // if (pTG->Comm.fEnableHandoff && pTG->Comm.fDataCall) { goto lNext; } // deinit overlapped structures, including freeing events... if (pTG->Comm.covAlloced) { DWORD dw; DebugPrintEx( DEBUG_WRN, "%lu IO's pending.", (unsigned long) pTG->Comm.covAlloced); if (pTG->Comm.lpovrCur) { ov_write(pTG, pTG->Comm.lpovrCur,&dw); pTG->Comm.lpovrCur=NULL; } ov_drain(pTG, FALSE); } lNext: while (u--) { lpovr->eState=eDEINIT; if (lpovr->ov.hEvent) CloseHandle(lpovr->ov.hEvent); _fmemset(&(lpovr->ov), 0, sizeof(lpovr->ov)); lpovr++; } pTG->Comm.fovInited=FALSE; end: return TRUE; } BOOL iov_flush(PThrdGlbl pTG, OVREC *lpovr, BOOL fLongTO) // On return, state of lpovr is *always* eALLOC, but // it returns FALSE if there was a comm error while trying // to flush (i.e. drain) the buffer. // If we timeout with the I/O operation still pending, we purge // the output buffer and abort all pending write operations. { DWORD dwcbPrev; DWORD dwStart = GetTickCount(); BOOL fRet=FALSE; DWORD dwWaitRes; DWORD dw; DEBUG_FUNCTION_NAME(_T("iov_flush")); DebugPrintEx(DEBUG_MSG,"fLongTo=%d lpovr=%lx",fLongTO,lpovr); if (!pTG->hComm) { lpovr->eState=eALLOC; goto end; } // We call // WaitForSingleObject multiple times ... basically // the same logic as the code in the old FComDirectWrite... // fLongTO is TRUE except when initing // the modem (see comments for FComDrain). GetCommErrorNT(pTG); // We want to check for progress, so we check the amount of bytes in the output buffer. dwcbPrev = pTG->Comm.comstat.cbOutQue; while( (dwWaitRes=WaitForSingleObject( lpovr->ov.hEvent, fLongTO ? LONG_DRAINTIMEOUT : SHORT_DRAINTIMEOUT))==WAIT_TIMEOUT) { BOOL fStuckOnce=FALSE; DebugPrintEx(DEBUG_MSG,"After WaitForSingleObject TIMEOUT"); GetCommErrorNT(pTG); // Timed out -- check if any progress if (dwcbPrev == pTG->Comm.comstat.cbOutQue) { // No pregess, the size of the output buffer is without any change. DebugPrintEx(DEBUG_WRN,"No progress %d",dwcbPrev); // No progress... If not in XOFFHold, we break.... if(!FComInXOFFHold(pTG)) { if(fStuckOnce) { DebugPrintEx( DEBUG_ERR, "No Progress -- OutQ still %d", (int)pTG->Comm.comstat.cbOutQue); goto done; } else { fStuckOnce=TRUE; } } } else { // Some progress... dwcbPrev= pTG->Comm.comstat.cbOutQue; fStuckOnce=FALSE; } // Independant deadcom timeout... I don't want // to use TO because of the 16bit limitation. { DWORD dwNow = GetTickCount(); DWORD dwDelta = (dwNow>dwStart) ? (dwNow-dwStart) : (0xFFFFFFFFL-dwStart) + dwNow; if (dwDelta > (unsigned long)((fLongTO)?LONG_DEADCOMMTIMEOUT:SHORT_DEADCOMMTIMEOUT)) { DebugPrintEx( DEBUG_ERR, "Drain:: Deadman Timer -- OutQ still %d", (int) pTG->Comm.comstat.cbOutQue); goto end; } } } if (dwWaitRes==WAIT_FAILED) { DebugPrintEx( DEBUG_ERR, "WaitForSingleObject failed (ec=%d)", GetLastError()); goto end; } done: DebugPrintEx(DEBUG_MSG,"Before GetOverlappedResult"); if (GetOverlappedResult(pTG->hComm, &(lpovr->ov), &dw, FALSE)) { fRet=TRUE; } else { dw = GetLastError(); DebugPrintEx( DEBUG_ERR, "GetOverlappedResult returns error 0x%lx", (unsigned long)dw); if (dw==ERROR_IO_INCOMPLETE) { // IO operation still pending, but we *have* to // reuse this buffer -- what should we do?!- // purge the output buffer and abort all pending // write operations on it.. DebugPrintEx(DEBUG_ERR, "Incomplete"); PurgeComm(pTG->hComm, PURGE_TXABORT); } fRet=FALSE; } OVL_CLEAR( &(lpovr->ov)); lpovr->eState=eALLOC; lpovr->dwcb=0; end: return fRet; } void WINAPI FComOverlappedIO(PThrdGlbl pTG, BOOL fBegin) { DEBUG_FUNCTION_NAME(_T("FComOverlappedIO")); DebugPrintEx(DEBUG_MSG,"Turning %s OVERLAPPED IO", (fBegin) ? "ON" : "OFF"); pTG->Comm.fDoOverlapped=fBegin; } BOOL CancellPendingIO ( PThrdGlbl pTG , HANDLE hComm , LPOVERLAPPED lpOverlapped , LPDWORD lpCounter) { BOOL retValue = TRUE; /* The CancelIo function cancels all pending input and output (I/O) operations that were issued by the calling thread for the specified file handle. The function does not cancel I/O operations issued for the file handle by other threads. */ DEBUG_FUNCTION_NAME(_T("CancellPendingIO")); if (!CancelIo(hComm)) { retValue = FALSE; DebugPrintEx(DEBUG_ERR, "CancelIO failed, ec=%x",GetLastError()); } else { DebugPrintEx(DEBUG_MSG,"CancelIO succeeded."); } (*lpCounter) = 0; if (!GetOverlappedResult (hComm , lpOverlapped, lpCounter , TRUE)) { DebugPrintEx( DEBUG_MSG, "GetOverlappedResult failed because we cancel the " "IO operation, ec=%x", GetLastError()); } else { // If the function was successful then something fishy with the CancellIo(hComm) // The operation succeeded cause the pending IO was finished before the 'CancelIo' DebugPrintEx( DEBUG_MSG, "GetOverlappedResult succeeded. Number of bytes read %d", *lpCounter); } ClearCommCache(pTG); return retValue; } void GetCommErrorNT(PThrdGlbl pTG) { DWORD err; DEBUG_FUNCTION_NAME(_T("GetCommErrorNT")); if (!ClearCommError( pTG->hComm, &err, &(pTG->Comm.comstat))) { DebugPrintEx( DEBUG_ERR, "(0x%lx) FAILS. Returns 0x%lu", pTG->hComm, GetLastError()); err = MYGETCOMMERROR_FAILED; } #ifdef DEBUG if (err) { D_PrintCE(err); D_PrintCOMSTAT(pTG, &pTG->Comm.comstat); } #endif // DEBUG } void ClearCommCache ( PThrdGlbl pTG ) { pTG->CommCache.dwCurrentSize = 0; pTG->CommCache.dwOffset = 0; }