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