mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
709 lines
15 KiB
709 lines
15 KiB
// SPDX-License-Identifier: eCos-2.0 |
|
/* |
|
*========================================================================== |
|
* |
|
* xyzModem.c |
|
* |
|
* RedBoot stream handler for xyzModem protocol |
|
* |
|
*========================================================================== |
|
*#####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; |
|
} |
|
|
|
|
|
#if defined(DEBUG) && !defined(CONFIG_USE_TINY_PRINTF) |
|
/* |
|
* 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); |
|
va_end(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 |
|
*/
|
|
|