/* * ser2net - A program for allowing telnet connection to serial ports * Copyright (C) 2001 Corey Minyard * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* This code handles generating the configuration for the serial port. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "ser2net.h" #include "selector.h" #include "utils.h" #include "dataxfer.h" #include "devio.h" #include struct devcfg_data { /* Information about the terminal device. */ char *devname; /* The full path to the device */ int devfd; /* The file descriptor for the device, only valid if the TCP port is open. */ struct termios termctl; void (*shutdown_done)(struct devio *); int default_bps; int default_data_size; int default_stop_bits; int default_parity_on; int current_bps; int current_data_size; int current_stop_bits; int current_parity_on; /* Holds whether break is on or not. */ int break_set; /* Disable break-commands */ int disablebreak; #if HAVE_DECL_TIOCSRS485 struct serial_rs485 *conf; #endif }; static struct baud_rates_s { int real_rate; int val; int cisco_ios_val; } baud_rates[] = { { 50, B50, -1 }, { 75, B75, -1 }, { 110, B110, -1 }, { 134, B134, -1 }, { 150, B150, -1 }, { 200, B200, -1 }, { 300, B300, 3 }, { 600, B600 , 4}, { 1200, B1200, 5 }, { 1800, B1800, -1 }, { 2400, B2400, 6 }, { 4800, B4800, 7 }, { 9600, B9600, 8 }, /* We don't support 14400 baud */ { 19200, B19200, 10 }, /* We don't support 28800 baud */ { 38400, B38400, 12 }, { 57600, B57600, 13 }, { 115200, B115200, 14 }, { 230400, B230400, 15 }, /* We don't support 460800 baud */ }; #define BAUD_RATES_LEN ((sizeof(baud_rates) / sizeof(struct baud_rates_s))) static int get_baud_rate(int rate, int *val, int cisco, int *bps) { unsigned int i; for (i = 0; i < BAUD_RATES_LEN; i++) { if (cisco) { if (rate == baud_rates[i].cisco_ios_val) { *val = baud_rates[i].val; *bps = baud_rates[i].real_rate; return 1; } } else { if (rate == baud_rates[i].real_rate) { *val = baud_rates[i].val; *bps = baud_rates[i].real_rate; return 1; } } } return 0; } static void get_rate_from_baud_rate(int baud_rate, int *val, int cisco) { unsigned int i; for (i = 0; i < BAUD_RATES_LEN; i++) { if (baud_rate == baud_rates[i].val) { if (cisco) { if (baud_rates[i].cisco_ios_val < 0) /* We are at a baud rate unsupported by the enumeration, just return zero. */ *val = 0; else *val = baud_rates[i].cisco_ios_val; } else { *val = baud_rates[i].real_rate; } return; } } } #ifdef USE_UUCP_LOCKING static char *uucp_lck_dir = "/var/lock/"; static char *dev_prefix = "/dev/"; static int uucp_fname_lock_size(char *devname) { int dev_prefix_len = strlen(dev_prefix); if (strncmp(dev_prefix, devname, dev_prefix_len) == 0) devname += dev_prefix_len; /* * Format is "/var/lock/LCK..". The 6 is for * the "LCK.." and the final nil char. */ return 6 + strlen(uucp_lck_dir) + strlen(devname); } static void uucp_fname_lock(char *buf, char *devname) { int i, dev_prefix_len = strlen(dev_prefix); if (strncmp(dev_prefix, devname, dev_prefix_len) == 0) devname += dev_prefix_len; sprintf(buf, "%sLCK..%s", uucp_lck_dir, devname); for (i = strlen(uucp_lck_dir); buf[i]; i++) { if (buf[i] == '/') buf[i] = '_'; } } static void uucp_rm_lock(char *devname) { char *lck_file; if (!uucp_locking_enabled) return; lck_file = malloc(uucp_fname_lock_size(devname)); if (lck_file == NULL) { return; } uucp_fname_lock(lck_file, devname); unlink(lck_file); free(lck_file); } /* return 0=OK, -1=error, 1=locked by other proces */ static int uucp_mk_lock(char *devname) { struct stat stt; int pid = -1; if (!uucp_locking_enabled) return 0; if (stat(uucp_lck_dir, &stt) == 0) { /* is lock file directory present? */ char *lck_file; union { uint32_t ival; char str[64]; } buf; int fd; lck_file = malloc(uucp_fname_lock_size(devname)); if (lck_file == NULL) return -1; uucp_fname_lock(lck_file, devname); pid = 0; if ((fd = open(lck_file, O_RDONLY)) >= 0) { int n; n = read(fd, &buf, sizeof(buf) - 1); close(fd); if (n == 4) /* Kermit-style lockfile. */ pid = buf.ival; else if (n > 0) { /* Ascii lockfile. */ buf.str[n] = '\0'; sscanf(buf.str, "%10d", &pid); } if (pid > 0 && kill((pid_t)pid, 0) < 0 && errno == ESRCH) { /* death lockfile - remove it */ unlink(lck_file); pid = 0; } else pid = 1; } if (pid == 0) { int mask; mask = umask(022); fd = open(lck_file, O_WRONLY | O_CREAT | O_EXCL, 0666); umask(mask); if (fd >= 0) { ssize_t rv; snprintf(buf.str, sizeof(buf), "%10ld\n", (long)getpid()); rv = write_full(fd, buf.str, strlen(buf.str)); close(fd); if (rv < 0) { pid = -1; unlink(lck_file); } } else { pid = -1; } } free(lck_file); } return pid; } #else static void uucp_rm_lock(char *devname) { } static int uucp_mk_lock(char *devname) { return 0; } #endif /* USE_UUCP_LOCKING */ #ifdef __CYGWIN__ static void cfmakeraw(struct termios *termios_p) { termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); termios_p->c_cflag &= ~(CSIZE|PARENB); termios_p->c_cflag |= CS8; } #endif static int set_termios_from_speed(struct devcfg_data *d, struct termios *termctl, int speed) { switch (speed) { case 300: cfsetospeed(termctl, B300); cfsetispeed(termctl, B300); break; case 600: cfsetospeed(termctl, B600); cfsetispeed(termctl, B600); break; case 1200: cfsetospeed(termctl, B1200); cfsetispeed(termctl, B1200); break; case 2400: cfsetospeed(termctl, B2400); cfsetispeed(termctl, B2400); break; case 4800: cfsetospeed(termctl, B4800); cfsetispeed(termctl, B4800); break; case 9600: cfsetospeed(termctl, B9600); cfsetispeed(termctl, B9600); break; case 19200: cfsetospeed(termctl, B19200); cfsetispeed(termctl, B19200); break; case 38400: cfsetospeed(termctl, B38400); cfsetispeed(termctl, B38400); break; case 57600: cfsetospeed(termctl, B57600); cfsetispeed(termctl, B57600); break; case 115200: cfsetospeed(termctl, B115200); cfsetispeed(termctl, B115200); break; #ifdef B230400 case 230400: cfsetospeed(termctl, B230400); cfsetispeed(termctl, B230400); break; #endif #ifdef B460800 case 460800: cfsetospeed(termctl, B460800); cfsetispeed(termctl, B460800); break; #endif #ifdef B500000 case 500000: cfsetospeed(termctl, B500000); cfsetispeed(termctl, B500000); break; #endif #ifdef B576000 case 576000: cfsetospeed(termctl, B576000); cfsetispeed(termctl, B576000); break; #endif #ifdef B921600 case 921600: cfsetospeed(termctl, B921600); cfsetispeed(termctl, B921600); break; #endif #ifdef B1000000 case 1000000: cfsetospeed(termctl, B1000000); cfsetispeed(termctl, B1000000); break; #endif #ifdef B1152000 case 1152000: cfsetospeed(termctl, B1152000); cfsetispeed(termctl, B1152000); break; #endif #ifdef B1500000 case 1500000: cfsetospeed(termctl, B1500000); cfsetispeed(termctl, B1500000); break; #endif #ifdef B2000000 case 2000000: cfsetospeed(termctl, B2000000); cfsetispeed(termctl, B2000000); break; #endif #ifdef B2500000 case 2500000: cfsetospeed(termctl, B2500000); cfsetispeed(termctl, B2500000); break; #endif #ifdef B3000000 case 3000000: cfsetospeed(termctl, B3000000); cfsetispeed(termctl, B3000000); break; #endif #ifdef B3500000 case 3500000: cfsetospeed(termctl, B3500000); cfsetispeed(termctl, B3500000); break; #endif #ifdef B4000000 case 4000000: cfsetospeed(termctl, B4000000); cfsetispeed(termctl, B4000000); break; #endif default: return -1; } d->default_bps = speed; return 0; } static void set_termios_parity(struct devcfg_data *d, struct termios *termctl, enum parity_vals val) { switch (val) { case PARITY_NONE: termctl->c_cflag &= ~(PARENB); break; case PARITY_EVEN: case PARITY_SPACE: termctl->c_cflag |= PARENB; termctl->c_cflag &= ~(PARODD); #ifdef CMSPAR if (val == PARITY_SPACE) termctl->c_cflag |= CMSPAR; #endif break; case PARITY_ODD: case PARITY_MARK: termctl->c_cflag |= PARENB | PARODD; #ifdef CMSPAR if (val == PARITY_MARK) termctl->c_cflag |= CMSPAR; #endif break; } d->default_parity_on = val != PARITY_NONE; } static void set_termios_xonoff(struct termios *termctl, int enabled) { if (enabled) { termctl->c_iflag |= (IXON | IXOFF | IXANY); termctl->c_cc[VSTART] = 17; termctl->c_cc[VSTOP] = 19; } else { termctl->c_iflag &= ~(IXON | IXOFF | IXANY); } } static void set_termios_rtscts(struct termios *termctl, int enabled) { if (enabled) termctl->c_cflag |= CRTSCTS; else termctl->c_cflag &= ~CRTSCTS; } static void set_termios_datasize(struct devcfg_data *d, struct termios *termctl, int size) { termctl->c_cflag &= ~CSIZE; switch (size) { case 5: termctl->c_cflag |= CS5; break; case 6: termctl->c_cflag |= CS6; break; case 7: termctl->c_cflag |= CS7; break; case 8: termctl->c_cflag |= CS8; break; } d->default_data_size = size; } /* Initialize a serial port control structure for the first time. This should only be called when the port is created. It sets the port to the default 9600N81. */ static void devinit(struct devcfg_data *d, struct termios *termctl) { cfmakeraw(termctl); set_termios_from_speed(d, termctl, find_default_int("speed")); set_termios_datasize(d, termctl, find_default_int("databits")); d->default_stop_bits = find_default_int("stopbits"); if (d->default_stop_bits == 1) termctl->c_cflag &= ~(CSTOPB); else termctl->c_cflag |= CSTOPB; set_termios_parity(d, termctl, find_default_int("parity")); set_termios_xonoff(termctl, find_default_int("xonxoff")); set_termios_rtscts(termctl, find_default_int("rtscts")); if (find_default_int("local")) termctl->c_cflag |= CLOCAL; else termctl->c_cflag &= ~CLOCAL; if (find_default_int("hangup_when_done")) termctl->c_cflag &= HUPCL; else termctl->c_cflag &= ~HUPCL; d->disablebreak = find_default_int("nobreak"); termctl->c_cflag |= CREAD; termctl->c_iflag |= IGNBRK; } /* Configure a serial port control structure based upon input strings in instr. These strings are described in the man page for this program. */ static int devconfig(struct devcfg_data *d, struct absout *eout, const char *instr, int (*otherconfig)(void *data, struct absout *eout, const char *item), void *data) { struct termios *termctl = &d->termctl; char *str; char *pos; char *strtok_data; int rv = 0, val; devinit(d, termctl); str = strdup(instr); if (str == NULL) { return -1; } pos = strtok_r(str, " \t", &strtok_data); while (pos != NULL) { if ((val = speedstr_to_speed(pos)) != -1) { rv = set_termios_from_speed(d, termctl, val); if (rv) { eout->out(eout, "Invalid baud rate: %d\n", d->default_bps); goto out; } } else if (strcmp(pos, "1STOPBIT") == 0) { termctl->c_cflag &= ~(CSTOPB); d->default_stop_bits = 1; } else if (strcmp(pos, "2STOPBITS") == 0) { termctl->c_cflag |= CSTOPB; d->default_stop_bits = 2; } else if (strcmp(pos, "5DATABITS") == 0) { set_termios_datasize(d, termctl, 5); } else if (strcmp(pos, "6DATABITS") == 0) { set_termios_datasize(d, termctl, 6); } else if (strcmp(pos, "7DATABITS") == 0) { set_termios_datasize(d, termctl, 7); } else if (strcmp(pos, "8DATABITS") == 0) { set_termios_datasize(d, termctl, 8); } else if ((val = lookup_parity(pos)) != -1) { set_termios_parity(d, termctl, val); } else if (strcmp(pos, "XONXOFF") == 0) { set_termios_xonoff(termctl, 1); } else if (strcmp(pos, "-XONXOFF") == 0) { set_termios_xonoff(termctl, 0); } else if (strcmp(pos, "RTSCTS") == 0) { set_termios_rtscts(termctl, 1); } else if (strcmp(pos, "-RTSCTS") == 0) { set_termios_rtscts(termctl, 0); } else if (strcmp(pos, "LOCAL") == 0) { termctl->c_cflag |= CLOCAL; } else if (strcmp(pos, "-LOCAL") == 0) { termctl->c_cflag &= ~CLOCAL; } else if (strcmp(pos, "HANGUP_WHEN_DONE") == 0) { termctl->c_cflag |= HUPCL; } else if (strcmp(pos, "-HANGUP_WHEN_DONE") == 0) { termctl->c_cflag &= ~HUPCL; } else if (strcmp(pos, "NOBREAK") == 0) { d->disablebreak = 1; } else if (strcmp(pos, "-NOBREAK") == 0) { d->disablebreak = 0; } else { if (otherconfig(data, eout, pos) == -1) goto out; } pos = strtok_r(NULL, " \t", &strtok_data); } #if HAVE_DECL_TIOCSRS485 d->conf = get_rs485_conf(data); #endif out: free(str); return rv; } static char * baud_string(int speed) { char *str; switch (speed) { case B300: str = "300"; break; case B600: str = "600"; break; case B1200: str = "1200"; break; case B2400: str = "2400"; break; case B4800: str = "4800"; break; case B9600: str = "9600"; break; case B19200: str = "19200"; break; case B38400: str = "38400"; break; case B57600: str = "57600"; break; case B115200: str = "115200"; break; #ifdef B230400 case B230400: str = "230400"; break; #endif #ifdef B460800 case B460800: str = "460800"; break; #endif #ifdef B500000 case B500000: str = "500000"; break; #endif #ifdef B576000 case B576000: str = "576000"; break; #endif #ifdef B921600 case B921600: str = "921600"; break; #endif #ifdef B1000000 case B1000000: str = "1000000"; break; #endif #ifdef B1152000 case B1152000: str = "1152000"; break; #endif #ifdef B1500000 case B1500000: str = "1500000"; break; #endif #ifdef B2000000 case B2000000: str = "2000000"; break; #endif #ifdef B2500000 case B2500000: str = "2500000"; break; #endif #ifdef B3000000 case B3000000: str = "3000000"; break; #endif #ifdef B3500000 case B3500000: str = "3500000"; break; #endif #ifdef B4000000 case B4000000: str = "4000000"; break; #endif default: str = "unknown speed"; } return str; } static void devcfg_serparm_to_str(struct devio *io, char *str, int strlen) { struct devcfg_data *d = io->my_data; struct termios *termctl = &d->termctl; speed_t speed = cfgetospeed(termctl); int stopbits = termctl->c_cflag & CSTOPB; int databits = termctl->c_cflag & CSIZE; int parity_enabled = termctl->c_cflag & PARENB; int parity = termctl->c_cflag & PARODD; char *sstr; char pchar, schar, dchar; sstr = baud_string(speed); if (stopbits) schar = '2'; else schar = '1'; switch (databits) { case CS7: dchar = '7'; break; case CS8: dchar = '8'; break; default: dchar = '?'; } if (parity_enabled) { if (parity) { pchar = 'O'; } else { pchar = 'E'; } } else { pchar = 'N'; } snprintf(str, strlen, "%s %c%c%c", sstr, pchar, dchar, schar); } /* Send the serial port device configuration to the control port. */ static void devcfg_show_devcfg(struct devio *io, struct absout *out) { struct devcfg_data *d = io->my_data; struct termios *termctl = &d->termctl; speed_t speed = cfgetospeed(termctl); int stopbits = termctl->c_cflag & CSTOPB; int databits = termctl->c_cflag & CSIZE; int parity_enabled = termctl->c_cflag & PARENB; int parity = termctl->c_cflag & PARODD; int xon = termctl->c_iflag & IXON; int xoff = termctl->c_iflag & IXOFF; int xany = termctl->c_iflag & IXANY; int flow_rtscts = termctl->c_cflag & CRTSCTS; int clocal = termctl->c_cflag & CLOCAL; int hangup_when_done = termctl->c_cflag & HUPCL; char *str; out->out(out, "%s ", baud_string(speed)); if (xon && xoff && xany) { out->out(out, "XONXOFF "); } if (flow_rtscts) { out->out(out, "RTSCTS "); } if (clocal) { out->out(out, "LOCAL "); } if (hangup_when_done) { out->out(out, "HANGUP_WHEN_DONE "); } if (stopbits) { str = "2STOPBITS"; } else { str = "1STOPBIT"; } out->out(out, "%s ", str); switch (databits) { case CS7: str = "7DATABITS"; break; case CS8: str = "8DATABITS"; break; default: str = "unknown databits"; } out->out(out, "%s ", str); if (parity_enabled) { if (parity) { str = "ODD"; } else { str = "EVEN"; } } else { str = "NONE"; } out->out(out, "%s", str); } static int devcfg_set_devcontrol(struct devio *io, const char *instr) { struct devcfg_data *d = io->my_data; int fd = d->devfd; int rv = 0; char *str; char *pos; int status; char *strtok_data; str = malloc(strlen(instr) + 1); if (str == NULL) { return -1; } strcpy(str, instr); pos = strtok_r(str, " \t", &strtok_data); while (pos != NULL) { if (strcmp(pos, "RTSHI") == 0) { ioctl(fd, TIOCMGET, &status); status |= TIOCM_RTS; ioctl(fd, TIOCMSET, &status); } else if (strcmp(pos, "RTSLO") == 0) { ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_RTS; ioctl(fd, TIOCMSET, &status); } else if (strcmp(pos, "DTRHI") == 0) { ioctl(fd, TIOCMGET, &status); status |= TIOCM_DTR; ioctl(fd, TIOCMSET, &status); } else if (strcmp(pos, "DTRLO") == 0) { ioctl(fd, TIOCMGET, &status); status &= ~TIOCM_DTR; /* AKA drop DTR */ ioctl(fd, TIOCMSET, &status); } else { rv = -1; goto out; } pos = strtok_r(NULL, " \t", &strtok_data); } out: free(str); return rv; } static void devcfg_show_devcontrol(struct devio *io, struct absout *out) { struct devcfg_data *d = io->my_data; char *str; int status; ioctl(d->devfd, TIOCMGET, &status); if (status & TIOCM_RTS) { str = "RTSHI"; } else { str = "RTSLO"; } out->out(out, "%s ", str); if (status & TIOCM_DTR) { str = "DTRHI"; } else { str = "DTRLO"; } out->out(out, "%s ", str); } static void do_read(int fd, void *data) { struct devio *io = data; io->read_handler(io); } static void do_write(int fd, void *data) { struct devio *io = data; io->write_handler(io); } static void do_except(int fd, void *data) { struct devio *io = data; io->except_handler(io); } static int calc_bpc(struct devcfg_data *d) { return d->current_data_size + d->current_stop_bits + d->current_parity_on + 1; } static void devfd_fd_cleared(int fd, void *cb_data) { struct devio *io = cb_data; struct devcfg_data *d = io->my_data; void (*shutdown_done)(struct devio *) = d->shutdown_done; struct termios termio; /* Disable flow control to avoid a long shutdown. */ if (tcgetattr(d->devfd, &termio) != -1) { termio.c_iflag &= ~(IXON | IXOFF); termio.c_cflag &= ~CRTSCTS; tcsetattr(d->devfd, TCSANOW, &termio); } tcflush(d->devfd, TCOFLUSH); close(d->devfd); d->devfd = -1; uucp_rm_lock(io->devname); shutdown_done(io); } static int devcfg_setup(struct devio *io, const char *name, const char **errstr, int *bps, int *bpc) { struct devcfg_data *d = io->my_data; int options; int rv; rv = uucp_mk_lock(io->devname); if (rv > 0 ) { *errstr = "Port already in use by another process\r\n"; return -1; } else if (rv < 0) { *errstr = "Error creating port lock file\r\n"; return -1; } *bps = d->current_bps = d->default_bps; d->current_data_size = d->default_data_size; d->current_stop_bits = d->default_stop_bits; d->current_parity_on = d->default_parity_on; *bpc = calc_bpc(d); /* Oct 05 2001 druzus: NOCTTY - don't make device control tty for our process */ options = O_NONBLOCK | O_NOCTTY; if (io->read_disabled) { options |= O_WRONLY; } else { options |= O_RDWR; } d->devfd = open(io->devname, options); if (d->devfd == -1) { syslog(LOG_ERR, "Could not open device %s for port %s: %m", io->devname, name); uucp_rm_lock(io->devname); return -1; } if (!io->read_disabled && tcsetattr(d->devfd, TCSANOW, &d->termctl) == -1) { close(d->devfd); d->devfd = -1; syslog(LOG_ERR, "Could not set up device %s for port %s: %m", io->devname, name); uucp_rm_lock(io->devname); return -1; } /* Turn off BREAK. */ if (!io->read_disabled && !d->disablebreak && ioctl(d->devfd, TIOCCBRK) == -1) { /* Probably not critical, but we should at least log something. */ syslog(LOG_ERR, "Could not turn off break for device %s port %s: %m", io->devname, name); } #if HAVE_DECL_TIOCSRS485 if (d->conf) { if (d->conf->flags & SER_RS485_ENABLED) { if (ioctl(d->devfd , TIOCSRS485, d->conf ) < 0) { syslog(LOG_ERR, "Could not set RS485 config for device %s port %s: %m", io->devname, name); return -1; } } } #endif sel_set_fd_handlers(ser2net_sel, d->devfd, io, io->read_disabled ? NULL : do_read, do_write, do_except, devfd_fd_cleared); return 0; } static void devcfg_shutdown(struct devio *io, void (*shutdown_done)(struct devio *)) { struct devcfg_data *d = io->my_data; /* To avoid blocking on close if we have written bytes and are in flow-control, we flush the output queue. */ if (d->devfd != -1) { d->shutdown_done = shutdown_done; sel_clear_fd_handlers(ser2net_sel, d->devfd); } else { shutdown_done(io); } } static int devcfg_read(struct devio *io, void *buf, size_t size) { struct devcfg_data *d = io->my_data; return read(d->devfd, buf, size); } static int devcfg_write(struct devio *io, void *buf, size_t size) { struct devcfg_data *d = io->my_data; return write(d->devfd, buf, size); } static void devcfg_read_handler_enable(struct devio *io, int enabled) { struct devcfg_data *d = io->my_data; sel_set_fd_read_handler(ser2net_sel, d->devfd, enabled ? SEL_FD_HANDLER_ENABLED : SEL_FD_HANDLER_DISABLED); } static void devcfg_write_handler_enable(struct devio *io, int enabled) { struct devcfg_data *d = io->my_data; sel_set_fd_write_handler(ser2net_sel, d->devfd, enabled ? SEL_FD_HANDLER_ENABLED : SEL_FD_HANDLER_DISABLED); } static void devcfg_except_handler_enable(struct devio *io, int enabled) { struct devcfg_data *d = io->my_data; sel_set_fd_except_handler(ser2net_sel, d->devfd, enabled ? SEL_FD_HANDLER_ENABLED : SEL_FD_HANDLER_DISABLED); } static int devcfg_send_break(struct devio *io) { struct devcfg_data *d = io->my_data; tcsendbreak(d->devfd, 0); return 0; } static int devcfg_get_modem_state(struct devio *io, unsigned char *modemstate) { struct devcfg_data *d = io->my_data; int val; if (ioctl(d->devfd, TIOCMGET, &val) != 0) return -1; *modemstate = 0; if (val & TIOCM_CD) *modemstate |= 0x80; if (val & TIOCM_RI) *modemstate |= 0x40; if (val & TIOCM_DSR) *modemstate |= 0x20; if (val & TIOCM_CTS) *modemstate |= 0x10; return 0; } static int devcfg_baud_rate(struct devio *io, int *val, int cisco, int *bps) { struct devcfg_data *d = io->my_data; struct termios termio; if (tcgetattr(d->devfd, &termio) == -1) { *val = 0; return -1; } if ((*val != 0) && (get_baud_rate(*val, val, cisco, bps))) { /* We have a valid baud rate. */ cfsetispeed(&termio, *val); cfsetospeed(&termio, *val); tcsetattr(d->devfd, TCSANOW, &termio); } tcgetattr(d->devfd, &termio); *val = cfgetispeed(&termio); get_rate_from_baud_rate(*val, val, cisco); return 0; } static int devcfg_data_size(struct devio *io, unsigned char *val, int *bpc) { struct devcfg_data *d = io->my_data; struct termios termio; if (tcgetattr(d->devfd, &termio) == -1) { *val = 0; return -1; } if ((*val >= 5) && (*val <= 8)) { termio.c_cflag &= ~CSIZE; switch (*val) { case 5: termio.c_cflag |= CS5; d->current_data_size = 5; break; case 6: termio.c_cflag |= CS6; d->current_data_size = 6; break; case 7: termio.c_cflag |= CS7; d->current_data_size = 7; break; case 8: termio.c_cflag |= CS8; d->current_data_size = 8; break; } tcsetattr(d->devfd, TCSANOW, &termio); } switch (termio.c_cflag & CSIZE) { case CS5: *val = 5; break; case CS6: *val = 6; break; case CS7: *val = 7; break; case CS8: *val = 8; break; default: *val = 0; } *bpc = calc_bpc(d); return 0; } static int devcfg_parity(struct devio *io, unsigned char *val, int *bpc) { struct devcfg_data *d = io->my_data; struct termios termio; if (tcgetattr(d->devfd, &termio) == -1) { *val = 0; return -1; } /* We don't support MARK or SPACE parity. */ if ((*val >= 1) && (*val <= 3)) { termio.c_cflag &= ~(PARENB | PARODD); switch (*val) { case 1: d->current_parity_on = 0; break; /* NONE */ case 2: termio.c_cflag |= PARENB | PARODD; /* ODD */ d->current_parity_on = 1; break; case 3: termio.c_cflag |= PARENB; /* EVEN */ d->current_parity_on = 1; break; } tcsetattr(d->devfd, TCSANOW, &termio); } if (termio.c_cflag & PARENB) { if (termio.c_cflag & PARODD) *val = 2; /* ODD */ else *val = 3; /* EVEN */ } else *val = 1; /* NONE */ *bpc = calc_bpc(d); return 0; } static int devcfg_stop_size(struct devio *io, unsigned char *val, int *bpc) { struct devcfg_data *d = io->my_data; struct termios termio; if (tcgetattr(d->devfd, &termio) == -1) { *val = 0; return -1; } if ((*val >= 1) && (*val <= 2)) { termio.c_cflag &= ~CSTOPB; switch (*val) { case 1: d->current_stop_bits = 1; break; /* 1 stop bit */ case 2: d->current_stop_bits = 2; /* 2 stop bits */ termio.c_cflag |= CSTOPB; break; } tcsetattr(d->devfd, TCSANOW, &termio); } if (termio.c_cflag & CSTOPB) *val = 2; /* 2 stop bits. */ else *val = 1; /* 1 stop bit. */ *bpc = calc_bpc(d); return 0; } static int devcfg_flow_control(struct devio *io, unsigned char val) { struct devcfg_data *d = io->my_data; tcflow(d->devfd, val ? TCIOFF : TCION); return 0; } static int devcfg_control(struct devio *io, unsigned char *val) { struct devcfg_data *d = io->my_data; struct termios termio; int ival; if (tcgetattr(d->devfd, &termio) == -1) { *val = 0; return -1; } switch (*val) { case 0: case 1: case 2: case 3: /* Outbound/both flow control */ if (tcgetattr(d->devfd, &termio) != -1) { if (*val != 0) { termio.c_iflag &= ~(IXON | IXOFF); termio.c_cflag &= ~CRTSCTS; switch (*val) { case 1: break; /* NONE */ case 2: termio.c_iflag |= IXON | IXOFF; break; case 3: termio.c_cflag |= CRTSCTS; break; } tcsetattr(d->devfd, TCSANOW, &termio); } if (termio.c_cflag & CRTSCTS) *val = 3; else if (termio.c_iflag & IXON) *val = 2; else *val = 1; } break; case 13: case 14: case 15: case 16: case 17: case 18: case 19: /* Inbound flow-control */ if (tcgetattr(d->devfd, &termio) != -1) { if (*val == 15) { /* We can only set XON/XOFF independently */ termio.c_iflag |= IXOFF; tcsetattr(d->devfd, TCSANOW, &termio); } if (termio.c_cflag & CRTSCTS) *val = 16; else if (termio.c_iflag & IXOFF) *val = 15; else *val = 14; } break; /* Handle BREAK stuff. */ case 6: if (ioctl(d->devfd, TIOCCBRK) != -1) d->break_set = 0; goto read_break_val; case 5: if (ioctl(d->devfd, TIOCSBRK) != -1) d->break_set = 1; goto read_break_val; case 4: read_break_val: if (d->break_set) *val = 5; else *val = 6; break; /* DTR handling */ case 8: #ifndef __CYGWIN__ ival = TIOCM_DTR; ioctl(d->devfd, TIOCMBIS, &ival); #else ioctl(d->devfd, TIOCMGET, &ival); ival |= TIOCM_DTR; ioctl(d->devfd, TIOCMSET, &ival); #endif goto read_dtr_val; case 9: #ifndef __CYGWIN__ ival = TIOCM_DTR; ioctl(d->devfd, TIOCMBIC, &ival); #else ioctl(d->devfd, TIOCMGET, &ival); ival &= ~TIOCM_DTR; ioctl(d->devfd, TIOCMSET, &ival); #endif goto read_dtr_val; case 7: read_dtr_val: if (ioctl(d->devfd, TIOCMGET, &ival) == -1) *val = 7; else if (ival & TIOCM_DTR) *val = 8; else *val = 9; break; /* RTS handling */ case 11: #ifndef __CYGWIN__ ival = TIOCM_RTS; ioctl(d->devfd, TIOCMBIS, &ival); #else ioctl(d->devfd, TIOCMGET, &ival); ival |= TIOCM_RTS; ioctl(d->devfd, TIOCMSET, &ival); #endif goto read_rts_val; case 12: #ifndef __CYGWIN__ ival = TIOCM_RTS; ioctl(d->devfd, TIOCMBIC, &ival); #else ioctl(d->devfd, TIOCMGET, &ival); ival &= ~TIOCM_RTS; ioctl(d->devfd, TIOCMSET, &ival); #endif goto read_rts_val; case 10: read_rts_val: if (ioctl(d->devfd, TIOCMGET, &ival) == -1) *val = 10; else if (ival & TIOCM_RTS) *val = 11; else *val = 12; break; default: *val = 0; return -1; } return 0; } static int devcfg_flush(struct devio *io, int *val) { struct devcfg_data *d = io->my_data; int ival; switch (*val) { case DEVIO_FLUSH_INPUT: ival = TCIFLUSH; goto purge_found; case DEVIO_FLUSH_OUTPUT: ival = TCOFLUSH; goto purge_found; case DEVIO_FLUSH_INPUT | DEVIO_FLUSH_OUTPUT: ival = TCIOFLUSH; goto purge_found; } *val = 0; return -1; purge_found: tcflush(d->devfd, ival); return 0; } static void devcfg_free(struct devio *io) { struct devcfg_data *d = io->my_data; if (d->devfd != -1) close(d->devfd); io->my_data = NULL; free(d); } static int devcfg_reconfig(struct devio *io, struct absout *eout, const char *instr, int (*otherconfig)(void *data, struct absout *eout, const char *item), void *data) { struct devcfg_data *d = io->my_data; return devconfig(d, eout, instr, otherconfig, data); } static struct devio_f devcfg_io_f = { .setup = devcfg_setup, .shutdown = devcfg_shutdown, .reconfig = devcfg_reconfig, .read = devcfg_read, .write = devcfg_write, .read_handler_enable = devcfg_read_handler_enable, .write_handler_enable = devcfg_write_handler_enable, .except_handler_enable = devcfg_except_handler_enable, .send_break = devcfg_send_break, .get_modem_state = devcfg_get_modem_state, .set_devcontrol = devcfg_set_devcontrol, .show_devcontrol = devcfg_show_devcontrol, .show_devcfg = devcfg_show_devcfg, .baud_rate = devcfg_baud_rate, .data_size = devcfg_data_size, .parity = devcfg_parity, .stop_size = devcfg_stop_size, .control = devcfg_control, .flow_control = devcfg_flow_control, .flush = devcfg_flush, .free = devcfg_free, .serparm_to_str = devcfg_serparm_to_str }; int devcfg_init(struct devio *io, struct absout *eout, const char *instr, int (*otherconfig)(void *data, struct absout *eout, const char *item), void *data) { struct devcfg_data *d; d = malloc(sizeof(*d)); if (!d) return -1; memset(d, 0, sizeof(*d)); d->devfd = -1; if (devconfig(d, eout, instr, otherconfig, data) == -1) { free(d); return -1; } io->my_data = d; io->f = &devcfg_io_f; return 0; }