| /* | 
 |  * 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> | 
 | #include <sys/param.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 | 
 |  | 
 | 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; | 
 |  | 
 | 	*ret_errno = 0; | 
 |  | 
 | 	/* 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("serial port BREAK ioctl()"); | 
 | 		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) { | 
 | 				if (fd == gdb_fd) | 
 | 					perror("gdb pty write()"); | 
 | 				else if (fd == serial_fd) | 
 | 					perror("serial port write()"); | 
 | 				else if (fd == term_fd) | 
 | 					perror("terminal emulator pty write()"); | 
 | 				else | 
 | 					perror("unknown write()"); | 
 | 				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, who 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 (ret_errno) { | 
 |  | 
 | 		/* | 
 | 		 * <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("gdb pty read() unexpected errno %d\n", | 
 | 			       ret_errno); | 
 | 			exit(EXIT_FAILURE); | 
 | 		} | 
 |  | 
 | 		pr_err("gdb pty read(): Input/output error, re-opening pty\n"); | 
 | 		pr_err("  Not an error 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("gdb pty close()"); | 
 |  | 
 | 		get_pty(&gdb_fd); | 
 | 		if (gdb_fd != old_gdb_fd) { | 
 | 			pr_err("new gdb pty fd is different than old gdb pty fd, 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"); | 
 | 		pr_err("\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("terminal emulator pty read(), unexpected errno %d\n", | 
 | 		       ret_errno); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	pr_err("terminal emulator pty read(): Input/output error, re-opening pty\n"); | 
 | 	pr_err("  Not an error if terminal emulator exited\n"); | 
 |  | 
 | 	old_term_fd = term_fd; | 
 | 	old_name = ptsname(term_fd); | 
 |  | 
 | 	ret = close(term_fd); | 
 | 	if (ret) | 
 | 		perror("terminal emulator pty close()"); | 
 |  | 
 | 	get_pty(&term_fd); | 
 | 	if (term_fd != old_term_fd) { | 
 | 		pr_err("new terminal emulator pty fd is different than old terminal emulator pty fd, giving up\n"); | 
 | 		exit(EXIT_FAILURE); | 
 | 	} | 
 |  | 
 | 	name = ptsname(term_fd); | 
 | 	pr_info("%s is slave pty 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"); | 
 | 	pr_err("\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 (ret_errno) { | 
 | 		pr_err("serial port read() [1] 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 (ret_errno) { | 
 | 				pr_err("serial port read() [2] 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 (ret_errno) { | 
 | 		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[MAXPATHLEN]; | 
 | 	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) >= sizeof(serial_port_path)) { | 
 | 				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[strlen("open() of ") + sizeof(serial_port_path) + 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() 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 terminal emulator\n", | 
 | 		name ? name : "ptsname() ERROR"); | 
 |  | 
 | 	name = ptsname(gdb_fd); | 
 | 	pr_info("%s is slave pty for gdb\n", name ? name : "ptsname() ERROR"); | 
 |  | 
 | 	pr_info("\n"); | 
 | 	pr_info("Use <ctrl>C to terminate program\n"); | 
 | 	pr_info("\n"); | 
 |  | 
 | 	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(); | 
 | 	} | 
 | } |