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