1/*
2 * tclWinSerial.c --
3 *
4 *	This file implements the Windows-specific serial port functions, and
5 *	the "serial" channel driver.
6 *
7 * Copyright (c) 1999 by Scriptics Corp.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * Serial functionality implemented by Rolf.Schroedter@dlr.de
13 *
14 * RCS: @(#) $Id: tclWinSerial.c,v 1.36.2.1 2010/01/31 23:51:37 nijtmans Exp $
15 */
16
17#include "tclWinInt.h"
18
19#include <sys/stat.h>
20
21/*
22 * The following variable is used to tell whether this module has been
23 * initialized.
24 */
25
26static int initialized = 0;
27
28/*
29 * The serialMutex locks around access to the initialized variable, and it is
30 * used to protect background threads from being terminated while they are
31 * using APIs that hold locks.
32 */
33
34TCL_DECLARE_MUTEX(serialMutex)
35
36/*
37 * Bit masks used in the flags field of the SerialInfo structure below.
38 */
39
40#define SERIAL_PENDING	(1<<0)	/* Message is pending in the queue. */
41#define SERIAL_ASYNC	(1<<1)	/* Channel is non-blocking. */
42
43/*
44 * Bit masks used in the sharedFlags field of the SerialInfo structure below.
45 */
46
47#define SERIAL_EOF	(1<<2)	/* Serial has reached EOF. */
48#define SERIAL_ERROR	(1<<4)
49
50/*
51 * Default time to block between checking status on the serial port.
52 */
53
54#define SERIAL_DEFAULT_BLOCKTIME 10	/* 10 msec */
55
56/*
57 * Define Win32 read/write error masks returned by ClearCommError()
58 */
59
60#define SERIAL_READ_ERRORS \
61	(CE_RXOVER | CE_OVERRUN | CE_RXPARITY | CE_FRAME  | CE_BREAK)
62#define SERIAL_WRITE_ERRORS \
63	(CE_TXFULL | CE_PTO)
64
65/*
66 * This structure describes per-instance data for a serial based channel.
67 */
68
69typedef struct SerialInfo {
70    HANDLE handle;
71    struct SerialInfo *nextPtr;	/* Pointer to next registered serial. */
72    Tcl_Channel channel;	/* Pointer to channel structure. */
73    int validMask;		/* OR'ed combination of TCL_READABLE,
74				 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
75				 * which operations are valid on the file. */
76    int watchMask;		/* OR'ed combination of TCL_READABLE,
77				 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
78				 * which events should be reported. */
79    int flags;			/* State flags, see above for a list. */
80    int readable;		/* Flag that the channel is readable. */
81    int writable;		/* Flag that the channel is writable. */
82    int blockTime;		/* Maximum blocktime in msec. */
83    unsigned int lastEventTime;	/* Time in milliseconds since last readable
84				 * event. */
85				/* Next readable event only after blockTime */
86    DWORD error;		/* pending error code returned by
87				 * ClearCommError() */
88    DWORD lastError;		/* last error code, can be fetched with
89				 * fconfigure chan -lasterror */
90    DWORD sysBufRead;		/* Win32 system buffer size for read ops,
91				 * default=4096 */
92    DWORD sysBufWrite;		/* Win32 system buffer size for write ops,
93				 * default=4096 */
94
95    Tcl_ThreadId threadId;	/* Thread to which events should be reported.
96				 * This value is used by the reader/writer
97				 * threads. */
98    OVERLAPPED osRead;		/* OVERLAPPED structure for read operations. */
99    OVERLAPPED osWrite;		/* OVERLAPPED structure for write operations */
100    HANDLE writeThread;		/* Handle to writer thread. */
101    CRITICAL_SECTION csWrite;	/* Writer thread synchronisation. */
102    HANDLE evWritable;		/* Manual-reset event to signal when the
103				 * writer thread has finished waiting for the
104				 * current buffer to be written. */
105    HANDLE evStartWriter;	/* Auto-reset event used by the main thread to
106				 * signal when the writer thread should
107				 * attempt to write to the serial. */
108    HANDLE evStopWriter;	/* Auto-reset event used by the main thread to
109				 * signal when the writer thread should close.
110				 */
111    DWORD writeError;		/* An error caused by the last background
112				 * write. Set to 0 if no error has been
113				 * detected. This word is shared with the
114				 * writer thread so access must be
115				 * synchronized with the evWritable object. */
116    char *writeBuf;		/* Current background output buffer. Access is
117				 * synchronized with the evWritable object. */
118    int writeBufLen;		/* Size of write buffer. Access is
119				 * synchronized with the evWritable object. */
120    int toWrite;		/* Current amount to be written. Access is
121				 * synchronized with the evWritable object. */
122    int writeQueue;		/* Number of bytes pending in output queue.
123				 * Offset to DCB.cbInQue. Used to query
124				 * [fconfigure -queue] */
125} SerialInfo;
126
127typedef struct ThreadSpecificData {
128    /*
129     * The following pointer refers to the head of the list of serials that
130     * are being watched for file events.
131     */
132
133    SerialInfo *firstSerialPtr;
134} ThreadSpecificData;
135
136static Tcl_ThreadDataKey dataKey;
137
138/*
139 * The following structure is what is added to the Tcl event queue when serial
140 * events are generated.
141 */
142
143typedef struct SerialEvent {
144    Tcl_Event header;		/* Information that is standard for all
145				 * events. */
146    SerialInfo *infoPtr;	/* Pointer to serial info structure. Note that
147				 * we still have to verify that the serial
148				 * exists before dereferencing this
149				 * pointer. */
150} SerialEvent;
151
152/*
153 * We don't use timeouts.
154 */
155
156static COMMTIMEOUTS no_timeout = {
157    0,			/* ReadIntervalTimeout */
158    0,			/* ReadTotalTimeoutMultiplier */
159    0,			/* ReadTotalTimeoutConstant */
160    0,			/* WriteTotalTimeoutMultiplier */
161    0,			/* WriteTotalTimeoutConstant */
162};
163
164/*
165 * Declarations for functions used only in this file.
166 */
167
168static int		SerialBlockProc(ClientData instanceData, int mode);
169static void		SerialCheckProc(ClientData clientData, int flags);
170static int		SerialCloseProc(ClientData instanceData,
171			    Tcl_Interp *interp);
172static int		SerialEventProc(Tcl_Event *evPtr, int flags);
173static void		SerialExitHandler(ClientData clientData);
174static int		SerialGetHandleProc(ClientData instanceData,
175			    int direction, ClientData *handlePtr);
176static ThreadSpecificData *SerialInit(void);
177static int		SerialInputProc(ClientData instanceData, char *buf,
178			    int toRead, int *errorCode);
179static int		SerialOutputProc(ClientData instanceData,
180			    CONST char *buf, int toWrite, int *errorCode);
181static void		SerialSetupProc(ClientData clientData, int flags);
182static void		SerialWatchProc(ClientData instanceData, int mask);
183static void		ProcExitHandler(ClientData clientData);
184static int		SerialGetOptionProc(ClientData instanceData,
185			    Tcl_Interp *interp, CONST char *optionName,
186			    Tcl_DString *dsPtr);
187static int		SerialSetOptionProc(ClientData instanceData,
188			    Tcl_Interp *interp, CONST char *optionName,
189			    CONST char *value);
190static DWORD WINAPI	SerialWriterThread(LPVOID arg);
191static void		SerialThreadActionProc(ClientData instanceData,
192			    int action);
193static int		SerialBlockingRead(SerialInfo *infoPtr, LPVOID buf,
194			    DWORD bufSize, LPDWORD lpRead, LPOVERLAPPED osPtr);
195static int		SerialBlockingWrite(SerialInfo *infoPtr, LPVOID buf,
196			    DWORD bufSize, LPDWORD lpWritten,
197			    LPOVERLAPPED osPtr);
198
199/*
200 * This structure describes the channel type structure for command serial
201 * based IO.
202 */
203
204static Tcl_ChannelType serialChannelType = {
205    "serial",			/* Type name. */
206    TCL_CHANNEL_VERSION_5,	/* v5 channel */
207    SerialCloseProc,		/* Close proc. */
208    SerialInputProc,		/* Input proc. */
209    SerialOutputProc,		/* Output proc. */
210    NULL,			/* Seek proc. */
211    SerialSetOptionProc,	/* Set option proc. */
212    SerialGetOptionProc,	/* Get option proc. */
213    SerialWatchProc,		/* Set up notifier to watch the channel. */
214    SerialGetHandleProc,	/* Get an OS handle from channel. */
215    NULL,			/* close2proc. */
216    SerialBlockProc,		/* Set blocking or non-blocking mode.*/
217    NULL,			/* flush proc. */
218    NULL,			/* handler proc. */
219    NULL,			/* wide seek proc */
220    SerialThreadActionProc,	/* thread action proc */
221    NULL,                       /* truncate */
222};
223
224/*
225 *----------------------------------------------------------------------
226 *
227 * SerialInit --
228 *
229 *	This function initializes the static variables for this file.
230 *
231 * Results:
232 *	None.
233 *
234 * Side effects:
235 *	Creates a new event source.
236 *
237 *----------------------------------------------------------------------
238 */
239
240static ThreadSpecificData *
241SerialInit(void)
242{
243    ThreadSpecificData *tsdPtr;
244
245    /*
246     * Check the initialized flag first, then check it again in the mutex.
247     * This is a speed enhancement.
248     */
249
250    if (!initialized) {
251	Tcl_MutexLock(&serialMutex);
252	if (!initialized) {
253	    initialized = 1;
254	    Tcl_CreateExitHandler(ProcExitHandler, NULL);
255	}
256	Tcl_MutexUnlock(&serialMutex);
257    }
258
259    tsdPtr = (ThreadSpecificData *) TclThreadDataKeyGet(&dataKey);
260    if (tsdPtr == NULL) {
261	tsdPtr = TCL_TSD_INIT(&dataKey);
262	tsdPtr->firstSerialPtr = NULL;
263	Tcl_CreateEventSource(SerialSetupProc, SerialCheckProc, NULL);
264	Tcl_CreateThreadExitHandler(SerialExitHandler, NULL);
265    }
266    return tsdPtr;
267}
268
269/*
270 *----------------------------------------------------------------------
271 *
272 * SerialExitHandler --
273 *
274 *	This function is called to cleanup the serial module before Tcl is
275 *	unloaded.
276 *
277 * Results:
278 *	None.
279 *
280 * Side effects:
281 *	Removes the serial event source.
282 *
283 *----------------------------------------------------------------------
284 */
285
286static void
287SerialExitHandler(
288    ClientData clientData)	/* Old window proc */
289{
290    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
291    SerialInfo *infoPtr;
292
293    /*
294     * Clear all eventually pending output. Otherwise Tcl's exit could totally
295     * block, because it performs a blocking flush on all open channels. Note
296     * that serial write operations may be blocked due to handshake.
297     */
298
299    for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL;
300	    infoPtr = infoPtr->nextPtr) {
301	PurgeComm(infoPtr->handle,
302		PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
303    }
304    Tcl_DeleteEventSource(SerialSetupProc, SerialCheckProc, NULL);
305}
306
307/*
308 *----------------------------------------------------------------------
309 *
310 * ProcExitHandler --
311 *
312 *	This function is called to cleanup the process list before Tcl is
313 *	unloaded.
314 *
315 * Results:
316 *	None.
317 *
318 * Side effects:
319 *	Resets the process list.
320 *
321 *----------------------------------------------------------------------
322 */
323
324static void
325ProcExitHandler(
326    ClientData clientData)	/* Old window proc */
327{
328    Tcl_MutexLock(&serialMutex);
329    initialized = 0;
330    Tcl_MutexUnlock(&serialMutex);
331}
332
333/*
334 *----------------------------------------------------------------------
335 *
336 * SerialBlockTime --
337 *
338 *	Wrapper to set Tcl's block time in msec
339 *
340 * Results:
341 *	None.
342 *
343 * Side effects:
344 *	Updates the maximum blocking time.
345 *
346 *----------------------------------------------------------------------
347 */
348
349static void
350SerialBlockTime(
351    int msec)			/* milli-seconds */
352{
353    Tcl_Time blockTime;
354
355    blockTime.sec  =  msec / 1000;
356    blockTime.usec = (msec % 1000) * 1000;
357    Tcl_SetMaxBlockTime(&blockTime);
358}
359
360/*
361 *----------------------------------------------------------------------
362 *
363 * SerialGetMilliseconds --
364 *
365 *	Get current time in milliseconds,ignoring integer overruns.
366 *
367 * Results:
368 *	The current time.
369 *
370 * Side effects:
371 *	None.
372 *
373 *----------------------------------------------------------------------
374 */
375
376static unsigned int
377SerialGetMilliseconds(void)
378{
379    Tcl_Time time;
380
381    TclpGetTime(&time);
382
383    return (time.sec * 1000 + time.usec / 1000);
384}
385
386/*
387 *----------------------------------------------------------------------
388 *
389 * SerialSetupProc --
390 *
391 *	This procedure is invoked before Tcl_DoOneEvent blocks waiting for an
392 *	event.
393 *
394 * Results:
395 *	None.
396 *
397 * Side effects:
398 *	Adjusts the block time if needed.
399 *
400 *----------------------------------------------------------------------
401 */
402
403void
404SerialSetupProc(
405    ClientData data,		/* Not used. */
406    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
407{
408    SerialInfo *infoPtr;
409    int block = 1;
410    int msec = INT_MAX;		/* min. found block time */
411    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
412
413    if (!(flags & TCL_FILE_EVENTS)) {
414	return;
415    }
416
417    /*
418     * Look to see if any events handlers installed. If they are, do not
419     * block.
420     */
421
422    for (infoPtr=tsdPtr->firstSerialPtr ; infoPtr!=NULL ;
423	    infoPtr=infoPtr->nextPtr) {
424	if (infoPtr->watchMask & TCL_WRITABLE) {
425	    if (WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) {
426		block = 0;
427		msec = min(msec, infoPtr->blockTime);
428	    }
429	}
430	if (infoPtr->watchMask & TCL_READABLE) {
431	    block = 0;
432	    msec = min(msec, infoPtr->blockTime);
433	}
434    }
435
436    if (!block) {
437	SerialBlockTime(msec);
438    }
439}
440
441/*
442 *----------------------------------------------------------------------
443 *
444 * SerialCheckProc --
445 *
446 *	This procedure is called by Tcl_DoOneEvent to check the serial event
447 *	source for events.
448 *
449 * Results:
450 *	None.
451 *
452 * Side effects:
453 *	May queue an event.
454 *
455 *----------------------------------------------------------------------
456 */
457
458static void
459SerialCheckProc(
460    ClientData data,		/* Not used. */
461    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
462{
463    SerialInfo *infoPtr;
464    SerialEvent *evPtr;
465    int needEvent;
466    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
467    COMSTAT cStat;
468    unsigned int time;
469
470    if (!(flags & TCL_FILE_EVENTS)) {
471	return;
472    }
473
474    /*
475     * Queue events for any ready serials that don't already have events
476     * queued.
477     */
478
479    for (infoPtr=tsdPtr->firstSerialPtr ; infoPtr!=NULL ;
480	    infoPtr=infoPtr->nextPtr) {
481	if (infoPtr->flags & SERIAL_PENDING) {
482	    continue;
483	}
484
485	needEvent = 0;
486
487	/*
488	 * If WRITABLE watch mask is set look for infoPtr->evWritable object.
489	 */
490
491	if (infoPtr->watchMask & TCL_WRITABLE &&
492		WaitForSingleObject(infoPtr->evWritable, 0) != WAIT_TIMEOUT) {
493	    infoPtr->writable = 1;
494	    needEvent = 1;
495	}
496
497	/*
498	 * If READABLE watch mask is set call ClearCommError to poll cbInQue.
499	 * Window errors are ignored here.
500	 */
501
502	if (infoPtr->watchMask & TCL_READABLE) {
503	    if (ClearCommError(infoPtr->handle, &infoPtr->error, &cStat)) {
504		/*
505		 * Look for characters already pending in windows queue. If
506		 * they are, poll.
507		 */
508
509		if (infoPtr->watchMask & TCL_READABLE) {
510		    /*
511		     * Force fileevent after serial read error.
512		     */
513
514		    if ((cStat.cbInQue > 0) ||
515			    (infoPtr->error & SERIAL_READ_ERRORS)) {
516			infoPtr->readable = 1;
517			time = SerialGetMilliseconds();
518			if ((unsigned int) (time - infoPtr->lastEventTime)
519				>= (unsigned int) infoPtr->blockTime) {
520			    needEvent = 1;
521			    infoPtr->lastEventTime = time;
522			}
523		    }
524		}
525	    }
526	}
527
528	/*
529	 * Queue an event if the serial is signaled for reading or writing.
530	 */
531
532	if (needEvent) {
533	    infoPtr->flags |= SERIAL_PENDING;
534	    evPtr = (SerialEvent *) ckalloc(sizeof(SerialEvent));
535	    evPtr->header.proc = SerialEventProc;
536	    evPtr->infoPtr = infoPtr;
537	    Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
538	}
539    }
540}
541
542/*
543 *----------------------------------------------------------------------
544 *
545 * SerialBlockProc --
546 *
547 *	Set blocking or non-blocking mode on channel.
548 *
549 * Results:
550 *	0 if successful, errno when failed.
551 *
552 * Side effects:
553 *	Sets the device into blocking or non-blocking mode.
554 *
555 *----------------------------------------------------------------------
556 */
557
558static int
559SerialBlockProc(
560    ClientData instanceData,    /* Instance data for channel. */
561    int mode)			/* TCL_MODE_BLOCKING or
562				 * TCL_MODE_NONBLOCKING. */
563{
564    int errorCode = 0;
565    SerialInfo *infoPtr = (SerialInfo *) instanceData;
566
567    /*
568     * Only serial READ can be switched between blocking & nonblocking using
569     * COMMTIMEOUTS. Serial write emulates blocking & nonblocking by the
570     * SerialWriterThread.
571     */
572
573    if (mode == TCL_MODE_NONBLOCKING) {
574	infoPtr->flags |= SERIAL_ASYNC;
575    } else {
576	infoPtr->flags &= ~(SERIAL_ASYNC);
577    }
578    return errorCode;
579}
580
581/*
582 *----------------------------------------------------------------------
583 *
584 * SerialCloseProc --
585 *
586 *	Closes a serial based IO channel.
587 *
588 * Results:
589 *	0 on success, errno otherwise.
590 *
591 * Side effects:
592 *	Closes the physical channel.
593 *
594 *----------------------------------------------------------------------
595 */
596
597static int
598SerialCloseProc(
599    ClientData instanceData,    /* Pointer to SerialInfo structure. */
600    Tcl_Interp *interp)		/* For error reporting. */
601{
602    SerialInfo *serialPtr = (SerialInfo *) instanceData;
603    int errorCode, result = 0;
604    SerialInfo *infoPtr, **nextPtrPtr;
605    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
606    DWORD exitCode;
607
608    errorCode = 0;
609
610    if (serialPtr->validMask & TCL_READABLE) {
611	PurgeComm(serialPtr->handle, PURGE_RXABORT | PURGE_RXCLEAR);
612	CloseHandle(serialPtr->osRead.hEvent);
613    }
614    serialPtr->validMask &= ~TCL_READABLE;
615
616    if (serialPtr->validMask & TCL_WRITABLE) {
617	/*
618	 * Generally we cannot wait for a pending write operation because it
619	 * may hang due to handshake
620	 *    WaitForSingleObject(serialPtr->evWritable, INFINITE);
621	 */
622
623	/*
624	 * The thread may have already closed on it's own. Check it's exit
625	 * code.
626	 */
627
628	GetExitCodeThread(serialPtr->writeThread, &exitCode);
629
630	if (exitCode == STILL_ACTIVE) {
631	    /*
632	     * Set the stop event so that if the writer thread is blocked in
633	     * SerialWriterThread on WaitForMultipleEvents, it will exit
634	     * cleanly.
635	     */
636
637	    SetEvent(serialPtr->evStopWriter);
638
639	    /*
640	     * Wait at most 20 milliseconds for the writer thread to close.
641	     */
642
643	    if (WaitForSingleObject(serialPtr->writeThread,
644		    20) == WAIT_TIMEOUT) {
645		/*
646		 * Forcibly terminate the background thread as a last resort.
647		 * Note that we need to guard against terminating the thread
648		 * while it is in the middle of Tcl_ThreadAlert because it
649		 * won't be able to release the notifier lock.
650		 */
651
652		Tcl_MutexLock(&serialMutex);
653
654		/* BUG: this leaks memory */
655		TerminateThread(serialPtr->writeThread, 0);
656
657		Tcl_MutexUnlock(&serialMutex);
658	    }
659	}
660
661	CloseHandle(serialPtr->writeThread);
662	CloseHandle(serialPtr->osWrite.hEvent);
663	CloseHandle(serialPtr->evWritable);
664	CloseHandle(serialPtr->evStartWriter);
665	CloseHandle(serialPtr->evStopWriter);
666	serialPtr->writeThread = NULL;
667
668	PurgeComm(serialPtr->handle, PURGE_TXABORT | PURGE_TXCLEAR);
669    }
670    serialPtr->validMask &= ~TCL_WRITABLE;
671
672    DeleteCriticalSection(&serialPtr->csWrite);
673
674    /*
675     * Don't close the Win32 handle if the handle is a standard channel during
676     * the thread exit process. Otherwise, one thread may kill the stdio of
677     * another.
678     */
679
680    if (!TclInThreadExit()
681	    || ((GetStdHandle(STD_INPUT_HANDLE) != serialPtr->handle)
682	    && (GetStdHandle(STD_OUTPUT_HANDLE) != serialPtr->handle)
683	    && (GetStdHandle(STD_ERROR_HANDLE) != serialPtr->handle))) {
684	if (CloseHandle(serialPtr->handle) == FALSE) {
685	    TclWinConvertError(GetLastError());
686	    errorCode = errno;
687	}
688    }
689
690    serialPtr->watchMask &= serialPtr->validMask;
691
692    /*
693     * Remove the file from the list of watched files.
694     */
695
696    for (nextPtrPtr=&(tsdPtr->firstSerialPtr), infoPtr=*nextPtrPtr;
697	    infoPtr!=NULL;
698	    nextPtrPtr=&infoPtr->nextPtr, infoPtr=*nextPtrPtr) {
699	if (infoPtr == (SerialInfo *)serialPtr) {
700	    *nextPtrPtr = infoPtr->nextPtr;
701	    break;
702	}
703    }
704
705    /*
706     * Wrap the error file into a channel and give it to the cleanup routine.
707     */
708
709    if (serialPtr->writeBuf != NULL) {
710	ckfree(serialPtr->writeBuf);
711	serialPtr->writeBuf = NULL;
712    }
713    ckfree((char*) serialPtr);
714
715    if (errorCode == 0) {
716	return result;
717    }
718    return errorCode;
719}
720
721/*
722 *----------------------------------------------------------------------
723 *
724 * SerialBlockingRead --
725 *
726 *	Perform a blocking read into the buffer given. Returns count of how
727 *	many bytes were actually read, and an error indication.
728 *
729 * Results:
730 *	A count of how many bytes were read is returned and an error
731 *	indication is returned.
732 *
733 * Side effects:
734 *	Reads input from the actual channel.
735 *
736 *----------------------------------------------------------------------
737 */
738
739static int
740SerialBlockingRead(
741    SerialInfo *infoPtr,	/* Serial info structure */
742    LPVOID buf,			/* The input buffer pointer */
743    DWORD bufSize,		/* The number of bytes to read */
744    LPDWORD lpRead,		/* Returns number of bytes read */
745    LPOVERLAPPED osPtr)		/* OVERLAPPED structure */
746{
747    /*
748     *  Perform overlapped blocking read.
749     *  1. Reset the overlapped event
750     *  2. Start overlapped read operation
751     *  3. Wait for completion
752     */
753
754    /*
755     * Set Offset to ZERO, otherwise NT4.0 may report an error.
756     */
757
758    osPtr->Offset = osPtr->OffsetHigh = 0;
759    ResetEvent(osPtr->hEvent);
760    if (!ReadFile(infoPtr->handle, buf, bufSize, lpRead, osPtr)) {
761	if (GetLastError() != ERROR_IO_PENDING) {
762	    /*
763	     * ReadFile failed, but it isn't delayed. Report error.
764	     */
765
766	    return FALSE;
767	} else {
768	    /*
769	     * Read is pending, wait for completion, timeout?
770	     */
771
772	    if (!GetOverlappedResult(infoPtr->handle, osPtr, lpRead, TRUE)) {
773		return FALSE;
774	    }
775	}
776    } else {
777	/*
778	 * ReadFile completed immediately.
779	 */
780    }
781    return TRUE;
782}
783
784/*
785 *----------------------------------------------------------------------
786 *
787 * SerialBlockingWrite --
788 *
789 *	Perform a blocking write from the buffer given. Returns count of how
790 *	many bytes were actually written, and an error indication.
791 *
792 * Results:
793 *	A count of how many bytes were written is returned and an error
794 *	indication is returned.
795 *
796 * Side effects:
797 *	Writes output to the actual channel.
798 *
799 *----------------------------------------------------------------------
800 */
801
802static int
803SerialBlockingWrite(
804    SerialInfo *infoPtr,	/* Serial info structure */
805    LPVOID buf,			/* The output buffer pointer */
806    DWORD bufSize,		/* The number of bytes to write */
807    LPDWORD lpWritten,		/* Returns number of bytes written */
808    LPOVERLAPPED osPtr)		/* OVERLAPPED structure */
809{
810    int result;
811
812    /*
813     * Perform overlapped blocking write.
814     *  1. Reset the overlapped event
815     *  2. Remove these bytes from the output queue counter
816     *  3. Start overlapped write operation
817     *  3. Remove these bytes from the output queue counter
818     *  4. Wait for completion
819     *  5. Adjust the output queue counter
820     */
821
822    ResetEvent(osPtr->hEvent);
823
824    EnterCriticalSection(&infoPtr->csWrite);
825    infoPtr->writeQueue -= bufSize;
826
827    /*
828     * Set Offset to ZERO, otherwise NT4.0 may report an error
829     */
830
831    osPtr->Offset = osPtr->OffsetHigh = 0;
832    result = WriteFile(infoPtr->handle, buf, bufSize, lpWritten, osPtr);
833    LeaveCriticalSection(&infoPtr->csWrite);
834
835    if (result == FALSE) {
836	int err = GetLastError();
837
838	switch (err) {
839	case ERROR_IO_PENDING:
840	    /*
841	     * Write is pending, wait for completion.
842	     */
843
844	    if (!GetOverlappedResult(infoPtr->handle, osPtr, lpWritten,
845		    TRUE)) {
846		return FALSE;
847	    }
848	    break;
849	case ERROR_COUNTER_TIMEOUT:
850	    /*
851	     * Write timeout handled in SerialOutputProc.
852	     */
853
854	    break;
855	default:
856	    /*
857	     * WriteFile failed, but it isn't delayed. Report error.
858	     */
859
860	    return FALSE;
861	}
862    } else {
863	/*
864	 * WriteFile completed immediately.
865	 */
866    }
867
868    EnterCriticalSection(&infoPtr->csWrite);
869    infoPtr->writeQueue += (*lpWritten - bufSize);
870    LeaveCriticalSection(&infoPtr->csWrite);
871
872    return TRUE;
873}
874
875/*
876 *----------------------------------------------------------------------
877 *
878 * SerialInputProc --
879 *
880 *	Reads input from the IO channel into the buffer given. Returns count
881 *	of how many bytes were actually read, and an error indication.
882 *
883 * Results:
884 *	A count of how many bytes were read is returned and an error
885 *	indication is returned in an output argument.
886 *
887 * Side effects:
888 *	Reads input from the actual channel.
889 *
890 *----------------------------------------------------------------------
891 */
892
893static int
894SerialInputProc(
895    ClientData instanceData,	/* Serial state. */
896    char *buf,			/* Where to store data read. */
897    int bufSize,		/* How much space is available in the
898				 * buffer? */
899    int *errorCode)		/* Where to store error code. */
900{
901    SerialInfo *infoPtr = (SerialInfo *) instanceData;
902    DWORD bytesRead = 0;
903    COMSTAT cStat;
904
905    *errorCode = 0;
906
907    /*
908     * Check if there is a CommError pending from SerialCheckProc
909     */
910
911    if (infoPtr->error & SERIAL_READ_ERRORS) {
912	goto commError;
913    }
914
915    /*
916     * Look for characters already pending in windows queue. This is the
917     * mainly restored good old code from Tcl8.0
918     */
919
920    if (ClearCommError(infoPtr->handle, &infoPtr->error, &cStat)) {
921	/*
922	 * Check for errors here, but not in the evSetup/Check procedures.
923	 */
924
925	if (infoPtr->error & SERIAL_READ_ERRORS) {
926	    goto commError;
927	}
928	if (infoPtr->flags & SERIAL_ASYNC) {
929	    /*
930	     * NON_BLOCKING mode: Avoid blocking by reading more bytes than
931	     * available in input buffer.
932	     */
933
934	    if (cStat.cbInQue > 0) {
935		if ((DWORD) bufSize > cStat.cbInQue) {
936		    bufSize = cStat.cbInQue;
937		}
938	    } else {
939		errno = *errorCode = EAGAIN;
940		return -1;
941	    }
942	} else {
943	    /*
944	     * BLOCKING mode: Tcl trys to read a full buffer of 4 kBytes here.
945	     */
946
947	    if (cStat.cbInQue > 0) {
948		if ((DWORD) bufSize > cStat.cbInQue) {
949		    bufSize = cStat.cbInQue;
950		}
951	    } else {
952		bufSize = 1;
953	    }
954	}
955    }
956
957    if (bufSize == 0) {
958	return bytesRead = 0;
959    }
960
961    /*
962     * Perform blocking read. Doesn't block in non-blocking mode, because we
963     * checked the number of available bytes.
964     */
965
966    if (SerialBlockingRead(infoPtr, (LPVOID) buf, (DWORD) bufSize, &bytesRead,
967	    &infoPtr->osRead) == FALSE) {
968	TclWinConvertError(GetLastError());
969	*errorCode = errno;
970	return -1;
971    }
972    return bytesRead;
973
974  commError:
975    infoPtr->lastError = infoPtr->error;
976				/* save last error code */
977    infoPtr->error = 0;		/* reset error code */
978    *errorCode = EIO;		/* to return read-error only once */
979    return -1;
980}
981
982/*
983 *----------------------------------------------------------------------
984 *
985 * SerialOutputProc --
986 *
987 *	Writes the given output on the IO channel. Returns count of how many
988 *	characters were actually written, and an error indication.
989 *
990 * Results:
991 *	A count of how many characters were written is returned and an error
992 *	indication is returned in an output argument.
993 *
994 * Side effects:
995 *	Writes output on the actual channel.
996 *
997 *----------------------------------------------------------------------
998 */
999
1000static int
1001SerialOutputProc(
1002    ClientData instanceData,	/* Serial state. */
1003    CONST char *buf,		/* The data buffer. */
1004    int toWrite,		/* How many bytes to write? */
1005    int *errorCode)		/* Where to store error code. */
1006{
1007    SerialInfo *infoPtr = (SerialInfo *) instanceData;
1008    DWORD bytesWritten, timeout;
1009
1010    *errorCode = 0;
1011
1012    /*
1013     * At EXIT Tcl trys to flush all open channels in blocking mode. We avoid
1014     * blocking output after ExitProc or CloseHandler(chan) has been called by
1015     * checking the corrresponding variables.
1016     */
1017
1018    if (!initialized || TclInExit()) {
1019	return toWrite;
1020    }
1021
1022    /*
1023     * Check if there is a CommError pending from SerialCheckProc
1024     */
1025
1026    if (infoPtr->error & SERIAL_WRITE_ERRORS) {
1027	infoPtr->lastError = infoPtr->error;
1028				/* save last error code */
1029	infoPtr->error = 0;	/* reset error code */
1030	errno = EIO;
1031	goto error;
1032    }
1033
1034    timeout = (infoPtr->flags & SERIAL_ASYNC) ? 0 : INFINITE;
1035    if (WaitForSingleObject(infoPtr->evWritable, timeout) == WAIT_TIMEOUT) {
1036	/*
1037	 * The writer thread is blocked waiting for a write to complete and
1038	 * the channel is in non-blocking mode.
1039	 */
1040
1041	errno = EWOULDBLOCK;
1042	goto error1;
1043    }
1044
1045    /*
1046     * Check for a background error on the last write.
1047     */
1048
1049    if (infoPtr->writeError) {
1050	TclWinConvertError(infoPtr->writeError);
1051	infoPtr->writeError = 0;
1052	goto error1;
1053    }
1054
1055    /*
1056     * Remember the number of bytes in output queue
1057     */
1058
1059    EnterCriticalSection(&infoPtr->csWrite);
1060    infoPtr->writeQueue += toWrite;
1061    LeaveCriticalSection(&infoPtr->csWrite);
1062
1063    if (infoPtr->flags & SERIAL_ASYNC) {
1064	/*
1065	 * The serial is non-blocking, so copy the data into the output buffer
1066	 * and restart the writer thread.
1067	 */
1068
1069	if (toWrite > infoPtr->writeBufLen) {
1070	    /*
1071	     * Reallocate the buffer to be large enough to hold the data.
1072	     */
1073
1074	    if (infoPtr->writeBuf) {
1075		ckfree(infoPtr->writeBuf);
1076	    }
1077	    infoPtr->writeBufLen = toWrite;
1078	    infoPtr->writeBuf = ckalloc((unsigned int) toWrite);
1079	}
1080	memcpy(infoPtr->writeBuf, buf, (size_t) toWrite);
1081	infoPtr->toWrite = toWrite;
1082	ResetEvent(infoPtr->evWritable);
1083	SetEvent(infoPtr->evStartWriter);
1084	bytesWritten = (DWORD) toWrite;
1085
1086    } else {
1087	/*
1088	 * In the blocking case, just try to write the buffer directly. This
1089	 * avoids an unnecessary copy.
1090	 */
1091
1092	if (!SerialBlockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite,
1093		&bytesWritten, &infoPtr->osWrite)) {
1094	    goto writeError;
1095	}
1096	if (bytesWritten != (DWORD) toWrite) {
1097	    /*
1098	     * Write timeout.
1099	     */
1100	    infoPtr->lastError |= CE_PTO;
1101	    errno = EIO;
1102	    goto error;
1103	}
1104    }
1105
1106    return (int) bytesWritten;
1107
1108  writeError:
1109    TclWinConvertError(GetLastError());
1110
1111  error:
1112    /*
1113     * Reset the output queue counter on error during blocking output
1114     */
1115
1116    /*
1117     * EnterCriticalSection(&infoPtr->csWrite);
1118     * infoPtr->writeQueue = 0;
1119     * LeaveCriticalSection(&infoPtr->csWrite);
1120     */
1121  error1:
1122    *errorCode = errno;
1123    return -1;
1124}
1125
1126/*
1127 *----------------------------------------------------------------------
1128 *
1129 * SerialEventProc --
1130 *
1131 *	This function is invoked by Tcl_ServiceEvent when a file event reaches
1132 *	the front of the event queue. This procedure invokes Tcl_NotifyChannel
1133 *	on the serial.
1134 *
1135 * Results:
1136 *	Returns 1 if the event was handled, meaning it should be removed from
1137 *	the queue. Returns 0 if the event was not handled, meaning it should
1138 *	stay on the queue. The only time the event isn't handled is if the
1139 *	TCL_FILE_EVENTS flag bit isn't set.
1140 *
1141 * Side effects:
1142 *	Whatever the notifier callback does.
1143 *
1144 *----------------------------------------------------------------------
1145 */
1146
1147static int
1148SerialEventProc(
1149    Tcl_Event *evPtr,		/* Event to service. */
1150    int flags)			/* Flags that indicate what events to handle,
1151				 * such as TCL_FILE_EVENTS. */
1152{
1153    SerialEvent *serialEvPtr = (SerialEvent *)evPtr;
1154    SerialInfo *infoPtr;
1155    int mask;
1156    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1157
1158    if (!(flags & TCL_FILE_EVENTS)) {
1159	return 0;
1160    }
1161
1162    /*
1163     * Search through the list of watched serials for the one whose handle
1164     * matches the event. We do this rather than simply dereferencing the
1165     * handle in the event so that serials can be deleted while the event is
1166     * in the queue.
1167     */
1168
1169    for (infoPtr = tsdPtr->firstSerialPtr; infoPtr != NULL;
1170	    infoPtr = infoPtr->nextPtr) {
1171	if (serialEvPtr->infoPtr == infoPtr) {
1172	    infoPtr->flags &= ~(SERIAL_PENDING);
1173	    break;
1174	}
1175    }
1176
1177    /*
1178     * Remove stale events.
1179     */
1180
1181    if (!infoPtr) {
1182	return 1;
1183    }
1184
1185    /*
1186     * Check to see if the serial is readable. Note that we can't tell if a
1187     * serial is writable, so we always report it as being writable unless we
1188     * have detected EOF.
1189     */
1190
1191    mask = 0;
1192    if (infoPtr->watchMask & TCL_WRITABLE) {
1193	if (infoPtr->writable) {
1194	    mask |= TCL_WRITABLE;
1195	    infoPtr->writable = 0;
1196	}
1197    }
1198
1199    if (infoPtr->watchMask & TCL_READABLE) {
1200	if (infoPtr->readable) {
1201	    mask |= TCL_READABLE;
1202	    infoPtr->readable = 0;
1203	}
1204    }
1205
1206    /*
1207     * Inform the channel of the events.
1208     */
1209
1210    Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask);
1211    return 1;
1212}
1213
1214/*
1215 *----------------------------------------------------------------------
1216 *
1217 * SerialWatchProc --
1218 *
1219 *	Called by the notifier to set up to watch for events on this channel.
1220 *
1221 * Results:
1222 *	None.
1223 *
1224 * Side effects:
1225 *	None.
1226 *
1227 *----------------------------------------------------------------------
1228 */
1229
1230static void
1231SerialWatchProc(
1232    ClientData instanceData,	/* Serial state. */
1233    int mask)			/* What events to watch for, OR-ed combination
1234				 * of TCL_READABLE, TCL_WRITABLE and
1235				 * TCL_EXCEPTION. */
1236{
1237    SerialInfo **nextPtrPtr, *ptr;
1238    SerialInfo *infoPtr = (SerialInfo *) instanceData;
1239    int oldMask = infoPtr->watchMask;
1240    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1241
1242    /*
1243     * Since the file is always ready for events, we set the block time so we
1244     * will poll.
1245     */
1246
1247    infoPtr->watchMask = mask & infoPtr->validMask;
1248    if (infoPtr->watchMask) {
1249	if (!oldMask) {
1250	    infoPtr->nextPtr = tsdPtr->firstSerialPtr;
1251	    tsdPtr->firstSerialPtr = infoPtr;
1252	}
1253	SerialBlockTime(infoPtr->blockTime);
1254    } else if (oldMask) {
1255	/*
1256	 * Remove the serial port from the list of watched serial ports.
1257	 */
1258
1259	for (nextPtrPtr=&(tsdPtr->firstSerialPtr), ptr=*nextPtrPtr; ptr!=NULL;
1260		nextPtrPtr=&ptr->nextPtr, ptr=*nextPtrPtr) {
1261	    if (infoPtr == ptr) {
1262		*nextPtrPtr = ptr->nextPtr;
1263		break;
1264	    }
1265	}
1266    }
1267}
1268
1269/*
1270 *----------------------------------------------------------------------
1271 *
1272 * SerialGetHandleProc --
1273 *
1274 *	Called from Tcl_GetChannelHandle to retrieve OS handles from inside a
1275 *	command serial port based channel.
1276 *
1277 * Results:
1278 *	Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no
1279 *	handle for the specified direction.
1280 *
1281 * Side effects:
1282 *	None.
1283 *
1284 *----------------------------------------------------------------------
1285 */
1286
1287static int
1288SerialGetHandleProc(
1289    ClientData instanceData,	/* The serial state. */
1290    int direction,		/* TCL_READABLE or TCL_WRITABLE */
1291    ClientData *handlePtr)	/* Where to store the handle. */
1292{
1293    SerialInfo *infoPtr = (SerialInfo *) instanceData;
1294
1295    *handlePtr = (ClientData) infoPtr->handle;
1296    return TCL_OK;
1297}
1298
1299/*
1300 *----------------------------------------------------------------------
1301 *
1302 * SerialWriterThread --
1303 *
1304 *	This function runs in a separate thread and writes data onto a serial.
1305 *
1306 * Results:
1307 *	Always returns 0.
1308 *
1309 * Side effects:
1310 *	Signals the main thread when an output operation is completed. May
1311 *	cause the main thread to wake up by posting a message.
1312 *
1313 *----------------------------------------------------------------------
1314 */
1315
1316static DWORD WINAPI
1317SerialWriterThread(
1318    LPVOID arg)
1319{
1320    SerialInfo *infoPtr = (SerialInfo *)arg;
1321    DWORD bytesWritten, toWrite, waitResult;
1322    char *buf;
1323    OVERLAPPED myWrite;		/* Have an own OVERLAPPED in this thread. */
1324    HANDLE wEvents[2];
1325
1326    /*
1327     * The stop event takes precedence by being first in the list.
1328     */
1329
1330    wEvents[0] = infoPtr->evStopWriter;
1331    wEvents[1] = infoPtr->evStartWriter;
1332
1333    for (;;) {
1334	/*
1335	 * Wait for the main thread to signal before attempting to write.
1336	 */
1337
1338	waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
1339
1340	if (waitResult != (WAIT_OBJECT_0 + 1)) {
1341	    /*
1342	     * The start event was not signaled. It might be the stop event or
1343	     * an error, so exit.
1344	     */
1345
1346	    break;
1347	}
1348
1349	buf = infoPtr->writeBuf;
1350	toWrite = infoPtr->toWrite;
1351
1352	myWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
1353
1354	/*
1355	 * Loop until all of the bytes are written or an error occurs.
1356	 */
1357
1358	while (toWrite > 0) {
1359	    /*
1360	     * Check for pending writeError. Ignore all write operations until
1361	     * the user has been notified.
1362	     */
1363
1364	    if (infoPtr->writeError) {
1365		break;
1366	    }
1367	    if (SerialBlockingWrite(infoPtr, (LPVOID) buf, (DWORD) toWrite,
1368		    &bytesWritten, &myWrite) == FALSE) {
1369		infoPtr->writeError = GetLastError();
1370		break;
1371	    }
1372	    if (bytesWritten != toWrite) {
1373		/*
1374		 * Write timeout.
1375		 */
1376
1377		infoPtr->writeError = ERROR_WRITE_FAULT;
1378		break;
1379	    }
1380	    toWrite -= bytesWritten;
1381	    buf += bytesWritten;
1382	}
1383
1384	CloseHandle(myWrite.hEvent);
1385
1386	/*
1387	 * Signal the main thread by signalling the evWritable event and then
1388	 * waking up the notifier thread.
1389	 */
1390
1391	SetEvent(infoPtr->evWritable);
1392
1393	/*
1394	 * Alert the foreground thread. Note that we need to treat this like a
1395	 * critical section so the foreground thread does not terminate this
1396	 * thread while we are holding a mutex in the notifier code.
1397	 */
1398
1399	Tcl_MutexLock(&serialMutex);
1400	if (infoPtr->threadId != NULL) {
1401	    /*
1402	     * TIP #218: When in flight ignore the event, no one will receive
1403	     * it anyway.
1404	     */
1405
1406	    Tcl_ThreadAlert(infoPtr->threadId);
1407	}
1408	Tcl_MutexUnlock(&serialMutex);
1409    }
1410
1411    return 0;
1412}
1413
1414/*
1415 *----------------------------------------------------------------------
1416 *
1417 * TclWinSerialReopen --
1418 *
1419 *	Reopens the serial port with the OVERLAPPED FLAG set
1420 *
1421 * Results:
1422 *	Returns the new handle, or INVALID_HANDLE_VALUE. Normally there
1423 *	shouldn't be any error, because the same channel has previously been
1424 *	succeesfully opened.
1425 *
1426 * Side effects:
1427 *	May close the original handle
1428 *
1429 *----------------------------------------------------------------------
1430 */
1431
1432HANDLE
1433TclWinSerialReopen(
1434    HANDLE handle,
1435    CONST TCHAR *name,
1436    DWORD access)
1437{
1438    ThreadSpecificData *tsdPtr;
1439
1440    tsdPtr = SerialInit();
1441
1442    /*
1443     * Multithreaded I/O needs the overlapped flag set otherwise
1444     * ClearCommError blocks under Windows NT/2000 until serial output is
1445     * finished
1446     */
1447
1448    if (CloseHandle(handle) == FALSE) {
1449	return INVALID_HANDLE_VALUE;
1450    }
1451    handle = (*tclWinProcs->createFileProc)(name, access, 0, 0,
1452	    OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
1453    return handle;
1454}
1455
1456/*
1457 *----------------------------------------------------------------------
1458 *
1459 * TclWinOpenSerialChannel --
1460 *
1461 *	Constructs a Serial port channel for the specified standard OS handle.
1462 *	This is a helper function to break up the construction of channels
1463 *	into File, Console, or Serial.
1464 *
1465 * Results:
1466 *	Returns the new channel, or NULL.
1467 *
1468 * Side effects:
1469 *	May open the channel
1470 *
1471 *----------------------------------------------------------------------
1472 */
1473
1474Tcl_Channel
1475TclWinOpenSerialChannel(
1476    HANDLE handle,
1477    char *channelName,
1478    int permissions)
1479{
1480    SerialInfo *infoPtr;
1481    DWORD id;
1482
1483    SerialInit();
1484
1485    infoPtr = (SerialInfo *) ckalloc((unsigned) sizeof(SerialInfo));
1486    memset(infoPtr, 0, sizeof(SerialInfo));
1487
1488    infoPtr->validMask = permissions;
1489    infoPtr->handle = handle;
1490    infoPtr->channel = (Tcl_Channel) NULL;
1491    infoPtr->readable = 0;
1492    infoPtr->writable = 1;
1493    infoPtr->toWrite = infoPtr->writeQueue = 0;
1494    infoPtr->blockTime = SERIAL_DEFAULT_BLOCKTIME;
1495    infoPtr->lastEventTime = 0;
1496    infoPtr->lastError = infoPtr->error = 0;
1497    infoPtr->threadId = Tcl_GetCurrentThread();
1498    infoPtr->sysBufRead = 4096;
1499    infoPtr->sysBufWrite = 4096;
1500
1501    /*
1502     * Use the pointer to keep the channel names unique, in case the handles
1503     * are shared between multiple channels (stdin/stdout).
1504     */
1505
1506    wsprintfA(channelName, "file%lx", (int) infoPtr);
1507
1508    infoPtr->channel = Tcl_CreateChannel(&serialChannelType, channelName,
1509	    (ClientData) infoPtr, permissions);
1510
1511
1512    SetupComm(handle, infoPtr->sysBufRead, infoPtr->sysBufWrite);
1513    PurgeComm(handle,
1514	    PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
1515
1516    /*
1517     * Default is blocking.
1518     */
1519
1520    SetCommTimeouts(handle, &no_timeout);
1521
1522    InitializeCriticalSection(&infoPtr->csWrite);
1523    if (permissions & TCL_READABLE) {
1524	infoPtr->osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
1525    }
1526    if (permissions & TCL_WRITABLE) {
1527	/*
1528	 * Initially the channel is writable and the writeThread is idle.
1529	 */
1530
1531	infoPtr->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
1532	infoPtr->evWritable = CreateEvent(NULL, TRUE, TRUE, NULL);
1533	infoPtr->evStartWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
1534	infoPtr->evStopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
1535	infoPtr->writeThread = CreateThread(NULL, 256, SerialWriterThread,
1536		infoPtr, 0, &id);
1537    }
1538
1539    /*
1540     * Files have default translation of AUTO and ^Z eof char, which means
1541     * that a ^Z will be accepted as EOF when reading.
1542     */
1543
1544    Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
1545    Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
1546
1547    return infoPtr->channel;
1548}
1549
1550/*
1551 *----------------------------------------------------------------------
1552 *
1553 * SerialErrorStr --
1554 *
1555 *	Converts a Win32 serial error code to a list of readable errors.
1556 *
1557 * Results:
1558 *	None.
1559 *
1560 * Side effects:
1561 *	Generates readable errors in the supplied DString.
1562 *
1563 *----------------------------------------------------------------------
1564 */
1565
1566static void
1567SerialErrorStr(
1568    DWORD error,		/* Win32 serial error code. */
1569    Tcl_DString *dsPtr)		/* Where to store string. */
1570{
1571    if (error & CE_RXOVER) {
1572	Tcl_DStringAppendElement(dsPtr, "RXOVER");
1573    }
1574    if (error & CE_OVERRUN) {
1575	Tcl_DStringAppendElement(dsPtr, "OVERRUN");
1576    }
1577    if (error & CE_RXPARITY) {
1578	Tcl_DStringAppendElement(dsPtr, "RXPARITY");
1579    }
1580    if (error & CE_FRAME) {
1581	Tcl_DStringAppendElement(dsPtr, "FRAME");
1582    }
1583    if (error & CE_BREAK) {
1584	Tcl_DStringAppendElement(dsPtr, "BREAK");
1585    }
1586    if (error & CE_TXFULL) {
1587	Tcl_DStringAppendElement(dsPtr, "TXFULL");
1588    }
1589    if (error & CE_PTO) {	/* PTO used to signal WRITE-TIMEOUT */
1590	Tcl_DStringAppendElement(dsPtr, "TIMEOUT");
1591    }
1592    if (error & ~((DWORD) (SERIAL_READ_ERRORS | SERIAL_WRITE_ERRORS))) {
1593	char buf[TCL_INTEGER_SPACE + 1];
1594
1595	wsprintfA(buf, "%d", error);
1596	Tcl_DStringAppendElement(dsPtr, buf);
1597    }
1598}
1599
1600/*
1601 *----------------------------------------------------------------------
1602 *
1603 * SerialModemStatusStr --
1604 *
1605 *	Converts a Win32 modem status list of readable flags
1606 *
1607 * Result:
1608 *	None.
1609 *
1610 * Side effects:
1611 *	Appends modem status flag strings to the given DString.
1612 *
1613 *----------------------------------------------------------------------
1614 */
1615
1616static void
1617SerialModemStatusStr(
1618    DWORD status,		/* Win32 modem status. */
1619    Tcl_DString *dsPtr)		/* Where to store string. */
1620{
1621    Tcl_DStringAppendElement(dsPtr, "CTS");
1622    Tcl_DStringAppendElement(dsPtr, (status & MS_CTS_ON)  ?  "1" : "0");
1623    Tcl_DStringAppendElement(dsPtr, "DSR");
1624    Tcl_DStringAppendElement(dsPtr, (status & MS_DSR_ON)   ? "1" : "0");
1625    Tcl_DStringAppendElement(dsPtr, "RING");
1626    Tcl_DStringAppendElement(dsPtr, (status & MS_RING_ON)  ? "1" : "0");
1627    Tcl_DStringAppendElement(dsPtr, "DCD");
1628    Tcl_DStringAppendElement(dsPtr, (status & MS_RLSD_ON)  ? "1" : "0");
1629}
1630
1631/*
1632 *----------------------------------------------------------------------
1633 *
1634 * SerialSetOptionProc --
1635 *
1636 *	Sets an option on a channel.
1637 *
1638 * Results:
1639 *	A standard Tcl result. Also sets the interp's result on error if
1640 *	interp is not NULL.
1641 *
1642 * Side effects:
1643 *	May modify an option on a device.
1644 *
1645 *----------------------------------------------------------------------
1646 */
1647
1648static int
1649SerialSetOptionProc(
1650    ClientData instanceData,	/* File state. */
1651    Tcl_Interp *interp,		/* For error reporting - can be NULL. */
1652    CONST char *optionName,	/* Which option to set? */
1653    CONST char *value)		/* New value for option. */
1654{
1655    SerialInfo *infoPtr;
1656    DCB dcb;
1657    BOOL result, flag;
1658    size_t len, vlen;
1659    Tcl_DString ds;
1660    CONST TCHAR *native;
1661    int argc;
1662    CONST char **argv;
1663
1664    infoPtr = (SerialInfo *) instanceData;
1665
1666    /*
1667     * Parse options. This would be far easier if we had Tcl_Objs to work with
1668     * as that would let us use Tcl_GetIndexFromObj()...
1669     */
1670
1671    len = strlen(optionName);
1672    vlen = strlen(value);
1673
1674    /*
1675     * Option -mode baud,parity,databits,stopbits
1676     */
1677
1678    if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) {
1679	if (!GetCommState(infoPtr->handle, &dcb)) {
1680	    if (interp != NULL) {
1681		Tcl_AppendResult(interp, "can't get comm state", NULL);
1682	    }
1683	    return TCL_ERROR;
1684	}
1685	native = Tcl_WinUtfToTChar(value, -1, &ds);
1686	result = (*tclWinProcs->buildCommDCBProc)(native, &dcb);
1687	Tcl_DStringFree(&ds);
1688
1689	if (result == FALSE) {
1690	    if (interp != NULL) {
1691		Tcl_AppendResult(interp, "bad value \"", value,
1692			"\" for -mode: should be baud,parity,data,stop", NULL);
1693	    }
1694	    return TCL_ERROR;
1695	}
1696
1697	/*
1698	 * Default settings for serial communications.
1699	 */
1700
1701	dcb.fBinary = TRUE;
1702	dcb.fErrorChar = FALSE;
1703	dcb.fNull = FALSE;
1704	dcb.fAbortOnError = FALSE;
1705
1706	if (!SetCommState(infoPtr->handle, &dcb)) {
1707	    if (interp != NULL) {
1708		Tcl_AppendResult(interp, "can't set comm state", NULL);
1709	    }
1710	    return TCL_ERROR;
1711	}
1712	return TCL_OK;
1713    }
1714
1715    /*
1716     * Option -handshake none|xonxoff|rtscts|dtrdsr
1717     */
1718
1719    if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) {
1720	if (!GetCommState(infoPtr->handle, &dcb)) {
1721	    if (interp != NULL) {
1722		Tcl_AppendResult(interp, "can't get comm state", NULL);
1723	    }
1724	    return TCL_ERROR;
1725	}
1726
1727	/*
1728	 * Reset all handshake options. DTR and RTS are ON by default.
1729	 */
1730
1731	dcb.fOutX = dcb.fInX = FALSE;
1732	dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = dcb.fDsrSensitivity = FALSE;
1733	dcb.fDtrControl = DTR_CONTROL_ENABLE;
1734	dcb.fRtsControl = RTS_CONTROL_ENABLE;
1735	dcb.fTXContinueOnXoff = FALSE;
1736
1737	/*
1738	 * Adjust the handshake limits. Yes, the XonXoff limits seem to
1739	 * influence even hardware handshake.
1740	 */
1741
1742	dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2);
1743	dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4);
1744
1745	if (strncasecmp(value, "NONE", vlen) == 0) {
1746	    /*
1747	     * Leave all handshake options disabled.
1748	     */
1749	} else if (strncasecmp(value, "XONXOFF", vlen) == 0) {
1750	    dcb.fOutX = dcb.fInX = TRUE;
1751	} else if (strncasecmp(value, "RTSCTS", vlen) == 0) {
1752	    dcb.fOutxCtsFlow = TRUE;
1753	    dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
1754	} else if (strncasecmp(value, "DTRDSR", vlen) == 0) {
1755	    dcb.fOutxDsrFlow = TRUE;
1756	    dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
1757	} else {
1758	    if (interp != NULL) {
1759		Tcl_AppendResult(interp, "bad value \"", value,
1760			"\" for -handshake: must be one of xonxoff, rtscts, "
1761			"dtrdsr or none", NULL);
1762	    }
1763	    return TCL_ERROR;
1764	}
1765
1766	if (!SetCommState(infoPtr->handle, &dcb)) {
1767	    if (interp != NULL) {
1768		Tcl_AppendResult(interp, "can't set comm state", NULL);
1769	    }
1770	    return TCL_ERROR;
1771	}
1772	return TCL_OK;
1773    }
1774
1775    /*
1776     * Option -xchar {\x11 \x13}
1777     */
1778
1779    if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) {
1780	if (!GetCommState(infoPtr->handle, &dcb)) {
1781	    if (interp != NULL) {
1782		Tcl_AppendResult(interp, "can't get comm state", NULL);
1783	    }
1784	    return TCL_ERROR;
1785	}
1786
1787	if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) {
1788	    return TCL_ERROR;
1789	}
1790	if (argc != 2) {
1791	badXchar:
1792	    if (interp != NULL) {
1793		Tcl_AppendResult(interp, "bad value for -xchar: should be "
1794			"a list of two elements with each a single character",
1795			NULL);
1796	    }
1797	    ckfree((char *) argv);
1798	    return TCL_ERROR;
1799	}
1800
1801	/*
1802	 * These dereferences are safe, even in the zero-length string cases,
1803	 * because that just makes the xon/xoff character into NUL. When the
1804	 * character looks like it is UTF-8 encoded, decode it before casting
1805	 * into the format required for the Win guts. Note that this does not
1806	 * convert character sets; it is expected that when people set the
1807	 * control characters to something large and custom, they'll know the
1808	 * hex/octal value rather than the printable form.
1809	 */
1810
1811	dcb.XonChar = argv[0][0];
1812	dcb.XoffChar = argv[1][0];
1813	if (argv[0][0] & 0x80 || argv[1][0] & 0x80) {
1814	    Tcl_UniChar character;
1815	    int charLen;
1816
1817	    charLen = Tcl_UtfToUniChar(argv[0], &character);
1818	    if (argv[0][charLen]) {
1819		goto badXchar;
1820	    }
1821	    dcb.XonChar = (char) character;
1822	    charLen = Tcl_UtfToUniChar(argv[1], &character);
1823	    if (argv[1][charLen]) {
1824		goto badXchar;
1825	    }
1826	    dcb.XoffChar = (char) character;
1827	}
1828	ckfree((char *) argv);
1829
1830	if (!SetCommState(infoPtr->handle, &dcb)) {
1831	    if (interp != NULL) {
1832		Tcl_AppendResult(interp, "can't set comm state", NULL);
1833	    }
1834	    return TCL_ERROR;
1835	}
1836	return TCL_OK;
1837    }
1838
1839    /*
1840     * Option -ttycontrol {DTR 1 RTS 0 BREAK 0}
1841     */
1842
1843    if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) {
1844	int i, result = TCL_OK;
1845
1846	if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) {
1847	    return TCL_ERROR;
1848	}
1849	if ((argc % 2) == 1) {
1850	    if (interp != NULL) {
1851		Tcl_AppendResult(interp, "bad value \"", value,
1852			"\" for -ttycontrol: should be a list of "
1853			"signal,value pairs", NULL);
1854	    }
1855	    ckfree((char *) argv);
1856	    return TCL_ERROR;
1857	}
1858
1859	for (i = 0; i < argc - 1; i += 2) {
1860	    if (Tcl_GetBoolean(interp, argv[i+1], &flag) == TCL_ERROR) {
1861		result = TCL_ERROR;
1862		break;
1863	    }
1864	    if (strncasecmp(argv[i], "DTR", strlen(argv[i])) == 0) {
1865		if (!EscapeCommFunction(infoPtr->handle,
1866			(DWORD) (flag ? SETDTR : CLRDTR))) {
1867		    if (interp != NULL) {
1868			Tcl_AppendResult(interp, "can't set DTR signal", NULL);
1869		    }
1870		    result = TCL_ERROR;
1871		    break;
1872		}
1873	    } else if (strncasecmp(argv[i], "RTS", strlen(argv[i])) == 0) {
1874		if (!EscapeCommFunction(infoPtr->handle,
1875			(DWORD) (flag ? SETRTS : CLRRTS))) {
1876		    if (interp != NULL) {
1877			Tcl_AppendResult(interp, "can't set RTS signal", NULL);
1878		    }
1879		    result = TCL_ERROR;
1880		    break;
1881		}
1882	    } else if (strncasecmp(argv[i], "BREAK", strlen(argv[i])) == 0) {
1883		if (!EscapeCommFunction(infoPtr->handle,
1884			(DWORD) (flag ? SETBREAK : CLRBREAK))) {
1885		    if (interp != NULL) {
1886			Tcl_AppendResult(interp,"can't set BREAK signal",NULL);
1887		    }
1888		    result = TCL_ERROR;
1889		    break;
1890		}
1891	    } else {
1892		if (interp != NULL) {
1893		    Tcl_AppendResult(interp, "bad signal name \"", argv[i],
1894			    "\" for -ttycontrol: must be DTR, RTS or BREAK",
1895			    NULL);
1896		}
1897		result = TCL_ERROR;
1898		break;
1899	    }
1900	}
1901
1902	ckfree((char *) argv);
1903	return result;
1904    }
1905
1906    /*
1907     * Option -sysbuffer {read_size write_size}
1908     * Option -sysbuffer read_size
1909     */
1910
1911    if ((len > 1) && (strncmp(optionName, "-sysbuffer", len) == 0)) {
1912	/*
1913	 * -sysbuffer 4096 or -sysbuffer {64536 4096}
1914	 */
1915
1916	size_t inSize = (size_t) -1, outSize = (size_t) -1;
1917
1918	if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) {
1919	    return TCL_ERROR;
1920	}
1921	if (argc == 1) {
1922	    inSize = atoi(argv[0]);
1923	    outSize = infoPtr->sysBufWrite;
1924	} else if (argc == 2) {
1925	    inSize  = atoi(argv[0]);
1926	    outSize = atoi(argv[1]);
1927	}
1928	ckfree((char *) argv);
1929
1930	if ((argc < 1) || (argc > 2) || (inSize <= 0) || (outSize <= 0)) {
1931	    if (interp != NULL) {
1932		Tcl_AppendResult(interp, "bad value \"", value,
1933			"\" for -sysbuffer: should be a list of one or two "
1934			"integers > 0", NULL);
1935	    }
1936	    return TCL_ERROR;
1937	}
1938
1939	if (!SetupComm(infoPtr->handle, inSize, outSize)) {
1940	    if (interp != NULL) {
1941		Tcl_AppendResult(interp, "can't setup comm buffers", NULL);
1942	    }
1943	    return TCL_ERROR;
1944	}
1945	infoPtr->sysBufRead  = inSize;
1946	infoPtr->sysBufWrite = outSize;
1947
1948	/*
1949	 * Adjust the handshake limits. Yes, the XonXoff limits seem to
1950	 * influence even hardware handshake.
1951	 */
1952
1953	if (!GetCommState(infoPtr->handle, &dcb)) {
1954	    if (interp != NULL) {
1955		Tcl_AppendResult(interp, "can't get comm state", NULL);
1956	    }
1957	    return TCL_ERROR;
1958	}
1959	dcb.XonLim = (WORD) (infoPtr->sysBufRead*1/2);
1960	dcb.XoffLim = (WORD) (infoPtr->sysBufRead*1/4);
1961	if (!SetCommState(infoPtr->handle, &dcb)) {
1962	    if (interp != NULL) {
1963		Tcl_AppendResult(interp, "can't set comm state", NULL);
1964	    }
1965	    return TCL_ERROR;
1966	}
1967	return TCL_OK;
1968    }
1969
1970    /*
1971     * Option -pollinterval msec
1972     */
1973
1974    if ((len > 1) && (strncmp(optionName, "-pollinterval", len) == 0)) {
1975	if (Tcl_GetInt(interp, value, &(infoPtr->blockTime)) != TCL_OK) {
1976	    return TCL_ERROR;
1977	}
1978	return TCL_OK;
1979    }
1980
1981    /*
1982     * Option -timeout msec
1983     */
1984
1985    if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) {
1986	int msec;
1987	COMMTIMEOUTS tout = {0,0,0,0,0};
1988
1989	if (Tcl_GetInt(interp, value, &msec) != TCL_OK) {
1990	    return TCL_ERROR;
1991	}
1992	tout.ReadTotalTimeoutConstant = msec;
1993	if (!SetCommTimeouts(infoPtr->handle, &tout)) {
1994	    if (interp != NULL) {
1995		Tcl_AppendResult(interp, "can't set comm timeouts", NULL);
1996	    }
1997	    return TCL_ERROR;
1998	}
1999
2000	return TCL_OK;
2001    }
2002
2003    return Tcl_BadChannelOption(interp, optionName,
2004	    "mode handshake pollinterval sysbuffer timeout ttycontrol xchar");
2005}
2006
2007/*
2008 *----------------------------------------------------------------------
2009 *
2010 * SerialGetOptionProc --
2011 *
2012 *	Gets a mode associated with an IO channel. If the optionName arg is
2013 *	non NULL, retrieves the value of that option. If the optionName arg is
2014 *	NULL, retrieves a list of alternating option names and values for the
2015 *	given channel.
2016 *
2017 * Results:
2018 *	A standard Tcl result. Also sets the supplied DString to the string
2019 *	value of the option(s) returned.
2020 *
2021 * Side effects:
2022 *	The string returned by this function is in static storage and may be
2023 *	reused at any time subsequent to the call.
2024 *
2025 *----------------------------------------------------------------------
2026 */
2027
2028static int
2029SerialGetOptionProc(
2030    ClientData instanceData,	/* File state. */
2031    Tcl_Interp *interp,		/* For error reporting - can be NULL. */
2032    CONST char *optionName,	/* Option to get. */
2033    Tcl_DString *dsPtr)		/* Where to store value(s). */
2034{
2035    SerialInfo *infoPtr;
2036    DCB dcb;
2037    size_t len;
2038    int valid = 0;		/* Flag if valid option parsed. */
2039
2040    infoPtr = (SerialInfo *) instanceData;
2041
2042    if (optionName == NULL) {
2043	len = 0;
2044    } else {
2045	len = strlen(optionName);
2046    }
2047
2048    /*
2049     * Get option -mode
2050     */
2051
2052    if (len == 0) {
2053	Tcl_DStringAppendElement(dsPtr, "-mode");
2054    }
2055    if (len==0 || (len>2 && (strncmp(optionName, "-mode", len) == 0))) {
2056	char parity;
2057	char *stop;
2058	char buf[2 * TCL_INTEGER_SPACE + 16];
2059
2060	if (!GetCommState(infoPtr->handle, &dcb)) {
2061	    if (interp != NULL) {
2062		Tcl_AppendResult(interp, "can't get comm state", NULL);
2063	    }
2064	    return TCL_ERROR;
2065	}
2066
2067	valid = 1;
2068	parity = 'n';
2069	if (dcb.Parity <= 4) {
2070	    parity = "noems"[dcb.Parity];
2071	}
2072	stop = (dcb.StopBits == ONESTOPBIT) ? "1" :
2073		(dcb.StopBits == ONE5STOPBITS) ? "1.5" : "2";
2074
2075	wsprintfA(buf, "%d,%c,%d,%s", dcb.BaudRate, parity,
2076		dcb.ByteSize, stop);
2077	Tcl_DStringAppendElement(dsPtr, buf);
2078    }
2079
2080    /*
2081     * Get option -pollinterval
2082     */
2083
2084    if (len == 0) {
2085	Tcl_DStringAppendElement(dsPtr, "-pollinterval");
2086    }
2087    if (len==0 || (len>1 && strncmp(optionName, "-pollinterval", len)==0)) {
2088	char buf[TCL_INTEGER_SPACE + 1];
2089
2090	valid = 1;
2091	wsprintfA(buf, "%d", infoPtr->blockTime);
2092	Tcl_DStringAppendElement(dsPtr, buf);
2093    }
2094
2095    /*
2096     * Get option -sysbuffer
2097     */
2098
2099    if (len == 0) {
2100	Tcl_DStringAppendElement(dsPtr, "-sysbuffer");
2101	Tcl_DStringStartSublist(dsPtr);
2102    }
2103    if (len==0 || (len>1 && strncmp(optionName, "-sysbuffer", len) == 0)) {
2104	char buf[TCL_INTEGER_SPACE + 1];
2105	valid = 1;
2106
2107	wsprintfA(buf, "%d", infoPtr->sysBufRead);
2108	Tcl_DStringAppendElement(dsPtr, buf);
2109	wsprintfA(buf, "%d", infoPtr->sysBufWrite);
2110	Tcl_DStringAppendElement(dsPtr, buf);
2111    }
2112    if (len == 0) {
2113	Tcl_DStringEndSublist(dsPtr);
2114    }
2115
2116    /*
2117     * Get option -xchar
2118     */
2119
2120    if (len == 0) {
2121	Tcl_DStringAppendElement(dsPtr, "-xchar");
2122	Tcl_DStringStartSublist(dsPtr);
2123    }
2124    if (len==0 || (len>1 && strncmp(optionName, "-xchar", len) == 0)) {
2125	char buf[4];
2126	valid = 1;
2127
2128	if (!GetCommState(infoPtr->handle, &dcb)) {
2129	    if (interp != NULL) {
2130		Tcl_AppendResult(interp, "can't get comm state", NULL);
2131	    }
2132	    return TCL_ERROR;
2133	}
2134	sprintf(buf, "%c", dcb.XonChar);
2135	Tcl_DStringAppendElement(dsPtr, buf);
2136	sprintf(buf, "%c", dcb.XoffChar);
2137	Tcl_DStringAppendElement(dsPtr, buf);
2138    }
2139    if (len == 0) {
2140	Tcl_DStringEndSublist(dsPtr);
2141    }
2142
2143    /*
2144     * Get option -lasterror
2145     *
2146     * Option is readonly and returned by [fconfigure chan -lasterror] but not
2147     * returned by unnamed [fconfigure chan].
2148     */
2149
2150    if (len>1 && strncmp(optionName, "-lasterror", len)==0) {
2151	valid = 1;
2152	SerialErrorStr(infoPtr->lastError, dsPtr);
2153    }
2154
2155    /*
2156     * get option -queue
2157     *
2158     * Option is readonly and returned by [fconfigure chan -queue].
2159     */
2160
2161    if (len>1 && strncmp(optionName, "-queue", len)==0) {
2162	char buf[TCL_INTEGER_SPACE + 1];
2163	COMSTAT cStat;
2164	DWORD error;
2165	int inBuffered, outBuffered, count;
2166
2167	valid = 1;
2168
2169	/*
2170	 * Query the pending data in Tcl's internal queues.
2171	 */
2172
2173	inBuffered  = Tcl_InputBuffered(infoPtr->channel);
2174	outBuffered = Tcl_OutputBuffered(infoPtr->channel);
2175
2176	/*
2177	 * Query the number of bytes in our output queue:
2178	 *     1. The bytes pending in the output thread
2179	 *     2. The bytes in the system drivers buffer
2180	 * The writer thread should not interfere this action.
2181	 */
2182
2183	EnterCriticalSection(&infoPtr->csWrite);
2184	ClearCommError(infoPtr->handle, &error, &cStat);
2185	count = (int) cStat.cbOutQue + infoPtr->writeQueue;
2186	LeaveCriticalSection(&infoPtr->csWrite);
2187
2188	wsprintfA(buf, "%d", inBuffered + cStat.cbInQue);
2189	Tcl_DStringAppendElement(dsPtr, buf);
2190	wsprintfA(buf, "%d", outBuffered + count);
2191	Tcl_DStringAppendElement(dsPtr, buf);
2192    }
2193
2194    /*
2195     * get option -ttystatus
2196     *
2197     * Option is readonly and returned by [fconfigure chan -ttystatus] but not
2198     * returned by unnamed [fconfigure chan].
2199     */
2200
2201    if (len>4 && strncmp(optionName, "-ttystatus", len)==0) {
2202	DWORD status;
2203
2204	if (!GetCommModemStatus(infoPtr->handle, &status)) {
2205	    if (interp != NULL) {
2206		Tcl_AppendResult(interp, "can't get tty status", NULL);
2207	    }
2208	    return TCL_ERROR;
2209	}
2210	valid = 1;
2211	SerialModemStatusStr(status, dsPtr);
2212    }
2213
2214    if (valid) {
2215	return TCL_OK;
2216    } else {
2217	return Tcl_BadChannelOption(interp, optionName,
2218		"mode pollinterval lasterror queue sysbuffer ttystatus xchar");
2219    }
2220}
2221
2222/*
2223 *----------------------------------------------------------------------
2224 *
2225 * SerialThreadActionProc --
2226 *
2227 *	Insert or remove any thread local refs to this channel.
2228 *
2229 * Results:
2230 *	None.
2231 *
2232 * Side effects:
2233 *	Changes thread local list of valid channels.
2234 *
2235 *----------------------------------------------------------------------
2236 */
2237
2238static void
2239SerialThreadActionProc(
2240    ClientData instanceData,
2241    int action)
2242{
2243    SerialInfo *infoPtr = (SerialInfo *) instanceData;
2244
2245    /*
2246     * We do not access firstSerialPtr in the thread structures. This is not
2247     * for all serials managed by the thread, but only those we are watching.
2248     * Removal of the filevent handlers before transfer thus takes care of
2249     * this structure.
2250     */
2251
2252    Tcl_MutexLock(&serialMutex);
2253    if (action == TCL_CHANNEL_THREAD_INSERT) {
2254	/*
2255	 * We can't copy the thread information from the channel when the
2256	 * channel is created. At this time the channel back pointer has not
2257	 * been set yet. However in that case the threadId has already been
2258	 * set by TclpCreateCommandChannel itself, so the structure is still
2259	 * good.
2260	 */
2261
2262	SerialInit();
2263	if (infoPtr->channel != NULL) {
2264	    infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel);
2265	}
2266    } else {
2267	infoPtr->threadId = NULL;
2268    }
2269    Tcl_MutexUnlock(&serialMutex);
2270}
2271
2272/*
2273 * Local Variables:
2274 * mode: c
2275 * c-basic-offset: 4
2276 * fill-column: 78
2277 * End:
2278 */
2279