sconsole

bare bones linux serial console tool
git clone http://frotz.net/git/sconsole.git
Log | Files | Refs

sconsole.c (10662B)


      1 // Copyright 2005-2022, Brian Swetland <swetland@frotz.net>
      2 // Licensed under the Apache License, Version 2.0
      3 //
      4 // sconsole - cheap serial console (for xterm, etc)
      5 
      6 #include <stdio.h>
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <unistd.h>
     10 #include <errno.h>
     11 #include <fcntl.h>
     12 
     13 #include <termios.h>
     14 #include <signal.h>
     15 #include <poll.h>
     16 #include <unistd.h>
     17 
     18 #include <sys/time.h>
     19 #include <sys/ioctl.h>
     20 #include <sys/fcntl.h>
     21 #include <sys/types.h>
     22 
     23 #include "litex/sfl.h"
     24 #include "litex/crc16.c"
     25 
     26 #ifdef __APPLE__
     27 #include "apple.h"
     28 #endif
     29 
     30 void *load_file(const char *fn, size_t *_sz) {
     31 	int fd;
     32 	off_t sz;
     33 	void *data = NULL;
     34 	fd = open(fn, O_RDONLY);
     35 	if (fd < 0) goto fail;
     36 	sz = lseek(fd, 0, SEEK_END);
     37 	if (sz < 0) goto fail;
     38 	if (lseek(fd, 0, SEEK_SET)) goto fail;
     39 	if ((data = malloc(sz + 4)) == NULL) goto fail;
     40 	if (read(fd, data, sz) != sz) goto fail;
     41 	*_sz = sz;
     42 	return data;
     43 fail:
     44 	if (data) free(data);
     45 	if (fd >= 0) close(fd);
     46 	return NULL;
     47 }
     48 
     49 typedef struct bootinfo {
     50 	struct bootinfo* next;	
     51 	const char* name;
     52 	unsigned addr;
     53 } bootinfo_t;
     54 
     55 static struct termios tio_save;
     56 
     57 static void stdin_raw_init(void) {
     58 	struct termios tio;
     59 
     60 	if (tcgetattr(0, &tio))
     61 		return;
     62 	if (tcgetattr(0, &tio_save))
     63 		return;
     64 
     65 	/* disable CANON, ECHO*, etc */
     66 	tio.c_lflag = 0;
     67 
     68 	/* no timeout but request at least one character per read */
     69 	tio.c_cc[VTIME] = 0;
     70 	tio.c_cc[VMIN] = 1;
     71 
     72 	tcsetattr(0, TCSANOW, &tio);
     73 	tcflush(0, TCIFLUSH);
     74 }
     75 
     76 static void stdin_raw_restore(void) {
     77 	tcsetattr(0, TCSANOW, &tio_save);
     78 	tcflush(0, TCIFLUSH);
     79 }
     80 
     81 void oops(int x) {
     82 	char *msg = "\n[ killed by signal ]\n";
     83 	write(2, msg, strlen(msg));
     84 	stdin_raw_restore();
     85 	exit(1);
     86 }
     87 
     88 int text2speed(const char *s) {
     89 	char *e;
     90 	unsigned n = strtoul(s, &e, 10);
     91 	switch (*e) {
     92 	case 'k':
     93 	case 'K':
     94 		n *= 1000;
     95 		break;
     96 	case 'm':
     97 	case 'M':
     98 		n *= 1000000;
     99 		break;
    100 	}
    101 	switch (n) {
    102 	case 4000000:	return B4000000;
    103 	case 3500000:	return B3500000;
    104 	case 3000000:	return B3000000;
    105 	case 2500000:	return B2500000;
    106 	case 2000000:	return B2000000;
    107 	case 1500000:	return B1500000;
    108 	case 1152000:	return B1152000;
    109 	case 1000000:	return B1000000;
    110 	case 921600:	return B921600;
    111 	case 115200:	return B115200;
    112 	case 57600:	return B57600;
    113 	case 38400:	return B38400;
    114 	case 19200:	return B19200;
    115 	case 9600:	return B9600;
    116 	default:
    117 		fprintf(stderr, "unsupported baud rate %dbps\n", n);
    118 		return B115200;
    119 	}
    120 }
    121 
    122 int openserial(const char *device, int speed) {
    123 	struct termios tio;
    124 	int fd;
    125 	int fl;
    126 
    127 	/* open the serial port non-blocking to avoid waiting for cd */
    128 	if ((fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) {
    129 		return -1;
    130 	}
    131 
    132 	/* then switch the fd to blocking */
    133 	if ((fl = fcntl(fd, F_GETFL, 0)) < 0) {
    134 		close(fd);
    135 		return -1;
    136 	}
    137 	if ((fl = fcntl(fd, F_SETFL,  fl & ~O_NDELAY)) < 0) {
    138 		close(fd);
    139 		return -1;
    140 	}
    141 
    142 	if (tcgetattr(fd, &tio)) {
    143 		memset(&tio, 0, sizeof(tio));
    144 	}
    145 
    146 	tio.c_cflag = B57600 | CS8 | CLOCAL | CREAD;
    147 	tio.c_ispeed = B57600;
    148 	tio.c_ospeed = B57600;
    149 	tio.c_iflag = IGNPAR;
    150 	tio.c_oflag &= ~ONLCR;
    151 	tio.c_lflag = 0; /* turn of CANON, ECHO*, etc */
    152 	tio.c_cc[VTIME] = 0;
    153 	tio.c_cc[VMIN] = 1;
    154 	tcsetattr(fd, TCSANOW, &tio);
    155 	tcflush(fd, TCIFLUSH);
    156 
    157 #ifdef __APPLE__
    158 	tio.c_cflag =  CS8 | CLOCAL | CREAD;
    159 #else
    160 	tio.c_cflag =  speed | CS8 | CLOCAL | CREAD;
    161 #endif
    162 	tio.c_ispeed = speed;
    163 	tio.c_ospeed = speed;
    164 
    165 	tcsetattr(fd, TCSANOW, &tio);
    166 
    167 #ifdef __APPLE__
    168 	if (speed >= 921600) {
    169 		if (ioctl(fd, IOSSIOSPEED, &speed) == -1) {
    170 			fprintf(stderr, "error IOSSIOSPEED ioctl: %s(%d)\n",
    171 				strerror(errno), errno);
    172 		}
    173 	}
    174 #endif
    175 
    176 	tcflush(fd, TCIFLUSH);
    177 	return fd;
    178 }
    179 
    180 static unsigned char valid[256];
    181 
    182 #define STATE_IDLE    0
    183 #define STATE_PREFIX  1
    184 #define STATE_COMMAND 2
    185 
    186 void usage(void) {
    187 	fprintf(stderr,
    188 		"usage: sconsole [ <flag> ]* [ <device> [ <speed> ] ]\n"
    189 		"\n"
    190 		"flags:   -t    transparent mode (don't filter unprintables, etc)\n"
    191 		"         -l    log to console.log (or -lsomeotherlogfile)\n"
    192 		"         -c    convert NL to CR on transmit\n"
    193 		"         -x    display characters in hex\n"
    194 		"         -b    serialboot file at addr (-bfilename@1000)\n"
    195 		"\n"
    196 		"default device /dev/ttyUSB and speed 115200\n"
    197 		);
    198 }
    199 
    200 int sfd = -1;
    201 
    202 int logfd = -1;
    203 int hexmode = 0;
    204 int decode_swv = 0;
    205 
    206 unsigned bootmatch = 0;
    207 bootinfo_t* bootlist = NULL;
    208 bootinfo_t* bootlast = NULL; // end of list
    209 
    210 bootinfo_t* bootitem = NULL;
    211 unsigned char* bootdata;
    212 unsigned char* bootptr;
    213 size_t bootsize = 0;
    214 unsigned bootaddr = 0;
    215 unsigned bootfail = 0;
    216 
    217 struct sfl_frame frame;
    218 
    219 void send_frame(unsigned cmd, unsigned param, void* data, unsigned len) {
    220 	frame.cmd = cmd;
    221 	if (param == SFL_CMD_ABORT) {
    222 		frame.payload_length = 0;
    223 	} else {
    224 		frame.payload_length = len + 4;
    225 		frame.payload[0] = param >> 24;
    226 		frame.payload[1] = param >> 16;
    227 		frame.payload[2] = param >> 8;
    228 		frame.payload[3] = param;
    229 		memcpy(frame.payload + 4, data, len);
    230 	}
    231 	unsigned crc = crc16(&frame.cmd, frame.payload_length + 1);
    232 	frame.crc[0] = crc >> 8;
    233 	frame.crc[1] = crc;
    234 	write(sfd, &frame, frame.payload_length + 4);
    235 	bootfail = 0;
    236 }
    237 
    238 void resend_frame(void) {
    239 	write(sfd, &frame, frame.payload_length + 4);
    240 	bootfail++;
    241 }
    242 
    243 int boot_start(bootinfo_t* item);
    244 
    245 int boot_next(int x) {
    246 	if ((x > ' ') && (x < 127)) {
    247 		fprintf(stderr, "%c", x);
    248 	} else {
    249 		fprintf(stderr, ".");
    250 	}
    251 	switch (x) {
    252 	case SFL_ACK_SUCCESS: {
    253 		if (bootsize == 0) {
    254 			free(bootdata);
    255 			if (bootitem->next) {
    256 				return boot_start(bootitem->next);
    257 			}
    258 			fprintf(stderr, "\n[ jump to 0x%08x ]\n", bootlist->addr);
    259 			send_frame(SFL_CMD_JUMP, bootlist->addr, NULL, 0);
    260 			bootitem = NULL;
    261 			return -1;
    262 		}
    263 		unsigned xfer = bootsize > 64 ? 64 : bootsize;
    264 		send_frame(SFL_CMD_LOAD, bootaddr, bootptr, xfer);
    265 		bootptr += xfer;
    266 		bootaddr += xfer;
    267 		bootsize -= xfer;
    268 		return 250;
    269 	}
    270 	case -1: // timeout
    271 	case SFL_ACK_CRCERROR:
    272 	case SFL_ACK_UNKNOWN:
    273 	case SFL_ACK_ERROR:
    274 		if (bootfail == 10) {
    275 			fprintf(stderr, "\n[ serial boot failed ]\n");
    276 			free(bootdata);
    277 			bootitem = NULL;
    278 			send_frame(SFL_CMD_ABORT, 0, NULL, 0);
    279 			return 250;
    280 		}
    281 		resend_frame();
    282 	}
    283 }
    284 
    285 int boot_start(bootinfo_t* item) {
    286 	bootitem = item;
    287 	bootdata = load_file(bootitem->name, &bootsize);
    288 	bootaddr = bootitem->addr;
    289 	if (bootdata == NULL) {
    290 		fprintf(stderr, "\n[ could not load '%s' ]\n", bootitem->name);
    291 		bootitem = NULL;
    292 		send_frame(SFL_CMD_ABORT, 0, NULL, 0);
    293 		return -1;
    294 	} else {
    295 		fprintf(stderr, "\n[ sending '%s' to 0x%08x ]\n",
    296 			bootitem->name, bootitem->addr);
    297 		bootptr = bootdata;
    298 		return boot_next(SFL_ACK_SUCCESS);
    299 	}
    300 }
    301 
    302 int serial_rx(int x) {
    303 	if (bootitem) {
    304 		return boot_next(x);
    305 	}
    306 	if (bootlist) {
    307 		// if we can serialboot, listen for magic to start
    308 		if (SFL_MAGIC_REQ[bootmatch] == x) {
    309 			bootmatch++;
    310 			if (bootmatch == SFL_MAGIC_LEN) {
    311 				fprintf(stderr, "\n[ serial boot requested ]\n");
    312 				bootmatch = 0;
    313 				write(sfd, SFL_MAGIC_ACK, SFL_MAGIC_LEN);
    314 				return boot_start(bootlist);
    315 			}
    316 		} else {
    317 			bootmatch = 0;
    318 		}
    319 	}
    320 	if (hexmode) {
    321 		char hex[4];
    322 		sprintf(hex, "%02x ", x);
    323 		write(1, hex, 3);
    324 		if (logfd != -1) {
    325 			write(logfd, &x, 1);
    326 		}
    327 #if WITH_SWV
    328 	} else if (decode_swv) {
    329 		handle_swv(&swv, x);
    330 #endif
    331 	} else {
    332 		unsigned char c = x;
    333 		if (!valid[x]) {
    334 			c = '.';
    335 		}
    336 		if (valid[x] != -1) {
    337 			write(1, &c, 1);
    338 			if (logfd != -1) {
    339 				write(logfd, &c, 1);
    340 			}
    341 		}
    342 	}
    343 	return -1;
    344 }
    345 
    346 unsigned char ESC = 27;
    347 static int escape = 0;
    348 int map_nl_to_cr = 0;
    349 
    350 void console_rx(int x) {
    351 	switch (escape) {
    352 	case 0:
    353 		if (x == 27) {
    354 			escape = 1;
    355 		} else {
    356 			if ((x == '\n') && map_nl_to_cr) {
    357 				x = '\r';
    358 				write(sfd, &x, 1);
    359 			} else {
    360 				write(sfd, &x, 1);
    361 			}
    362 		}
    363 		break;
    364 	case 1:
    365 		if (x == 27) {
    366 			escape = 2;
    367 			fprintf(stderr, "\n[ (b)reak? e(x)it? ]\n");
    368 		} else {
    369 			escape = 0;
    370 			write(sfd, &ESC, 1);
    371 			write(sfd, &x, 1);
    372 		}
    373 		break;
    374 	case 2:
    375 		escape = 0;
    376 		switch (x) {
    377 		case 27:
    378 			write(sfd, &x, 1);
    379 			break;
    380 		case 'b':
    381 			fprintf(stderr, "[ break ]\n");
    382 			tcsendbreak(sfd, 0);
    383 			break;
    384 		case 'x':
    385 			fprintf(stderr, "[ exit ]\n");
    386 			stdin_raw_restore();
    387 			exit(0);
    388 		default:
    389 			fprintf(stderr, "[ huh? ]\n");
    390 		break;
    391 		}
    392 	}
    393 }
    394 
    395 int main(int argc, char *argv[]) {
    396 	struct pollfd fds[2];
    397 	int speed = B115200;
    398 #ifdef __APPLE__
    399 	const char *device = "/dev/tty.usbserial";
    400 #else
    401 	const char *device = "/dev/ttyUSB0";
    402 #endif
    403 	const char *logfile = "console.log";
    404 	int timeout;
    405 	int n, r;
    406 
    407 	for (n = ' '; n < 127; n++)
    408 		valid[n] = 1;
    409 
    410 	valid[7] = -1; /* bell */
    411 	valid[8] = 1; /* backspace */
    412 	valid[9] = 1; /* tab */
    413 	valid[10] = 1; /* newline */
    414 	valid[13] = 1; /* carriage return */
    415 
    416 	while ((argc > 1) && (argv[1][0] == '-')) {
    417 		switch (argv[1][1]) {
    418 		case 'b': {
    419 			bootinfo_t *item = malloc(sizeof(bootinfo_t));
    420 			item->name = &argv[1][2];
    421 			item->next = NULL;
    422 			char *tmp = strchr(item->name, '@');
    423 			if (tmp) {
    424 				item->addr = strtoul(tmp + 1, NULL, 16);
    425 				*tmp = 0;
    426 			} else {
    427 				item->addr = 0x40000000;
    428 			}
    429 			if (bootlast) {
    430 				bootlast->next = item;
    431 			} else {
    432 				bootlist = item;
    433 			}
    434 			bootlast = item;
    435 			break;
    436 		}
    437 		case 'x':
    438 			hexmode = 1;
    439 			break;
    440 		case 't':
    441 			/* transparent mode */
    442 			for (n = 0; n < 256; n++)
    443 				valid[n] = 1;
    444 			break;
    445 		case 'l': /* log */
    446 			if (argv[1][2])
    447 				logfile = &argv[1][2];
    448 			logfd = open(logfile, O_CREAT | O_WRONLY, 0644);
    449 			break;
    450 		case 'c':
    451 			/* carriage return mode -- map \n to \r */
    452 			map_nl_to_cr = 1;
    453 			break;
    454 		case 'h':
    455 			usage();
    456 			return 0;
    457 #if WITH_SWV
    458 		case 's':
    459 			decode_swv = 1;
    460 			break;
    461 #endif
    462 		default:
    463 			fprintf(stderr, "error: unknown option %s\n\n", argv[1]);
    464 			usage();
    465 			return 1;
    466 		}
    467 		argv++;
    468 		argc--;
    469 	}
    470 
    471 	if (argc > 1) {
    472 		device = argv[1];
    473 		argc--;
    474 		argv++;
    475 	}
    476 
    477 	if (argc > 1) {
    478 		speed = text2speed(argv[1]);
    479 		fprintf(stderr, "SPEED: %s %08x\n", argv[1], speed);
    480 		argc--;
    481 		argv++;
    482 	}
    483 
    484 	sfd = openserial(device, speed);
    485 	if (sfd < 0) {
    486 		fprintf(stderr, "error opening '%s': %s\n",
    487 			device, strerror(errno));
    488 		return -1;
    489 	}
    490 
    491 	stdin_raw_init();
    492 	signal(SIGINT, oops);
    493 
    494 	fds[0].fd = 0;
    495 	fds[0].events = POLLIN;
    496 
    497 	fds[1].fd = sfd;
    498 	fds[1].events = POLLIN;
    499 
    500 	fprintf(stderr, "[ %s ]\n", device);
    501 
    502 	timeout = -1;
    503 	for (;;) {
    504 		unsigned char x;
    505 
    506 		r = poll(fds, 2, timeout);
    507 		if (r < 1 ) {
    508 			continue;
    509 		} else if (r == 0) {
    510 			timeout = serial_rx(-1);
    511 		}
    512 		if (fds[0].revents & (POLLERR | POLLHUP)) {
    513 			fprintf(stderr, "\n[ stdin port closed ]\n");
    514 			break;
    515 		}
    516 		if (fds[1].revents & (POLLERR | POLLHUP)) {
    517 			fprintf(stderr, "\n[ serial port closed ]\n");
    518 			break;
    519 		}
    520 		if ((fds[0].revents & POLLIN) && (read(0, &x, 1) == 1)) {
    521 			console_rx(x);
    522 		}
    523 		if ((fds[1].revents & POLLIN) && (read(sfd, &x, 1) == 1)) {
    524 			timeout = serial_rx(x);
    525 		}
    526 	}
    527 
    528 	stdin_raw_restore();
    529 	return 0;
    530 }