From 434f12d55669f76e9a0ce7b0898354b1cbc9414a Mon Sep 17 00:00:00 2001 From: hobbs Date: Tue, 26 Feb 2002 20:03:05 +0000 Subject: * unix/tclUnixChan.c: added Unix implementation of TIP #35, serial port support. [Patch #438509] (schroedter) --- unix/tclUnixChan.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 385 insertions(+), 16 deletions(-) diff --git a/unix/tclUnixChan.c b/unix/tclUnixChan.c index 19cbbba..3110478 100644 --- a/unix/tclUnixChan.c +++ b/unix/tclUnixChan.c @@ -10,7 +10,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclUnixChan.c,v 1.29 2002/02/15 14:28:50 dkf Exp $ + * RCS: @(#) $Id: tclUnixChan.c,v 1.30 2002/02/26 20:03:05 hobbs Exp $ */ #include "tclInt.h" /* Internal definitions for Tcl. */ @@ -45,16 +45,42 @@ #ifdef USE_TERMIOS # include +# ifdef HAVE_SYS_IOCTL_H +# include +# endif +# ifdef HAVE_SYS_MODEM_H +# include +# endif # define IOSTATE struct termios # define GETIOSTATE(fd, statePtr) tcgetattr((fd), (statePtr)) # define SETIOSTATE(fd, statePtr) tcsetattr((fd), TCSADRAIN, (statePtr)) +# define GETCONTROL(fd, intPtr) ioctl((fd), TIOCMGET, (intPtr)) +# define SETCONTROL(fd, intPtr) ioctl((fd), TIOCMSET, (intPtr)) +# define TTYFLUSH(fd) tcflush((fd), TCIOFLUSH); +# ifdef FIONREAD +# define GETREADQUEUE(fd, int) ioctl((fd), FIONREAD, &(int)) +# elif defined(FIORDCHK) +# define GETREADQUEUE(fd, int) int = ioctl((fd), FIORDCHK, NULL) +# endif +# ifdef TIOCOUTQ +# define GETWRITEQUEUE(fd, int) ioctl((fd), TIOCOUTQ, &(int)) +# endif +# if defined(TIOCSBRK) && defined(TIOCCBRK) +# define SETBREAK(fd, flag) \ + ioctl((fd), (unsigned) ((flag) ? TIOCSBRK:TIOCCBRK), NULL) +# endif +# if !defined(CRTSCTS) && defined(CNEW_RTSCTS) +# define CRTSCTS CNEW_RTSCTS +# endif #else /* !USE_TERMIOS */ + #ifdef USE_TERMIO # include # define IOSTATE struct termio # define GETIOSTATE(fd, statePtr) ioctl((fd), TCGETA, (statePtr)) # define SETIOSTATE(fd, statePtr) ioctl((fd), TCSETAW, (statePtr)) #else /* !USE_TERMIO */ + #ifdef USE_SGTTY # include # define IOSTATE struct sgttyb @@ -63,6 +89,7 @@ #else /* !USE_SGTTY */ # undef SUPPORTS_TTY #endif /* !USE_SGTTY */ + #endif /* !USE_TERMIO */ #endif /* !USE_TERMIOS */ @@ -220,6 +247,8 @@ static int TtyGetOptionProc _ANSI_ARGS_((ClientData instanceData, Tcl_Interp *interp, CONST char *optionName, Tcl_DString *dsPtr)); static FileState * TtyInit _ANSI_ARGS_((int fd, int initialize)); +static int TtyOutputProc _ANSI_ARGS_((ClientData instanceData, + CONST char *buf, int toWrite, int *errorCode)); static int TtyParseMode _ANSI_ARGS_((Tcl_Interp *interp, CONST char *mode, int *speedPtr, int *parityPtr, int *dataPtr, int *stopPtr)); @@ -264,7 +293,7 @@ static Tcl_ChannelType ttyChannelType = { TCL_CHANNEL_VERSION_2, /* v2 channel */ TtyCloseProc, /* Close proc. */ FileInputProc, /* Input proc. */ - FileOutputProc, /* Output proc. */ + TtyOutputProc, /* Output proc. */ NULL, /* Seek proc. */ TtySetOptionProc, /* Set option proc. */ TtyGetOptionProc, /* Get option proc. */ @@ -631,11 +660,10 @@ FileGetHandleProc(instanceData, direction, handlePtr) * 0 if successful, errno if failed. * * Side effects: - * Restores the settings and closes the device of the channel. + * Closes the device of the channel. * *---------------------------------------------------------------------- */ - static int TtyCloseProc(instanceData, interp) ClientData instanceData; /* Tty state. */ @@ -644,15 +672,102 @@ TtyCloseProc(instanceData, interp) TtyState *ttyPtr; ttyPtr = (TtyState *) instanceData; +#ifdef TTYFLUSH + TTYFLUSH(ttyPtr->fs.fd); +#endif +#if 0 + /* + * TIP#35 agreed to remove the unsave so that TCL could be used as a + * simple stty. + * It would be cleaner to remove all the stuff related to + * TtyState.stateUpdated + * TtyState.savedState + * Then the structure TtyState would be the same as FileState. + * IMO this cleanup could better be done for the final 8.4 release + * after nobody complained about the missing unsave. -- schroedter + */ if (ttyPtr->stateUpdated) { SETIOSTATE(ttyPtr->fs.fd, &ttyPtr->savedState); } +#endif return FileCloseProc(instanceData, interp); } /* *---------------------------------------------------------------------- * + * TtyOutputProc-- + * + * This procedure is invoked from the generic IO level to write + * output to a TTY channel. + * + * Results: + * The number of bytes written is returned or -1 on error. An + * output argument contains a POSIX error code if an error occurred, + * or zero. + * + * Side effects: + * Writes output on the output device of the channel + * if the channel is not designated to be closed. + * + *---------------------------------------------------------------------- + */ + +static int +TtyOutputProc(instanceData, buf, toWrite, errorCodePtr) + ClientData instanceData; /* File state. */ + CONST char *buf; /* The data buffer. */ + int toWrite; /* How many bytes to write? */ + int *errorCodePtr; /* Where to store error code. */ +{ + if ( TclInExit() ) { + /* + * Do not write data during Tcl exit. + * Serial port may block preventing Tcl from exit. + */ + return toWrite; + } else { + return FileOutputProc(instanceData, buf, toWrite, errorCodePtr); + } +} + +#ifdef USE_TERMIOS +/* + *---------------------------------------------------------------------- + * + * TtyModemStatusStr -- + * + * Converts a RS232 modem status list of readable flags + * + *---------------------------------------------------------------------- + */ +static void +TtyModemStatusStr(status, dsPtr) + int status; /* RS232 modem status */ + Tcl_DString *dsPtr; /* Where to store string */ +{ +#ifdef TIOCM_CTS + Tcl_DStringAppendElement(dsPtr, "CTS"); + Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CTS) ? "1" : "0" ); +#endif +#ifdef TIOCM_DSR + Tcl_DStringAppendElement(dsPtr, "DSR"); + Tcl_DStringAppendElement(dsPtr, (status & TIOCM_DSR) ? "1" : "0" ); +#endif +#ifdef TIOCM_RNG + Tcl_DStringAppendElement(dsPtr, "RING"); + Tcl_DStringAppendElement(dsPtr, (status & TIOCM_RNG) ? "1" : "0" ); +#endif +#ifdef TIOCM_CD + Tcl_DStringAppendElement(dsPtr, "DCD"); + Tcl_DStringAppendElement(dsPtr, (status & TIOCM_CD) ? "1" : "0" ); +#endif +} +#endif + +/* + *---------------------------------------------------------------------- + * * TtySetOptionProc -- * * Sets an option on a channel. @@ -676,11 +791,21 @@ TtySetOptionProc(instanceData, interp, optionName, value) CONST char *value; /* New value for option. */ { FileState *fsPtr = (FileState *) instanceData; - unsigned int len; + unsigned int len, vlen; TtyAttrs tty; +#ifdef USE_TERMIOS + int flag, control, argc; + CONST char **argv; + IOSTATE iostate; +#endif len = strlen(optionName); - if ((len > 1) && (strncmp(optionName, "-mode", len) == 0)) { + vlen = strlen(value); + + /* + * Option -mode baud,parity,databits,stopbits + */ + if ((len > 2) && (strncmp(optionName, "-mode", len) == 0)) { if (TtyParseMode(interp, value, &tty.baud, &tty.parity, &tty.data, &tty.stop) != TCL_OK) { return TCL_ERROR; @@ -692,9 +817,181 @@ TtySetOptionProc(instanceData, interp, optionName, value) TtySetAttributes(fsPtr->fd, &tty); ((TtyState *) fsPtr)->stateUpdated = 1; return TCL_OK; + } + +#ifdef USE_TERMIOS + + /* + * Option -handshake none|xonxoff|rtscts|dtrdsr + */ + if ((len > 1) && (strncmp(optionName, "-handshake", len) == 0)) { + /* + * Reset all handshake options + * DTR and RTS are ON by default + */ + GETIOSTATE(fsPtr->fd, &iostate); + iostate.c_iflag &= ~(IXON | IXOFF | IXANY); +#ifdef CRTSCTS + iostate.c_cflag &= ~CRTSCTS; +#endif + if (strncasecmp(value, "NONE", vlen) == 0) { + /* leave all handshake options disabled */ + } else if (strncasecmp(value, "XONXOFF", vlen) == 0) { + iostate.c_iflag |= (IXON | IXOFF | IXANY); + } else if (strncasecmp(value, "RTSCTS", vlen) == 0) { +#ifdef CRTSCTS + iostate.c_cflag |= CRTSCTS; +#else + if (interp) { + Tcl_AppendResult(interp, "-handshake RTSCTS ", + "not supported for this platform", + (char *) NULL); + } + return TCL_ERROR; +#endif + } else if (strncasecmp(value, "DTRDSR", vlen) == 0) { + if (interp) { + Tcl_AppendResult(interp, "-handshake DTRDSR ", + "not supported for this platform", + (char *) NULL); + } + return TCL_ERROR; } else { - return Tcl_BadChannelOption(interp, optionName, "mode"); + if (interp) { + Tcl_AppendResult(interp, "bad value for -handshake: ", + "must be one of xonxoff, rtscts, dtrdsr or none", + (char *) NULL); + return TCL_ERROR; + } + } + SETIOSTATE(fsPtr->fd, &iostate); + return TCL_OK; } + + /* + * Option -xchar {\x11 \x13} + */ + if ((len > 1) && (strncmp(optionName, "-xchar", len) == 0)) { + + GETIOSTATE(fsPtr->fd, &iostate); + if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { + return TCL_ERROR; + } + if (argc == 2) { + iostate.c_cc[VSTART] = argv[0][0]; + iostate.c_cc[VSTOP] = argv[1][0]; + } else { + if (interp) { + Tcl_AppendResult(interp, + "bad value for -xchar: should be a list of two elements", + (char *) NULL); + } + return TCL_ERROR; + } + SETIOSTATE(fsPtr->fd, &iostate); + return TCL_OK; + } + + /* + * Option -timeout msec + */ + if ((len > 2) && (strncmp(optionName, "-timeout", len) == 0)) { + int msec; + + GETIOSTATE(fsPtr->fd, &iostate); + if ( Tcl_GetInt(interp, value, &msec) != TCL_OK ) { + return TCL_ERROR; + } + iostate.c_cc[VMIN] = 0; + iostate.c_cc[VTIME] = (msec == 0) ? 0 : (msec < 100) ? 1 : (msec+50)/100; + SETIOSTATE(fsPtr->fd, &iostate); + return TCL_OK; + } + + /* + * Option -ttycontrol {DTR 1 RTS 0 BREAK 0} + */ + if ((len > 4) && (strncmp(optionName, "-ttycontrol", len) == 0)) { + + if (Tcl_SplitList(interp, value, &argc, &argv) == TCL_ERROR) { + return TCL_ERROR; + } + if ((argc % 2) == 1) { + if (interp) { + Tcl_AppendResult(interp, + "bad value for -ttycontrol: should be a list of", + "signal,value pairs", (char *) NULL); + } + return TCL_ERROR; + } + + GETCONTROL(fsPtr->fd, &control); + while (argc > 1) { + if (Tcl_GetBoolean(interp, argv[1], &flag) == TCL_ERROR) { + return TCL_ERROR; + } + if (strncasecmp(argv[0], "DTR", strlen(argv[0])) == 0) { +#ifdef TIOCM_DTR + if (flag ) { + control |= TIOCM_DTR; + } else { + control &= ~TIOCM_DTR; + } +#else + if (interp) { + Tcl_AppendResult(interp, "-ttycontrol DTR ", + "not supported for this platform", + (char *) NULL); + } + return TCL_ERROR; +#endif + } else if (strncasecmp(argv[0], "RTS", strlen(argv[0])) == 0) { +#ifdef TIOCM_RTS + if (flag ) { + control |= TIOCM_RTS; + } else { + control &= ~TIOCM_RTS; + } +#else + if (interp) { + Tcl_AppendResult(interp, "-ttycontrol RTS ", + "not supported for this platform", + (char *) NULL); + } + return TCL_ERROR; +#endif + } else if (strncasecmp(argv[0], "BREAK", strlen(argv[0])) == 0) { +#ifdef SETBREAK + SETBREAK(fsPtr->fd, flag); +#else + if (interp) { + Tcl_AppendResult(interp, "-ttycontrol BREAK ", + "not supported for this platform", + (char *) NULL); + } + return TCL_ERROR; +#endif + } else { + if (interp) { + Tcl_AppendResult(interp, + "bad signal for -ttycontrol: must be ", + "DTR, RTS or BREAK", (char *) NULL); + } + return TCL_ERROR; + } + argc -= 2, argv += 2; + } /* while (argc > 1) */ + + SETCONTROL(fsPtr->fd, &control); + return TCL_OK; + } + + return Tcl_BadChannelOption(interp, optionName, + "mode handshake timeout ttycontrol xchar "); + +#else /* not USE_TERMIOS */ + return Tcl_BadChannelOption(interp, optionName, "mode"); +#endif } /* @@ -730,21 +1027,94 @@ TtyGetOptionProc(instanceData, interp, optionName, dsPtr) unsigned int len; char buf[3 * TCL_INTEGER_SPACE + 16]; TtyAttrs tty; + int valid = 0; /* flag if valid option parsed */ if (optionName == NULL) { - Tcl_DStringAppendElement(dsPtr, "-mode"); len = 0; } else { len = strlen(optionName); } + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-mode"); + } if ((len == 0) || - ((len > 1) && (strncmp(optionName, "-mode", len) == 0))) { + ((len > 2) && (strncmp(optionName, "-mode", len) == 0))) { + valid = 1; TtyGetAttributes(fsPtr->fd, &tty); sprintf(buf, "%d,%c,%d,%d", tty.baud, tty.parity, tty.data, tty.stop); Tcl_DStringAppendElement(dsPtr, buf); + } + +#ifdef USE_TERMIOS + /* + * get option -xchar + */ + if (len == 0) { + Tcl_DStringAppendElement(dsPtr, "-xchar"); + Tcl_DStringStartSublist(dsPtr); + } + if ((len == 0) || + ((len > 1) && (strncmp(optionName, "-xchar", len) == 0))) { + + IOSTATE iostate; + valid = 1; + + GETIOSTATE(fsPtr->fd, &iostate); + sprintf(buf, "%c", iostate.c_cc[VSTART]); + Tcl_DStringAppendElement(dsPtr, buf); + sprintf(buf, "%c", iostate.c_cc[VSTOP]); + Tcl_DStringAppendElement(dsPtr, buf); + } + if (len == 0) { + Tcl_DStringEndSublist(dsPtr); + } + + /* + * get option -queue + * option is readonly and returned by [fconfigure chan -queue] + * but not returned by unnamed [fconfigure chan] + */ + if ( (len > 1) && (strncmp(optionName, "-queue", len) == 0) ) { + int inQueue=0, outQueue=0; + int inBuffered, outBuffered; + valid = 1; +#ifdef GETREADQUEUE + GETREADQUEUE(fsPtr->fd, inQueue); +#endif +#ifdef GETWRITEQUEUE + GETWRITEQUEUE(fsPtr->fd, outQueue); +#endif + inBuffered = Tcl_InputBuffered(fsPtr->channel); + outBuffered = Tcl_OutputBuffered(fsPtr->channel); + + sprintf(buf, "%d", inBuffered+inQueue); + Tcl_DStringAppendElement(dsPtr, buf); + sprintf(buf, "%d", outBuffered+outQueue); + Tcl_DStringAppendElement(dsPtr, buf); + } + + /* + * get option -ttystatus + * option is readonly and returned by [fconfigure chan -ttystatus] + * but not returned by unnamed [fconfigure chan] + */ + if ( (len > 4) && (strncmp(optionName, "-ttystatus", len) == 0) ) { + int status; + valid = 1; + GETCONTROL(fsPtr->fd, &status); + TtyModemStatusStr(status, dsPtr); + } +#endif + + if (valid) { return TCL_OK; } else { - return Tcl_BadChannelOption(interp, optionName, "mode"); + return Tcl_BadChannelOption(interp, optionName, +#ifdef USE_TERMIOS + "mode queue ttystatus xchar"); +#else + "mode"); +#endif } } @@ -1462,7 +1832,7 @@ Tcl_MakeFileChannel(handle, mode) ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); #endif int socketType = 0; - int argLength = sizeof(int); + size_t argLength = sizeof(int); if (mode == 0) { return NULL; @@ -1822,7 +2192,7 @@ TcpGetOptionProc(instanceData, interp, optionName, dsPtr) struct sockaddr_in sockname; struct sockaddr_in peername; struct hostent *hostEntPtr; - int size = sizeof(struct sockaddr_in); + size_t size = sizeof(struct sockaddr_in); size_t len = 0; char buf[TCL_INTEGER_SPACE]; @@ -1832,10 +2202,9 @@ TcpGetOptionProc(instanceData, interp, optionName, dsPtr) if ((len > 1) && (optionName[1] == 'e') && (strncmp(optionName, "-error", len) == 0)) { - int optlen; + size_t optlen = sizeof(int); int err, ret; - optlen = sizeof(int); ret = getsockopt(statePtr->fd, SOL_SOCKET, SO_ERROR, (char *)&err, &optlen); if (ret < 0) { @@ -1844,7 +2213,7 @@ TcpGetOptionProc(instanceData, interp, optionName, dsPtr) if (err != 0) { Tcl_DStringAppend(dsPtr, Tcl_ErrnoMsg(err), -1); } - return TCL_OK; + return TCL_OK; } if ((len == 0) || @@ -2454,7 +2823,7 @@ TcpAccept(data, mask) int newsock; /* The new client socket */ TcpState *newSockState; /* State for new socket. */ struct sockaddr_in addr; /* The remote address */ - int len; /* For accept interface */ + size_t len; /* For accept interface */ char channelName[16 + TCL_INTEGER_SPACE]; sockState = (TcpState *) data; -- cgit v0.12