diff options
author | Frank Rowand <frank.rowand@sonymobile.com> | 2014-09-04 21:17:07 -0700 |
---|---|---|
committer | Jason Wessel <jason.wessel@windriver.com> | 2014-09-11 17:25:24 -0500 |
commit | 36fff68356fee861eaccab50cfef1efd7dac92ef (patch) | |
tree | bd58a0561ce1ccc6b56cbfd0346ceef0693e2201 | |
parent | e6c9d3d996bd55e7ab14dbd74deb7841e0c3a4f1 (diff) | |
download | agent-proxy-36fff68356fee861eaccab50cfef1efd7dac92ef.tar.gz |
kgdb: agent-proxy: Initial version of kdmx.c
Initial version of kdmx.c
kdmx is a program designed to split GDB packets and other trafic coming
from a target on a serial line into 2 separate pseudo-ttys.
Author: Tom Rini <trini@mvista.com>
Significant update by: Frank Rowand <frank.rowand@sonymobile.com>
Updates include:
- Fix issues that prevented use of serial USB port.
- Detect and handle I/O errors.
- Add additional error checking.
- Detect and handle disconnect by gdb and terminal emulator.
- Add ability to send BREAK to serial port.
- Replace do_read() and do_write() debug mode.
- Expand usage text.
- Allow terminal emulator to send the null character to the serial port.
- Restructure and change to Linux kernel coding style.
- General clean up.
Signed-off-by: Frank Rowand <frank.rowand@sonymobile.com>
Acked-by: Tom Rini <trini@kernel.crashing.org>
Signed-off-by: Jason Wessel <jason.wessel@windriver.com>
-rw-r--r-- | kdmx/kdmx.c | 937 |
1 files changed, 937 insertions, 0 deletions
diff --git a/kdmx/kdmx.c b/kdmx/kdmx.c new file mode 100644 index 0000000..54278c3 --- /dev/null +++ b/kdmx/kdmx.c @@ -0,0 +1,937 @@ +/* + * kdmx.c + * + * The guts of a gdb/console demuxer. We take from one input both + * GDB packets (typically from KGDB, but could be gdbserver) and regular + * console I/O and split them into two pseudo-ttys. + * + * Author: Tom Rini <trini@mvista.com> + * Rename and significant update by: Frank Rowand <frank.rowand@sonymobile.com> + * + * 2004 (c) MontaVista Software, Inc. + * Copyright (c) 2014 Sony Mobile Communications Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. See the file COPYING for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <termios.h> +#include <unistd.h> + +#include <sys/ioctl.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +/* + * "Version Update Fix" + * Format: YYMMDDX + * YY is year + * MM is month + * DD is day + * X is an incrementing value a..z, restarting at 'a' for each new YYMMDD + */ +#define VUFX "140904a" + +#define pr_debug(fmt, ...) \ +({ \ + if (debug) \ + fprintf(stderr, fmt, ##__VA_ARGS__); \ +}) + +#define pr_err(fmt, ...) \ + fprintf(stderr, fmt, ##__VA_ARGS__) + +#define pr_info(fmt, ...) \ + printf(fmt, ##__VA_ARGS__) + +int print_g; +int print_s; +int print_t; +int print_G; +int print_S; +int print_T; +int print_label; + +int prev_fd = -1; + +#define DW_FLAG_BREAK 0x00000001 + +enum { + PREV_IO_NONE, + PREV_IO_READ, + PREV_IO_WRITE +} prev_io = PREV_IO_NONE; + +#define DEFAULT_SERIAL "/dev/ttyS0" +#define BUFMAX 2048 +#define SERIAL_PORT_PATHLEN 512 + +int debug; +int passthru_null_from_term; + +speed_t new_baudrate = B9600; /* see /usr/include/bits/termios.h */ + +int serial_fd; +int term_fd; /* terminal emulator pty master */ +int gdb_fd; /* gdb pty master */ + +int die(const char *msg) +{ + perror(msg); + exit(EXIT_FAILURE); +} + +/* + * Open /dev/ptmx and get us a master/slave combo. This assumes a + * Unix98 style environment. + */ +void get_pty(int *master) +{ + /* Get the master */ + (*master) = open("/dev/ptmx", O_RDWR); + if (grantpt((*master))) + die("grantpt"); + + if (unlockpt((*master))) + die("unlockpt"); +} + +/* + * Read one char at a time, and return it. Optionally print out + * what / where it happened. + */ +char do_read(int fd, int *ret_errno) +{ + unsigned char buf; + int ret; + + /* Perform the read */ + ret = read(fd, &buf, sizeof(buf)); + if (ret == -1) { + *ret_errno = errno; + return -1; + } + + /* + * select() reports read fd is readable on end-of-file. + * Do not get into an infinite loop in that case. + */ + if (ret == 0) { + pr_err("End of file on "); + if (fd == gdb_fd) + pr_err("gdb pty\n"); + if (fd == serial_fd) + pr_err("serial port\n"); + if (fd == term_fd) + pr_err("terminal pty\n"); + exit(EXIT_FAILURE); + } + + if (print_label) { + if ((prev_io != PREV_IO_READ) || (prev_fd != fd)) { + if (print_g && (fd == gdb_fd)) { + pr_debug("\ng> "); + prev_fd = fd; + prev_io = PREV_IO_READ; + } else if (print_S && (fd == serial_fd)) { + pr_debug("\ns< "); + prev_fd = fd; + prev_io = PREV_IO_READ; + } else if (print_t && (fd == term_fd)) { + pr_debug("\nt> "); + prev_fd = fd; + prev_io = PREV_IO_READ; + } + } + } + + if ((print_g && (fd == gdb_fd)) || + (print_S && (fd == serial_fd)) || + (print_t && (fd == term_fd)) + ) { + + if ((buf > 0x1f) && (buf < 0x7f)) + pr_debug("%c", buf); + else + pr_debug(" 0x%02x ", buf); + + if (((fd != serial_fd) && (buf == '\r')) || + ((fd == serial_fd) && (buf == '\n')) + ) { + pr_debug("\n"); + } + } + + return buf; +} + +/* Write a buffer, and optionally print out what / where it happened */ +void do_write(int fd, char *buf, size_t len, int dw_flag) +{ + char *_buf; + int _len; + int k; + int retry_count = 0; + int sel_ret; /* select() */ + ssize_t ret; /* write() */ + fd_set writefds; + + if ((print_label && len) || (dw_flag & DW_FLAG_BREAK)) { + if ((prev_io != PREV_IO_WRITE) || (prev_fd != fd)) { + if (print_G && (fd == gdb_fd)) { + pr_debug("\ng< "); + prev_fd = fd; + prev_io = PREV_IO_WRITE; + } else if (print_s && (fd == serial_fd)) { + pr_debug("\ns> "); + prev_fd = fd; + prev_io = PREV_IO_WRITE; + } else if (print_T && (fd == term_fd)) { + pr_debug("\nt< "); + prev_fd = fd; + prev_io = PREV_IO_WRITE; + } + } + } + + + + if ((dw_flag & DW_FLAG_BREAK) && (fd == serial_fd)) { + int ret; + ret = ioctl(fd, TCSBRK, 0); + if (ret) + perror("BREAK ioctl() on serial port"); + else if (print_s) + /* zzz not a unique string.... */ + pr_debug("__BREAK__"); + } + + + + /* Perform the write */ + while (len > 0) { + + if (retry_count++) { + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + sel_ret = select(fd + 1, NULL, &writefds, NULL, NULL); + if (sel_ret == -1) + die("select"); + } + + _buf = buf; + ret = write(fd, buf, len); + _len = ret; + + if (ret == -1) { + if (errno != EAGAIN) { + perror("write() "); + if (fd == gdb_fd) + pr_err(" write() was to gdb pty\n"); + else if (fd == serial_fd) + pr_err(" write() was to serial port\n"); + else if (fd == term_fd) + pr_err(" write() was to term pty\n"); + exit(EXIT_FAILURE); + } + } else if (ret != len) { + buf += ret; + len -= ret; + pr_debug("do_write() failed to write full buffer, len = %ld, ret = %ld\n", + len, ret); + } else { + len = 0; + } + + if ((print_G && (fd == gdb_fd)) || + (print_s && (fd == serial_fd)) || + (print_T && (fd == term_fd)) + ) { + + for (k = 0; k < _len; k++, _buf++) { + + if ((*_buf > 0x1f) && (*_buf < 0x7f)) + pr_debug("%c", *_buf); + else + pr_debug(" 0x%02x ", *_buf); + + if (((fd == serial_fd) && (*_buf == '\r')) || + ((fd != serial_fd) && (*_buf == '\n')) + ) { + pr_debug("\n"); + } + } + } + } +} + + +/* Convert a single hex char to its int value */ +int hex(unsigned char ch) +{ + if (ch >= 'a' && ch <= 'f') + return ch - 'a' + 10; + if (ch >= '0' && ch <= '9') + return ch - '0'; + if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + return 0; /* No value */ +} + +void parse_debug(char *options) +{ + for (; *options; options++) { + switch (*options) { + case 'g': + print_g = 1; + break; + case 's': + print_s = 1; + break; + case 't': + print_t = 1; + break; + case 'G': + print_G = 1; + break; + case 'S': + print_S = 1; + break; + case 'T': + print_T = 1; + break; + default: + pr_err("Invalid 'D' sub-option\n"); + exit(EXIT_FAILURE); + } + } +} + +/* + * String input of a baud rate. Convert to an int, make sure it is valid. + */ +void parse_baud(char *rate) +{ + int baudrate; + + baudrate = strtol(rate, NULL, 10); + + /* + * 9600 or better (an artificial limit) + * + * When modifying list of cases, update usage() to match. + */ + switch (baudrate) { + case 9600: + new_baudrate = B9600; + break; + case 19200: + new_baudrate = B19200; + break; + case 38400: + new_baudrate = B38400; + break; + case 57600: + new_baudrate = B57600; + break; + case 115200: + new_baudrate = B115200; + break; + case 230400: + new_baudrate = B230400; + break; + default: + pr_err("Invalid baud rate given\n"); + exit(EXIT_FAILURE); + } +} + +void +usage(void) +{ + /* + * Output should fit in 79 columns: + * + * 1 2 3 4 5 6 7 \n"); + * 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + */ + + pr_err("\n"); + pr_err("Usage: kdmx [options]\n"); + pr_err("\n"); + pr_err(" -? Print this message\n"); + pr_err(" -b rate Set serial port baud rate. default: 9600\n"); + pr_err(" 9600, 19200, 38400, 57600, 115200, 230400\n"); + pr_err(" -d Enable debug messages to stderr\n"); + pr_err(" -Dx Enable debug print of data stream(s) to stderr\n"); + pr_err(" Any number of values for 'x' allowed\n"); + pr_err(" x == lower case is from host to target\n"); + pr_err(" x == upper case is from target to host\n"); + pr_err(" g from gdb\n"); + pr_err(" s to serial port\n"); + pr_err(" t from terminal emulator (eg minicom)\n"); + pr_err(" G to gdb\n"); + pr_err(" S from serial port\n"); + pr_err(" T to terminal emulator (eg minicom)\n"); + pr_err(" -h Print this message\n"); + pr_err(" -n Allow terminal emulator to send null (\\0) characters\n"); + pr_err(" -p port Serial port path. default: %s\n", DEFAULT_SERIAL); + pr_err(" -v Print version\n"); + pr_err("\n"); + pr_err(" Console multiplexor for kgdb Linux kernel debugger.\n"); + pr_err(" Splits the console traffic to/from a serial port between two\n"); + pr_err(" pseudo-terminals. One pty connects to a terminal emulator, such as\n"); + pr_err(" minicom. The second pty connects to gdb. The names of the ptys are\n"); + pr_err(" reported when this program is executed.\n"); + pr_err("\n"); + pr_err(" To exit from kdmx, kill the program or issue a control-c.\n"); + pr_err("\n"); + pr_err("\n"); + pr_err(" KNOWN ISSUES:\n"); + pr_err("\n"); + pr_err(" The intended user of this program is an advanced kernel debugger, that is\n"); + pr_err(" a 'wizard'. With this intended user, there are some 'sharp edges' that\n"); + pr_err(" have not been removed from kdmx -- use this program with caution.\n"); + pr_err("\n"); + pr_err(" kdmx:\n"); + pr_err("\n"); + pr_err(" - The BREAK signal can not be sent from the terminal emulator to kdmx.\n"); + pr_err(" Workaround: Enter '~B' in the terminal emulator, immediately following\n"); + pr_err(" a carriage return. kdmx will strip the '~B' from the input stream and\n"); + pr_err(" replace it with a BREAK signal on the serial port.\n"); + pr_err("\n"); + pr_err(" target system:\n"); + pr_err("\n"); + pr_err(" - Usage:\n"); + pr_err(" Trigger the connect sequence on the target before trying to connect\n"); + pr_err(" from gdb. Methods to trigger include:\n"); + pr_err(" 1) send a magic sysrq command via the proc file system:\n"); + pr_err(" echo g > /proc/sysrq-trigger\n"); + pr_err(" 2) send a magic sysrq command via a BREAK sequence:\n"); + pr_err(" '~Bg'\n"); + pr_err(" 3) via the boot command line. Add 'kgdbwait' to the command line.\n"); + pr_err("\n"); + pr_err(" gdb:\n"); + pr_err("\n"); + pr_err(" - Usage:\n"); + pr_err(" After triggering the connect sequence on the target, connect from\n"); + pr_err(" gdb. For example:\n"); + pr_err(" (gdb) target remote /dev/pts/30\n"); + pr_err("\n"); + pr_err(" Minicom:\n"); + pr_err("\n"); + pr_err(" - If minicom is connected to kdmx when kdmx is killed, then minicom will\n"); + pr_err(" repeatedly attempt to reopen the slave pty that it was connected to.\n"); + pr_err(" If a new terminal emulator (such as gnome-terminal, xfce Terminal, or\n"); + pr_err(" Konsole) is executed during this time, it is likely to create a new\n"); + pr_err(" slave pty with the same name as the slave pty that minicom is attempting\n"); + pr_err(" to reopen. Both the terminal emulator and minicom will open the same\n"); + pr_err(" slave tty, with the result that some output will go to minicom and some\n"); + pr_err(" will go to the terminal emulator. The terminal emulator will appear\n"); + pr_err(" to randomly lose characters.\n"); + pr_err(" Workaround: terminate minicom before killing kdmx.\n"); + pr_err(" [minicom version 2.5]\n"); + pr_err("\n"); + pr_err(" - If minicom is connected to kdmx when kdmx is killed, then minicom will\n"); + pr_err(" not respond to 'CTRL-A Z', which is a normal way to quit minicom.\n"); + pr_err(" Workaround: terminate minicom before killing kdmx.\n"); + pr_err(" [minicom version 2.5]\n"); + pr_err("\n"); + pr_err(" Debugging the serial connection:\n"); + pr_err("\n"); + pr_err(" - It may to useful to view the traffic between kdmx and the serial port.\n"); + pr_err(" An example kdmx invocation to enable this is:\n"); + pr_err(" kdmx -DsS -d -p/dev/ttyUSB0 -b115200 2>kdmx_debug\n"); + pr_err(" Then in another terminal window:\n"); + pr_err(" tail -f kdmx_debug\n"); + pr_err(" '-DsS' enables debug print of the data to and from the serial port\n"); + pr_err(" '2>FILE' redirects the debug data to the FILE.\n"); + pr_err("\n"); +} + + +int debug_opened = 0; /* Has GDB opened up yet? */ +int response_pending = 0; /* waiting for an ACK / NAK ? */ + +void +handle_gdb(void) +{ + char rcv; + int ret_errno; + int old_gdb_fd; + int ret; + char *name; + char *old_name; + + debug_opened = 1; + response_pending = 1; + + rcv = do_read(gdb_fd, &ret_errno); + if (rcv == -1) { + + /* + * <ctrl>C in gdb or the gdb kill command will result in + * reads of the gdb pty returning errno == EIO. + * + * The gdb session for the two cases look like: + * + * ^C + * ^CInterrupted while waiting for the program. + * Give up (and stop debugging it)? (y or n) y + * (gdb) + * + * (gdb) k + * Kill the program being debugged? (y or n) y + * (gdb) + * + */ + + if (ret_errno != EIO) { + pr_err("read() of gdb pty returned unexpected errno %d\n", + ret_errno); + exit(EXIT_FAILURE); + } + + pr_err("read() of gdb pty returned EIO,\n"); + pr_err(" closing and re-opening gdb pty.\n"); + pr_err(" EIO is expected if gdb closed the connection.\n"); + + /* + * The gdb / kgdb (gdb_server) conversation is done. Do not + * divert '-' and '+' from the serial port to gdb. + */ + response_pending = 0; + + old_gdb_fd = gdb_fd; + old_name = ptsname(gdb_fd); + + ret = close(gdb_fd); + if (ret) + perror("close of gdb pty failed"); + + get_pty(&gdb_fd); + if (gdb_fd != old_gdb_fd) { + perror("new gdb pty fd is different than old gdb pty, giving up\n"); + exit(EXIT_FAILURE); + } + + name = ptsname(gdb_fd); + pr_info("%s is slave pty for gdb\n", + name ? name : "ptsname() ERROR"); + + if (strcmp(name, old_name)) + pr_err("WARNING: gdb slave pty path has changed\n"); + else + pr_err("gdb slave pty path is unchanged\n"); + + } else { + do_write(serial_fd, &rcv, sizeof(rcv), 0); + } +} + +void +reset_term(int ret_errno) +{ + int old_term_fd; + int ret; + char *name; + char *old_name; + + /* + * Terminating minicom will result in reads of + * the terminal emulator pty returning errno == EIO. + */ + + if (ret_errno != EIO) { + pr_err("read() of terminal emulator pty returned unexpected errno %d\n", + ret_errno); + exit(EXIT_FAILURE); + } + + pr_err("read() of terminal emulator pty returned EIO,\n"); + pr_err(" closing and re-opening terminal emulator pty.\n"); + pr_err(" EIO is expected if terminal emulator exited.\n"); + + old_term_fd = term_fd; + old_name = ptsname(term_fd); + + ret = close(term_fd); + if (ret) + perror("close of terminal emulator pty failed"); + + get_pty(&term_fd); + if (term_fd != old_term_fd) { + perror("new terminal emulator pty fd is different than old terminal emulator pty, giving up\n"); + exit(EXIT_FAILURE); + } + + name = ptsname(term_fd); + pr_info("%s is slave pty for for terminal emulator\n", + name ? name : "ptsname() ERROR"); + + if (strcmp(name, old_name)) + pr_err("WARNING: terminal emulator slave pty path has changed\n"); + else + pr_err("terminal emulator slave pty path is unchanged\n"); +} + +void +handle_serial(void) +{ + int buf_pos; + int sel_ret; + int ret_errno; + int seen_hash; + fd_set readfds; + char buf[BUFMAX + 1]; + unsigned char run_csum; + unsigned char rcv_csum; + char rcv; + + memset(buf, 0, sizeof(buf)); + + rcv = do_read(serial_fd, &ret_errno); + if (rcv == -1) { + pr_err("read() of serial port returned unexpected errno %d\n", + ret_errno); + exit(EXIT_FAILURE); + } + + /* response to a GDB packet? */ + if (response_pending && (rcv == '+' || rcv == '-')) { + response_pending = 0; + /* Write it out */ + do_write(gdb_fd, &rcv, sizeof(rcv), 0); + + } else if (rcv == '$') { + + /* + * Both console and KGDB traffic are on the same serial + * port. If get a '$' from the the serial port, it may be + * the start of a packet to gdb, stop checking whether the + * console pty is readable until finished processing the + * GDB packet. + * + * If the buffer gets to BUFMAX, assume that it is not a + * GDB packet. + */ + buf[0] = '$'; /* already received */ + buf_pos = 1; + + run_csum = 0; + seen_hash = 0; + while (1) { + struct timeval tv; + /* + * If don't get anything for 0.07 seconds, assume + * it's not a gdb packet. + */ + tv.tv_sec = 0; + tv.tv_usec = 70000; + + FD_ZERO(&readfds); + FD_SET(serial_fd, &readfds); + + sel_ret = select(serial_fd + 1, &readfds, NULL, NULL, &tv); + if (sel_ret == 0) { + /* Timeout, write buffer to terminal emulator */ + do_write(term_fd, buf, buf_pos, 0); + break; + } else if (sel_ret == -1) { + die("select"); + } + + /* + * If have already read BUFMAX characters, + * assume it's not a gdb packet. + */ + if (buf_pos == BUFMAX) { + do_write(term_fd, buf, buf_pos, 0); + break; + } + + /* read one char */ + rcv = do_read(serial_fd, &ret_errno); + if (rcv == -1) { + pr_err("read() of serial port returned unexpected errno %d\n", + ret_errno); + exit(EXIT_FAILURE); + } + buf[buf_pos++] = rcv; + if (rcv == '#') { + seen_hash = 1; + } else if (!seen_hash) { + run_csum += rcv; + } else if (seen_hash == 1) { + rcv_csum = hex(rcv) << 4; + seen_hash = 2; + } else if (seen_hash == 2) { + rcv_csum |= hex(rcv); + seen_hash = 3; + } + + if (seen_hash == 3) { + /* packet completed */ + run_csum %= 256; + if (rcv_csum == run_csum) { + if (debug_opened) + do_write(gdb_fd, buf, buf_pos, 0); + } else { + /* write corrupt packet to terminal */ + do_write(term_fd, buf, buf_pos, 0); + } + seen_hash = 0; + break; + } + } + + } else + /* Just pass this along to the console */ + do_write(term_fd, &rcv, sizeof(rcv), 0); +} + +void +handle_term(void) +{ + static int in_escape; + static char last_cons_rcv; + + int ret_errno; + int drop_char = 0; + char escape_char = '~'; /* using same value as ssh */ + char rcv; + + rcv = do_read(term_fd, &ret_errno); + if (rcv == -1) { + reset_term(ret_errno); + return; + } + + if (in_escape) { + in_escape = 0; + drop_char = 1; + + if (rcv == 'B') { + do_write(serial_fd, NULL, 0, DW_FLAG_BREAK); + } else { + pr_err("invalid escape character: 0x%2x '%c'\n", rcv, + (rcv >= 0x20) && (rcv <= 0x7e) ? rcv : '.'); + } + } + + if ((last_cons_rcv == '\r') && (rcv == escape_char)) { + in_escape = 1; + drop_char = 1; + } + + last_cons_rcv = rcv; + + if (drop_char) + return; + + if ((rcv != 0) || passthru_null_from_term) + do_write(serial_fd, &rcv, sizeof(rcv), 0); +} + +int +main(int argc, char **argv) +{ + int select_nfds; + struct termios termios; + char serial_port_path[SERIAL_PORT_PATHLEN + 1]; + char *name; + fd_set readfds; + + /* default serial port */ + memset(serial_port_path, 0, sizeof(serial_port_path)); + strcpy(serial_port_path, DEFAULT_SERIAL); + + /* parse options */ + while (1) { + int opt; + + optopt = '?'; + + opt = getopt(argc, argv, "?b:dhl:np:vD:"); + + if (opt == -1) + break; + + switch (opt) { + + case 'b': + parse_baud(optarg); + break; + + case 'd': + debug = 1; + break; + + case 'D': + parse_debug(optarg); + break; + + /* case 'h': see "default:" */ + + case 'n': + passthru_null_from_term = 1; + break; + + case 'p': + if (strlen(optarg) > SERIAL_PORT_PATHLEN) { + pr_err("Path length for serial port too long\n"); + exit(EXIT_FAILURE); + } + strcpy(serial_port_path, optarg); + break; + + case 'v': + pr_err("kdmx %s\n", VUFX); + exit(EXIT_SUCCESS); + break; + + case '?': + if (optopt != 0) { + /* + * getopt() reports error as opt == '?' and + * optopt == the unknown argument or argument + * with bad value + */ + pr_err("Use the '-h' argument to print usage information\n"); + exit(EXIT_FAILURE); + } else { + usage(); + exit(EXIT_SUCCESS); + } + break; + + case 'h': + usage(); + exit(EXIT_SUCCESS); + break; + + default: + /* + * getopt() reports error as opt == '?' + * Should only get here if option added to + * getopt(,, opstring) but not added to this case. + */ + pr_err("Use the '-h' argument to print usage information\n"); + exit(EXIT_SUCCESS); + break; + } + + } + + /* print stream label only if multiple streams are printed */ + print_label = print_g + print_s + print_t + print_G + print_S + print_T; + if (print_label < 2) + print_label = 0; + + pr_debug("serial port: %s\n", serial_port_path); + + serial_fd = open(serial_port_path, O_RDWR|O_NDELAY|O_NOCTTY); + if (serial_fd == -1) { + char msg[SERIAL_PORT_PATHLEN + strlen("open of ") + 1]; + memset(msg, 0, sizeof(msg)); + sprintf(msg, "open of %s", serial_port_path); + die(msg); + } + + switch (new_baudrate) { + case B9600: + pr_debug("Initalizing the serial port to 9600 8n1\n"); + break; + case B19200: + pr_debug("Initalizing the serial port to 19200 8n1\n"); + break; + case B38400: + pr_debug("Initalizing the serial port to 38400 8n1\n"); + break; + case B57600: + pr_debug("Initalizing the serial port to 57600 8n1\n"); + break; + case B115200: + pr_debug("Initalizing the serial port to 115200 8n1\n"); + break; + case B230400: + pr_debug("Initalizing the serial port to 230400 8n1\n"); + break; + default: + /* + * parse_baud() was updated without updating this switch + */ + pr_debug("Initalizing the serial port to ???\n"); + break; + } + + /* Get the current infos on the serial port */ + if (tcgetattr(serial_fd, &termios)) + die("tcgetattr() of serial port"); + + /* Modify the speed */ + cfsetispeed(&termios, new_baudrate); + cfsetospeed(&termios, new_baudrate); + + termios.c_iflag = IGNBRK; + termios.c_iflag |= IXON | IXOFF | IXANY; + + termios.c_oflag = 0; + + termios.c_cflag &= ~(PARENB|CSTOPB); + termios.c_cflag &= ~(CSIZE); + termios.c_cflag |= CS8 | CLOCAL | CREAD; + + termios.c_lflag = 0; + + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 5; + + if (tcsetattr(serial_fd, TCSANOW, &termios)) + die("tcsetattr"); + + get_pty(&term_fd); + get_pty(&gdb_fd); + select_nfds = gdb_fd + 1; + + name = ptsname(term_fd); + pr_info("%s is slave pty for for terminal emulator\n", + name ? name : "ptsname() ERROR"); + + name = ptsname(gdb_fd); + pr_info("%s is slave pty for gdb\n", name ? name : "ptsname() ERROR"); + + while (1) { + + FD_ZERO(&readfds); + + FD_SET(gdb_fd, &readfds); + FD_SET(serial_fd, &readfds); + FD_SET(term_fd, &readfds); + + if (select(select_nfds, &readfds, NULL, NULL, NULL) == -1) + die("select"); + + /* Order of handling readable descriptors matters */ + + if (FD_ISSET(serial_fd, &readfds)) + + handle_serial(); + + else if (FD_ISSET(gdb_fd, &readfds)) + + handle_gdb(); + + else if (FD_ISSET(term_fd, &readfds)) + + handle_term(); + } +} |