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 ETX 0x03                /* ^C for interrupt */
  36#define EOT 0x04
  37#define ACK 0x06
  38#define BSP 0x08
  39#define NAK 0x15
  40#define CAN 0x18
  41#define EOF 0x1A                /* ^Z for DOS officionados */
  42
  43/* Data & state local to the protocol */
  44static struct
  45{
  46  int *__chan;
  47  unsigned char pkt[1024], *bufp;
  48  unsigned char blk, cblk, crc1, crc2;
  49  unsigned char next_blk;       /* Expected block */
  50  int len, mode, total_retries;
  51  int total_SOH, total_STX, total_CAN;
  52  bool crc_mode, at_eof, tx_ack;
  53  unsigned long file_length, read_length;
  54} xyz;
  55
  56#define xyzModem_CHAR_TIMEOUT            2000   /* 2 seconds */
  57#define xyzModem_MAX_RETRIES             20
  58#define xyzModem_MAX_RETRIES_WITH_CRC    10
  59#define xyzModem_CAN_COUNT                3     /* Wait for 3 CAN before quitting */
  60
  61
  62typedef int cyg_int32;
  63static int
  64CYGACC_COMM_IF_GETC_TIMEOUT (char chan, char *c)
  65{
  66
  67  ulong now = get_timer(0);
  68  WATCHDOG_RESET();
  69  while (!tstc ())
  70    {
  71      if (get_timer(now) > xyzModem_CHAR_TIMEOUT)
  72        break;
  73    }
  74  if (tstc ())
  75    {
  76      *c = getchar();
  77      return 1;
  78    }
  79  return 0;
  80}
  81
  82static void
  83CYGACC_COMM_IF_PUTC (char x, char y)
  84{
  85  putc (y);
  86}
  87
  88/* Validate a hex character */
  89__inline__ static bool
  90_is_hex (char c)
  91{
  92  return (((c >= '0') && (c <= '9')) ||
  93          ((c >= 'A') && (c <= 'F')) || ((c >= 'a') && (c <= 'f')));
  94}
  95
  96/* Convert a single hex nibble */
  97__inline__ static int
  98_from_hex (char c)
  99{
 100  int ret = 0;
 101
 102  if ((c >= '0') && (c <= '9'))
 103    {
 104      ret = (c - '0');
 105    }
 106  else if ((c >= 'a') && (c <= 'f'))
 107    {
 108      ret = (c - 'a' + 0x0a);
 109    }
 110  else if ((c >= 'A') && (c <= 'F'))
 111    {
 112      ret = (c - 'A' + 0x0A);
 113    }
 114  return ret;
 115}
 116
 117/* Convert a character to lower case */
 118__inline__ static char
 119_tolower (char c)
 120{
 121  if ((c >= 'A') && (c <= 'Z'))
 122    {
 123      c = (c - 'A') + 'a';
 124    }
 125  return c;
 126}
 127
 128/* Parse (scan) a number */
 129static bool
 130parse_num (char *s, unsigned long *val, char **es, char *delim)
 131{
 132  bool first = true;
 133  int radix = 10;
 134  char c;
 135  unsigned long result = 0;
 136  int digit;
 137
 138  while (*s == ' ')
 139    s++;
 140  while (*s)
 141    {
 142      if (first && (s[0] == '0') && (_tolower (s[1]) == 'x'))
 143        {
 144          radix = 16;
 145          s += 2;
 146        }
 147      first = false;
 148      c = *s++;
 149      if (_is_hex (c) && ((digit = _from_hex (c)) < radix))
 150        {
 151          /* Valid digit */
 152          result = (result * radix) + digit;
 153        }
 154      else
 155        {
 156          if (delim != (char *) 0)
 157            {
 158              /* See if this character is one of the delimiters */
 159              char *dp = delim;
 160              while (*dp && (c != *dp))
 161                dp++;
 162              if (*dp)
 163                break;          /* Found a good delimiter */
 164            }
 165          return false;         /* Malformatted number */
 166        }
 167    }
 168  *val = result;
 169  if (es != (char **) 0)
 170    {
 171      *es = s;
 172    }
 173  return true;
 174}
 175
 176
 177#if defined(DEBUG) && !CONFIG_IS_ENABLED(USE_TINY_PRINTF)
 178/*
 179 * Note: this debug setup works by storing the strings in a fixed buffer
 180 */
 181static char zm_debug_buf[8192];
 182static char *zm_out = zm_debug_buf;
 183static char *zm_out_start = zm_debug_buf;
 184
 185static int
 186zm_dprintf(char *fmt, ...)
 187{
 188        int len;
 189        va_list args;
 190
 191        va_start(args, fmt);
 192        len = diag_vsprintf(zm_out, fmt, args);
 193        va_end(args);
 194        zm_out += len;
 195        return len;
 196}
 197
 198static void
 199zm_flush (void)
 200{
 201  zm_out = zm_out_start;
 202}
 203
 204static void
 205zm_dump_buf (void *buf, int len)
 206{
 207
 208}
 209
 210static unsigned char zm_buf[2048];
 211static unsigned char *zm_bp;
 212
 213static void
 214zm_new (void)
 215{
 216  zm_bp = zm_buf;
 217}
 218
 219static void
 220zm_save (unsigned char c)
 221{
 222  *zm_bp++ = c;
 223}
 224
 225static void
 226zm_dump (int line)
 227{
 228  zm_dprintf ("Packet at line: %d\n", line);
 229  zm_dump_buf (zm_buf, zm_bp - zm_buf);
 230}
 231
 232#define ZM_DEBUG(x) x
 233#else
 234#define ZM_DEBUG(x)
 235#endif
 236
 237/* Wait for the line to go idle */
 238static void
 239xyzModem_flush (void)
 240{
 241  int res;
 242  char c;
 243  while (true)
 244    {
 245      res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
 246      if (!res)
 247        return;
 248    }
 249}
 250
 251static int
 252xyzModem_get_hdr (void)
 253{
 254  char c;
 255  int res;
 256  bool hdr_found = false;
 257  int i, can_total, hdr_chars;
 258  unsigned short cksum;
 259
 260  ZM_DEBUG (zm_new ());
 261  /* Find the start of a header */
 262  can_total = 0;
 263  hdr_chars = 0;
 264
 265  if (xyz.tx_ack)
 266    {
 267      CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
 268      xyz.tx_ack = false;
 269    }
 270  while (!hdr_found)
 271    {
 272      res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
 273      ZM_DEBUG (zm_save (c));
 274      if (res)
 275        {
 276          hdr_chars++;
 277          switch (c)
 278            {
 279            case SOH:
 280              xyz.total_SOH++;
 281            case STX:
 282              if (c == STX)
 283                xyz.total_STX++;
 284              hdr_found = true;
 285              break;
 286            case CAN:
 287            case ETX:
 288              xyz.total_CAN++;
 289              ZM_DEBUG (zm_dump (__LINE__));
 290              if (++can_total == xyzModem_CAN_COUNT)
 291                {
 292                  return xyzModem_cancel;
 293                }
 294              else
 295                {
 296                  /* Wait for multiple CAN to avoid early quits */
 297                  break;
 298                }
 299            case EOT:
 300              /* EOT only supported if no noise */
 301              if (hdr_chars == 1)
 302                {
 303                  CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
 304                  ZM_DEBUG (zm_dprintf ("ACK on EOT #%d\n", __LINE__));
 305                  ZM_DEBUG (zm_dump (__LINE__));
 306                  return xyzModem_eof;
 307                }
 308            default:
 309              /* Ignore, waiting for start of header */
 310              ;
 311            }
 312        }
 313      else
 314        {
 315          /* Data stream timed out */
 316          xyzModem_flush ();    /* Toss any current input */
 317          ZM_DEBUG (zm_dump (__LINE__));
 318          CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000);
 319          return xyzModem_timeout;
 320        }
 321    }
 322
 323  /* Header found, now read the data */
 324  res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.blk);
 325  ZM_DEBUG (zm_save (xyz.blk));
 326  if (!res)
 327    {
 328      ZM_DEBUG (zm_dump (__LINE__));
 329      return xyzModem_timeout;
 330    }
 331  res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.cblk);
 332  ZM_DEBUG (zm_save (xyz.cblk));
 333  if (!res)
 334    {
 335      ZM_DEBUG (zm_dump (__LINE__));
 336      return xyzModem_timeout;
 337    }
 338  xyz.len = (c == SOH) ? 128 : 1024;
 339  xyz.bufp = xyz.pkt;
 340  for (i = 0; i < xyz.len; i++)
 341    {
 342      res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
 343      ZM_DEBUG (zm_save (c));
 344      if (res)
 345        {
 346          xyz.pkt[i] = c;
 347        }
 348      else
 349        {
 350          ZM_DEBUG (zm_dump (__LINE__));
 351          return xyzModem_timeout;
 352        }
 353    }
 354  res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc1);
 355  ZM_DEBUG (zm_save (xyz.crc1));
 356  if (!res)
 357    {
 358      ZM_DEBUG (zm_dump (__LINE__));
 359      return xyzModem_timeout;
 360    }
 361  if (xyz.crc_mode)
 362    {
 363      res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc2);
 364      ZM_DEBUG (zm_save (xyz.crc2));
 365      if (!res)
 366        {
 367          ZM_DEBUG (zm_dump (__LINE__));
 368          return xyzModem_timeout;
 369        }
 370    }
 371  ZM_DEBUG (zm_dump (__LINE__));
 372  /* Validate the message */
 373  if ((xyz.blk ^ xyz.cblk) != (unsigned char) 0xFF)
 374    {
 375      ZM_DEBUG (zm_dprintf
 376                ("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk,
 377                 (xyz.blk ^ xyz.cblk)));
 378      ZM_DEBUG (zm_dump_buf (xyz.pkt, xyz.len));
 379      xyzModem_flush ();
 380      return xyzModem_frame;
 381    }
 382  /* Verify checksum/CRC */
 383  if (xyz.crc_mode)
 384    {
 385      cksum = crc16_ccitt(0, xyz.pkt, xyz.len);
 386      if (cksum != ((xyz.crc1 << 8) | xyz.crc2))
 387        {
 388          ZM_DEBUG (zm_dprintf ("CRC error - recvd: %02x%02x, computed: %x\n",
 389                                xyz.crc1, xyz.crc2, cksum & 0xFFFF));
 390          return xyzModem_cksum;
 391        }
 392    }
 393  else
 394    {
 395      cksum = 0;
 396      for (i = 0; i < xyz.len; i++)
 397        {
 398          cksum += xyz.pkt[i];
 399        }
 400      if (xyz.crc1 != (cksum & 0xFF))
 401        {
 402          ZM_DEBUG (zm_dprintf
 403                    ("Checksum error - recvd: %x, computed: %x\n", xyz.crc1,
 404                     cksum & 0xFF));
 405          return xyzModem_cksum;
 406        }
 407    }
 408  /* If we get here, the message passes [structural] muster */
 409  return 0;
 410}
 411
 412int
 413xyzModem_stream_open (connection_info_t * info, int *err)
 414{
 415  int stat = 0;
 416  int retries = xyzModem_MAX_RETRIES;
 417  int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC;
 418
 419/*    ZM_DEBUG(zm_out = zm_out_start); */
 420#ifdef xyzModem_zmodem
 421  if (info->mode == xyzModem_zmodem)
 422    {
 423      *err = xyzModem_noZmodem;
 424      return -1;
 425    }
 426#endif
 427
 428/* TODO: CHECK ! */
 429  int dummy = 0;
 430  xyz.__chan = &dummy;
 431  xyz.len = 0;
 432  xyz.crc_mode = true;
 433  xyz.at_eof = false;
 434  xyz.tx_ack = false;
 435  xyz.mode = info->mode;
 436  xyz.total_retries = 0;
 437  xyz.total_SOH = 0;
 438  xyz.total_STX = 0;
 439  xyz.total_CAN = 0;
 440  xyz.read_length = 0;
 441  xyz.file_length = 0;
 442
 443  CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
 444
 445  if (xyz.mode == xyzModem_xmodem)
 446    {
 447      /* X-modem doesn't have an information header - exit here */
 448      xyz.next_blk = 1;
 449      return 0;
 450    }
 451
 452  while (retries-- > 0)
 453    {
 454      stat = xyzModem_get_hdr ();
 455      if (stat == 0)
 456        {
 457          /* Y-modem file information header */
 458          if (xyz.blk == 0)
 459            {
 460              /* skip filename */
 461              while (*xyz.bufp++);
 462              /* get the length */
 463              parse_num ((char *) xyz.bufp, &xyz.file_length, NULL, " ");
 464              /* The rest of the file name data block quietly discarded */
 465              xyz.tx_ack = true;
 466            }
 467          xyz.next_blk = 1;
 468          xyz.len = 0;
 469          return 0;
 470        }
 471      else if (stat == xyzModem_timeout)
 472        {
 473          if (--crc_retries <= 0)
 474            xyz.crc_mode = false;
 475          CYGACC_CALL_IF_DELAY_US (5 * 100000); /* Extra delay for startup */
 476          CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
 477          xyz.total_retries++;
 478          ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__));
 479        }
 480      if (stat == xyzModem_cancel)
 481        {
 482          break;
 483        }
 484    }
 485  *err = stat;
 486  ZM_DEBUG (zm_flush ());
 487  return -1;
 488}
 489
 490int
 491xyzModem_stream_read (char *buf, int size, int *err)
 492{
 493  int stat, total, len;
 494  int retries;
 495
 496  total = 0;
 497  stat = xyzModem_cancel;
 498  /* Try and get 'size' bytes into the buffer */
 499  while (!xyz.at_eof && xyz.len >= 0 && (size > 0))
 500    {
 501      if (xyz.len == 0)
 502        {
 503          retries = xyzModem_MAX_RETRIES;
 504          while (retries-- > 0)
 505            {
 506              stat = xyzModem_get_hdr ();
 507              if (stat == 0)
 508                {
 509                  if (xyz.blk == xyz.next_blk)
 510                    {
 511                      xyz.tx_ack = true;
 512                      ZM_DEBUG (zm_dprintf
 513                                ("ACK block %d (%d)\n", xyz.blk, __LINE__));
 514                      xyz.next_blk = (xyz.next_blk + 1) & 0xFF;
 515
 516                      if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0)
 517                        {
 518                          /* Data blocks can be padded with ^Z (EOF) characters */
 519                          /* This code tries to detect and remove them */
 520                          if ((xyz.bufp[xyz.len - 1] == EOF) &&
 521                              (xyz.bufp[xyz.len - 2] == EOF) &&
 522                              (xyz.bufp[xyz.len - 3] == EOF))
 523                            {
 524                              while (xyz.len
 525                                     && (xyz.bufp[xyz.len - 1] == EOF))
 526                                {
 527                                  xyz.len--;
 528                                }
 529                            }
 530                        }
 531
 532                      /*
 533                       * See if accumulated length exceeds that of the file.
 534                       * If so, reduce size (i.e., cut out pad bytes)
 535                       * Only do this for Y-modem (and Z-modem should it ever
 536                       * be supported since it can fall back to Y-modem mode).
 537                       */
 538                      if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length)
 539                        {
 540                          xyz.read_length += xyz.len;
 541                          if (xyz.read_length > xyz.file_length)
 542                            {
 543                              xyz.len -= (xyz.read_length - xyz.file_length);
 544                            }
 545                        }
 546                      break;
 547                    }
 548                  else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF))
 549                    {
 550                      /* Just re-ACK this so sender will get on with it */
 551                      CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
 552                      continue; /* Need new header */
 553                    }
 554                  else
 555                    {
 556                      stat = xyzModem_sequence;
 557                    }
 558                }
 559              if (stat == xyzModem_cancel)
 560                {
 561                  break;
 562                }
 563              if (stat == xyzModem_eof)
 564                {
 565                  CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
 566                  ZM_DEBUG (zm_dprintf ("ACK (%d)\n", __LINE__));
 567                  if (xyz.mode == xyzModem_ymodem)
 568                    {
 569                      CYGACC_COMM_IF_PUTC (*xyz.__chan,
 570                                           (xyz.crc_mode ? 'C' : NAK));
 571                      xyz.total_retries++;
 572                      ZM_DEBUG (zm_dprintf ("Reading Final Header\n"));
 573                      stat = xyzModem_get_hdr ();
 574                      CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
 575                      ZM_DEBUG (zm_dprintf ("FINAL ACK (%d)\n", __LINE__));
 576                    }
 577                  else
 578                    stat = 0;
 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 && xyz.len > 0)
 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
 610void
 611xyzModem_stream_close (int *err)
 612{
 613  ZM_DEBUG (zm_dprintf
 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 */
 622void
 623xyzModem_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
 678char *
 679xyzModem_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