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