summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFrank Rowand <frank.rowand@sonymobile.com>2014-09-04 21:17:07 -0700
committerJason Wessel <jason.wessel@windriver.com>2014-09-11 17:25:24 -0500
commit36fff68356fee861eaccab50cfef1efd7dac92ef (patch)
treebd58a0561ce1ccc6b56cbfd0346ceef0693e2201
parente6c9d3d996bd55e7ab14dbd74deb7841e0c3a4f1 (diff)
downloadagent-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.c937
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();
+ }
+}