1 // SPDX-License-Identifier: eCos-2.0 2 /* 3 *========================================================================== 4 * 5 * xyzModem.c 6 * 7 * RedBoot stream handler for xyzModem protocol 8 * 9 *========================================================================== 10 *#####DESCRIPTIONBEGIN#### 11 * 12 * Author(s): gthomas 13 * Contributors: gthomas, tsmith, Yoshinori Sato 14 * Date: 2000-07-14 15 * Purpose: 16 * Description: 17 * 18 * This code is part of RedBoot (tm). 19 * 20 *####DESCRIPTIONEND#### 21 * 22 *========================================================================== 23 */ 24 #include <common.h> 25 #include <xyzModem.h> 26 #include <stdarg.h> 27 #include <u-boot/crc.h> 28 #include <watchdog.h> 29 30 /* Assumption - run xyzModem protocol over the console port */ 31 32 /* Values magic to the protocol */ 33 #define SOH 0x01 34 #define STX 0x02 35 #define EOT 0x04 36 #define ACK 0x06 37 #define BSP 0x08 38 #define NAK 0x15 39 #define CAN 0x18 40 #define EOF 0x1A /* ^Z for DOS officionados */ 41 42 /* Data & state local to the protocol */ 43 static struct 44 { 45 int *__chan; 46 unsigned char pkt[1024], *bufp; 47 unsigned char blk, cblk, crc1, crc2; 48 unsigned char next_blk; /* Expected block */ 49 int len, mode, total_retries; 50 int total_SOH, total_STX, total_CAN; 51 bool crc_mode, at_eof, tx_ack; 52 unsigned long file_length, read_length; 53 } xyz; 54 55 #define xyzModem_CHAR_TIMEOUT 2000 /* 2 seconds */ 56 #define xyzModem_MAX_RETRIES 20 57 #define xyzModem_MAX_RETRIES_WITH_CRC 10 58 #define xyzModem_CAN_COUNT 3 /* Wait for 3 CAN before quitting */ 59 60 61 typedef int cyg_int32; 62 static int 63 CYGACC_COMM_IF_GETC_TIMEOUT (char chan, char *c) 64 { 65 66 ulong now = get_timer(0); 67 WATCHDOG_RESET(); 68 while (!tstc ()) 69 { 70 if (get_timer(now) > xyzModem_CHAR_TIMEOUT) 71 break; 72 } 73 if (tstc ()) 74 { 75 *c = getc (); 76 return 1; 77 } 78 return 0; 79 } 80 81 static void 82 CYGACC_COMM_IF_PUTC (char x, char y) 83 { 84 putc (y); 85 } 86 87 /* Validate a hex character */ 88 __inline__ static bool 89 _is_hex (char c) 90 { 91 return (((c >= '0') && (c <= '9')) || 92 ((c >= 'A') && (c <= 'F')) || ((c >= 'a') && (c <= 'f'))); 93 } 94 95 /* Convert a single hex nibble */ 96 __inline__ static int 97 _from_hex (char c) 98 { 99 int ret = 0; 100 101 if ((c >= '0') && (c <= '9')) 102 { 103 ret = (c - '0'); 104 } 105 else if ((c >= 'a') && (c <= 'f')) 106 { 107 ret = (c - 'a' + 0x0a); 108 } 109 else if ((c >= 'A') && (c <= 'F')) 110 { 111 ret = (c - 'A' + 0x0A); 112 } 113 return ret; 114 } 115 116 /* Convert a character to lower case */ 117 __inline__ static char 118 _tolower (char c) 119 { 120 if ((c >= 'A') && (c <= 'Z')) 121 { 122 c = (c - 'A') + 'a'; 123 } 124 return c; 125 } 126 127 /* Parse (scan) a number */ 128 static bool 129 parse_num (char *s, unsigned long *val, char **es, char *delim) 130 { 131 bool first = true; 132 int radix = 10; 133 char c; 134 unsigned long result = 0; 135 int digit; 136 137 while (*s == ' ') 138 s++; 139 while (*s) 140 { 141 if (first && (s[0] == '0') && (_tolower (s[1]) == 'x')) 142 { 143 radix = 16; 144 s += 2; 145 } 146 first = false; 147 c = *s++; 148 if (_is_hex (c) && ((digit = _from_hex (c)) < radix)) 149 { 150 /* Valid digit */ 151 result = (result * radix) + digit; 152 } 153 else 154 { 155 if (delim != (char *) 0) 156 { 157 /* See if this character is one of the delimiters */ 158 char *dp = delim; 159 while (*dp && (c != *dp)) 160 dp++; 161 if (*dp) 162 break; /* Found a good delimiter */ 163 } 164 return false; /* Malformatted number */ 165 } 166 } 167 *val = result; 168 if (es != (char **) 0) 169 { 170 *es = s; 171 } 172 return true; 173 } 174 175 176 #if defined(DEBUG) && !defined(CONFIG_USE_TINY_PRINTF) 177 /* 178 * Note: this debug setup works by storing the strings in a fixed buffer 179 */ 180 static char zm_debug_buf[8192]; 181 static char *zm_out = zm_debug_buf; 182 static char *zm_out_start = zm_debug_buf; 183 184 static int 185 zm_dprintf(char *fmt, ...) 186 { 187 int len; 188 va_list args; 189 190 va_start(args, fmt); 191 len = diag_vsprintf(zm_out, fmt, args); 192 va_end(args); 193 zm_out += len; 194 return len; 195 } 196 197 static void 198 zm_flush (void) 199 { 200 zm_out = zm_out_start; 201 } 202 203 static void 204 zm_dump_buf (void *buf, int len) 205 { 206 207 } 208 209 static unsigned char zm_buf[2048]; 210 static unsigned char *zm_bp; 211 212 static void 213 zm_new (void) 214 { 215 zm_bp = zm_buf; 216 } 217 218 static void 219 zm_save (unsigned char c) 220 { 221 *zm_bp++ = c; 222 } 223 224 static void 225 zm_dump (int line) 226 { 227 zm_dprintf ("Packet at line: %d\n", line); 228 zm_dump_buf (zm_buf, zm_bp - zm_buf); 229 } 230 231 #define ZM_DEBUG(x) x 232 #else 233 #define ZM_DEBUG(x) 234 #endif 235 236 /* Wait for the line to go idle */ 237 static void 238 xyzModem_flush (void) 239 { 240 int res; 241 char c; 242 while (true) 243 { 244 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 245 if (!res) 246 return; 247 } 248 } 249 250 static int 251 xyzModem_get_hdr (void) 252 { 253 char c; 254 int res; 255 bool hdr_found = false; 256 int i, can_total, hdr_chars; 257 unsigned short cksum; 258 259 ZM_DEBUG (zm_new ()); 260 /* Find the start of a header */ 261 can_total = 0; 262 hdr_chars = 0; 263 264 if (xyz.tx_ack) 265 { 266 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 267 xyz.tx_ack = false; 268 } 269 while (!hdr_found) 270 { 271 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 272 ZM_DEBUG (zm_save (c)); 273 if (res) 274 { 275 hdr_chars++; 276 switch (c) 277 { 278 case SOH: 279 xyz.total_SOH++; 280 case STX: 281 if (c == STX) 282 xyz.total_STX++; 283 hdr_found = true; 284 break; 285 case CAN: 286 xyz.total_CAN++; 287 ZM_DEBUG (zm_dump (__LINE__)); 288 if (++can_total == xyzModem_CAN_COUNT) 289 { 290 return xyzModem_cancel; 291 } 292 else 293 { 294 /* Wait for multiple CAN to avoid early quits */ 295 break; 296 } 297 case EOT: 298 /* EOT only supported if no noise */ 299 if (hdr_chars == 1) 300 { 301 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 302 ZM_DEBUG (zm_dprintf ("ACK on EOT #%d\n", __LINE__)); 303 ZM_DEBUG (zm_dump (__LINE__)); 304 return xyzModem_eof; 305 } 306 default: 307 /* Ignore, waiting for start of header */ 308 ; 309 } 310 } 311 else 312 { 313 /* Data stream timed out */ 314 xyzModem_flush (); /* Toss any current input */ 315 ZM_DEBUG (zm_dump (__LINE__)); 316 CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000); 317 return xyzModem_timeout; 318 } 319 } 320 321 /* Header found, now read the data */ 322 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.blk); 323 ZM_DEBUG (zm_save (xyz.blk)); 324 if (!res) 325 { 326 ZM_DEBUG (zm_dump (__LINE__)); 327 return xyzModem_timeout; 328 } 329 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.cblk); 330 ZM_DEBUG (zm_save (xyz.cblk)); 331 if (!res) 332 { 333 ZM_DEBUG (zm_dump (__LINE__)); 334 return xyzModem_timeout; 335 } 336 xyz.len = (c == SOH) ? 128 : 1024; 337 xyz.bufp = xyz.pkt; 338 for (i = 0; i < xyz.len; i++) 339 { 340 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 341 ZM_DEBUG (zm_save (c)); 342 if (res) 343 { 344 xyz.pkt[i] = c; 345 } 346 else 347 { 348 ZM_DEBUG (zm_dump (__LINE__)); 349 return xyzModem_timeout; 350 } 351 } 352 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc1); 353 ZM_DEBUG (zm_save (xyz.crc1)); 354 if (!res) 355 { 356 ZM_DEBUG (zm_dump (__LINE__)); 357 return xyzModem_timeout; 358 } 359 if (xyz.crc_mode) 360 { 361 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc2); 362 ZM_DEBUG (zm_save (xyz.crc2)); 363 if (!res) 364 { 365 ZM_DEBUG (zm_dump (__LINE__)); 366 return xyzModem_timeout; 367 } 368 } 369 ZM_DEBUG (zm_dump (__LINE__)); 370 /* Validate the message */ 371 if ((xyz.blk ^ xyz.cblk) != (unsigned char) 0xFF) 372 { 373 ZM_DEBUG (zm_dprintf 374 ("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk, 375 (xyz.blk ^ xyz.cblk))); 376 ZM_DEBUG (zm_dump_buf (xyz.pkt, xyz.len)); 377 xyzModem_flush (); 378 return xyzModem_frame; 379 } 380 /* Verify checksum/CRC */ 381 if (xyz.crc_mode) 382 { 383 cksum = crc16_ccitt(0, xyz.pkt, xyz.len); 384 if (cksum != ((xyz.crc1 << 8) | xyz.crc2)) 385 { 386 ZM_DEBUG (zm_dprintf ("CRC error - recvd: %02x%02x, computed: %x\n", 387 xyz.crc1, xyz.crc2, cksum & 0xFFFF)); 388 return xyzModem_cksum; 389 } 390 } 391 else 392 { 393 cksum = 0; 394 for (i = 0; i < xyz.len; i++) 395 { 396 cksum += xyz.pkt[i]; 397 } 398 if (xyz.crc1 != (cksum & 0xFF)) 399 { 400 ZM_DEBUG (zm_dprintf 401 ("Checksum error - recvd: %x, computed: %x\n", xyz.crc1, 402 cksum & 0xFF)); 403 return xyzModem_cksum; 404 } 405 } 406 /* If we get here, the message passes [structural] muster */ 407 return 0; 408 } 409 410 int 411 xyzModem_stream_open (connection_info_t * info, int *err) 412 { 413 int stat = 0; 414 int retries = xyzModem_MAX_RETRIES; 415 int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC; 416 417 /* ZM_DEBUG(zm_out = zm_out_start); */ 418 #ifdef xyzModem_zmodem 419 if (info->mode == xyzModem_zmodem) 420 { 421 *err = xyzModem_noZmodem; 422 return -1; 423 } 424 #endif 425 426 /* TODO: CHECK ! */ 427 int dummy = 0; 428 xyz.__chan = &dummy; 429 xyz.len = 0; 430 xyz.crc_mode = true; 431 xyz.at_eof = false; 432 xyz.tx_ack = false; 433 xyz.mode = info->mode; 434 xyz.total_retries = 0; 435 xyz.total_SOH = 0; 436 xyz.total_STX = 0; 437 xyz.total_CAN = 0; 438 xyz.read_length = 0; 439 xyz.file_length = 0; 440 441 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 442 443 if (xyz.mode == xyzModem_xmodem) 444 { 445 /* X-modem doesn't have an information header - exit here */ 446 xyz.next_blk = 1; 447 return 0; 448 } 449 450 while (retries-- > 0) 451 { 452 stat = xyzModem_get_hdr (); 453 if (stat == 0) 454 { 455 /* Y-modem file information header */ 456 if (xyz.blk == 0) 457 { 458 /* skip filename */ 459 while (*xyz.bufp++); 460 /* get the length */ 461 parse_num ((char *) xyz.bufp, &xyz.file_length, NULL, " "); 462 /* The rest of the file name data block quietly discarded */ 463 xyz.tx_ack = true; 464 } 465 xyz.next_blk = 1; 466 xyz.len = 0; 467 return 0; 468 } 469 else if (stat == xyzModem_timeout) 470 { 471 if (--crc_retries <= 0) 472 xyz.crc_mode = false; 473 CYGACC_CALL_IF_DELAY_US (5 * 100000); /* Extra delay for startup */ 474 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 475 xyz.total_retries++; 476 ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__)); 477 } 478 if (stat == xyzModem_cancel) 479 { 480 break; 481 } 482 } 483 *err = stat; 484 ZM_DEBUG (zm_flush ()); 485 return -1; 486 } 487 488 int 489 xyzModem_stream_read (char *buf, int size, int *err) 490 { 491 int stat, total, len; 492 int retries; 493 494 total = 0; 495 stat = xyzModem_cancel; 496 /* Try and get 'size' bytes into the buffer */ 497 while (!xyz.at_eof && (size > 0)) 498 { 499 if (xyz.len == 0) 500 { 501 retries = xyzModem_MAX_RETRIES; 502 while (retries-- > 0) 503 { 504 stat = xyzModem_get_hdr (); 505 if (stat == 0) 506 { 507 if (xyz.blk == xyz.next_blk) 508 { 509 xyz.tx_ack = true; 510 ZM_DEBUG (zm_dprintf 511 ("ACK block %d (%d)\n", xyz.blk, __LINE__)); 512 xyz.next_blk = (xyz.next_blk + 1) & 0xFF; 513 514 if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0) 515 { 516 /* Data blocks can be padded with ^Z (EOF) characters */ 517 /* This code tries to detect and remove them */ 518 if ((xyz.bufp[xyz.len - 1] == EOF) && 519 (xyz.bufp[xyz.len - 2] == EOF) && 520 (xyz.bufp[xyz.len - 3] == EOF)) 521 { 522 while (xyz.len 523 && (xyz.bufp[xyz.len - 1] == EOF)) 524 { 525 xyz.len--; 526 } 527 } 528 } 529 530 /* 531 * See if accumulated length exceeds that of the file. 532 * If so, reduce size (i.e., cut out pad bytes) 533 * Only do this for Y-modem (and Z-modem should it ever 534 * be supported since it can fall back to Y-modem mode). 535 */ 536 if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length) 537 { 538 xyz.read_length += xyz.len; 539 if (xyz.read_length > xyz.file_length) 540 { 541 xyz.len -= (xyz.read_length - xyz.file_length); 542 } 543 } 544 break; 545 } 546 else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF)) 547 { 548 /* Just re-ACK this so sender will get on with it */ 549 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 550 continue; /* Need new header */ 551 } 552 else 553 { 554 stat = xyzModem_sequence; 555 } 556 } 557 if (stat == xyzModem_cancel) 558 { 559 break; 560 } 561 if (stat == xyzModem_eof) 562 { 563 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 564 ZM_DEBUG (zm_dprintf ("ACK (%d)\n", __LINE__)); 565 if (xyz.mode == xyzModem_ymodem) 566 { 567 CYGACC_COMM_IF_PUTC (*xyz.__chan, 568 (xyz.crc_mode ? 'C' : NAK)); 569 xyz.total_retries++; 570 ZM_DEBUG (zm_dprintf ("Reading Final Header\n")); 571 stat = xyzModem_get_hdr (); 572 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 573 ZM_DEBUG (zm_dprintf ("FINAL ACK (%d)\n", __LINE__)); 574 } 575 xyz.at_eof = true; 576 break; 577 } 578 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 579 xyz.total_retries++; 580 ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__)); 581 } 582 if (stat < 0) 583 { 584 *err = stat; 585 xyz.len = -1; 586 return total; 587 } 588 } 589 /* Don't "read" data from the EOF protocol package */ 590 if (!xyz.at_eof) 591 { 592 len = xyz.len; 593 if (size < len) 594 len = size; 595 memcpy (buf, xyz.bufp, len); 596 size -= len; 597 buf += len; 598 total += len; 599 xyz.len -= len; 600 xyz.bufp += len; 601 } 602 } 603 return total; 604 } 605 606 void 607 xyzModem_stream_close (int *err) 608 { 609 diag_printf 610 ("xyzModem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n", 611 xyz.crc_mode ? "CRC" : "Cksum", xyz.total_SOH, xyz.total_STX, 612 xyz.total_CAN, xyz.total_retries); 613 ZM_DEBUG (zm_flush ()); 614 } 615 616 /* Need to be able to clean out the input buffer, so have to take the */ 617 /* getc */ 618 void 619 xyzModem_stream_terminate (bool abort, int (*getc) (void)) 620 { 621 int c; 622 623 if (abort) 624 { 625 ZM_DEBUG (zm_dprintf ("!!!! TRANSFER ABORT !!!!\n")); 626 switch (xyz.mode) 627 { 628 case xyzModem_xmodem: 629 case xyzModem_ymodem: 630 /* The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal */ 631 /* number of Backspaces is a friendly way to get the other end to abort. */ 632 CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN); 633 CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN); 634 CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN); 635 CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN); 636 CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP); 637 CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP); 638 CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP); 639 CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP); 640 /* Now consume the rest of what's waiting on the line. */ 641 ZM_DEBUG (zm_dprintf ("Flushing serial line.\n")); 642 xyzModem_flush (); 643 xyz.at_eof = true; 644 break; 645 #ifdef xyzModem_zmodem 646 case xyzModem_zmodem: 647 /* Might support it some day I suppose. */ 648 #endif 649 break; 650 } 651 } 652 else 653 { 654 ZM_DEBUG (zm_dprintf ("Engaging cleanup mode...\n")); 655 /* 656 * Consume any trailing crap left in the inbuffer from 657 * previous received blocks. Since very few files are an exact multiple 658 * of the transfer block size, there will almost always be some gunk here. 659 * If we don't eat it now, RedBoot will think the user typed it. 660 */ 661 ZM_DEBUG (zm_dprintf ("Trailing gunk:\n")); 662 while ((c = (*getc) ()) > -1) 663 ; 664 ZM_DEBUG (zm_dprintf ("\n")); 665 /* 666 * Make a small delay to give terminal programs like minicom 667 * time to get control again after their file transfer program 668 * exits. 669 */ 670 CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000); 671 } 672 } 673 674 char * 675 xyzModem_error (int err) 676 { 677 switch (err) 678 { 679 case xyzModem_access: 680 return "Can't access file"; 681 break; 682 case xyzModem_noZmodem: 683 return "Sorry, zModem not available yet"; 684 break; 685 case xyzModem_timeout: 686 return "Timed out"; 687 break; 688 case xyzModem_eof: 689 return "End of file"; 690 break; 691 case xyzModem_cancel: 692 return "Cancelled"; 693 break; 694 case xyzModem_frame: 695 return "Invalid framing"; 696 break; 697 case xyzModem_cksum: 698 return "CRC/checksum error"; 699 break; 700 case xyzModem_sequence: 701 return "Block sequence error"; 702 break; 703 default: 704 return "Unknown error"; 705 break; 706 } 707 } 708 709 /* 710 * RedBoot interface 711 */ 712