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