b09ece0836
Under the plethora of #ifdefs, the xyzModem code hid this pearl: static char *zm_out = (char *) 0x00380000; This was only enabled when DEBUG is defined, so it's probably why it went unnoticed for so long. No idea what platform had memory at that exact location, but the this approach is extremely hacky. Use a static buffer instead. Signed-off-by: Alexandru Gagniuc <alex.g@adaptrum.com>
710 lines
15 KiB
C
710 lines
15 KiB
C
/*
|
|
*==========================================================================
|
|
*
|
|
* xyzModem.c
|
|
*
|
|
* RedBoot stream handler for xyzModem protocol
|
|
*
|
|
*==========================================================================
|
|
* SPDX-License-Identifier: eCos-2.0
|
|
*==========================================================================
|
|
*#####DESCRIPTIONBEGIN####
|
|
*
|
|
* Author(s): gthomas
|
|
* Contributors: gthomas, tsmith, Yoshinori Sato
|
|
* Date: 2000-07-14
|
|
* Purpose:
|
|
* Description:
|
|
*
|
|
* This code is part of RedBoot (tm).
|
|
*
|
|
*####DESCRIPTIONEND####
|
|
*
|
|
*==========================================================================
|
|
*/
|
|
#include <common.h>
|
|
#include <xyzModem.h>
|
|
#include <stdarg.h>
|
|
#include <crc.h>
|
|
|
|
/* Assumption - run xyzModem protocol over the console port */
|
|
|
|
/* Values magic to the protocol */
|
|
#define SOH 0x01
|
|
#define STX 0x02
|
|
#define EOT 0x04
|
|
#define ACK 0x06
|
|
#define BSP 0x08
|
|
#define NAK 0x15
|
|
#define CAN 0x18
|
|
#define EOF 0x1A /* ^Z for DOS officionados */
|
|
|
|
/* Data & state local to the protocol */
|
|
static struct
|
|
{
|
|
int *__chan;
|
|
unsigned char pkt[1024], *bufp;
|
|
unsigned char blk, cblk, crc1, crc2;
|
|
unsigned char next_blk; /* Expected block */
|
|
int len, mode, total_retries;
|
|
int total_SOH, total_STX, total_CAN;
|
|
bool crc_mode, at_eof, tx_ack;
|
|
unsigned long file_length, read_length;
|
|
} xyz;
|
|
|
|
#define xyzModem_CHAR_TIMEOUT 2000 /* 2 seconds */
|
|
#define xyzModem_MAX_RETRIES 20
|
|
#define xyzModem_MAX_RETRIES_WITH_CRC 10
|
|
#define xyzModem_CAN_COUNT 3 /* Wait for 3 CAN before quitting */
|
|
|
|
|
|
typedef int cyg_int32;
|
|
static int
|
|
CYGACC_COMM_IF_GETC_TIMEOUT (char chan, char *c)
|
|
{
|
|
|
|
ulong now = get_timer(0);
|
|
while (!tstc ())
|
|
{
|
|
if (get_timer(now) > xyzModem_CHAR_TIMEOUT)
|
|
break;
|
|
}
|
|
if (tstc ())
|
|
{
|
|
*c = getc ();
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
CYGACC_COMM_IF_PUTC (char x, char y)
|
|
{
|
|
putc (y);
|
|
}
|
|
|
|
/* Validate a hex character */
|
|
__inline__ static bool
|
|
_is_hex (char c)
|
|
{
|
|
return (((c >= '0') && (c <= '9')) ||
|
|
((c >= 'A') && (c <= 'F')) || ((c >= 'a') && (c <= 'f')));
|
|
}
|
|
|
|
/* Convert a single hex nibble */
|
|
__inline__ static int
|
|
_from_hex (char c)
|
|
{
|
|
int ret = 0;
|
|
|
|
if ((c >= '0') && (c <= '9'))
|
|
{
|
|
ret = (c - '0');
|
|
}
|
|
else if ((c >= 'a') && (c <= 'f'))
|
|
{
|
|
ret = (c - 'a' + 0x0a);
|
|
}
|
|
else if ((c >= 'A') && (c <= 'F'))
|
|
{
|
|
ret = (c - 'A' + 0x0A);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Convert a character to lower case */
|
|
__inline__ static char
|
|
_tolower (char c)
|
|
{
|
|
if ((c >= 'A') && (c <= 'Z'))
|
|
{
|
|
c = (c - 'A') + 'a';
|
|
}
|
|
return c;
|
|
}
|
|
|
|
/* Parse (scan) a number */
|
|
static bool
|
|
parse_num (char *s, unsigned long *val, char **es, char *delim)
|
|
{
|
|
bool first = true;
|
|
int radix = 10;
|
|
char c;
|
|
unsigned long result = 0;
|
|
int digit;
|
|
|
|
while (*s == ' ')
|
|
s++;
|
|
while (*s)
|
|
{
|
|
if (first && (s[0] == '0') && (_tolower (s[1]) == 'x'))
|
|
{
|
|
radix = 16;
|
|
s += 2;
|
|
}
|
|
first = false;
|
|
c = *s++;
|
|
if (_is_hex (c) && ((digit = _from_hex (c)) < radix))
|
|
{
|
|
/* Valid digit */
|
|
result = (result * radix) + digit;
|
|
}
|
|
else
|
|
{
|
|
if (delim != (char *) 0)
|
|
{
|
|
/* See if this character is one of the delimiters */
|
|
char *dp = delim;
|
|
while (*dp && (c != *dp))
|
|
dp++;
|
|
if (*dp)
|
|
break; /* Found a good delimiter */
|
|
}
|
|
return false; /* Malformatted number */
|
|
}
|
|
}
|
|
*val = result;
|
|
if (es != (char **) 0)
|
|
{
|
|
*es = s;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
/*
|
|
* Note: this debug setup works by storing the strings in a fixed buffer
|
|
*/
|
|
static char zm_debug_buf[8192];
|
|
static char *zm_out = zm_debug_buf;
|
|
static char *zm_out_start = zm_debug_buf;
|
|
|
|
static int
|
|
zm_dprintf (char *fmt, ...)
|
|
{
|
|
int len;
|
|
va_list args;
|
|
|
|
va_start (args, fmt);
|
|
len = diag_vsprintf (zm_out, fmt, args);
|
|
zm_out += len;
|
|
return len;
|
|
}
|
|
|
|
static void
|
|
zm_flush (void)
|
|
{
|
|
zm_out = zm_out_start;
|
|
}
|
|
|
|
static void
|
|
zm_dump_buf (void *buf, int len)
|
|
{
|
|
|
|
}
|
|
|
|
static unsigned char zm_buf[2048];
|
|
static unsigned char *zm_bp;
|
|
|
|
static void
|
|
zm_new (void)
|
|
{
|
|
zm_bp = zm_buf;
|
|
}
|
|
|
|
static void
|
|
zm_save (unsigned char c)
|
|
{
|
|
*zm_bp++ = c;
|
|
}
|
|
|
|
static void
|
|
zm_dump (int line)
|
|
{
|
|
zm_dprintf ("Packet at line: %d\n", line);
|
|
zm_dump_buf (zm_buf, zm_bp - zm_buf);
|
|
}
|
|
|
|
#define ZM_DEBUG(x) x
|
|
#else
|
|
#define ZM_DEBUG(x)
|
|
#endif
|
|
|
|
/* Wait for the line to go idle */
|
|
static void
|
|
xyzModem_flush (void)
|
|
{
|
|
int res;
|
|
char c;
|
|
while (true)
|
|
{
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
|
|
if (!res)
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int
|
|
xyzModem_get_hdr (void)
|
|
{
|
|
char c;
|
|
int res;
|
|
bool hdr_found = false;
|
|
int i, can_total, hdr_chars;
|
|
unsigned short cksum;
|
|
|
|
ZM_DEBUG (zm_new ());
|
|
/* Find the start of a header */
|
|
can_total = 0;
|
|
hdr_chars = 0;
|
|
|
|
if (xyz.tx_ack)
|
|
{
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
|
|
xyz.tx_ack = false;
|
|
}
|
|
while (!hdr_found)
|
|
{
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
|
|
ZM_DEBUG (zm_save (c));
|
|
if (res)
|
|
{
|
|
hdr_chars++;
|
|
switch (c)
|
|
{
|
|
case SOH:
|
|
xyz.total_SOH++;
|
|
case STX:
|
|
if (c == STX)
|
|
xyz.total_STX++;
|
|
hdr_found = true;
|
|
break;
|
|
case CAN:
|
|
xyz.total_CAN++;
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
if (++can_total == xyzModem_CAN_COUNT)
|
|
{
|
|
return xyzModem_cancel;
|
|
}
|
|
else
|
|
{
|
|
/* Wait for multiple CAN to avoid early quits */
|
|
break;
|
|
}
|
|
case EOT:
|
|
/* EOT only supported if no noise */
|
|
if (hdr_chars == 1)
|
|
{
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
|
|
ZM_DEBUG (zm_dprintf ("ACK on EOT #%d\n", __LINE__));
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_eof;
|
|
}
|
|
default:
|
|
/* Ignore, waiting for start of header */
|
|
;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Data stream timed out */
|
|
xyzModem_flush (); /* Toss any current input */
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000);
|
|
return xyzModem_timeout;
|
|
}
|
|
}
|
|
|
|
/* Header found, now read the data */
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.blk);
|
|
ZM_DEBUG (zm_save (xyz.blk));
|
|
if (!res)
|
|
{
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_timeout;
|
|
}
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.cblk);
|
|
ZM_DEBUG (zm_save (xyz.cblk));
|
|
if (!res)
|
|
{
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_timeout;
|
|
}
|
|
xyz.len = (c == SOH) ? 128 : 1024;
|
|
xyz.bufp = xyz.pkt;
|
|
for (i = 0; i < xyz.len; i++)
|
|
{
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, &c);
|
|
ZM_DEBUG (zm_save (c));
|
|
if (res)
|
|
{
|
|
xyz.pkt[i] = c;
|
|
}
|
|
else
|
|
{
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_timeout;
|
|
}
|
|
}
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc1);
|
|
ZM_DEBUG (zm_save (xyz.crc1));
|
|
if (!res)
|
|
{
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_timeout;
|
|
}
|
|
if (xyz.crc_mode)
|
|
{
|
|
res = CYGACC_COMM_IF_GETC_TIMEOUT (*xyz.__chan, (char *) &xyz.crc2);
|
|
ZM_DEBUG (zm_save (xyz.crc2));
|
|
if (!res)
|
|
{
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
return xyzModem_timeout;
|
|
}
|
|
}
|
|
ZM_DEBUG (zm_dump (__LINE__));
|
|
/* Validate the message */
|
|
if ((xyz.blk ^ xyz.cblk) != (unsigned char) 0xFF)
|
|
{
|
|
ZM_DEBUG (zm_dprintf
|
|
("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk,
|
|
(xyz.blk ^ xyz.cblk)));
|
|
ZM_DEBUG (zm_dump_buf (xyz.pkt, xyz.len));
|
|
xyzModem_flush ();
|
|
return xyzModem_frame;
|
|
}
|
|
/* Verify checksum/CRC */
|
|
if (xyz.crc_mode)
|
|
{
|
|
cksum = crc16_ccitt(0, xyz.pkt, xyz.len);
|
|
if (cksum != ((xyz.crc1 << 8) | xyz.crc2))
|
|
{
|
|
ZM_DEBUG (zm_dprintf ("CRC error - recvd: %02x%02x, computed: %x\n",
|
|
xyz.crc1, xyz.crc2, cksum & 0xFFFF));
|
|
return xyzModem_cksum;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cksum = 0;
|
|
for (i = 0; i < xyz.len; i++)
|
|
{
|
|
cksum += xyz.pkt[i];
|
|
}
|
|
if (xyz.crc1 != (cksum & 0xFF))
|
|
{
|
|
ZM_DEBUG (zm_dprintf
|
|
("Checksum error - recvd: %x, computed: %x\n", xyz.crc1,
|
|
cksum & 0xFF));
|
|
return xyzModem_cksum;
|
|
}
|
|
}
|
|
/* If we get here, the message passes [structural] muster */
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
xyzModem_stream_open (connection_info_t * info, int *err)
|
|
{
|
|
int stat = 0;
|
|
int retries = xyzModem_MAX_RETRIES;
|
|
int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC;
|
|
|
|
/* ZM_DEBUG(zm_out = zm_out_start); */
|
|
#ifdef xyzModem_zmodem
|
|
if (info->mode == xyzModem_zmodem)
|
|
{
|
|
*err = xyzModem_noZmodem;
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
/* TODO: CHECK ! */
|
|
int dummy = 0;
|
|
xyz.__chan = &dummy;
|
|
xyz.len = 0;
|
|
xyz.crc_mode = true;
|
|
xyz.at_eof = false;
|
|
xyz.tx_ack = false;
|
|
xyz.mode = info->mode;
|
|
xyz.total_retries = 0;
|
|
xyz.total_SOH = 0;
|
|
xyz.total_STX = 0;
|
|
xyz.total_CAN = 0;
|
|
xyz.read_length = 0;
|
|
xyz.file_length = 0;
|
|
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
|
|
|
|
if (xyz.mode == xyzModem_xmodem)
|
|
{
|
|
/* X-modem doesn't have an information header - exit here */
|
|
xyz.next_blk = 1;
|
|
return 0;
|
|
}
|
|
|
|
while (retries-- > 0)
|
|
{
|
|
stat = xyzModem_get_hdr ();
|
|
if (stat == 0)
|
|
{
|
|
/* Y-modem file information header */
|
|
if (xyz.blk == 0)
|
|
{
|
|
/* skip filename */
|
|
while (*xyz.bufp++);
|
|
/* get the length */
|
|
parse_num ((char *) xyz.bufp, &xyz.file_length, NULL, " ");
|
|
/* The rest of the file name data block quietly discarded */
|
|
xyz.tx_ack = true;
|
|
}
|
|
xyz.next_blk = 1;
|
|
xyz.len = 0;
|
|
return 0;
|
|
}
|
|
else if (stat == xyzModem_timeout)
|
|
{
|
|
if (--crc_retries <= 0)
|
|
xyz.crc_mode = false;
|
|
CYGACC_CALL_IF_DELAY_US (5 * 100000); /* Extra delay for startup */
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
|
|
xyz.total_retries++;
|
|
ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__));
|
|
}
|
|
if (stat == xyzModem_cancel)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
*err = stat;
|
|
ZM_DEBUG (zm_flush ());
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
xyzModem_stream_read (char *buf, int size, int *err)
|
|
{
|
|
int stat, total, len;
|
|
int retries;
|
|
|
|
total = 0;
|
|
stat = xyzModem_cancel;
|
|
/* Try and get 'size' bytes into the buffer */
|
|
while (!xyz.at_eof && (size > 0))
|
|
{
|
|
if (xyz.len == 0)
|
|
{
|
|
retries = xyzModem_MAX_RETRIES;
|
|
while (retries-- > 0)
|
|
{
|
|
stat = xyzModem_get_hdr ();
|
|
if (stat == 0)
|
|
{
|
|
if (xyz.blk == xyz.next_blk)
|
|
{
|
|
xyz.tx_ack = true;
|
|
ZM_DEBUG (zm_dprintf
|
|
("ACK block %d (%d)\n", xyz.blk, __LINE__));
|
|
xyz.next_blk = (xyz.next_blk + 1) & 0xFF;
|
|
|
|
if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0)
|
|
{
|
|
/* Data blocks can be padded with ^Z (EOF) characters */
|
|
/* This code tries to detect and remove them */
|
|
if ((xyz.bufp[xyz.len - 1] == EOF) &&
|
|
(xyz.bufp[xyz.len - 2] == EOF) &&
|
|
(xyz.bufp[xyz.len - 3] == EOF))
|
|
{
|
|
while (xyz.len
|
|
&& (xyz.bufp[xyz.len - 1] == EOF))
|
|
{
|
|
xyz.len--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* See if accumulated length exceeds that of the file.
|
|
* If so, reduce size (i.e., cut out pad bytes)
|
|
* Only do this for Y-modem (and Z-modem should it ever
|
|
* be supported since it can fall back to Y-modem mode).
|
|
*/
|
|
if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length)
|
|
{
|
|
xyz.read_length += xyz.len;
|
|
if (xyz.read_length > xyz.file_length)
|
|
{
|
|
xyz.len -= (xyz.read_length - xyz.file_length);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF))
|
|
{
|
|
/* Just re-ACK this so sender will get on with it */
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
|
|
continue; /* Need new header */
|
|
}
|
|
else
|
|
{
|
|
stat = xyzModem_sequence;
|
|
}
|
|
}
|
|
if (stat == xyzModem_cancel)
|
|
{
|
|
break;
|
|
}
|
|
if (stat == xyzModem_eof)
|
|
{
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
|
|
ZM_DEBUG (zm_dprintf ("ACK (%d)\n", __LINE__));
|
|
if (xyz.mode == xyzModem_ymodem)
|
|
{
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan,
|
|
(xyz.crc_mode ? 'C' : NAK));
|
|
xyz.total_retries++;
|
|
ZM_DEBUG (zm_dprintf ("Reading Final Header\n"));
|
|
stat = xyzModem_get_hdr ();
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, ACK);
|
|
ZM_DEBUG (zm_dprintf ("FINAL ACK (%d)\n", __LINE__));
|
|
}
|
|
xyz.at_eof = true;
|
|
break;
|
|
}
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
|
|
xyz.total_retries++;
|
|
ZM_DEBUG (zm_dprintf ("NAK (%d)\n", __LINE__));
|
|
}
|
|
if (stat < 0)
|
|
{
|
|
*err = stat;
|
|
xyz.len = -1;
|
|
return total;
|
|
}
|
|
}
|
|
/* Don't "read" data from the EOF protocol package */
|
|
if (!xyz.at_eof)
|
|
{
|
|
len = xyz.len;
|
|
if (size < len)
|
|
len = size;
|
|
memcpy (buf, xyz.bufp, len);
|
|
size -= len;
|
|
buf += len;
|
|
total += len;
|
|
xyz.len -= len;
|
|
xyz.bufp += len;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
void
|
|
xyzModem_stream_close (int *err)
|
|
{
|
|
diag_printf
|
|
("xyzModem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n",
|
|
xyz.crc_mode ? "CRC" : "Cksum", xyz.total_SOH, xyz.total_STX,
|
|
xyz.total_CAN, xyz.total_retries);
|
|
ZM_DEBUG (zm_flush ());
|
|
}
|
|
|
|
/* Need to be able to clean out the input buffer, so have to take the */
|
|
/* getc */
|
|
void
|
|
xyzModem_stream_terminate (bool abort, int (*getc) (void))
|
|
{
|
|
int c;
|
|
|
|
if (abort)
|
|
{
|
|
ZM_DEBUG (zm_dprintf ("!!!! TRANSFER ABORT !!!!\n"));
|
|
switch (xyz.mode)
|
|
{
|
|
case xyzModem_xmodem:
|
|
case xyzModem_ymodem:
|
|
/* The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal */
|
|
/* number of Backspaces is a friendly way to get the other end to abort. */
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, CAN);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP);
|
|
CYGACC_COMM_IF_PUTC (*xyz.__chan, BSP);
|
|
/* Now consume the rest of what's waiting on the line. */
|
|
ZM_DEBUG (zm_dprintf ("Flushing serial line.\n"));
|
|
xyzModem_flush ();
|
|
xyz.at_eof = true;
|
|
break;
|
|
#ifdef xyzModem_zmodem
|
|
case xyzModem_zmodem:
|
|
/* Might support it some day I suppose. */
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ZM_DEBUG (zm_dprintf ("Engaging cleanup mode...\n"));
|
|
/*
|
|
* Consume any trailing crap left in the inbuffer from
|
|
* previous received blocks. Since very few files are an exact multiple
|
|
* of the transfer block size, there will almost always be some gunk here.
|
|
* If we don't eat it now, RedBoot will think the user typed it.
|
|
*/
|
|
ZM_DEBUG (zm_dprintf ("Trailing gunk:\n"));
|
|
while ((c = (*getc) ()) > -1)
|
|
;
|
|
ZM_DEBUG (zm_dprintf ("\n"));
|
|
/*
|
|
* Make a small delay to give terminal programs like minicom
|
|
* time to get control again after their file transfer program
|
|
* exits.
|
|
*/
|
|
CYGACC_CALL_IF_DELAY_US ((cyg_int32) 250000);
|
|
}
|
|
}
|
|
|
|
char *
|
|
xyzModem_error (int err)
|
|
{
|
|
switch (err)
|
|
{
|
|
case xyzModem_access:
|
|
return "Can't access file";
|
|
break;
|
|
case xyzModem_noZmodem:
|
|
return "Sorry, zModem not available yet";
|
|
break;
|
|
case xyzModem_timeout:
|
|
return "Timed out";
|
|
break;
|
|
case xyzModem_eof:
|
|
return "End of file";
|
|
break;
|
|
case xyzModem_cancel:
|
|
return "Cancelled";
|
|
break;
|
|
case xyzModem_frame:
|
|
return "Invalid framing";
|
|
break;
|
|
case xyzModem_cksum:
|
|
return "CRC/checksum error";
|
|
break;
|
|
case xyzModem_sequence:
|
|
return "Block sequence error";
|
|
break;
|
|
default:
|
|
return "Unknown error";
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* RedBoot interface
|
|
*/
|