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