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 #ifdef DEBUG 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 zm_out += len; 191 return len; 192 } 193 194 static void 195 zm_flush (void) 196 { 197 zm_out = zm_out_start; 198 } 199 200 static void 201 zm_dump_buf (void *buf, int len) 202 { 203 204 } 205 206 static unsigned char zm_buf[2048]; 207 static unsigned char *zm_bp; 208 209 static void 210 zm_new (void) 211 { 212 zm_bp = zm_buf; 213 } 214 215 static void 216 zm_save (unsigned char c) 217 { 218 *zm_bp++ = c; 219 } 220 221 static void 222 zm_dump (int line) 223 { 224 zm_dprintf ("Packet at line: %d\n", line); 225 zm_dump_buf (zm_buf, zm_bp - zm_buf); 226 } 227 228 #define ZM_DEBUG(x) x 229 #else 230 #define ZM_DEBUG(x) 231 #endif 232 233 /* Wait for the line to go idle */ 234 static void 235 xyzModem_flush (void) 236 { 237 int res; 238 char c; 239 while (true) 240 { 241 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 242 if (!res) 243 return; 244 } 245 } 246 247 static int 248 xyzModem_get_hdr (void) 249 { 250 char c; 251 int res; 252 bool hdr_found = false; 253 int i, can_total, hdr_chars; 254 unsigned short cksum; 255 256 ZM_DEBUG (zm_new ()); 257 /* Find the start of a header */ 258 can_total = 0; 259 hdr_chars = 0; 260 261 if (xyz.tx_ack) 262 { 263 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 264 xyz.tx_ack = false; 265 } 266 while (!hdr_found) 267 { 268 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 269 ZM_DEBUG (zm_save (c)); 270 if (res) 271 { 272 hdr_chars++; 273 switch (c) 274 { 275 case SOH: 276 xyz.total_SOH++; 277 case STX: 278 if (c == STX) 279 xyz.total_STX++; 280 hdr_found = true; 281 break; 282 case CAN: 283 xyz.total_CAN++; 284 ZM_DEBUG (zm_dump (__LINE__)); 285 if (++can_total == xyzModem_CAN_COUNT) 286 { 287 return xyzModem_cancel; 288 } 289 else 290 { 291 /* Wait for multiple CAN to avoid early quits */ 292 break; 293 } 294 case EOT: 295 /* EOT only supported if no noise */ 296 if (hdr_chars == 1) 297 { 298 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 299 ZM_DEBUG (zm_dprintf ("ACK on EOT #%d\n", __LINE__)); 300 ZM_DEBUG (zm_dump (__LINE__)); 301 return xyzModem_eof; 302 } 303 default: 304 /* Ignore, waiting for start of header */ 305 ; 306 } 307 } 308 else 309 { 310 /* Data stream timed out */ 311 xyzModem_flush (); /* Toss any current input */ 312 ZM_DEBUG (zm_dump (__LINE__)); 313 CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000); 314 return xyzModem_timeout; 315 } 316 } 317 318 /* Header found, now read the data */ 319 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.blk); 320 ZM_DEBUG (zm_save (xyz.blk)); 321 if (!res) 322 { 323 ZM_DEBUG (zm_dump (__LINE__)); 324 return xyzModem_timeout; 325 } 326 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.cblk); 327 ZM_DEBUG (zm_save (xyz.cblk)); 328 if (!res) 329 { 330 ZM_DEBUG (zm_dump (__LINE__)); 331 return xyzModem_timeout; 332 } 333 xyz.len = (c == SOH) ? 128 : 1024; 334 xyz.bufp = xyz.pkt; 335 for (i = 0; i < xyz.len; i++) 336 { 337 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c); 338 ZM_DEBUG (zm_save (c)); 339 if (res) 340 { 341 xyz.pkt[i] = c; 342 } 343 else 344 { 345 ZM_DEBUG (zm_dump (__LINE__)); 346 return xyzModem_timeout; 347 } 348 } 349 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc1); 350 ZM_DEBUG (zm_save (xyz.crc1)); 351 if (!res) 352 { 353 ZM_DEBUG (zm_dump (__LINE__)); 354 return xyzModem_timeout; 355 } 356 if (xyz.crc_mode) 357 { 358 res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc2); 359 ZM_DEBUG (zm_save (xyz.crc2)); 360 if (!res) 361 { 362 ZM_DEBUG (zm_dump (__LINE__)); 363 return xyzModem_timeout; 364 } 365 } 366 ZM_DEBUG (zm_dump (__LINE__)); 367 /* Validate the message */ 368 if ((xyz.blk ^ xyz.cblk) != (unsigned char) 0xFF) 369 { 370 ZM_DEBUG (zm_dprintf 371 ("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk, 372 (xyz.blk ^ xyz.cblk))); 373 ZM_DEBUG (zm_dump_buf (xyz.pkt, xyz.len)); 374 xyzModem_flush (); 375 return xyzModem_frame; 376 } 377 /* Verify checksum/CRC */ 378 if (xyz.crc_mode) 379 { 380 cksum = crc16_ccitt(0, xyz.pkt, xyz.len); 381 if (cksum != ((xyz.crc1 << 8) | xyz.crc2)) 382 { 383 ZM_DEBUG (zm_dprintf ("CRC error - recvd: %02x%02x, computed: %x\n", 384 xyz.crc1, xyz.crc2, cksum & 0xFFFF)); 385 return xyzModem_cksum; 386 } 387 } 388 else 389 { 390 cksum = 0; 391 for (i = 0; i < xyz.len; i++) 392 { 393 cksum += xyz.pkt[i]; 394 } 395 if (xyz.crc1 != (cksum & 0xFF)) 396 { 397 ZM_DEBUG (zm_dprintf 398 ("Checksum error - recvd: %x, computed: %x\n", xyz.crc1, 399 cksum & 0xFF)); 400 return xyzModem_cksum; 401 } 402 } 403 /* If we get here, the message passes [structural] muster */ 404 return 0; 405 } 406 407 int 408 xyzModem_stream_open (connection_info_t * info, int *err) 409 { 410 int stat = 0; 411 int retries = xyzModem_MAX_RETRIES; 412 int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC; 413 414 /* ZM_DEBUG(zm_out = zm_out_start); */ 415 #ifdef xyzModem_zmodem 416 if (info->mode == xyzModem_zmodem) 417 { 418 *err = xyzModem_noZmodem; 419 return -1; 420 } 421 #endif 422 423 /* TODO: CHECK ! */ 424 int dummy = 0; 425 xyz.__chan = &dummy; 426 xyz.len = 0; 427 xyz.crc_mode = true; 428 xyz.at_eof = false; 429 xyz.tx_ack = false; 430 xyz.mode = info->mode; 431 xyz.total_retries = 0; 432 xyz.total_SOH = 0; 433 xyz.total_STX = 0; 434 xyz.total_CAN = 0; 435 xyz.read_length = 0; 436 xyz.file_length = 0; 437 438 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 439 440 if (xyz.mode == xyzModem_xmodem) 441 { 442 /* X-modem doesn't have an information header - exit here */ 443 xyz.next_blk = 1; 444 return 0; 445 } 446 447 while (retries-- > 0) 448 { 449 stat = xyzModem_get_hdr (); 450 if (stat == 0) 451 { 452 /* Y-modem file information header */ 453 if (xyz.blk == 0) 454 { 455 /* skip filename */ 456 while (*xyz.bufp++); 457 /* get the length */ 458 parse_num ((char *) xyz.bufp, &xyz.file_length, NULL, " "); 459 /* The rest of the file name data block quietly discarded */ 460 xyz.tx_ack = true; 461 } 462 xyz.next_blk = 1; 463 xyz.len = 0; 464 return 0; 465 } 466 else if (stat == xyzModem_timeout) 467 { 468 if (--crc_retries <= 0) 469 xyz.crc_mode = false; 470 CYGACC_CALL_IF_DELAY_US (5 * 100000); /* Extra delay for startup */ 471 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 472 xyz.total_retries++; 473 ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__)); 474 } 475 if (stat == xyzModem_cancel) 476 { 477 break; 478 } 479 } 480 *err = stat; 481 ZM_DEBUG (zm_flush ()); 482 return -1; 483 } 484 485 int 486 xyzModem_stream_read (char *buf, int size, int *err) 487 { 488 int stat, total, len; 489 int retries; 490 491 total = 0; 492 stat = xyzModem_cancel; 493 /* Try and get 'size' bytes into the buffer */ 494 while (!xyz.at_eof && (size > 0)) 495 { 496 if (xyz.len == 0) 497 { 498 retries = xyzModem_MAX_RETRIES; 499 while (retries-- > 0) 500 { 501 stat = xyzModem_get_hdr (); 502 if (stat == 0) 503 { 504 if (xyz.blk == xyz.next_blk) 505 { 506 xyz.tx_ack = true; 507 ZM_DEBUG (zm_dprintf 508 ("ACK block %d (%d)\n", xyz.blk, __LINE__)); 509 xyz.next_blk = (xyz.next_blk + 1) & 0xFF; 510 511 if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0) 512 { 513 /* Data blocks can be padded with ^Z (EOF) characters */ 514 /* This code tries to detect and remove them */ 515 if ((xyz.bufp[xyz.len - 1] == EOF) && 516 (xyz.bufp[xyz.len - 2] == EOF) && 517 (xyz.bufp[xyz.len - 3] == EOF)) 518 { 519 while (xyz.len 520 && (xyz.bufp[xyz.len - 1] == EOF)) 521 { 522 xyz.len--; 523 } 524 } 525 } 526 527 /* 528 * See if accumulated length exceeds that of the file. 529 * If so, reduce size (i.e., cut out pad bytes) 530 * Only do this for Y-modem (and Z-modem should it ever 531 * be supported since it can fall back to Y-modem mode). 532 */ 533 if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length) 534 { 535 xyz.read_length += xyz.len; 536 if (xyz.read_length > xyz.file_length) 537 { 538 xyz.len -= (xyz.read_length - xyz.file_length); 539 } 540 } 541 break; 542 } 543 else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF)) 544 { 545 /* Just re-ACK this so sender will get on with it */ 546 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 547 continue; /* Need new header */ 548 } 549 else 550 { 551 stat = xyzModem_sequence; 552 } 553 } 554 if (stat == xyzModem_cancel) 555 { 556 break; 557 } 558 if (stat == xyzModem_eof) 559 { 560 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 561 ZM_DEBUG (zm_dprintf ("ACK (%d)\n", __LINE__)); 562 if (xyz.mode == xyzModem_ymodem) 563 { 564 CYGACC_COMM_IF_PUTC (*xyz.__chan, 565 (xyz.crc_mode ? 'C' : NAK)); 566 xyz.total_retries++; 567 ZM_DEBUG (zm_dprintf ("Reading Final Header\n")); 568 stat = xyzModem_get_hdr (); 569 CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK); 570 ZM_DEBUG (zm_dprintf ("FINAL ACK (%d)\n", __LINE__)); 571 } 572 xyz.at_eof = true; 573 break; 574 } 575 CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK)); 576 xyz.total_retries++; 577 ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__)); 578 } 579 if (stat < 0) 580 { 581 *err = stat; 582 xyz.len = -1; 583 return total; 584 } 585 } 586 /* Don't "read" data from the EOF protocol package */ 587 if (!xyz.at_eof) 588 { 589 len = xyz.len; 590 if (size < len) 591 len = size; 592 memcpy (buf, xyz.bufp, len); 593 size -= len; 594 buf += len; 595 total += len; 596 xyz.len -= len; 597 xyz.bufp += len; 598 } 599 } 600 return total; 601 } 602 603 void 604 xyzModem_stream_close (int *err) 605 { 606 diag_printf 607 ("xyzModem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n", 608 xyz.crc_mode ? "CRC" : "Cksum", xyz.total_SOH, xyz.total_STX, 609 xyz.total_CAN, xyz.total_retries); 610 ZM_DEBUG (zm_flush ()); 611 } 612 613 /* Need to be able to clean out the input buffer, so have to take the */ 614 /* getc */ 615 void 616 xyzModem_stream_terminate (bool abort, int (*getc) (void)) 617 { 618 int c; 619 620 if (abort) 621 { 622 ZM_DEBUG (zm_dprintf ("!!!! TRANSFER ABORT !!!!\n")); 623 switch (xyz.mode) 624 { 625 case xyzModem_xmodem: 626 case xyzModem_ymodem: 627 /* The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal */ 628 /* number of Backspaces is a friendly way to get the other end to abort. */ 629 CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN); 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, BSP); 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 /* Now consume the rest of what's waiting on the line. */ 638 ZM_DEBUG (zm_dprintf ("Flushing serial line.\n")); 639 xyzModem_flush (); 640 xyz.at_eof = true; 641 break; 642 #ifdef xyzModem_zmodem 643 case xyzModem_zmodem: 644 /* Might support it some day I suppose. */ 645 #endif 646 break; 647 } 648 } 649 else 650 { 651 ZM_DEBUG (zm_dprintf ("Engaging cleanup mode...\n")); 652 /* 653 * Consume any trailing crap left in the inbuffer from 654 * previous received blocks. Since very few files are an exact multiple 655 * of the transfer block size, there will almost always be some gunk here. 656 * If we don't eat it now, RedBoot will think the user typed it. 657 */ 658 ZM_DEBUG (zm_dprintf ("Trailing gunk:\n")); 659 while ((c = (*getc) ()) > -1) 660 ; 661 ZM_DEBUG (zm_dprintf ("\n")); 662 /* 663 * Make a small delay to give terminal programs like minicom 664 * time to get control again after their file transfer program 665 * exits. 666 */ 667 CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000); 668 } 669 } 670 671 char * 672 xyzModem_error (int err) 673 { 674 switch (err) 675 { 676 case xyzModem_access: 677 return "Can't access file"; 678 break; 679 case xyzModem_noZmodem: 680 return "Sorry, zModem not available yet"; 681 break; 682 case xyzModem_timeout: 683 return "Timed out"; 684 break; 685 case xyzModem_eof: 686 return "End of file"; 687 break; 688 case xyzModem_cancel: 689 return "Cancelled"; 690 break; 691 case xyzModem_frame: 692 return "Invalid framing"; 693 break; 694 case xyzModem_cksum: 695 return "CRC/checksum error"; 696 break; 697 case xyzModem_sequence: 698 return "Block sequence error"; 699 break; 700 default: 701 return "Unknown error"; 702 break; 703 } 704 } 705 706 /* 707 * RedBoot interface 708 */ 709