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