blob: 3c86b138b36764a3a32d8c60219657de26f4fef4 [file] [log] [blame]
Markus Klotzbuecherf2841d32006-03-30 13:40:55 +02001//==========================================================================
2//
3// xyzModem.c
4//
5// RedBoot stream handler for xyzModem protocol
6//
7//==========================================================================
8//####ECOSGPLCOPYRIGHTBEGIN####
9// -------------------------------------------
10// This file is part of eCos, the Embedded Configurable Operating System.
11// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
12// Copyright (C) 2002 Gary Thomas
13//
14// eCos is free software; you can redistribute it and/or modify it under
15// the terms of the GNU General Public License as published by the Free
16// Software Foundation; either version 2 or (at your option) any later version.
17//
18// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
19// WARRANTY; without even the implied warranty of MERCHANTABILITY or
20// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21// for more details.
22//
23// You should have received a copy of the GNU General Public License along
24// with eCos; if not, write to the Free Software Foundation, Inc.,
25// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
26//
27// As a special exception, if other files instantiate templates or use macros
28// or inline functions from this file, or you compile this file and link it
29// with other works to produce a work based on this file, this file does not
30// by itself cause the resulting work to be covered by the GNU General Public
31// License. However the source code for this file must still be made available
32// in accordance with section (3) of the GNU General Public License.
33//
34// This exception does not invalidate any other reasons why a work based on
35// this file might be covered by the GNU General Public License.
36//
37// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
38// at http://sources.redhat.com/ecos/ecos-license/
39// -------------------------------------------
40//####ECOSGPLCOPYRIGHTEND####
41//==========================================================================
42//#####DESCRIPTIONBEGIN####
43//
44// Author(s): gthomas
45// Contributors: gthomas, tsmith, Yoshinori Sato
46// Date: 2000-07-14
47// Purpose:
48// Description:
49//
50// This code is part of RedBoot (tm).
51//
52//####DESCRIPTIONEND####
53//
54//==========================================================================
55#include <common.h>
56#include <xyzModem.h>
57#include <stdarg.h>
58#include <crc.h>
59
60// Assumption - run xyzModem protocol over the console port
61
62// Values magic to the protocol
63#define SOH 0x01
64#define STX 0x02
65#define EOT 0x04
66#define ACK 0x06
67#define BSP 0x08
68#define NAK 0x15
69#define CAN 0x18
70#define EOF 0x1A // ^Z for DOS officionados
71
72#define USE_YMODEM_LENGTH
73
74// Data & state local to the protocol
75static struct {
76#ifdef REDBOOT
77 hal_virtual_comm_table_t* __chan;
78#else
79 int *__chan;
80#endif
81 unsigned char pkt[1024], *bufp;
82 unsigned char blk,cblk,crc1,crc2;
83 unsigned char next_blk; // Expected block
84 int len, mode, total_retries;
85 int total_SOH, total_STX, total_CAN;
86 bool crc_mode, at_eof, tx_ack;
87#ifdef USE_YMODEM_LENGTH
88 unsigned long file_length, read_length;
89#endif
90} xyz;
91
92#define xyzModem_CHAR_TIMEOUT 2000 // 2 seconds
93#define xyzModem_MAX_RETRIES 20
94#define xyzModem_MAX_RETRIES_WITH_CRC 10
95#define xyzModem_CAN_COUNT 3 // Wait for 3 CAN before quitting
96
97
98#ifndef REDBOOT //SB
99typedef int cyg_int32;
100int CYGACC_COMM_IF_GETC_TIMEOUT (char chan,char *c) {
101#define DELAY 20
102 unsigned long counter=0;
103 while (!tstc() && (counter < xyzModem_CHAR_TIMEOUT*1000/DELAY)) {
104 udelay(DELAY);
105 counter++;
106 }
107 if (tstc()) {
108 *c=getc();
109 return 1;
110 }
111 return 0;
112}
113
114void CYGACC_COMM_IF_PUTC(char x,char y) {
115 putc(y);
116}
117
118// Validate a hex character
119__inline__ static bool
120_is_hex(char c)
121{
122 return (((c >= '0') && (c <= '9')) ||
123 ((c >= 'A') && (c <= 'F')) ||
124 ((c >= 'a') && (c <= 'f')));
125}
126
127// Convert a single hex nibble
128__inline__ static int
129_from_hex(char c)
130{
131 int ret = 0;
132
133 if ((c >= '0') && (c <= '9')) {
134 ret = (c - '0');
135 } else if ((c >= 'a') && (c <= 'f')) {
136 ret = (c - 'a' + 0x0a);
137 } else if ((c >= 'A') && (c <= 'F')) {
138 ret = (c - 'A' + 0x0A);
139 }
140 return ret;
141}
142
143// Convert a character to lower case
144__inline__ static char
145_tolower(char c)
146{
147 if ((c >= 'A') && (c <= 'Z')) {
148 c = (c - 'A') + 'a';
149 }
150 return c;
151}
152
153
154
155//
156// Parse (scan) a number
157//
158bool
159parse_num(char *s, unsigned long *val, char **es, char *delim)
160{
161 bool first = true;
162 int radix = 10;
163 char c;
164 unsigned long result = 0;
165 int digit;
166
167 while (*s == ' ') s++;
168 while (*s) {
169 if (first && (s[0] == '0') && (_tolower(s[1]) == 'x')) {
170 radix = 16;
171 s += 2;
172 }
173 first = false;
174 c = *s++;
175 if (_is_hex(c) && ((digit = _from_hex(c)) < radix)) {
176 // Valid digit
177#ifdef CYGPKG_HAL_MIPS
178 // FIXME: tx49 compiler generates 0x2539018 for MUL which
179 // isn't any good.
180 if (16 == radix)
181 result = result << 4;
182 else
183 result = 10 * result;
184 result += digit;
185#else
186 result = (result * radix) + digit;
187#endif
188 } else {
189 if (delim != (char *)0) {
190 // See if this character is one of the delimiters
191 char *dp = delim;
192 while (*dp && (c != *dp)) dp++;
193 if (*dp) break; // Found a good delimiter
194 }
195 return false; // Malformatted number
196 }
197 }
198 *val = result;
199 if (es != (char **)0) {
200 *es = s;
201 }
202 return true;
203}
204
205#endif
206
207#define USE_SPRINTF
208#ifdef DEBUG
209#ifndef USE_SPRINTF
210//
211// Note: this debug setup only works if the target platform has two serial ports
212// available so that the other one (currently only port 1) can be used for debug
213// messages.
214//
215static int
216zm_dprintf(char *fmt, ...)
217{
218 int cur_console;
219 va_list args;
220
221 va_start(args, fmt);
222#ifdef REDBOOT
223 cur_console = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);
224 CYGACC_CALL_IF_SET_CONSOLE_COMM(1);
225#endif
226 diag_vprintf(fmt, args);
227#ifdef REDBOOT
228 CYGACC_CALL_IF_SET_CONSOLE_COMM(cur_console);
229#endif
230}
231
232static void
233zm_flush(void)
234{
235}
236
237#else
238//
239// Note: this debug setup works by storing the strings in a fixed buffer
240//
241#define FINAL
242#ifdef FINAL
243static char *zm_out = (char *)0x00380000;
244static char *zm_out_start = (char *)0x00380000;
245#else
246static char zm_buf[8192];
247static char *zm_out=zm_buf;
248static char *zm_out_start = zm_buf;
249
250#endif
251static int
252zm_dprintf(char *fmt, ...)
253{
254 int len;
255 va_list args;
256
257 va_start(args, fmt);
258 len = diag_vsprintf(zm_out, fmt, args);
259 zm_out += len;
260 return len;
261}
262
263static void
264zm_flush(void)
265{
266 char *p = zm_out_start;
267#ifdef REDBOOT
268 while (*p) mon_write_char(*p++);
269#endif
270 zm_out = zm_out_start;
271}
272#endif
273
274static void
275zm_dump_buf(void *buf, int len)
276{
277#ifdef REDBOOT
278 diag_vdump_buf_with_offset(zm_dprintf, buf, len, 0);
279#else
280
281#endif
282}
283
284static unsigned char zm_buf[2048];
285static unsigned char *zm_bp;
286
287static void
288zm_new(void)
289{
290 zm_bp = zm_buf;
291}
292
293static void
294zm_save(unsigned char c)
295{
296 *zm_bp++ = c;
297}
298
299static void
300zm_dump(int line)
301{
302 zm_dprintf("Packet at line: %d\n", line);
303 zm_dump_buf(zm_buf, zm_bp-zm_buf);
304}
305
306#define ZM_DEBUG(x) x
307#else
308#define ZM_DEBUG(x)
309#endif
310
311// Wait for the line to go idle
312static void
313xyzModem_flush(void)
314{
315 int res;
316 char c;
317 while (true) {
318 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
319 if (!res) return;
320 }
321}
322
323static int
324xyzModem_get_hdr(void)
325{
326 char c;
327 int res;
328 bool hdr_found = false;
329 int i, can_total, hdr_chars;
330 unsigned short cksum;
331
332 ZM_DEBUG(zm_new());
333 // Find the start of a header
334 can_total = 0;
335 hdr_chars = 0;
336
337 if (xyz.tx_ack) {
338 CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
339 xyz.tx_ack = false;
340 }
341 while (!hdr_found) {
342 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
343 ZM_DEBUG(zm_save(c));
344 if (res) {
345 hdr_chars++;
346 switch (c) {
347 case SOH:
348 xyz.total_SOH++;
349 case STX:
350 if (c == STX) xyz.total_STX++;
351 hdr_found = true;
352 break;
353 case CAN:
354 xyz.total_CAN++;
355 ZM_DEBUG(zm_dump(__LINE__));
356 if (++can_total == xyzModem_CAN_COUNT) {
357 return xyzModem_cancel;
358 } else {
359 // Wait for multiple CAN to avoid early quits
360 break;
361 }
362 case EOT:
363 // EOT only supported if no noise
364 if (hdr_chars == 1) {
365 CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
366 ZM_DEBUG(zm_dprintf("ACK on EOT #%d\n", __LINE__));
367 ZM_DEBUG(zm_dump(__LINE__));
368 return xyzModem_eof;
369 }
370 default:
371 // Ignore, waiting for start of header
372 ;
373 }
374 } else {
375 // Data stream timed out
376 xyzModem_flush(); // Toss any current input
377 ZM_DEBUG(zm_dump(__LINE__));
378 CYGACC_CALL_IF_DELAY_US((cyg_int32)250000);
379 return xyzModem_timeout;
380 }
381 }
382
383 // Header found, now read the data
384 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.blk);
385 ZM_DEBUG(zm_save(xyz.blk));
386 if (!res) {
387 ZM_DEBUG(zm_dump(__LINE__));
388 return xyzModem_timeout;
389 }
390 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.cblk);
391 ZM_DEBUG(zm_save(xyz.cblk));
392 if (!res) {
393 ZM_DEBUG(zm_dump(__LINE__));
394 return xyzModem_timeout;
395 }
396 xyz.len = (c == SOH) ? 128 : 1024;
397 xyz.bufp = xyz.pkt;
398 for (i = 0; i < xyz.len; i++) {
399 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
400 ZM_DEBUG(zm_save(c));
401 if (res) {
402 xyz.pkt[i] = c;
403 } else {
404 ZM_DEBUG(zm_dump(__LINE__));
405 return xyzModem_timeout;
406 }
407 }
408 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.crc1);
409 ZM_DEBUG(zm_save(xyz.crc1));
410 if (!res) {
411 ZM_DEBUG(zm_dump(__LINE__));
412 return xyzModem_timeout;
413 }
414 if (xyz.crc_mode) {
415 res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.crc2);
416 ZM_DEBUG(zm_save(xyz.crc2));
417 if (!res) {
418 ZM_DEBUG(zm_dump(__LINE__));
419 return xyzModem_timeout;
420 }
421 }
422 ZM_DEBUG(zm_dump(__LINE__));
423 // Validate the message
424 if ((xyz.blk ^ xyz.cblk) != (unsigned char)0xFF) {
425 ZM_DEBUG(zm_dprintf("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk, (xyz.blk ^ xyz.cblk)));
426 ZM_DEBUG(zm_dump_buf(xyz.pkt, xyz.len));
427 xyzModem_flush();
428 return xyzModem_frame;
429 }
430 // Verify checksum/CRC
431 if (xyz.crc_mode) {
432 cksum = cyg_crc16(xyz.pkt, xyz.len);
433 if (cksum != ((xyz.crc1 << 8) | xyz.crc2)) {
434 ZM_DEBUG(zm_dprintf("CRC error - recvd: %02x%02x, computed: %x\n",
435 xyz.crc1, xyz.crc2, cksum & 0xFFFF));
436 return xyzModem_cksum;
437 }
438 } else {
439 cksum = 0;
440 for (i = 0; i < xyz.len; i++) {
441 cksum += xyz.pkt[i];
442 }
443 if (xyz.crc1 != (cksum & 0xFF)) {
444 ZM_DEBUG(zm_dprintf("Checksum error - recvd: %x, computed: %x\n", xyz.crc1, cksum & 0xFF));
445 return xyzModem_cksum;
446 }
447 }
448 // If we get here, the message passes [structural] muster
449 return 0;
450}
451
452int
453xyzModem_stream_open(connection_info_t *info, int *err)
454{
455 int console_chan, stat=0;
456 int retries = xyzModem_MAX_RETRIES;
457 int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC;
458
459// ZM_DEBUG(zm_out = zm_out_start);
460#ifdef xyzModem_zmodem
461 if (info->mode == xyzModem_zmodem) {
462 *err = xyzModem_noZmodem;
463 return -1;
464 }
465#endif
466
467#ifdef REDBOOT
468 // Set up the I/O channel. Note: this allows for using a different port in the future
469 console_chan = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);
470 if (info->chan >= 0) {
471 CYGACC_CALL_IF_SET_CONSOLE_COMM(info->chan);
472 } else {
473 CYGACC_CALL_IF_SET_CONSOLE_COMM(console_chan);
474 }
475 xyz.__chan = CYGACC_CALL_IF_CONSOLE_PROCS();
476
477 CYGACC_CALL_IF_SET_CONSOLE_COMM(console_chan);
478 CYGACC_COMM_IF_CONTROL(*xyz.__chan, __COMMCTL_SET_TIMEOUT, xyzModem_CHAR_TIMEOUT);
479#else
480// TODO: CHECK !
481 int dummy;
482 xyz.__chan=&dummy;
483#endif
484 xyz.len = 0;
485 xyz.crc_mode = true;
486 xyz.at_eof = false;
487 xyz.tx_ack = false;
488 xyz.mode = info->mode;
489 xyz.total_retries = 0;
490 xyz.total_SOH = 0;
491 xyz.total_STX = 0;
492 xyz.total_CAN = 0;
493#ifdef USE_YMODEM_LENGTH
494 xyz.read_length = 0;
495 xyz.file_length = 0;
496#endif
497
498 CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
499
500 if (xyz.mode == xyzModem_xmodem) {
501 // X-modem doesn't have an information header - exit here
502 xyz.next_blk = 1;
503 return 0;
504 }
505
506 while (retries-- > 0) {
507 stat = xyzModem_get_hdr();
508 if (stat == 0) {
509 // Y-modem file information header
510 if (xyz.blk == 0) {
511#ifdef USE_YMODEM_LENGTH
512 // skip filename
513 while (*xyz.bufp++);
514 // get the length
515 parse_num(xyz.bufp, &xyz.file_length, NULL, " ");
516#endif
517 // The rest of the file name data block quietly discarded
518 xyz.tx_ack = true;
519 }
520 xyz.next_blk = 1;
521 xyz.len = 0;
522 return 0;
523 } else
524 if (stat == xyzModem_timeout) {
525 if (--crc_retries <= 0) xyz.crc_mode = false;
526 CYGACC_CALL_IF_DELAY_US(5*100000); // Extra delay for startup
527 CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
528 xyz.total_retries++;
529 ZM_DEBUG(zm_dprintf("NAK (%d)\n", __LINE__));
530 }
531 if (stat == xyzModem_cancel) {
532 break;
533 }
534 }
535 *err = stat;
536 ZM_DEBUG(zm_flush());
537 return -1;
538}
539
540int
541xyzModem_stream_read(char *buf, int size, int *err)
542{
543 int stat, total, len;
544 int retries;
545
546 total = 0;
547 stat = xyzModem_cancel;
548 // Try and get 'size' bytes into the buffer
549 while (!xyz.at_eof && (size > 0)) {
550 if (xyz.len == 0) {
551 retries = xyzModem_MAX_RETRIES;
552 while (retries-- > 0) {
553 stat = xyzModem_get_hdr();
554 if (stat == 0) {
555 if (xyz.blk == xyz.next_blk) {
556 xyz.tx_ack = true;
557 ZM_DEBUG(zm_dprintf("ACK block %d (%d)\n", xyz.blk, __LINE__));
558 xyz.next_blk = (xyz.next_blk + 1) & 0xFF;
559
560#if defined(xyzModem_zmodem) || defined(USE_YMODEM_LENGTH)
561 if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0) {
562#else
563 if (1) {
564#endif
565 // Data blocks can be padded with ^Z (EOF) characters
566 // This code tries to detect and remove them
567 if ((xyz.bufp[xyz.len-1] == EOF) &&
568 (xyz.bufp[xyz.len-2] == EOF) &&
569 (xyz.bufp[xyz.len-3] == EOF)) {
570 while (xyz.len && (xyz.bufp[xyz.len-1] == EOF)) {
571 xyz.len--;
572 }
573 }
574 }
575
576#ifdef USE_YMODEM_LENGTH
577 // See if accumulated length exceeds that of the file.
578 // If so, reduce size (i.e., cut out pad bytes)
579 // Only do this for Y-modem (and Z-modem should it ever
580 // be supported since it can fall back to Y-modem mode).
581 if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length) {
582 xyz.read_length += xyz.len;
583 if (xyz.read_length > xyz.file_length) {
584 xyz.len -= (xyz.read_length - xyz.file_length);
585 }
586 }
587#endif
588 break;
589 } else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF)) {
590 // Just re-ACK this so sender will get on with it
591 CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
592 continue; // Need new header
593 } else {
594 stat = xyzModem_sequence;
595 }
596 }
597 if (stat == xyzModem_cancel) {
598 break;
599 }
600 if (stat == xyzModem_eof) {
601 CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
602 ZM_DEBUG(zm_dprintf("ACK (%d)\n", __LINE__));
603 if (xyz.mode == xyzModem_ymodem) {
604 CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
605 xyz.total_retries++;
606 ZM_DEBUG(zm_dprintf("Reading Final Header\n"));
607 stat = xyzModem_get_hdr();
608 CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
609 ZM_DEBUG(zm_dprintf("FINAL ACK (%d)\n", __LINE__));
610 }
611 xyz.at_eof = true;
612 break;
613 }
614 CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
615 xyz.total_retries++;
616 ZM_DEBUG(zm_dprintf("NAK (%d)\n", __LINE__));
617 }
618 if (stat < 0) {
619 *err = stat;
620 xyz.len = -1;
621 return total;
622 }
623 }
624 // Don't "read" data from the EOF protocol package
625 if (!xyz.at_eof) {
626 len = xyz.len;
627 if (size < len) len = size;
628 memcpy(buf, xyz.bufp, len);
629 size -= len;
630 buf += len;
631 total += len;
632 xyz.len -= len;
633 xyz.bufp += len;
634 }
635 }
636 return total;
637}
638
639void
640xyzModem_stream_close(int *err)
641{
642 diag_printf("xyzModem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n",
643 xyz.crc_mode ? "CRC" : "Cksum",
644 xyz.total_SOH, xyz.total_STX, xyz.total_CAN,
645 xyz.total_retries);
646 ZM_DEBUG(zm_flush());
647}
648
649// Need to be able to clean out the input buffer, so have to take the
650// getc
651void xyzModem_stream_terminate(bool abort, int (*getc)(void))
652{
653 int c;
654
655 if (abort) {
656 ZM_DEBUG(zm_dprintf("!!!! TRANSFER ABORT !!!!\n"));
657 switch (xyz.mode) {
658 case xyzModem_xmodem:
659 case xyzModem_ymodem:
660 // The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal
661 // number of Backspaces is a friendly way to get the other end to abort.
662 CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
663 CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
664 CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
665 CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
666 CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
667 CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
668 CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
669 CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
670 // Now consume the rest of what's waiting on the line.
671 ZM_DEBUG(zm_dprintf("Flushing serial line.\n"));
672 xyzModem_flush();
673 xyz.at_eof = true;
674 break;
675#ifdef xyzModem_zmodem
676 case xyzModem_zmodem:
677 // Might support it some day I suppose.
678#endif
679 break;
680 }
681 } else {
682 ZM_DEBUG(zm_dprintf("Engaging cleanup mode...\n"));
683 // Consume any trailing crap left in the inbuffer from
684 // previous recieved blocks. Since very few files are an exact multiple
685 // of the transfer block size, there will almost always be some gunk here.
686 // If we don't eat it now, RedBoot will think the user typed it.
687 ZM_DEBUG(zm_dprintf("Trailing gunk:\n"));
688 while ((c = (*getc)()) > -1) ;
689 ZM_DEBUG(zm_dprintf("\n"));
690 // Make a small delay to give terminal programs like minicom
691 // time to get control again after their file transfer program
692 // exits.
693 CYGACC_CALL_IF_DELAY_US((cyg_int32)250000);
694 }
695}
696
697char *
698xyzModem_error(int err)
699{
700 switch (err) {
701 case xyzModem_access:
702 return "Can't access file";
703 break;
704 case xyzModem_noZmodem:
705 return "Sorry, zModem not available yet";
706 break;
707 case xyzModem_timeout:
708 return "Timed out";
709 break;
710 case xyzModem_eof:
711 return "End of file";
712 break;
713 case xyzModem_cancel:
714 return "Cancelled";
715 break;
716 case xyzModem_frame:
717 return "Invalid framing";
718 break;
719 case xyzModem_cksum:
720 return "CRC/checksum error";
721 break;
722 case xyzModem_sequence:
723 return "Block sequence error";
724 break;
725 default:
726 return "Unknown error";
727 break;
728 }
729}
730
731//
732// RedBoot interface
733//
734#if 0 // SB
735GETC_IO_FUNCS(xyzModem_io, xyzModem_stream_open, xyzModem_stream_close,
736 xyzModem_stream_terminate, xyzModem_stream_read, xyzModem_error);
737RedBoot_load(xmodem, xyzModem_io, false, false, xyzModem_xmodem);
738RedBoot_load(ymodem, xyzModem_io, false, false, xyzModem_ymodem);
739#endif