1 /* 2 * Boot a Marvell SoC, with Xmodem over UART0. 3 * supports Kirkwood, Dove, Armada 370, Armada XP 4 * 5 * (c) 2012 Daniel Stodden <daniel.stodden@gmail.com> 6 * 7 * References: marvell.com, "88F6180, 88F6190, 88F6192, and 88F6281 8 * Integrated Controller: Functional Specifications" December 2, 9 * 2008. Chapter 24.2 "BootROM Firmware". 10 */ 11 12 #include <stdlib.h> 13 #include <stdio.h> 14 #include <string.h> 15 #include <stdarg.h> 16 #include <libgen.h> 17 #include <fcntl.h> 18 #include <errno.h> 19 #include <unistd.h> 20 #include <stdint.h> 21 #include <termios.h> 22 #include <sys/mman.h> 23 #include <sys/stat.h> 24 25 #include "kwbimage.h" 26 27 #ifdef __GNUC__ 28 #define PACKED __attribute((packed)) 29 #else 30 #define PACKED 31 #endif 32 33 /* 34 * Marvell BootROM UART Sensing 35 */ 36 37 static unsigned char kwboot_msg_boot[] = { 38 0xBB, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 39 }; 40 41 static unsigned char kwboot_msg_debug[] = { 42 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 43 }; 44 45 /* Defines known to work on Kirkwood */ 46 #define KWBOOT_MSG_REQ_DELAY 10 /* ms */ 47 #define KWBOOT_MSG_RSP_TIMEO 50 /* ms */ 48 49 /* Defines known to work on Armada XP */ 50 #define KWBOOT_MSG_REQ_DELAY_AXP 1000 /* ms */ 51 #define KWBOOT_MSG_RSP_TIMEO_AXP 1000 /* ms */ 52 53 /* 54 * Xmodem Transfers 55 */ 56 57 #define SOH 1 /* sender start of block header */ 58 #define EOT 4 /* sender end of block transfer */ 59 #define ACK 6 /* target block ack */ 60 #define NAK 21 /* target block negative ack */ 61 #define CAN 24 /* target/sender transfer cancellation */ 62 63 struct kwboot_block { 64 uint8_t soh; 65 uint8_t pnum; 66 uint8_t _pnum; 67 uint8_t data[128]; 68 uint8_t csum; 69 } PACKED; 70 71 #define KWBOOT_BLK_RSP_TIMEO 1000 /* ms */ 72 73 static int kwboot_verbose; 74 75 static int msg_req_delay = KWBOOT_MSG_REQ_DELAY; 76 static int msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO; 77 78 static void 79 kwboot_printv(const char *fmt, ...) 80 { 81 va_list ap; 82 83 if (kwboot_verbose) { 84 va_start(ap, fmt); 85 vprintf(fmt, ap); 86 va_end(ap); 87 fflush(stdout); 88 } 89 } 90 91 static void 92 __spinner(void) 93 { 94 const char seq[] = { '-', '\\', '|', '/' }; 95 const int div = 8; 96 static int state, bs; 97 98 if (state % div == 0) { 99 fputc(bs, stdout); 100 fputc(seq[state / div % sizeof(seq)], stdout); 101 fflush(stdout); 102 } 103 104 bs = '\b'; 105 state++; 106 } 107 108 static void 109 kwboot_spinner(void) 110 { 111 if (kwboot_verbose) 112 __spinner(); 113 } 114 115 static void 116 __progress(int pct, char c) 117 { 118 const int width = 70; 119 static const char *nl = ""; 120 static int pos; 121 122 if (pos % width == 0) 123 printf("%s%3d %% [", nl, pct); 124 125 fputc(c, stdout); 126 127 nl = "]\n"; 128 pos++; 129 130 if (pct == 100) { 131 while (pos++ < width) 132 fputc(' ', stdout); 133 fputs(nl, stdout); 134 } 135 136 fflush(stdout); 137 138 } 139 140 static void 141 kwboot_progress(int _pct, char c) 142 { 143 static int pct; 144 145 if (_pct != -1) 146 pct = _pct; 147 148 if (kwboot_verbose) 149 __progress(pct, c); 150 } 151 152 static int 153 kwboot_tty_recv(int fd, void *buf, size_t len, int timeo) 154 { 155 int rc, nfds; 156 fd_set rfds; 157 struct timeval tv; 158 ssize_t n; 159 160 rc = -1; 161 162 FD_ZERO(&rfds); 163 FD_SET(fd, &rfds); 164 165 tv.tv_sec = 0; 166 tv.tv_usec = timeo * 1000; 167 if (tv.tv_usec > 1000000) { 168 tv.tv_sec += tv.tv_usec / 1000000; 169 tv.tv_usec %= 1000000; 170 } 171 172 do { 173 nfds = select(fd + 1, &rfds, NULL, NULL, &tv); 174 if (nfds < 0) 175 goto out; 176 if (!nfds) { 177 errno = ETIMEDOUT; 178 goto out; 179 } 180 181 n = read(fd, buf, len); 182 if (n < 0) 183 goto out; 184 185 buf = (char *)buf + n; 186 len -= n; 187 } while (len > 0); 188 189 rc = 0; 190 out: 191 return rc; 192 } 193 194 static int 195 kwboot_tty_send(int fd, const void *buf, size_t len) 196 { 197 int rc; 198 ssize_t n; 199 200 if (!buf) 201 return 0; 202 203 rc = -1; 204 205 do { 206 n = write(fd, buf, len); 207 if (n < 0) 208 goto out; 209 210 buf = (char *)buf + n; 211 len -= n; 212 } while (len > 0); 213 214 rc = tcdrain(fd); 215 out: 216 return rc; 217 } 218 219 static int 220 kwboot_tty_send_char(int fd, unsigned char c) 221 { 222 return kwboot_tty_send(fd, &c, 1); 223 } 224 225 static speed_t 226 kwboot_tty_speed(int baudrate) 227 { 228 switch (baudrate) { 229 case 115200: 230 return B115200; 231 case 57600: 232 return B57600; 233 case 38400: 234 return B38400; 235 case 19200: 236 return B19200; 237 case 9600: 238 return B9600; 239 } 240 241 return -1; 242 } 243 244 static int 245 kwboot_open_tty(const char *path, speed_t speed) 246 { 247 int rc, fd; 248 struct termios tio; 249 250 rc = -1; 251 252 fd = open(path, O_RDWR|O_NOCTTY|O_NDELAY); 253 if (fd < 0) 254 goto out; 255 256 memset(&tio, 0, sizeof(tio)); 257 258 tio.c_iflag = 0; 259 tio.c_cflag = CREAD|CLOCAL|CS8; 260 261 tio.c_cc[VMIN] = 1; 262 tio.c_cc[VTIME] = 10; 263 264 cfsetospeed(&tio, speed); 265 cfsetispeed(&tio, speed); 266 267 rc = tcsetattr(fd, TCSANOW, &tio); 268 if (rc) 269 goto out; 270 271 rc = fd; 272 out: 273 if (rc < 0) { 274 if (fd >= 0) 275 close(fd); 276 } 277 278 return rc; 279 } 280 281 static int 282 kwboot_bootmsg(int tty, void *msg) 283 { 284 int rc; 285 char c; 286 287 if (msg == NULL) 288 kwboot_printv("Please reboot the target into UART boot mode..."); 289 else 290 kwboot_printv("Sending boot message. Please reboot the target..."); 291 292 do { 293 rc = tcflush(tty, TCIOFLUSH); 294 if (rc) 295 break; 296 297 rc = kwboot_tty_send(tty, msg, 8); 298 if (rc) { 299 usleep(msg_req_delay * 1000); 300 continue; 301 } 302 303 rc = kwboot_tty_recv(tty, &c, 1, msg_rsp_timeo); 304 305 kwboot_spinner(); 306 307 } while (rc || c != NAK); 308 309 kwboot_printv("\n"); 310 311 return rc; 312 } 313 314 static int 315 kwboot_debugmsg(int tty, void *msg) 316 { 317 int rc; 318 319 kwboot_printv("Sending debug message. Please reboot the target..."); 320 321 do { 322 char buf[16]; 323 324 rc = tcflush(tty, TCIOFLUSH); 325 if (rc) 326 break; 327 328 rc = kwboot_tty_send(tty, msg, 8); 329 if (rc) { 330 usleep(msg_req_delay * 1000); 331 continue; 332 } 333 334 rc = kwboot_tty_recv(tty, buf, 16, msg_rsp_timeo); 335 336 kwboot_spinner(); 337 338 } while (rc); 339 340 kwboot_printv("\n"); 341 342 return rc; 343 } 344 345 static int 346 kwboot_xm_makeblock(struct kwboot_block *block, const void *data, 347 size_t size, int pnum) 348 { 349 const size_t blksz = sizeof(block->data); 350 size_t n; 351 int i; 352 353 block->soh = SOH; 354 block->pnum = pnum; 355 block->_pnum = ~block->pnum; 356 357 n = size < blksz ? size : blksz; 358 memcpy(&block->data[0], data, n); 359 memset(&block->data[n], 0, blksz - n); 360 361 block->csum = 0; 362 for (i = 0; i < n; i++) 363 block->csum += block->data[i]; 364 365 return n; 366 } 367 368 static int 369 kwboot_xm_sendblock(int fd, struct kwboot_block *block) 370 { 371 int rc, retries; 372 char c; 373 374 retries = 16; 375 do { 376 rc = kwboot_tty_send(fd, block, sizeof(*block)); 377 if (rc) 378 break; 379 380 do { 381 rc = kwboot_tty_recv(fd, &c, 1, KWBOOT_BLK_RSP_TIMEO); 382 if (rc) 383 break; 384 385 if (c != ACK && c != NAK && c != CAN) 386 printf("%c", c); 387 388 } while (c != ACK && c != NAK && c != CAN); 389 390 if (c != ACK) 391 kwboot_progress(-1, '+'); 392 393 } while (c == NAK && retries-- > 0); 394 395 rc = -1; 396 397 switch (c) { 398 case ACK: 399 rc = 0; 400 break; 401 case NAK: 402 errno = EBADMSG; 403 break; 404 case CAN: 405 errno = ECANCELED; 406 break; 407 default: 408 errno = EPROTO; 409 break; 410 } 411 412 return rc; 413 } 414 415 static int 416 kwboot_xmodem(int tty, const void *_data, size_t size) 417 { 418 const uint8_t *data = _data; 419 int rc, pnum, N, err; 420 421 pnum = 1; 422 N = 0; 423 424 kwboot_printv("Sending boot image...\n"); 425 426 do { 427 struct kwboot_block block; 428 int n; 429 430 n = kwboot_xm_makeblock(&block, 431 data + N, size - N, 432 pnum++); 433 if (n < 0) 434 goto can; 435 436 if (!n) 437 break; 438 439 rc = kwboot_xm_sendblock(tty, &block); 440 if (rc) 441 goto out; 442 443 N += n; 444 kwboot_progress(N * 100 / size, '.'); 445 } while (1); 446 447 rc = kwboot_tty_send_char(tty, EOT); 448 449 out: 450 return rc; 451 452 can: 453 err = errno; 454 kwboot_tty_send_char(tty, CAN); 455 errno = err; 456 goto out; 457 } 458 459 static int 460 kwboot_term_pipe(int in, int out, char *quit, int *s) 461 { 462 ssize_t nin, nout; 463 char _buf[128], *buf = _buf; 464 465 nin = read(in, buf, sizeof(buf)); 466 if (nin < 0) 467 return -1; 468 469 if (quit) { 470 int i; 471 472 for (i = 0; i < nin; i++) { 473 if (*buf == quit[*s]) { 474 (*s)++; 475 if (!quit[*s]) 476 return 0; 477 buf++; 478 nin--; 479 } else 480 while (*s > 0) { 481 nout = write(out, quit, *s); 482 if (nout <= 0) 483 return -1; 484 (*s) -= nout; 485 } 486 } 487 } 488 489 while (nin > 0) { 490 nout = write(out, buf, nin); 491 if (nout <= 0) 492 return -1; 493 nin -= nout; 494 } 495 496 return 0; 497 } 498 499 static int 500 kwboot_terminal(int tty) 501 { 502 int rc, in, s; 503 char *quit = "\34c"; 504 struct termios otio, tio; 505 506 rc = -1; 507 508 in = STDIN_FILENO; 509 if (isatty(in)) { 510 rc = tcgetattr(in, &otio); 511 if (!rc) { 512 tio = otio; 513 cfmakeraw(&tio); 514 rc = tcsetattr(in, TCSANOW, &tio); 515 } 516 if (rc) { 517 perror("tcsetattr"); 518 goto out; 519 } 520 521 kwboot_printv("[Type Ctrl-%c + %c to quit]\r\n", 522 quit[0]|0100, quit[1]); 523 } else 524 in = -1; 525 526 rc = 0; 527 s = 0; 528 529 do { 530 fd_set rfds; 531 int nfds = 0; 532 533 FD_SET(tty, &rfds); 534 nfds = nfds < tty ? tty : nfds; 535 536 if (in >= 0) { 537 FD_SET(in, &rfds); 538 nfds = nfds < in ? in : nfds; 539 } 540 541 nfds = select(nfds + 1, &rfds, NULL, NULL, NULL); 542 if (nfds < 0) 543 break; 544 545 if (FD_ISSET(tty, &rfds)) { 546 rc = kwboot_term_pipe(tty, STDOUT_FILENO, NULL, NULL); 547 if (rc) 548 break; 549 } 550 551 if (FD_ISSET(in, &rfds)) { 552 rc = kwboot_term_pipe(in, tty, quit, &s); 553 if (rc) 554 break; 555 } 556 } while (quit[s] != 0); 557 558 tcsetattr(in, TCSANOW, &otio); 559 out: 560 return rc; 561 } 562 563 static void * 564 kwboot_mmap_image(const char *path, size_t *size, int prot) 565 { 566 int rc, fd, flags; 567 struct stat st; 568 void *img; 569 570 rc = -1; 571 img = NULL; 572 573 fd = open(path, O_RDONLY); 574 if (fd < 0) 575 goto out; 576 577 rc = fstat(fd, &st); 578 if (rc) 579 goto out; 580 581 flags = (prot & PROT_WRITE) ? MAP_PRIVATE : MAP_SHARED; 582 583 img = mmap(NULL, st.st_size, prot, flags, fd, 0); 584 if (img == MAP_FAILED) { 585 img = NULL; 586 goto out; 587 } 588 589 rc = 0; 590 *size = st.st_size; 591 out: 592 if (rc && img) { 593 munmap(img, st.st_size); 594 img = NULL; 595 } 596 if (fd >= 0) 597 close(fd); 598 599 return img; 600 } 601 602 static uint8_t 603 kwboot_img_csum8(void *_data, size_t size) 604 { 605 uint8_t *data = _data, csum; 606 607 for (csum = 0; size-- > 0; data++) 608 csum += *data; 609 610 return csum; 611 } 612 613 static int 614 kwboot_img_patch_hdr(void *img, size_t size) 615 { 616 int rc; 617 bhr_t *hdr; 618 uint8_t csum; 619 const size_t hdrsz = sizeof(*hdr); 620 621 rc = -1; 622 hdr = img; 623 624 if (size < hdrsz) { 625 errno = EINVAL; 626 goto out; 627 } 628 629 csum = kwboot_img_csum8(hdr, hdrsz) - hdr->checkSum; 630 if (csum != hdr->checkSum) { 631 errno = EINVAL; 632 goto out; 633 } 634 635 if (hdr->blockid == IBR_HDR_UART_ID) { 636 rc = 0; 637 goto out; 638 } 639 640 hdr->blockid = IBR_HDR_UART_ID; 641 642 hdr->nandeccmode = IBR_HDR_ECC_DISABLED; 643 hdr->nandpagesize = 0; 644 645 hdr->srcaddr = hdr->ext 646 ? sizeof(struct kwb_header) 647 : sizeof(*hdr); 648 649 hdr->checkSum = kwboot_img_csum8(hdr, hdrsz) - csum; 650 651 rc = 0; 652 out: 653 return rc; 654 } 655 656 static void 657 kwboot_usage(FILE *stream, char *progname) 658 { 659 fprintf(stream, 660 "Usage: %s [-d | -a | -b <image> | -D <image> ] [ -t ] [-B <baud> ] <TTY>\n", 661 progname); 662 fprintf(stream, "\n"); 663 fprintf(stream, 664 " -b <image>: boot <image> with preamble (Kirkwood, Armada 370/XP)\n"); 665 fprintf(stream, " -p: patch <image> to type 0x69 (uart boot)\n"); 666 fprintf(stream, 667 " -D <image>: boot <image> without preamble (Dove)\n"); 668 fprintf(stream, " -d: enter debug mode\n"); 669 fprintf(stream, " -a: use timings for Armada XP\n"); 670 fprintf(stream, "\n"); 671 fprintf(stream, " -t: mini terminal\n"); 672 fprintf(stream, "\n"); 673 fprintf(stream, " -B <baud>: set baud rate\n"); 674 fprintf(stream, "\n"); 675 } 676 677 int 678 main(int argc, char **argv) 679 { 680 const char *ttypath, *imgpath; 681 int rv, rc, tty, term, prot, patch; 682 void *bootmsg; 683 void *debugmsg; 684 void *img; 685 size_t size; 686 speed_t speed; 687 688 rv = 1; 689 tty = -1; 690 bootmsg = NULL; 691 debugmsg = NULL; 692 imgpath = NULL; 693 img = NULL; 694 term = 0; 695 patch = 0; 696 size = 0; 697 speed = B115200; 698 699 kwboot_verbose = isatty(STDOUT_FILENO); 700 701 do { 702 int c = getopt(argc, argv, "hb:ptaB:dD:"); 703 if (c < 0) 704 break; 705 706 switch (c) { 707 case 'b': 708 bootmsg = kwboot_msg_boot; 709 imgpath = optarg; 710 break; 711 712 case 'D': 713 bootmsg = NULL; 714 imgpath = optarg; 715 break; 716 717 case 'd': 718 debugmsg = kwboot_msg_debug; 719 break; 720 721 case 'p': 722 patch = 1; 723 break; 724 725 case 't': 726 term = 1; 727 break; 728 729 case 'a': 730 msg_req_delay = KWBOOT_MSG_REQ_DELAY_AXP; 731 msg_rsp_timeo = KWBOOT_MSG_RSP_TIMEO_AXP; 732 break; 733 734 case 'B': 735 speed = kwboot_tty_speed(atoi(optarg)); 736 if (speed == -1) 737 goto usage; 738 break; 739 740 case 'h': 741 rv = 0; 742 default: 743 goto usage; 744 } 745 } while (1); 746 747 if (!bootmsg && !term && !debugmsg) 748 goto usage; 749 750 if (patch && !imgpath) 751 goto usage; 752 753 if (argc - optind < 1) 754 goto usage; 755 756 ttypath = argv[optind++]; 757 758 tty = kwboot_open_tty(ttypath, speed); 759 if (tty < 0) { 760 perror(ttypath); 761 goto out; 762 } 763 764 if (imgpath) { 765 prot = PROT_READ | (patch ? PROT_WRITE : 0); 766 767 img = kwboot_mmap_image(imgpath, &size, prot); 768 if (!img) { 769 perror(imgpath); 770 goto out; 771 } 772 } 773 774 if (patch) { 775 rc = kwboot_img_patch_hdr(img, size); 776 if (rc) { 777 fprintf(stderr, "%s: Invalid image.\n", imgpath); 778 goto out; 779 } 780 } 781 782 if (debugmsg) { 783 rc = kwboot_debugmsg(tty, debugmsg); 784 if (rc) { 785 perror("debugmsg"); 786 goto out; 787 } 788 } else { 789 rc = kwboot_bootmsg(tty, bootmsg); 790 if (rc) { 791 perror("bootmsg"); 792 goto out; 793 } 794 } 795 796 if (img) { 797 rc = kwboot_xmodem(tty, img, size); 798 if (rc) { 799 perror("xmodem"); 800 goto out; 801 } 802 } 803 804 if (term) { 805 rc = kwboot_terminal(tty); 806 if (rc && !(errno == EINTR)) { 807 perror("terminal"); 808 goto out; 809 } 810 } 811 812 rv = 0; 813 out: 814 if (tty >= 0) 815 close(tty); 816 817 if (img) 818 munmap(img, size); 819 820 return rv; 821 822 usage: 823 kwboot_usage(rv ? stderr : stdout, basename(argv[0])); 824 goto out; 825 } 826