sl@0: /* sl@0: * tclWinSerial.c -- sl@0: * sl@0: * This file implements the Windows-specific serial port functions, sl@0: * and the "serial" channel driver. sl@0: * sl@0: * Copyright (c) 1999 by Scriptics Corp. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * Serial functionality implemented by Rolf.Schroedter@dlr.de sl@0: * sl@0: * RCS: @(#) $Id: tclWinSerial.c,v 1.25.2.3 2005/10/05 06:33:52 hobbs Exp $ sl@0: */ sl@0: sl@0: #include "tclWinInt.h" sl@0: sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * The following variable is used to tell whether this module has been sl@0: * initialized. sl@0: */ sl@0: sl@0: static int initialized = 0; sl@0: sl@0: /* sl@0: * The serialMutex locks around access to the initialized variable, and it is sl@0: * used to protect background threads from being terminated while they are sl@0: * using APIs that hold locks. sl@0: */ sl@0: sl@0: TCL_DECLARE_MUTEX(serialMutex) sl@0: sl@0: /* sl@0: * Bit masks used in the flags field of the SerialInfo structure below. sl@0: */ sl@0: sl@0: #define SERIAL_PENDING (1<<0) /* Message is pending in the queue. */ sl@0: #define SERIAL_ASYNC (1<<1) /* Channel is non-blocking. */ sl@0: sl@0: /* sl@0: * Bit masks used in the sharedFlags field of the SerialInfo structure below. sl@0: */ sl@0: sl@0: #define SERIAL_EOF (1<<2) /* Serial has reached EOF. */ sl@0: #define SERIAL_ERROR (1<<4) sl@0: sl@0: /* sl@0: * Default time to block between checking status on the serial port. sl@0: */ sl@0: #define SERIAL_DEFAULT_BLOCKTIME 10 /* 10 msec */ sl@0: sl@0: /* sl@0: * Define Win32 read/write error masks returned by ClearCommError() sl@0: */ sl@0: #define SERIAL_READ_ERRORS ( CE_RXOVER | CE_OVERRUN | CE_RXPARITY \ sl@0: | CE_FRAME | CE_BREAK ) sl@0: #define SERIAL_WRITE_ERRORS ( CE_TXFULL | CE_PTO ) sl@0: sl@0: /* sl@0: * This structure describes per-instance data for a serial based channel. sl@0: */ sl@0: sl@0: typedef struct SerialInfo { sl@0: HANDLE handle; sl@0: struct SerialInfo *nextPtr; /* Pointer to next registered serial. */ sl@0: Tcl_Channel channel; /* Pointer to channel structure. */ sl@0: int validMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which operations are valid on the file. */ sl@0: int watchMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which events should be reported. */ sl@0: int flags; /* State flags, see above for a list. */ sl@0: int readable; /* flag that the channel is readable */ sl@0: int writable; /* flag that the channel is writable */ sl@0: int blockTime; /* max. blocktime in msec */ sl@0: unsigned int lastEventTime; /* Time in milliseconds since last readable event */ sl@0: /* Next readable event only after blockTime */ sl@0: DWORD error; /* pending error code returned by sl@0: * ClearCommError() */ sl@0: DWORD lastError; /* last error code, can be fetched with sl@0: * fconfigure chan -lasterror */ sl@0: DWORD sysBufRead; /* Win32 system buffer size for read ops, sl@0: * default=4096 */ sl@0: DWORD sysBufWrite; /* Win32 system buffer size for write ops, sl@0: * default=4096 */ sl@0: sl@0: Tcl_ThreadId threadId; /* Thread to which events should be reported. sl@0: * This value is used by the reader/writer sl@0: * threads. */ sl@0: OVERLAPPED osRead; /* OVERLAPPED structure for read operations */ sl@0: OVERLAPPED osWrite; /* OVERLAPPED structure for write operations */ sl@0: HANDLE writeThread; /* Handle to writer thread. */ sl@0: CRITICAL_SECTION csWrite; /* Writer thread synchronisation */ sl@0: HANDLE evWritable; /* Manual-reset event to signal when the sl@0: * writer thread has finished waiting for sl@0: * the current buffer to be written. */ sl@0: HANDLE evStartWriter; /* Auto-reset event used by the main thread to sl@0: * signal when the writer thread should attempt sl@0: * to write to the serial. */ sl@0: HANDLE evStopWriter; /* Auto-reset event used by the main thread to sl@0: * signal when the writer thread should close. sl@0: */ sl@0: DWORD writeError; /* An error caused by the last background sl@0: * write. Set to 0 if no error has been sl@0: * detected. This word is shared with the sl@0: * writer thread so access must be sl@0: * synchronized with the evWritable object. sl@0: */ sl@0: char *writeBuf; /* Current background output buffer. sl@0: * Access is synchronized with the evWritable sl@0: * object. */ sl@0: int writeBufLen; /* Size of write buffer. Access is sl@0: * synchronized with the evWritable sl@0: * object. */ sl@0: int toWrite; /* Current amount to be written. Access is sl@0: * synchronized with the evWritable object. */ sl@0: int writeQueue; /* Number of bytes pending in output queue. sl@0: * Offset to DCB.cbInQue. sl@0: * Used to query [fconfigure -queue] */ sl@0: } SerialInfo; sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: /* sl@0: * The following pointer refers to the head of the list of serials sl@0: * that are being watched for file events. sl@0: */ sl@0: sl@0: SerialInfo *firstSerialPtr; sl@0: } ThreadSpecificData; sl@0: sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * serial events are generated. sl@0: */ sl@0: sl@0: typedef struct SerialEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: SerialInfo *infoPtr; /* Pointer to serial info structure. Note sl@0: * that we still have to verify that the sl@0: * serial exists before dereferencing this sl@0: * pointer. */ sl@0: } SerialEvent; sl@0: sl@0: /* sl@0: * We don't use timeouts. sl@0: */ sl@0: sl@0: static COMMTIMEOUTS no_timeout = { sl@0: 0, /* ReadIntervalTimeout */ sl@0: 0, /* ReadTotalTimeoutMultiplier */ sl@0: 0, /* ReadTotalTimeoutConstant */ sl@0: 0, /* WriteTotalTimeoutMultiplier */ sl@0: 0, /* WriteTotalTimeoutConstant */ sl@0: }; sl@0: sl@0: /* sl@0: * Declarations for functions used only in this file. sl@0: */ sl@0: sl@0: static int SerialBlockProc(ClientData instanceData, int mode); sl@0: static void SerialCheckProc(ClientData clientData, int flags); sl@0: static int SerialCloseProc(ClientData instanceData, sl@0: Tcl_Interp *interp); sl@0: static int SerialEventProc(Tcl_Event *evPtr, int flags); sl@0: static void SerialExitHandler(ClientData clientData); sl@0: static int SerialGetHandleProc(ClientData instanceData, sl@0: int direction, ClientData *handlePtr); sl@0: static ThreadSpecificData *SerialInit(void); sl@0: static int SerialInputProc(ClientData instanceData, char *buf, sl@0: int toRead, int *errorCode); sl@0: static int SerialOutputProc(ClientData instanceData, CONST char *buf, sl@0: int toWrite, int *errorCode); sl@0: static void SerialSetupProc(ClientData clientData, int flags); sl@0: static void SerialWatchProc(ClientData instanceData, int mask); sl@0: static void ProcExitHandler(ClientData clientData); sl@0: static int SerialGetOptionProc _ANSI_ARGS_((ClientData instanceData, sl@0: Tcl_Interp *interp, CONST char *optionName, sl@0: Tcl_DString *dsPtr)); sl@0: static int SerialSetOptionProc _ANSI_ARGS_((ClientData instanceData, sl@0: Tcl_Interp *interp, CONST char *optionName, sl@0: CONST char *value)); sl@0: static DWORD WINAPI SerialWriterThread(LPVOID arg); sl@0: sl@0: static void SerialThreadActionProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int action)); sl@0: sl@0: /* sl@0: * This structure describes the channel type structure for command serial sl@0: * based IO. sl@0: */ sl@0: sl@0: static Tcl_ChannelType serialChannelType = { sl@0: "serial", /* Type name. */ sl@0: TCL_CHANNEL_VERSION_4, /* v4 channel */ sl@0: SerialCloseProc, /* Close proc. */ sl@0: SerialInputProc, /* Input proc. */ sl@0: SerialOutputProc, /* Output proc. */ sl@0: NULL, /* Seek proc. */ sl@0: SerialSetOptionProc, /* Set option proc. */ sl@0: SerialGetOptionProc, /* Get option proc. */ sl@0: SerialWatchProc, /* Set up notifier to watch the channel. */ sl@0: SerialGetHandleProc, /* Get an OS handle from channel. */ sl@0: NULL, /* close2proc. */ sl@0: SerialBlockProc, /* Set blocking or non-blocking mode.*/ sl@0: NULL, /* flush proc. */ sl@0: NULL, /* handler proc. */ sl@0: NULL, /* wide seek proc */ sl@0: SerialThreadActionProc, /* thread action proc */ sl@0: }; sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialInit -- sl@0: * sl@0: * This function initializes the static variables for this file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static ThreadSpecificData * sl@0: SerialInit() sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: /* sl@0: * Check the initialized flag first, then check it again in the mutex. sl@0: * This is a speed enhancement. sl@0: */ sl@0: sl@0: if (!initialized) { sl@0: Tcl_MutexLock(&serialMutex); sl@0: if (!initialized) { sl@0: initialized = 1; sl@0: Tcl_CreateExitHandler(ProcExitHandler, NULL); sl@0: } sl@0: Tcl_MutexUnlock(&serialMutex); sl@0: } sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr == NULL) { sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: tsdPtr->firstSerialPtr = NULL; sl@0: Tcl_CreateEventSource(SerialSetupProc, SerialCheckProc, NULL); sl@0: Tcl_CreateThreadExitHandler(SerialExitHandler, NULL); sl@0: } sl@0: return tsdPtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialExitHandler -- sl@0: * sl@0: * This function is called to cleanup the serial module before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the serial event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SerialExitHandler( sl@0: ClientData clientData) /* Old window proc */ sl@0: { sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: SerialInfo *infoPtr; sl@0: sl@0: /* sl@0: * Clear all eventually pending output. sl@0: * Otherwise Tcl's exit could totally block, sl@0: * because it performs a blocking flush on all open channels. sl@0: * Note that serial write operations may be blocked due to handshake. sl@0: */ sl@0: for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: PurgeComm(infoPtr->handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR sl@0: | PURGE_RXCLEAR); sl@0: sl@0: } sl@0: Tcl_DeleteEventSource(SerialSetupProc, SerialCheckProc, NULL); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * ProcExitHandler -- sl@0: * sl@0: * This function is called to cleanup the process list before sl@0: * Tcl is unloaded. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Resets the process list. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: ProcExitHandler( sl@0: ClientData clientData) /* Old window proc */ sl@0: { sl@0: Tcl_MutexLock(&serialMutex); sl@0: initialized = 0; sl@0: Tcl_MutexUnlock(&serialMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialBlockTime -- sl@0: * sl@0: * Wrapper to set Tcl's block time in msec sl@0: * sl@0: * Results: sl@0: * None. sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SerialBlockTime( sl@0: int msec) /* milli-seconds */ sl@0: { sl@0: Tcl_Time blockTime; sl@0: sl@0: blockTime.sec = msec / 1000; sl@0: blockTime.usec = (msec % 1000) * 1000; sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: } sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialGetMilliseconds -- sl@0: * sl@0: * Get current time in milliseconds, sl@0: * Don't care about integer overruns sl@0: * sl@0: * Results: sl@0: * None. sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static unsigned int sl@0: SerialGetMilliseconds( sl@0: void) sl@0: { sl@0: Tcl_Time time; sl@0: sl@0: TclpGetTime(&time); sl@0: sl@0: return (time.sec * 1000 + time.usec / 1000); sl@0: } sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialSetupProc -- sl@0: * sl@0: * This procedure is invoked before Tcl_DoOneEvent blocks waiting sl@0: * for an event. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Adjusts the block time if needed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: SerialSetupProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: SerialInfo *infoPtr; sl@0: int block = 1; sl@0: int msec = INT_MAX; /* min. found block time */ sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Look to see if any events handlers installed. If they are, do not block. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: if (WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) { sl@0: block = 0; sl@0: msec = min( msec, infoPtr->blockTime ); sl@0: } sl@0: } sl@0: if( infoPtr->watchMask & TCL_READABLE ) { sl@0: block = 0; sl@0: msec = min( msec, infoPtr->blockTime ); sl@0: } sl@0: } sl@0: sl@0: if (!block) { sl@0: SerialBlockTime(msec); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialCheckProc -- sl@0: * sl@0: * This procedure is called by Tcl_DoOneEvent to check the serial sl@0: * event source for events. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May queue an event. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SerialCheckProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: SerialInfo *infoPtr; sl@0: SerialEvent *evPtr; sl@0: int needEvent; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: COMSTAT cStat; sl@0: unsigned int time; sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Queue events for any ready serials that don't already have events sl@0: * queued. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->flags & SERIAL_PENDING) { sl@0: continue; sl@0: } sl@0: sl@0: needEvent = 0; sl@0: sl@0: /* sl@0: * If WRITABLE watch mask is set sl@0: * look for infoPtr->evWritable object sl@0: */ sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: if (WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) { sl@0: infoPtr->writable = 1; sl@0: needEvent = 1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * If READABLE watch mask is set sl@0: * call ClearCommError to poll cbInQue sl@0: * Window errors are ignored here sl@0: */ sl@0: sl@0: if( infoPtr->watchMask & TCL_READABLE ) { sl@0: if( ClearCommError( infoPtr->handle, &infoPtr->error, &cStat ) ) { sl@0: /* sl@0: * Look for characters already pending in windows queue. sl@0: * If they are, poll. sl@0: */ sl@0: sl@0: if( infoPtr->watchMask & TCL_READABLE ) { sl@0: /* sl@0: * force fileevent after serial read error sl@0: */ sl@0: if( (cStat.cbInQue > 0) || sl@0: (infoPtr->error & SERIAL_READ_ERRORS) ) { sl@0: infoPtr->readable = 1; sl@0: time = SerialGetMilliseconds(); sl@0: if ((unsigned int) (time - infoPtr->lastEventTime) sl@0: >= (unsigned int) infoPtr->blockTime) { sl@0: needEvent = 1; sl@0: infoPtr->lastEventTime = time; sl@0: } sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Queue an event if the serial is signaled for reading or writing. sl@0: */ sl@0: if (needEvent) { sl@0: infoPtr->flags |= SERIAL_PENDING; sl@0: evPtr = (SerialEvent *) ckalloc(sizeof(SerialEvent)); sl@0: evPtr->header.proc = SerialEventProc; sl@0: evPtr->infoPtr = infoPtr; sl@0: Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialBlockProc -- sl@0: * sl@0: * Set blocking or non-blocking mode on channel. sl@0: * sl@0: * Results: sl@0: * 0 if successful, errno when failed. sl@0: * sl@0: * Side effects: sl@0: * Sets the device into blocking or non-blocking mode. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SerialBlockProc( sl@0: ClientData instanceData, /* Instance data for channel. */ sl@0: int mode) /* TCL_MODE_BLOCKING or sl@0: * TCL_MODE_NONBLOCKING. */ sl@0: { sl@0: int errorCode = 0; sl@0: sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: sl@0: /* sl@0: * Only serial READ can be switched between blocking & nonblocking sl@0: * using COMMTIMEOUTS. sl@0: * Serial write emulates blocking & nonblocking by the SerialWriterThread. sl@0: */ sl@0: sl@0: if (mode == TCL_MODE_NONBLOCKING) { sl@0: infoPtr->flags |= SERIAL_ASYNC; sl@0: } else { sl@0: infoPtr->flags &= ~(SERIAL_ASYNC); sl@0: } sl@0: return errorCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialCloseProc -- sl@0: * sl@0: * Closes a serial based IO channel. sl@0: * sl@0: * Results: sl@0: * 0 on success, errno otherwise. sl@0: * sl@0: * Side effects: sl@0: * Closes the physical channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SerialCloseProc( sl@0: ClientData instanceData, /* Pointer to SerialInfo structure. */ sl@0: Tcl_Interp *interp) /* For error reporting. */ sl@0: { sl@0: SerialInfo *serialPtr = (SerialInfo *) instanceData; sl@0: int errorCode, result = 0; sl@0: SerialInfo *infoPtr, **nextPtrPtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: DWORD exitCode; sl@0: sl@0: errorCode = 0; sl@0: sl@0: if (serialPtr->validMask & TCL_READABLE) { sl@0: PurgeComm(serialPtr->handle, PURGE_RXABORT | PURGE_RXCLEAR); sl@0: CloseHandle(serialPtr->osRead.hEvent); sl@0: } sl@0: serialPtr->validMask &= ~TCL_READABLE; sl@0: sl@0: if (serialPtr->validMask & TCL_WRITABLE) { sl@0: sl@0: /* sl@0: * Generally we cannot wait for a pending write operation sl@0: * because it may hang due to handshake sl@0: * WaitForSingleObject(serialPtr->evWritable, INFINITE); sl@0: */ sl@0: sl@0: /* sl@0: * The thread may have already closed on it's own. Check it's sl@0: * exit code. sl@0: */ sl@0: sl@0: GetExitCodeThread(serialPtr->writeThread, &exitCode); sl@0: sl@0: if (exitCode == STILL_ACTIVE) { sl@0: /* sl@0: * Set the stop event so that if the writer thread is sl@0: * blocked in SerialWriterThread on WaitForMultipleEvents, it sl@0: * will exit cleanly. sl@0: */ sl@0: sl@0: SetEvent(serialPtr->evStopWriter); sl@0: sl@0: /* sl@0: * Wait at most 20 milliseconds for the writer thread to sl@0: * close. sl@0: */ sl@0: sl@0: if (WaitForSingleObject(serialPtr->writeThread, 20) sl@0: == WAIT_TIMEOUT) { sl@0: /* sl@0: * Forcibly terminate the background thread as a last sl@0: * resort. Note that we need to guard against sl@0: * terminating the thread while it is in the middle of sl@0: * Tcl_ThreadAlert because it won't be able to release sl@0: * the notifier lock. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&serialMutex); sl@0: sl@0: /* BUG: this leaks memory */ sl@0: TerminateThread(serialPtr->writeThread, 0); sl@0: sl@0: Tcl_MutexUnlock(&serialMutex); sl@0: } sl@0: } sl@0: sl@0: CloseHandle(serialPtr->writeThread); sl@0: CloseHandle(serialPtr->osWrite.hEvent); sl@0: DeleteCriticalSection(&serialPtr->csWrite); sl@0: CloseHandle(serialPtr->evWritable); sl@0: CloseHandle(serialPtr->evStartWriter); sl@0: CloseHandle(serialPtr->evStopWriter); sl@0: serialPtr->writeThread = NULL; sl@0: sl@0: PurgeComm(serialPtr->handle, PURGE_TXABORT | PURGE_TXCLEAR); sl@0: } sl@0: serialPtr->validMask &= ~TCL_WRITABLE; sl@0: sl@0: /* sl@0: * Don't close the Win32 handle if the handle is a standard channel sl@0: * during the thread exit process. Otherwise, one thread may kill sl@0: * the stdio of another. sl@0: */ sl@0: sl@0: if (!TclInThreadExit() sl@0: || ((GetStdHandle(STD_INPUT_HANDLE) != serialPtr->handle) sl@0: && (GetStdHandle(STD_OUTPUT_HANDLE) != serialPtr->handle) sl@0: && (GetStdHandle(STD_ERROR_HANDLE) != serialPtr->handle))) { sl@0: if (CloseHandle(serialPtr->handle) == FALSE) { sl@0: TclWinConvertError(GetLastError()); sl@0: errorCode = errno; sl@0: } sl@0: } sl@0: sl@0: serialPtr->watchMask &= serialPtr->validMask; sl@0: sl@0: /* sl@0: * Remove the file from the list of watched files. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstSerialPtr), infoPtr = *nextPtrPtr; sl@0: infoPtr != NULL; sl@0: nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { sl@0: if (infoPtr == (SerialInfo *)serialPtr) { sl@0: *nextPtrPtr = infoPtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Wrap the error file into a channel and give it to the cleanup sl@0: * routine. sl@0: */ sl@0: if (serialPtr->writeBuf != NULL) { sl@0: ckfree(serialPtr->writeBuf); sl@0: serialPtr->writeBuf = NULL; sl@0: } sl@0: ckfree((char*) serialPtr); sl@0: sl@0: if (errorCode == 0) { sl@0: return result; sl@0: } sl@0: return errorCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * blockingRead -- sl@0: * sl@0: * Perform a blocking read into the buffer given. Returns sl@0: * count of how many bytes were actually read, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were read is returned and an error sl@0: * indication is returned. sl@0: * sl@0: * Side effects: sl@0: * Reads input from the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static int sl@0: blockingRead( sl@0: SerialInfo *infoPtr, /* Serial info structure */ sl@0: LPVOID buf, /* The input buffer pointer */ sl@0: DWORD bufSize, /* The number of bytes to read */ sl@0: LPDWORD lpRead, /* Returns number of bytes read */ sl@0: LPOVERLAPPED osPtr ) /* OVERLAPPED structure */ sl@0: { sl@0: /* sl@0: * Perform overlapped blocking read. sl@0: * 1. Reset the overlapped event sl@0: * 2. Start overlapped read operation sl@0: * 3. Wait for completion sl@0: */ sl@0: sl@0: /* sl@0: * Set Offset to ZERO, otherwise NT4.0 may report an error. sl@0: */ sl@0: osPtr->Offset = osPtr->OffsetHigh = 0; sl@0: ResetEvent(osPtr->hEvent); sl@0: if (! ReadFile(infoPtr->handle, buf, bufSize, lpRead, osPtr) ) { sl@0: if (GetLastError() != ERROR_IO_PENDING) { sl@0: /* ReadFile failed, but it isn't delayed. Report error. */ sl@0: return FALSE; sl@0: } else { sl@0: /* Read is pending, wait for completion, timeout ? */ sl@0: if (! GetOverlappedResult(infoPtr->handle, osPtr, lpRead, TRUE) ) { sl@0: return FALSE; sl@0: } sl@0: } sl@0: } else { sl@0: /* ReadFile completed immediately. */ sl@0: } sl@0: return TRUE; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * blockingWrite -- sl@0: * sl@0: * Perform a blocking write from the buffer given. Returns sl@0: * count of how many bytes were actually written, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were written is returned and an error sl@0: * indication is returned. sl@0: * sl@0: * Side effects: sl@0: * Writes output to the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static int sl@0: blockingWrite( sl@0: SerialInfo *infoPtr, /* Serial info structure */ sl@0: LPVOID buf, /* The output buffer pointer */ sl@0: DWORD bufSize, /* The number of bytes to write */ sl@0: LPDWORD lpWritten, /* Returns number of bytes written */ sl@0: LPOVERLAPPED osPtr ) /* OVERLAPPED structure */ sl@0: { sl@0: int result; sl@0: /* sl@0: * Perform overlapped blocking write. sl@0: * 1. Reset the overlapped event sl@0: * 2. Remove these bytes from the output queue counter sl@0: * 3. Start overlapped write operation sl@0: * 3. Remove these bytes from the output queue counter sl@0: * 4. Wait for completion sl@0: * 5. Adjust the output queue counter sl@0: */ sl@0: ResetEvent(osPtr->hEvent); sl@0: sl@0: EnterCriticalSection(&infoPtr->csWrite); sl@0: infoPtr->writeQueue -= bufSize; sl@0: /* sl@0: * Set Offset to ZERO, otherwise NT4.0 may report an error sl@0: */ sl@0: osPtr->Offset = osPtr->OffsetHigh = 0; sl@0: result = WriteFile(infoPtr->handle, buf, bufSize, lpWritten, osPtr); sl@0: LeaveCriticalSection(&infoPtr->csWrite); sl@0: sl@0: if (result == FALSE ) { sl@0: int err = GetLastError(); sl@0: switch (err) { sl@0: case ERROR_IO_PENDING: sl@0: /* Write is pending, wait for completion */ sl@0: if (! GetOverlappedResult(infoPtr->handle, osPtr, lpWritten, TRUE) ) { sl@0: return FALSE; sl@0: } sl@0: break; sl@0: case ERROR_COUNTER_TIMEOUT: sl@0: /* Write timeout handled in SerialOutputProc */ sl@0: break; sl@0: default: sl@0: /* WriteFile failed, but it isn't delayed. Report error */ sl@0: return FALSE; sl@0: } sl@0: } else { sl@0: /* WriteFile completed immediately. */ sl@0: } sl@0: sl@0: EnterCriticalSection(&infoPtr->csWrite); sl@0: infoPtr->writeQueue += (*lpWritten - bufSize); sl@0: LeaveCriticalSection(&infoPtr->csWrite); sl@0: sl@0: return TRUE; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialInputProc -- sl@0: * sl@0: * Reads input from the IO channel into the buffer given. Returns sl@0: * count of how many bytes were actually read, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were read is returned and an error sl@0: * indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Reads input from the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static int sl@0: SerialInputProc( sl@0: ClientData instanceData, /* Serial state. */ sl@0: char *buf, /* Where to store data read. */ sl@0: int bufSize, /* How much space is available sl@0: * in the buffer? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: DWORD bytesRead = 0; sl@0: COMSTAT cStat; sl@0: sl@0: *errorCode = 0; sl@0: sl@0: /* sl@0: * Check if there is a CommError pending from SerialCheckProc sl@0: */ sl@0: if( infoPtr->error & SERIAL_READ_ERRORS ){ sl@0: goto commError; sl@0: } sl@0: sl@0: /* sl@0: * Look for characters already pending in windows queue. sl@0: * This is the mainly restored good old code from Tcl8.0 sl@0: */ sl@0: sl@0: if( ClearCommError( infoPtr->handle, &infoPtr->error, &cStat ) ) { sl@0: /* sl@0: * Check for errors here, but not in the evSetup/Check procedures sl@0: */ sl@0: sl@0: if( infoPtr->error & SERIAL_READ_ERRORS ) { sl@0: goto commError; sl@0: } sl@0: if( infoPtr->flags & SERIAL_ASYNC ) { sl@0: /* sl@0: * NON_BLOCKING mode: sl@0: * Avoid blocking by reading more bytes than available sl@0: * in input buffer sl@0: */ sl@0: sl@0: if( cStat.cbInQue > 0 ) { sl@0: if( (DWORD) bufSize > cStat.cbInQue ) { sl@0: bufSize = cStat.cbInQue; sl@0: } sl@0: } else { sl@0: errno = *errorCode = EAGAIN; sl@0: return -1; sl@0: } sl@0: } else { sl@0: /* sl@0: * BLOCKING mode: sl@0: * Tcl trys to read a full buffer of 4 kBytes here sl@0: */ sl@0: sl@0: if( cStat.cbInQue > 0 ) { sl@0: if( (DWORD) bufSize > cStat.cbInQue ) { sl@0: bufSize = cStat.cbInQue; sl@0: } sl@0: } else { sl@0: bufSize = 1; sl@0: } sl@0: } sl@0: } sl@0: sl@0: if( bufSize == 0 ) { sl@0: return bytesRead = 0; sl@0: } sl@0: sl@0: /* sl@0: * Perform blocking read. Doesn't block in non-blocking mode, sl@0: * because we checked the number of available bytes. sl@0: */ sl@0: if (blockingRead(infoPtr, (LPVOID) buf, (DWORD) bufSize, &bytesRead, sl@0: &infoPtr->osRead) == FALSE) { sl@0: goto error; sl@0: } sl@0: return bytesRead; sl@0: sl@0: error: sl@0: TclWinConvertError(GetLastError()); sl@0: *errorCode = errno; sl@0: return -1; sl@0: sl@0: commError: sl@0: infoPtr->lastError = infoPtr->error; /* save last error code */ sl@0: infoPtr->error = 0; /* reset error code */ sl@0: *errorCode = EIO; /* to return read-error only once */ sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialOutputProc -- sl@0: * sl@0: * Writes the given output on the IO channel. Returns count of how sl@0: * many characters were actually written, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many characters were written is returned and an sl@0: * error indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Writes output on the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SerialOutputProc( sl@0: ClientData instanceData, /* Serial state. */ sl@0: CONST char *buf, /* The data buffer. */ sl@0: int toWrite, /* How many bytes to write? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: DWORD bytesWritten, timeout; sl@0: sl@0: *errorCode = 0; sl@0: sl@0: /* sl@0: * At EXIT Tcl trys to flush all open channels in blocking mode. sl@0: * We avoid blocking output after ExitProc or CloseHandler(chan) sl@0: * has been called by checking the corrresponding variables. sl@0: */ sl@0: if( ! initialized || TclInExit() ) { sl@0: return toWrite; sl@0: } sl@0: sl@0: /* sl@0: * Check if there is a CommError pending from SerialCheckProc sl@0: */ sl@0: if( infoPtr->error & SERIAL_WRITE_ERRORS ){ sl@0: infoPtr->lastError = infoPtr->error; /* save last error code */ sl@0: infoPtr->error = 0; /* reset error code */ sl@0: errno = EIO; sl@0: goto error; sl@0: } sl@0: sl@0: timeout = (infoPtr->flags & SERIAL_ASYNC) ? 0 : INFINITE; sl@0: if (WaitForSingleObject(infoPtr->evWritable, timeout) == WAIT_TIMEOUT) { sl@0: /* sl@0: * The writer thread is blocked waiting for a write to complete sl@0: * and the channel is in non-blocking mode. sl@0: */ sl@0: sl@0: errno = EWOULDBLOCK; sl@0: goto error1; sl@0: } sl@0: /* sl@0: * Check for a background error on the last write. sl@0: */ sl@0: sl@0: if (infoPtr->writeError) { sl@0: TclWinConvertError(infoPtr->writeError); sl@0: infoPtr->writeError = 0; sl@0: goto error1; sl@0: } sl@0: sl@0: /* sl@0: * Remember the number of bytes in output queue sl@0: */ sl@0: EnterCriticalSection(&infoPtr->csWrite); sl@0: infoPtr->writeQueue += toWrite; sl@0: LeaveCriticalSection(&infoPtr->csWrite); sl@0: sl@0: if (infoPtr->flags & SERIAL_ASYNC) { sl@0: /* sl@0: * The serial is non-blocking, so copy the data into the output sl@0: * buffer and restart the writer thread. sl@0: */ sl@0: sl@0: if (toWrite > infoPtr->writeBufLen) { sl@0: /* sl@0: * Reallocate the buffer to be large enough to hold the data. sl@0: */ sl@0: sl@0: if (infoPtr->writeBuf) { sl@0: ckfree(infoPtr->writeBuf); sl@0: } sl@0: infoPtr->writeBufLen = toWrite; sl@0: infoPtr->writeBuf = ckalloc((unsigned int) toWrite); sl@0: } sl@0: memcpy(infoPtr->writeBuf, buf, (size_t) toWrite); sl@0: infoPtr->toWrite = toWrite; sl@0: ResetEvent(infoPtr->evWritable); sl@0: SetEvent(infoPtr->evStartWriter); sl@0: bytesWritten = (DWORD) toWrite; sl@0: sl@0: } else { sl@0: /* sl@0: * In the blocking case, just try to write the buffer directly. sl@0: * This avoids an unnecessary copy. sl@0: */ sl@0: if (! blockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite, sl@0: &bytesWritten, &infoPtr->osWrite) ) { sl@0: goto writeError; sl@0: } sl@0: if (bytesWritten != (DWORD) toWrite) { sl@0: /* Write timeout */ sl@0: infoPtr->lastError |= CE_PTO; sl@0: errno = EIO; sl@0: goto error; sl@0: } sl@0: } sl@0: sl@0: return (int) bytesWritten; sl@0: sl@0: writeError: sl@0: TclWinConvertError(GetLastError()); sl@0: sl@0: error: sl@0: /* sl@0: * Reset the output queue counter on error during blocking output sl@0: */ sl@0: /* sl@0: EnterCriticalSection(&infoPtr->csWrite); sl@0: infoPtr->writeQueue = 0; sl@0: LeaveCriticalSection(&infoPtr->csWrite); sl@0: */ sl@0: error1: sl@0: *errorCode = errno; sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialEventProc -- sl@0: * sl@0: * This function is invoked by Tcl_ServiceEvent when a file event sl@0: * reaches the front of the event queue. This procedure invokes sl@0: * Tcl_NotifyChannel on the serial. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the notifier callback does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SerialEventProc( sl@0: Tcl_Event *evPtr, /* Event to service. */ sl@0: int flags) /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: SerialEvent *serialEvPtr = (SerialEvent *)evPtr; sl@0: SerialInfo *infoPtr; sl@0: int mask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the list of watched serials for the one whose handle sl@0: * matches the event. We do this rather than simply dereferencing sl@0: * the handle in the event so that serials can be deleted while the sl@0: * event is in the queue. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (serialEvPtr->infoPtr == infoPtr) { sl@0: infoPtr->flags &= ~(SERIAL_PENDING); sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Remove stale events. sl@0: */ sl@0: sl@0: if (!infoPtr) { sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Check to see if the serial is readable. Note sl@0: * that we can't tell if a serial is writable, so we always report it sl@0: * as being writable unless we have detected EOF. sl@0: */ sl@0: sl@0: mask = 0; sl@0: if( infoPtr->watchMask & TCL_WRITABLE ) { sl@0: if( infoPtr->writable ) { sl@0: mask |= TCL_WRITABLE; sl@0: infoPtr->writable = 0; sl@0: } sl@0: } sl@0: sl@0: if( infoPtr->watchMask & TCL_READABLE ) { sl@0: if( infoPtr->readable ) { sl@0: mask |= TCL_READABLE; sl@0: infoPtr->readable = 0; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Inform the channel of the events. sl@0: */ sl@0: sl@0: Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialWatchProc -- sl@0: * sl@0: * Called by the notifier to set up to watch for events on this sl@0: * channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SerialWatchProc( sl@0: ClientData instanceData, /* Serial state. */ sl@0: int mask) /* What events to watch for, OR-ed sl@0: * combination of TCL_READABLE, sl@0: * TCL_WRITABLE and TCL_EXCEPTION. */ sl@0: { sl@0: SerialInfo **nextPtrPtr, *ptr; sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: int oldMask = infoPtr->watchMask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: /* sl@0: * Since the file is always ready for events, we set the block time sl@0: * so we will poll. sl@0: */ sl@0: sl@0: infoPtr->watchMask = mask & infoPtr->validMask; sl@0: if (infoPtr->watchMask) { sl@0: if (!oldMask) { sl@0: infoPtr->nextPtr = tsdPtr->firstSerialPtr; sl@0: tsdPtr->firstSerialPtr = infoPtr; sl@0: } sl@0: SerialBlockTime(infoPtr->blockTime); sl@0: } else { sl@0: if (oldMask) { sl@0: /* sl@0: * Remove the serial port from the list of watched serial ports. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstSerialPtr), ptr = *nextPtrPtr; sl@0: ptr != NULL; sl@0: nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { sl@0: if (infoPtr == ptr) { sl@0: *nextPtrPtr = ptr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialGetHandleProc -- sl@0: * sl@0: * Called from Tcl_GetChannelHandle to retrieve OS handles from sl@0: * inside a command serial port based channel. sl@0: * sl@0: * Results: sl@0: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if sl@0: * there is no handle for the specified direction. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: SerialGetHandleProc( sl@0: ClientData instanceData, /* The serial state. */ sl@0: int direction, /* TCL_READABLE or TCL_WRITABLE */ sl@0: ClientData *handlePtr) /* Where to store the handle. */ sl@0: { sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: sl@0: *handlePtr = (ClientData) infoPtr->handle; sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialWriterThread -- sl@0: * sl@0: * This function runs in a separate thread and writes data sl@0: * onto a serial. sl@0: * sl@0: * Results: sl@0: * Always returns 0. sl@0: * sl@0: * Side effects: sl@0: * Signals the main thread when an output operation is completed. sl@0: * May cause the main thread to wake up by posting a message. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: SerialWriterThread(LPVOID arg) sl@0: { sl@0: sl@0: SerialInfo *infoPtr = (SerialInfo *)arg; sl@0: DWORD bytesWritten, toWrite, waitResult; sl@0: char *buf; sl@0: OVERLAPPED myWrite; /* have an own OVERLAPPED in this thread */ sl@0: HANDLE wEvents[2]; sl@0: sl@0: /* sl@0: * The stop event takes precedence by being first in the list. sl@0: */ sl@0: wEvents[0] = infoPtr->evStopWriter; sl@0: wEvents[1] = infoPtr->evStartWriter; sl@0: sl@0: for (;;) { sl@0: /* sl@0: * Wait for the main thread to signal before attempting to write. sl@0: */ sl@0: sl@0: waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); sl@0: sl@0: if (waitResult != (WAIT_OBJECT_0 + 1)) { sl@0: /* sl@0: * The start event was not signaled. It might be the stop event sl@0: * or an error, so exit. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: sl@0: buf = infoPtr->writeBuf; sl@0: toWrite = infoPtr->toWrite; sl@0: sl@0: myWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); sl@0: sl@0: /* sl@0: * Loop until all of the bytes are written or an error occurs. sl@0: */ sl@0: sl@0: while (toWrite > 0) { sl@0: /* sl@0: * Check for pending writeError sl@0: * Ignore all write operations until the user has been notified sl@0: */ sl@0: if (infoPtr->writeError) { sl@0: break; sl@0: } sl@0: if (blockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite, sl@0: &bytesWritten, &myWrite) == FALSE) { sl@0: infoPtr->writeError = GetLastError(); sl@0: break; sl@0: } sl@0: if (bytesWritten != toWrite) { sl@0: /* Write timeout */ sl@0: infoPtr->writeError = ERROR_WRITE_FAULT; sl@0: break; sl@0: } sl@0: toWrite -= bytesWritten; sl@0: buf += bytesWritten; sl@0: } sl@0: sl@0: CloseHandle(myWrite.hEvent); sl@0: /* sl@0: * Signal the main thread by signalling the evWritable event and sl@0: * then waking up the notifier thread. sl@0: */ sl@0: SetEvent(infoPtr->evWritable); sl@0: sl@0: /* sl@0: * Alert the foreground thread. Note that we need to treat this like sl@0: * a critical section so the foreground thread does not terminate sl@0: * this thread while we are holding a mutex in the notifier code. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&serialMutex); sl@0: if (infoPtr->threadId != NULL) { sl@0: /* TIP #218. When in flight ignore the event, no one will receive it anyway */ sl@0: Tcl_ThreadAlert(infoPtr->threadId); sl@0: } sl@0: Tcl_MutexUnlock(&serialMutex); sl@0: } sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclWinSerialReopen -- sl@0: * sl@0: * Reopens the serial port with the OVERLAPPED FLAG set sl@0: * sl@0: * Results: sl@0: * Returns the new handle, or INVALID_HANDLE_VALUE sl@0: * Normally there shouldn't be any error, sl@0: * because the same channel has previously been succeesfully opened. sl@0: * sl@0: * Side effects: sl@0: * May close the original handle sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: HANDLE sl@0: TclWinSerialReopen(handle, name, access) sl@0: HANDLE handle; sl@0: CONST TCHAR *name; sl@0: DWORD access; sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: tsdPtr = SerialInit(); sl@0: sl@0: /* sl@0: * Multithreaded I/O needs the overlapped flag set sl@0: * otherwise ClearCommError blocks under Windows NT/2000 until serial sl@0: * output is finished sl@0: */ sl@0: if (CloseHandle(handle) == FALSE) { sl@0: return INVALID_HANDLE_VALUE; sl@0: } sl@0: handle = (*tclWinProcs->createFileProc)(name, access, sl@0: 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); sl@0: return handle; sl@0: } sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclWinOpenSerialChannel -- sl@0: * sl@0: * Constructs a Serial port channel for the specified standard OS handle. sl@0: * This is a helper function to break up the construction of sl@0: * channels into File, Console, or Serial. sl@0: * sl@0: * Results: sl@0: * Returns the new channel, or NULL. sl@0: * sl@0: * Side effects: sl@0: * May open the channel sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: TclWinOpenSerialChannel(handle, channelName, permissions) sl@0: HANDLE handle; sl@0: char *channelName; sl@0: int permissions; sl@0: { sl@0: SerialInfo *infoPtr; sl@0: DWORD id; sl@0: sl@0: SerialInit(); sl@0: sl@0: infoPtr = (SerialInfo *) ckalloc((unsigned) sizeof(SerialInfo)); sl@0: memset(infoPtr, 0, sizeof(SerialInfo)); sl@0: sl@0: infoPtr->validMask = permissions; sl@0: infoPtr->handle = handle; sl@0: infoPtr->channel = (Tcl_Channel) NULL; sl@0: infoPtr->readable = 0; sl@0: infoPtr->writable = 1; sl@0: infoPtr->toWrite = infoPtr->writeQueue = 0; sl@0: infoPtr->blockTime = SERIAL_DEFAULT_BLOCKTIME; sl@0: infoPtr->lastEventTime = 0; sl@0: infoPtr->lastError = infoPtr->error = 0; sl@0: infoPtr->threadId = Tcl_GetCurrentThread(); sl@0: infoPtr->sysBufRead = 4096; sl@0: infoPtr->sysBufWrite = 4096; sl@0: sl@0: /* sl@0: * Use the pointer to keep the channel names unique, in case sl@0: * the handles are shared between multiple channels (stdin/stdout). sl@0: */ sl@0: sl@0: wsprintfA(channelName, "file%lx", (int) infoPtr); sl@0: sl@0: infoPtr->channel = Tcl_CreateChannel(&serialChannelType, channelName, sl@0: (ClientData) infoPtr, permissions); sl@0: sl@0: sl@0: SetupComm(handle, infoPtr->sysBufRead, infoPtr->sysBufWrite); sl@0: PurgeComm(handle, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR sl@0: | PURGE_RXCLEAR); sl@0: sl@0: /* sl@0: * default is blocking sl@0: */ sl@0: SetCommTimeouts(handle, &no_timeout); sl@0: sl@0: sl@0: if (permissions & TCL_READABLE) { sl@0: infoPtr->osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); sl@0: } sl@0: if (permissions & TCL_WRITABLE) { sl@0: /* sl@0: * Initially the channel is writable sl@0: * and the writeThread is idle. sl@0: */ sl@0: infoPtr->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); sl@0: infoPtr->evWritable = CreateEvent(NULL, TRUE, TRUE, NULL); sl@0: infoPtr->evStartWriter = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->evStopWriter = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: InitializeCriticalSection(&infoPtr->csWrite); sl@0: infoPtr->writeThread = CreateThread(NULL, 256, SerialWriterThread, sl@0: infoPtr, 0, &id); sl@0: } sl@0: sl@0: /* sl@0: * Files have default translation of AUTO and ^Z eof char, which sl@0: * means that a ^Z will be accepted as EOF when reading. sl@0: */ sl@0: sl@0: Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); sl@0: Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); sl@0: sl@0: return infoPtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialErrorStr -- sl@0: * sl@0: * Converts a Win32 serial error code to a list of readable errors sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static void sl@0: SerialErrorStr(error, dsPtr) sl@0: DWORD error; /* Win32 serial error code */ sl@0: Tcl_DString *dsPtr; /* Where to store string */ sl@0: { sl@0: if( (error & CE_RXOVER) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "RXOVER"); sl@0: } sl@0: if( (error & CE_OVERRUN) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "OVERRUN"); sl@0: } sl@0: if( (error & CE_RXPARITY) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "RXPARITY"); sl@0: } sl@0: if( (error & CE_FRAME) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "FRAME"); sl@0: } sl@0: if( (error & CE_BREAK) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "BREAK"); sl@0: } sl@0: if( (error & CE_TXFULL) != 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "TXFULL"); sl@0: } sl@0: if( (error & CE_PTO) != 0) { /* PTO used to signal WRITE-TIMEOUT */ sl@0: Tcl_DStringAppendElement(dsPtr, "TIMEOUT"); sl@0: } sl@0: if( (error & ~((DWORD) (SERIAL_READ_ERRORS | SERIAL_WRITE_ERRORS))) != 0) { sl@0: char buf[TCL_INTEGER_SPACE + 1]; sl@0: wsprintfA(buf, "%d", error); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: } sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialModemStatusStr -- sl@0: * sl@0: * Converts a Win32 modem status list of readable flags sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static void sl@0: SerialModemStatusStr(status, dsPtr) sl@0: DWORD status; /* Win32 modem status */ sl@0: Tcl_DString *dsPtr; /* Where to store string */ sl@0: { sl@0: Tcl_DStringAppendElement(dsPtr, "CTS"); sl@0: Tcl_DStringAppendElement(dsPtr, (status & MS_CTS_ON) ? "1" : "0"); sl@0: Tcl_DStringAppendElement(dsPtr, "DSR"); sl@0: Tcl_DStringAppendElement(dsPtr, (status & MS_DSR_ON) ? "1" : "0"); sl@0: Tcl_DStringAppendElement(dsPtr, "RING"); sl@0: Tcl_DStringAppendElement(dsPtr, (status & MS_RING_ON) ? "1" : "0"); sl@0: Tcl_DStringAppendElement(dsPtr, "DCD"); sl@0: Tcl_DStringAppendElement(dsPtr, (status & MS_RLSD_ON) ? "1" : "0"); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialSetOptionProc -- sl@0: * sl@0: * Sets an option on a channel. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. Also sets the interp's result on error if sl@0: * interp is not NULL. sl@0: * sl@0: * Side effects: sl@0: * May modify an option on a device. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static int sl@0: SerialSetOptionProc(instanceData, interp, optionName, value) sl@0: ClientData instanceData; /* File state. */ sl@0: Tcl_Interp *interp; /* For error reporting - can be NULL. */ sl@0: CONST char *optionName; /* Which option to set? */ sl@0: CONST char *value; /* New value for option. */ sl@0: { sl@0: SerialInfo *infoPtr; sl@0: DCB dcb; sl@0: BOOL result, flag; sl@0: size_t len, vlen; sl@0: Tcl_DString ds; sl@0: CONST TCHAR *native; sl@0: int argc; sl@0: CONST char **argv; sl@0: sl@0: infoPtr = (SerialInfo *) instanceData; sl@0: sl@0: /* sl@0: * Parse options sl@0: */ sl@0: len = strlen(optionName); sl@0: vlen = strlen(value); sl@0: sl@0: /* sl@0: * Option -mode baud,parity,databits,stopbits sl@0: */ sl@0: if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) { sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: native = Tcl_WinUtfToTChar(value, -1, &ds); sl@0: result = (*tclWinProcs->buildCommDCBProc)(native, &dcb); sl@0: Tcl_DStringFree(&ds); sl@0: sl@0: if (result == FALSE) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "bad value for -mode: should be baud,parity,data,stop", sl@0: (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* Default settings for serial communications */ sl@0: dcb.fBinary = TRUE; sl@0: dcb.fErrorChar = FALSE; sl@0: dcb.fNull = FALSE; sl@0: dcb.fAbortOnError = FALSE; sl@0: sl@0: if (! SetCommState(infoPtr->handle, &dcb) ) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: * Option -handshake none|xonxoff|rtscts|dtrdsr sl@0: */ sl@0: if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) { sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: /* sl@0: * Reset all handshake options sl@0: * DTR and RTS are ON by default sl@0: */ sl@0: dcb.fOutX = dcb.fInX = FALSE; sl@0: dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = dcb.fDsrSensitivity = FALSE; sl@0: dcb.fDtrControl = DTR_CONTROL_ENABLE; sl@0: dcb.fRtsControl = RTS_CONTROL_ENABLE; sl@0: dcb.fTXContinueOnXoff = FALSE; sl@0: sl@0: /* sl@0: * Adjust the handshake limits. sl@0: * Yes, the XonXoff limits seem to influence even hardware handshake sl@0: */ sl@0: dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2); sl@0: dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4); sl@0: sl@0: if (strnicmp(value, "NONE", vlen) == 0) { sl@0: /* leave all handshake options disabled */ sl@0: } else if (strnicmp(value, "XONXOFF", vlen) == 0) { sl@0: dcb.fOutX = dcb.fInX = TRUE; sl@0: } else if (strnicmp(value, "RTSCTS", vlen) == 0) { sl@0: dcb.fOutxCtsFlow = TRUE; sl@0: dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; sl@0: } else if (strnicmp(value, "DTRDSR", vlen) == 0) { sl@0: dcb.fOutxDsrFlow = TRUE; sl@0: dcb.fDtrControl = DTR_CONTROL_HANDSHAKE; sl@0: } else { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, "bad value for -handshake: ", sl@0: "must be one of xonxoff, rtscts, dtrdsr or none", sl@0: (char *) NULL); sl@0: return TCL_ERROR; sl@0: } sl@0: } sl@0: sl@0: if (! SetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: * Option -xchar {\x11 \x13} sl@0: */ sl@0: if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) { sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: if (argc == 2) { sl@0: dcb.XonChar = argv[0][0]; sl@0: dcb.XoffChar = argv[1][0]; sl@0: ckfree((char *) argv); sl@0: } else { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "bad value for -xchar: should be a list of two elements", sl@0: (char *) NULL); sl@0: } sl@0: ckfree((char *) argv); sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: if (! SetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: * Option -ttycontrol {DTR 1 RTS 0 BREAK 0} sl@0: */ sl@0: if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) { sl@0: int i, result = TCL_OK; sl@0: sl@0: if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: if ((argc % 2) == 1) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "bad value for -ttycontrol: should be a list of signal,value pairs", sl@0: (char *) NULL); sl@0: } sl@0: ckfree((char *) argv); sl@0: return TCL_ERROR; sl@0: } sl@0: for (i = 0; i < argc - 1; i += 2) { sl@0: if (Tcl_GetBoolean(interp, argv[i+1], &flag) == TCL_ERROR) { sl@0: result = TCL_ERROR; sl@0: break; sl@0: } sl@0: if (strnicmp(argv[i], "DTR", strlen(argv[i])) == 0) { sl@0: if (!EscapeCommFunction(infoPtr->handle, flag ? sl@0: (DWORD) SETDTR : (DWORD) CLRDTR)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set DTR signal", (char *) NULL); sl@0: } sl@0: result = TCL_ERROR; sl@0: break; sl@0: } sl@0: } else if (strnicmp(argv[i], "RTS", strlen(argv[i])) == 0) { sl@0: if (!EscapeCommFunction(infoPtr->handle, flag ? sl@0: (DWORD) SETRTS : (DWORD) CLRRTS)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set RTS signal", (char *) NULL); sl@0: } sl@0: result = TCL_ERROR; sl@0: break; sl@0: } sl@0: } else if (strnicmp(argv[i], "BREAK", strlen(argv[i])) == 0) { sl@0: if (!EscapeCommFunction(infoPtr->handle, flag ? sl@0: (DWORD) SETBREAK : (DWORD) CLRBREAK)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set BREAK signal", (char *) NULL); sl@0: } sl@0: result = TCL_ERROR; sl@0: break; sl@0: } sl@0: } else { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, "bad signal for -ttycontrol: ", sl@0: "must be DTR, RTS or BREAK", (char *) NULL); sl@0: } sl@0: result = TCL_ERROR; sl@0: break; sl@0: } sl@0: } sl@0: sl@0: ckfree((char *) argv); sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: * Option -sysbuffer {read_size write_size} sl@0: * Option -sysbuffer read_size sl@0: */ sl@0: if ((len > 1) && (strncmp(optionName, "-sysbuffer", len) == 0)) { sl@0: /* sl@0: * -sysbuffer 4096 or -sysbuffer {64536 4096} sl@0: */ sl@0: size_t inSize = (size_t) -1, outSize = (size_t) -1; sl@0: sl@0: if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { sl@0: return TCL_ERROR; sl@0: } sl@0: if (argc == 1) { sl@0: inSize = atoi(argv[0]); sl@0: outSize = infoPtr->sysBufWrite; sl@0: } else if (argc == 2) { sl@0: inSize = atoi(argv[0]); sl@0: outSize = atoi(argv[1]); sl@0: } sl@0: ckfree((char *) argv); sl@0: if ((inSize <= 0) || (outSize <= 0)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "bad value for -sysbuffer: should be a list of one or two integers > 0", sl@0: (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: if (! SetupComm(infoPtr->handle, inSize, outSize)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't setup comm buffers", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: infoPtr->sysBufRead = inSize; sl@0: infoPtr->sysBufWrite = outSize; sl@0: sl@0: /* sl@0: * Adjust the handshake limits. sl@0: * Yes, the XonXoff limits seem to influence even hardware handshake sl@0: */ sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2); sl@0: dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4); sl@0: if (! SetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: * Option -pollinterval msec sl@0: */ sl@0: if ((len > 1) && (strncmp(optionName, "-pollinterval", len) == 0)) { sl@0: sl@0: if ( Tcl_GetInt(interp, value, &(infoPtr->blockTime)) != TCL_OK ) { sl@0: return TCL_ERROR; sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: * Option -timeout msec sl@0: */ sl@0: if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) { sl@0: int msec; sl@0: COMMTIMEOUTS tout = {0,0,0,0,0}; sl@0: sl@0: if ( Tcl_GetInt(interp, value, &msec) != TCL_OK ) { sl@0: return TCL_ERROR; sl@0: } sl@0: tout.ReadTotalTimeoutConstant = msec; sl@0: if (! SetCommTimeouts(infoPtr->handle, &tout)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't set comm timeouts", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: return TCL_OK; sl@0: } sl@0: sl@0: return Tcl_BadChannelOption(interp, optionName, sl@0: "mode handshake pollinterval sysbuffer timeout ttycontrol xchar"); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialGetOptionProc -- sl@0: * sl@0: * Gets a mode associated with an IO channel. If the optionName arg sl@0: * is non NULL, retrieves the value of that option. If the optionName sl@0: * arg is NULL, retrieves a list of alternating option names and sl@0: * values for the given channel. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. Also sets the supplied DString to the sl@0: * string value of the option(s) returned. sl@0: * sl@0: * Side effects: sl@0: * The string returned by this function is in static storage and sl@0: * may be reused at any time subsequent to the call. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: static int sl@0: SerialGetOptionProc(instanceData, interp, optionName, dsPtr) sl@0: ClientData instanceData; /* File state. */ sl@0: Tcl_Interp *interp; /* For error reporting - can be NULL. */ sl@0: CONST char *optionName; /* Option to get. */ sl@0: Tcl_DString *dsPtr; /* Where to store value(s). */ sl@0: { sl@0: SerialInfo *infoPtr; sl@0: DCB dcb; sl@0: size_t len; sl@0: int valid = 0; /* flag if valid option parsed */ sl@0: sl@0: infoPtr = (SerialInfo *) instanceData; sl@0: sl@0: if (optionName == NULL) { sl@0: len = 0; sl@0: } else { sl@0: len = strlen(optionName); sl@0: } sl@0: sl@0: /* sl@0: * get option -mode sl@0: */ sl@0: sl@0: if (len == 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "-mode"); sl@0: } sl@0: if ((len == 0) || sl@0: ((len > 2) && (strncmp(optionName, "-mode", len) == 0))) { sl@0: sl@0: char parity; sl@0: char *stop; sl@0: char buf[2 * TCL_INTEGER_SPACE + 16]; sl@0: sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: valid = 1; sl@0: parity = 'n'; sl@0: if (dcb.Parity <= 4) { sl@0: parity = "noems"[dcb.Parity]; sl@0: } sl@0: stop = (dcb.StopBits == ONESTOPBIT) ? "1" : sl@0: (dcb.StopBits == ONE5STOPBITS) ? "1.5" : "2"; sl@0: sl@0: wsprintfA(buf, "%d,%c,%d,%s", dcb.BaudRate, parity, sl@0: dcb.ByteSize, stop); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: sl@0: /* sl@0: * get option -pollinterval sl@0: */ sl@0: sl@0: if (len == 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "-pollinterval"); sl@0: } sl@0: if ((len == 0) || sl@0: ((len > 1) && (strncmp(optionName, "-pollinterval", len) == 0))) { sl@0: char buf[TCL_INTEGER_SPACE + 1]; sl@0: sl@0: valid = 1; sl@0: wsprintfA(buf, "%d", infoPtr->blockTime); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: sl@0: /* sl@0: * get option -sysbuffer sl@0: */ sl@0: sl@0: if (len == 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "-sysbuffer"); sl@0: Tcl_DStringStartSublist(dsPtr); sl@0: } sl@0: if ((len == 0) || sl@0: ((len > 1) && (strncmp(optionName, "-sysbuffer", len) == 0))) { sl@0: sl@0: char buf[TCL_INTEGER_SPACE + 1]; sl@0: valid = 1; sl@0: sl@0: wsprintfA(buf, "%d", infoPtr->sysBufRead); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: wsprintfA(buf, "%d", infoPtr->sysBufWrite); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: if (len == 0) { sl@0: Tcl_DStringEndSublist(dsPtr); sl@0: } sl@0: sl@0: /* sl@0: * get option -xchar sl@0: */ sl@0: sl@0: if (len == 0) { sl@0: Tcl_DStringAppendElement(dsPtr, "-xchar"); sl@0: Tcl_DStringStartSublist(dsPtr); sl@0: } sl@0: if ((len == 0) || sl@0: ((len > 1) && (strncmp(optionName, "-xchar", len) == 0))) { sl@0: sl@0: char buf[4]; sl@0: valid = 1; sl@0: sl@0: if (! GetCommState(infoPtr->handle, &dcb)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get comm state", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sprintf(buf, "%c", dcb.XonChar); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: sprintf(buf, "%c", dcb.XoffChar); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: if (len == 0) { sl@0: Tcl_DStringEndSublist(dsPtr); sl@0: } sl@0: sl@0: /* sl@0: * get option -lasterror sl@0: * option is readonly and returned by [fconfigure chan -lasterror] sl@0: * but not returned by unnamed [fconfigure chan] sl@0: */ sl@0: sl@0: if ( (len > 1) && (strncmp(optionName, "-lasterror", len) == 0) ) { sl@0: valid = 1; sl@0: SerialErrorStr(infoPtr->lastError, dsPtr); sl@0: } sl@0: sl@0: /* sl@0: * get option -queue sl@0: * option is readonly and returned by [fconfigure chan -queue] sl@0: */ sl@0: sl@0: if ((len > 1) && (strncmp(optionName, "-queue", len) == 0)) { sl@0: char buf[TCL_INTEGER_SPACE + 1]; sl@0: COMSTAT cStat; sl@0: DWORD error; sl@0: int inBuffered, outBuffered, count; sl@0: sl@0: valid = 1; sl@0: sl@0: /* sl@0: * Query the pending data in Tcl's internal queues sl@0: */ sl@0: inBuffered = Tcl_InputBuffered(infoPtr->channel); sl@0: outBuffered = Tcl_OutputBuffered(infoPtr->channel); sl@0: sl@0: /* sl@0: * Query the number of bytes in our output queue: sl@0: * 1. The bytes pending in the output thread sl@0: * 2. The bytes in the system drivers buffer sl@0: * The writer thread should not interfere this action. sl@0: */ sl@0: EnterCriticalSection(&infoPtr->csWrite); sl@0: ClearCommError( infoPtr->handle, &error, &cStat ); sl@0: count = (int)cStat.cbOutQue + infoPtr->writeQueue; sl@0: LeaveCriticalSection(&infoPtr->csWrite); sl@0: sl@0: wsprintfA(buf, "%d", inBuffered + cStat.cbInQue); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: wsprintfA(buf, "%d", outBuffered + count); sl@0: Tcl_DStringAppendElement(dsPtr, buf); sl@0: } sl@0: sl@0: /* sl@0: * get option -ttystatus sl@0: * option is readonly and returned by [fconfigure chan -ttystatus] sl@0: * but not returned by unnamed [fconfigure chan] sl@0: */ sl@0: if ((len > 4) && (strncmp(optionName, "-ttystatus", len) == 0)) { sl@0: sl@0: DWORD status; sl@0: sl@0: if (! GetCommModemStatus(infoPtr->handle, &status)) { sl@0: if (interp) { sl@0: Tcl_AppendResult(interp, sl@0: "can't get tty status", (char *) NULL); sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: valid = 1; sl@0: SerialModemStatusStr(status, dsPtr); sl@0: } sl@0: sl@0: if (valid) { sl@0: return TCL_OK; sl@0: } else { sl@0: return Tcl_BadChannelOption(interp, optionName, sl@0: "mode pollinterval lasterror queue sysbuffer ttystatus xchar"); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * SerialThreadActionProc -- sl@0: * sl@0: * Insert or remove any thread local refs to this channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Changes thread local list of valid channels. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: SerialThreadActionProc (instanceData, action) sl@0: ClientData instanceData; sl@0: int action; sl@0: { sl@0: SerialInfo *infoPtr = (SerialInfo *) instanceData; sl@0: sl@0: /* We do not access firstSerialPtr in the thread structures. This is sl@0: * not for all serials managed by the thread, but only those we are sl@0: * watching. Removal of the filevent handlers before transfer thus sl@0: * takes care of this structure. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&serialMutex); sl@0: if (action == TCL_CHANNEL_THREAD_INSERT) { sl@0: /* We can't copy the thread information from the channel when sl@0: * the channel is created. At this time the channel back sl@0: * pointer has not been set yet. However in that case the sl@0: * threadId has already been set by TclpCreateCommandChannel sl@0: * itself, so the structure is still good. sl@0: */ sl@0: sl@0: SerialInit (); sl@0: if (infoPtr->channel != NULL) { sl@0: infoPtr->threadId = Tcl_GetChannelThread (infoPtr->channel); sl@0: } sl@0: } else { sl@0: infoPtr->threadId = NULL; sl@0: } sl@0: Tcl_MutexUnlock(&serialMutex); sl@0: }