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 }