diff options
author | Steve Bennett <steveb@workware.net.au> | 2016-09-02 17:05:51 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2016-09-09 17:38:37 +1000 |
commit | 685edd21efef7311ad8ef7dcb4023db30201b99f (patch) | |
tree | 9e587fa0a7fbcf21ae4d780b8c1e9c41548ef0b2 | |
parent | 4baa884e739b59dfd50cbf606786c41de1d289a8 (diff) | |
download | jimtcl-685edd21efef7311ad8ef7dcb4023db30201b99f.zip jimtcl-685edd21efef7311ad8ef7dcb4023db30201b99f.tar.gz jimtcl-685edd21efef7311ad8ef7dcb4023db30201b99f.tar.bz2 |
aio: add tty settings support (via termios)
Enough to make serial ports work and support raw and cooked input/output
Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r-- | auto.def | 3 | ||||
-rw-r--r-- | jim-aio.c | 59 | ||||
-rw-r--r-- | jim-tty.c | 327 | ||||
-rw-r--r-- | jim-tty.h | 30 |
4 files changed, 418 insertions, 1 deletions
@@ -350,6 +350,9 @@ if {[have-feature windows]} { user-error "cygwin/mingw require --shared for dynamic modules" } } +if {[have-feature termios.h]} { + lappend extra_objs jim-tty.o +} if {[ext-get-status regexp] in {y m}} { if {![have-feature regcomp]} { @@ -68,6 +68,10 @@ #include <openssl/err.h> #endif +#ifdef HAVE_TERMIOS_H +#include <jim-tty.h> +#endif + #include "jim-eventloop.h" #include "jim-subcmd.h" @@ -490,7 +494,6 @@ static void JimAioDelProc(Jim_Interp *interp, void *privData) SSL_free(af->ssl); } #endif - if (!(af->openFlags & AIO_KEEPOPEN)) { fclose(af->fp); } @@ -1207,6 +1210,51 @@ static int aio_cmd_unlock(Jim_Interp *interp, int argc, Jim_Obj *const *argv) } #endif /* JIM_BOOTSTRAP */ +#if defined(HAVE_TERMIOS_H) && !defined(JIM_BOOTSTRAP) +static int aio_cmd_tty(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + Jim_Obj *dictObjPtr; + int ret; + + if (argc == 0) { + /* get the current settings as a dictionary */ + dictObjPtr = Jim_GetTtySettings(interp, af->fd); + if (dictObjPtr == NULL) { + JimAioSetError(interp, NULL); + return JIM_ERR; + } + Jim_SetResult(interp, dictObjPtr); + return JIM_OK; + } + + if (argc > 1) { + /* Convert name value arguments to a dictionary */ + dictObjPtr = Jim_NewListObj(interp, argv, argc); + } + else { + /* The settings are already given as a list */ + dictObjPtr = argv[0]; + } + Jim_IncrRefCount(dictObjPtr); + + if (Jim_ListLength(interp, dictObjPtr) % 2) { + /* Must be a valid dictionary */ + Jim_DecrRefCount(interp, dictObjPtr); + return -1; + } + + ret = Jim_SetTtySettings(interp, af->fd, dictObjPtr); + if (ret < 0) { + JimAioSetError(interp, NULL); + ret = JIM_ERR; + } + Jim_DecrRefCount(interp, dictObjPtr); + + return ret; +} +#endif /* JIM_BOOTSTRAP */ + static const jim_subcmd_type aio_command_table[] = { { "read", "?-nonewline? ?len?", @@ -1397,6 +1445,15 @@ static const jim_subcmd_type aio_command_table[] = { /* Description: Relase a lock. */ }, #endif /* JIM_BOOTSTRAP */ +#if defined(HAVE_TERMIOS_H) && !defined(JIM_BOOTSTRAP) + { "tty", + "?baud rate? ?data bits? ?stop bits? ?parity even|odd|none? ?handshake xonxoff|rtscts|none? ?input raw|cooked? ?output raw|cooked? ?vmin n? ?vtime n?", + aio_cmd_tty, + 0, + -1, + /* Description: Get or set tty settings - valid only on a tty */ + }, +#endif /* JIM_BOOTSTRAP */ { NULL } }; diff --git a/jim-tty.c b/jim-tty.c new file mode 100644 index 0000000..e9c6ee9 --- /dev/null +++ b/jim-tty.c @@ -0,0 +1,327 @@ +/* + * Support for tty settings using termios + * + * (c) 2016 Steve Bennett <steveb@workware.net.au> + * + */ + +/* termios support is required */ + +#include <jim-tty.h> +#include <termios.h> + +static const struct { + unsigned baud; + speed_t speed; +} baudtable[] = { + { 0, B0 }, + { 50, B50 }, + { 75, B75 }, + { 110, B110 }, + { 134, B134 }, + { 150, B150 }, + { 200, B200 }, + { 300, B300 }, + { 600, B600 }, + { 1200, B1200 }, + { 1800, B1800 }, + { 2400, B2400 }, + { 4800, B4800 }, + { 9600, B9600 }, + { 19200, B19200 }, + { 38400, B38400 }, + { 57600, B57600 }, + { 115200, B115200 }, +#ifdef B230400 + { 230400, B230400 }, +#endif +#ifdef B460800 + { 460800, B460800 } +#endif +}; + +struct flag_name_map { + const char *name; + unsigned value; +}; + +static const struct flag_name_map parity_map[] = { + { "none", 0 }, + { "even", PARENB }, + { "odd", PARENB | PARODD }, +}; +static const struct flag_name_map data_size_map[] = { + { "5", CS5 }, + { "6", CS6 }, + { "7", CS7 }, + { "8", CS8 }, +}; +static const struct flag_name_map stop_size_map[] = { + { "1", 0 }, + { "2", CSTOPB }, +}; +static const struct flag_name_map input_map[] = { + { "raw", 0 }, + { "cooked", ICANON }, +}; +static const struct flag_name_map output_map[] = { + { "raw", 0 }, + { "cooked", OPOST }, +}; + +static const char * const tty_settings_names[] = { + "baud", + "data", + "handshake", + "input", + "output", + "parity", + "stop", + "vmin", + "vtime", + NULL +}; + +enum { + OPT_BAUD, + OPT_DATA, + OPT_HANDSHAKE, + OPT_INPUT, + OPT_OUTPUT, + OPT_PARITY, + OPT_STOP, + OPT_VMIN, + OPT_VTIME +}; + + +#define ARRAYSIZE(A) (sizeof(A)/sizeof(*(A))) + +/** + * Search the flag/name map for an entry with the given name. + * (Actually, just matching the first char) + * Returns a pointer to the entry if found, or NULL if not. + */ +static const struct flag_name_map *flag_name_to_value(const struct flag_name_map *map, int len, const char *name) +{ + int i; + + for (i = 0; i < len; i++) { + /* Only need to compare the first character since all names are unique in the first char */ + if (*name == *map[i].name) { + return &map[i]; + } + } + return NULL; +} + +/** + * Search the flag/name map for an entry with the matching value. + * Returns the corresponding name if found, or NULL if no match. + */ +static const char *flag_value_to_name(const struct flag_name_map *map, int len, unsigned value) +{ + int i; + + for (i = 0; i < len; i++) { + if (value == map[i].value) { + return map[i].name; + } + } + return NULL; +} + +/** + * If 'str2' is not NULL, appends 'str1' and 'str2' to the list. + * Otherwise does nothing. + */ +static void JimListAddPair(Jim_Interp *interp, Jim_Obj *listObjPtr, const char *str1, const char *str2) +{ + if (str2) { + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, str1, -1)); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, str2, -1)); + } +} + +Jim_Obj *Jim_GetTtySettings(Jim_Interp *interp, int fd) +{ + struct termios tio; + int i; + const char *p; + Jim_Obj *listObjPtr; + speed_t speed; + int baud; + + if (tcgetattr(fd, &tio) < 0) { + return NULL; + } + + listObjPtr = Jim_NewListObj(interp, NULL, 0); + + p = flag_value_to_name(parity_map, ARRAYSIZE(parity_map), tio.c_cflag & (PARENB | PARODD)); + JimListAddPair(interp, listObjPtr, "parity", p); + p = flag_value_to_name(data_size_map, ARRAYSIZE(data_size_map), tio.c_cflag & CSIZE); + JimListAddPair(interp, listObjPtr, "data", p); + p = flag_value_to_name(stop_size_map, ARRAYSIZE(stop_size_map), tio.c_cflag & CSTOPB); + JimListAddPair(interp, listObjPtr, "stop", p); + if (tio.c_iflag & (IXON | IXOFF)) { + p = "xonxoff"; + } + else if (tio.c_cflag & CRTSCTS) { + p = "rtscts"; + } + else { + p = "none"; + } + JimListAddPair(interp, listObjPtr, "handshake", p); + p = flag_value_to_name(input_map, ARRAYSIZE(input_map), tio.c_lflag & ICANON); + JimListAddPair(interp, listObjPtr, "input", p); + p = flag_value_to_name(output_map, ARRAYSIZE(output_map), tio.c_oflag & OPOST); + JimListAddPair(interp, listObjPtr, "output", p); + + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, "vmin", -1)); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewIntObj(interp, tio.c_cc[VMIN])); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, "vtime", -1)); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewIntObj(interp, tio.c_cc[VTIME])); + + speed = cfgetispeed(&tio); + baud = 0; + for (i = 0; i < sizeof(baudtable) / sizeof(*baudtable); i++) { + if (baudtable[i].speed == speed) { + baud = baudtable[i].baud; + break; + } + } + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, "baud", -1)); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewIntObj(interp, baud)); + + return listObjPtr; +} + +int Jim_SetTtySettings(Jim_Interp *interp, int fd, Jim_Obj *dictObjPtr) +{ + int len = Jim_ListLength(interp, dictObjPtr); + int i; + + struct termios tio; + + if (tcgetattr(fd, &tio) < 0) { + return -1; + } + + for (i = 0; i < len; i += 2) { + Jim_Obj *nameObj = Jim_ListGetIndex(interp, dictObjPtr, i); + Jim_Obj *valueObj = Jim_ListGetIndex(interp, dictObjPtr, i + 1); + int opt; + const struct flag_name_map *p; + long l; + int j; + + if (Jim_GetEnum(interp, nameObj, tty_settings_names, &opt, "setting", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + + switch (opt) { + case OPT_BAUD: + if (Jim_GetLong(interp, valueObj, &l) != JIM_OK) { + goto badvalue; + } + for (j = 0; j < ARRAYSIZE(baudtable); j++) { + if (baudtable[j].baud == l) { + break; + } + } + if (j == ARRAYSIZE(baudtable)) { + goto badvalue; + } + cfsetospeed(&tio, baudtable[j].speed); + cfsetispeed(&tio, baudtable[j].speed); + break; + + case OPT_PARITY: + p = flag_name_to_value(parity_map, ARRAYSIZE(parity_map), Jim_String(valueObj)); + if (p == NULL) { +badvalue: + Jim_SetResultFormatted(interp, "bad value for %#s: %#s", nameObj, valueObj); + return JIM_ERR; + } + tio.c_cflag &= ~(PARENB | PARODD); + tio.c_cflag |= p->value; + break; + + case OPT_STOP: + p = flag_name_to_value(stop_size_map, ARRAYSIZE(stop_size_map), Jim_String(valueObj)); + if (p == NULL) { + goto badvalue; + } + tio.c_cflag &= ~CSTOPB; + tio.c_cflag |= p->value; + break; + + case OPT_DATA: + p = flag_name_to_value(data_size_map, ARRAYSIZE(data_size_map), Jim_String(valueObj)); + if (p == NULL) { + goto badvalue; + } + tio.c_cflag &= ~CSIZE; + tio.c_cflag |= p->value; + break; + + case OPT_HANDSHAKE: + tio.c_iflag &= ~(IXON | IXOFF); + tio.c_cflag &= ~(CRTSCTS); + if (Jim_CompareStringImmediate(interp, valueObj, "xonxoff")) { + tio.c_iflag |= (IXON | IXOFF); + } + else if (Jim_CompareStringImmediate(interp, valueObj, "rtscts")) { + tio.c_cflag |= CRTSCTS; + } + else if (!Jim_CompareStringImmediate(interp, valueObj, "none")) { + goto badvalue; + } + break; + + case OPT_VMIN: + if (Jim_GetLong(interp, valueObj, &l) != JIM_OK) { + goto badvalue; + } + tio.c_cc[VMIN] = l; + break; + + case OPT_VTIME: + if (Jim_GetLong(interp, valueObj, &l) != JIM_OK) { + goto badvalue; + } + tio.c_cc[VTIME] = l; + break; + + case OPT_OUTPUT: + p = flag_name_to_value(output_map, ARRAYSIZE(output_map), Jim_String(valueObj)); + if (p == NULL) { + goto badvalue; + } + tio.c_oflag &= ~OPOST; + tio.c_oflag |= p->value; + break; + + case OPT_INPUT: + p = flag_name_to_value(input_map, ARRAYSIZE(input_map), Jim_String(valueObj)); + if (p == NULL) { + goto badvalue; + } + if (p->value) { + tio.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH | TOSTOP); + } + else { + tio.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON | IEXTEN | ISIG | NOFLSH | TOSTOP); + } + break; + + } + } + + if (tcsetattr(fd, TCSAFLUSH, &tio) < 0) { + return -1; + } + return 0; +} diff --git a/jim-tty.h b/jim-tty.h new file mode 100644 index 0000000..d052d73 --- /dev/null +++ b/jim-tty.h @@ -0,0 +1,30 @@ +#ifndef JIM_TTY_H +#define JIM_TTY_H + +#include <jim.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Return the tty settings for the given file descriptor as a dictionary + * with a zero reference count. + * + * Returns NULL and sets errno file descriptor is not a valid tty. + */ +Jim_Obj *Jim_GetTtySettings(Jim_Interp *interp, int fd); + +/** + * Sets the tty settings given in 'dictObjPtr' + * + * Returns JIM_OK if OK, JIM_ERR if any settings are invalid, + * or -1 (and sets errno) if the file descriptor is not a valid tty. + */ +int Jim_SetTtySettings(Jim_Interp *interp, int fd, Jim_Obj *dictObjPtr); + +#ifdef __cplusplus +} +#endif + +#endif |