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