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