You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2408 lines
79 KiB
2408 lines
79 KiB
/***************************************************************************
|
|
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 <comdevi.h>
|
|
#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 <cb on timeout,
|
|
or error.
|
|
|
|
Revision Log
|
|
Num Date Name Description
|
|
--- -------- ---------- -----------------------------------------------
|
|
101 06/03/92 arulm Created it
|
|
***************************************************************************/
|
|
|
|
// This is WRONG -- see below!!
|
|
// totally arbitrary should be no more than the time as it would
|
|
// take to write WRITEQUANTUM out at the fastest speed
|
|
// (say 14400 approx 2 bytes/ms)
|
|
// #define WRITETIMEOUT min((WRITEQUANTUM / 2), 200)
|
|
|
|
// This timeout was too low. We wanted it low so we don't spend too much
|
|
// time trying to talk to a non-existent modem during Init/Setup. But in
|
|
// those cases we _never_ reach full buffer, so we don't wait here
|
|
// we wait in FComDrain(). Here we wait _only_ in PhaseC, so 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 long
|
|
// 2secs should be about long enough
|
|
#define WRITETIMEOUT 2000
|
|
|
|
|
|
UWORD FComDirectWrite(PThrdGlbl pTG, LPB lpb, UWORD cb)
|
|
{
|
|
DWORD cbLeft = cb;
|
|
|
|
DEBUG_FUNCTION_NAME(_T("FComDirectWrite"));
|
|
|
|
while(cbLeft)
|
|
{
|
|
DWORD dwcbCopy;
|
|
DWORD dwcbWrote;
|
|
|
|
if (!pTG->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; i<cbAvail; )
|
|
{
|
|
if ( *(pSrc+i) == DLE)
|
|
{
|
|
if ( *(pSrc+i+1) == DLE )
|
|
{
|
|
*(pDest+j) = DLE;
|
|
j += 1;
|
|
i += 2;
|
|
}
|
|
else if ( *(pSrc+i+1) == ETX )
|
|
{
|
|
*(pDest+j) = DLE;
|
|
*(pDest+j+1) = ETX;
|
|
j += 2;
|
|
i += 2;
|
|
}
|
|
else
|
|
{
|
|
i += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*(pDest+j) = *(pSrc+i);
|
|
i++;
|
|
j++;
|
|
}
|
|
}
|
|
|
|
pTG->CommCache.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 <dle>
|
|
// 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;u<NUM_OVS;u++,lpovr++)
|
|
{
|
|
OVERLAPPED *lpov = &(lpovr->ov);
|
|
_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;
|
|
}
|