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