unified MX27, MX25, MX37 trees
[karo-tx-redboot.git] / packages / redboot / v2_0 / src / xyzModem.c
1 //==========================================================================
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
56 #include <redboot.h>
57 #include <xyzModem.h>
58
59 // Assumption - run xyzModem protocol over the console port
60
61 // Values magic to the protocol
62 #define SOH 0x01
63 #define STX 0x02
64 #define EOT 0x04
65 #define ACK 0x06
66 #define BSP 0x08
67 #define NAK 0x15
68 #define CAN 0x18
69 #define EOF 0x1A  // ^Z for DOS officionados
70
71 #define USE_YMODEM_LENGTH
72
73 // Data & state local to the protocol
74 static struct {
75     hal_virtual_comm_table_t* __chan;
76     unsigned char pkt[1024], *bufp;
77     unsigned char blk,cblk,crc1,crc2;
78     unsigned char next_blk;  // Expected block
79     int len, mode, total_retries;
80     int total_SOH, total_STX, total_CAN;
81     bool crc_mode, at_eof, tx_ack;
82 #ifdef USE_YMODEM_LENGTH
83     unsigned long file_length, read_length;
84 #endif
85 } xyz;
86
87 #define xyzModem_CHAR_TIMEOUT            2000  // 2 seconds
88 #define xyzModem_MAX_RETRIES             20
89 #define xyzModem_MAX_RETRIES_WITH_CRC    10
90 #define xyzModem_CAN_COUNT                3    // Wait for 3 CAN before quitting
91
92 #ifdef DEBUG
93 #ifndef USE_SPRINTF
94 //
95 // Note: this debug setup only works if the target platform has two serial ports
96 // available so that the other one (currently only port 1) can be used for debug
97 // messages.
98 //
99 static int
100 zm_dprintf(char *fmt, ...)
101 {
102     int cur_console;
103     va_list args;
104
105     va_start(args, fmt);
106     cur_console = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);
107     CYGACC_CALL_IF_SET_CONSOLE_COMM(1);
108     diag_vprintf(fmt, args);
109     CYGACC_CALL_IF_SET_CONSOLE_COMM(cur_console);
110 }
111
112 static void
113 zm_flush(void)
114 {
115 }
116
117 #else
118 //
119 // Note: this debug setup works by storing the strings in a fixed buffer
120 //
121 static char *zm_out = (char *)0x00380000;
122 static char *zm_out_start = (char *)0x00380000;
123
124 static int
125 zm_dprintf(char *fmt, ...)
126 {
127     int len;
128     va_list args;
129
130     va_start(args, fmt);
131     len = diag_vsprintf(zm_out, fmt, args);
132     zm_out += len;
133     return len;
134 }
135
136 static void
137 zm_flush(void)
138 {
139     char *p = zm_out_start;
140     while (*p) mon_write_char(*p++);
141     zm_out = zm_out_start;
142 }
143 #endif
144
145 static void
146 zm_dump_buf(void *buf, int len)
147 {
148     diag_vdump_buf_with_offset(zm_dprintf, buf, len, 0);
149 }
150
151 static unsigned char zm_buf[2048];
152 static unsigned char *zm_bp;
153
154 static void
155 zm_new(void)
156 {
157     zm_bp = zm_buf;
158 }
159
160 static void
161 zm_save(unsigned char c)
162 {
163     *zm_bp++ = c;
164 }
165
166 static void
167 zm_dump(int line)
168 {
169     zm_dprintf("Packet at line: %d\n", line);
170     zm_dump_buf(zm_buf, zm_bp-zm_buf);
171 }
172
173 #define ZM_DEBUG(x) x
174 #else
175 #define ZM_DEBUG(x)
176 #endif
177
178 // Wait for the line to go idle
179 static void
180 xyzModem_flush(void)
181 {
182     int res;
183     char c;
184     while (true) {
185         res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
186         if (!res) return;
187     }
188 }
189
190 static int
191 xyzModem_get_hdr(void)
192 {
193     char c;
194     int res;
195     bool hdr_found = false;
196     int i, can_total, hdr_chars;
197     unsigned short cksum;
198
199     ZM_DEBUG(zm_new());
200     // Find the start of a header
201     can_total = 0;
202     hdr_chars = 0;
203
204     if (xyz.tx_ack) {
205         CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
206         xyz.tx_ack = false;
207     }
208     while (!hdr_found) {
209         res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
210         ZM_DEBUG(zm_save(c));
211         if (res) {
212             hdr_chars++;
213             switch (c) {
214             case SOH:
215                 xyz.total_SOH++;
216             case STX:
217                 if (c == STX) xyz.total_STX++;
218                 hdr_found = true;
219                 break;
220             case CAN:
221                 xyz.total_CAN++;
222                 ZM_DEBUG(zm_dump(__LINE__));
223                 if (++can_total == xyzModem_CAN_COUNT) {
224                     return xyzModem_cancel;
225                 } else {
226                     // Wait for multiple CAN to avoid early quits
227                     break;
228                 }
229             case EOT:
230                 // EOT only supported if no noise
231                 if (hdr_chars == 1) {
232                     CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
233                     ZM_DEBUG(zm_dprintf("ACK on EOT #%d\n", __LINE__));
234                     ZM_DEBUG(zm_dump(__LINE__));
235                     return xyzModem_eof;
236                 }
237             default:
238                 // Ignore, waiting for start of header
239                 ;
240             }
241         } else {
242             // Data stream timed out
243             xyzModem_flush();  // Toss any current input
244             ZM_DEBUG(zm_dump(__LINE__));
245             CYGACC_CALL_IF_DELAY_US((cyg_int32)250000);
246             return xyzModem_timeout;
247         }
248     }
249
250     // Header found, now read the data
251     res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.blk);
252     ZM_DEBUG(zm_save(xyz.blk));
253     if (!res) {
254         ZM_DEBUG(zm_dump(__LINE__));
255         return xyzModem_timeout;
256     }
257     res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.cblk);
258     ZM_DEBUG(zm_save(xyz.cblk));
259     if (!res) {
260         ZM_DEBUG(zm_dump(__LINE__));
261         return xyzModem_timeout;
262     }
263     xyz.len = (c == SOH) ? 128 : 1024;
264     xyz.bufp = xyz.pkt;
265     for (i = 0;  i < xyz.len;  i++) {
266         res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &c);
267         ZM_DEBUG(zm_save(c));
268         if (res) {
269             xyz.pkt[i] = c;
270         } else {
271             ZM_DEBUG(zm_dump(__LINE__));
272             return xyzModem_timeout;
273         }
274     }
275     res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.crc1);
276     ZM_DEBUG(zm_save(xyz.crc1));
277     if (!res) {
278         ZM_DEBUG(zm_dump(__LINE__));
279         return xyzModem_timeout;
280     }
281     if (xyz.crc_mode) {
282         res = CYGACC_COMM_IF_GETC_TIMEOUT(*xyz.__chan, &xyz.crc2);
283         ZM_DEBUG(zm_save(xyz.crc2));
284         if (!res) {
285             ZM_DEBUG(zm_dump(__LINE__));
286             return xyzModem_timeout;
287         }
288     }
289     ZM_DEBUG(zm_dump(__LINE__));
290     // Validate the message
291     if ((xyz.blk ^ xyz.cblk) != (unsigned char)0xFF) {
292         ZM_DEBUG(zm_dprintf("Framing error - blk: %x/%x/%x\n", xyz.blk, xyz.cblk, (xyz.blk ^ xyz.cblk)));
293         ZM_DEBUG(zm_dump_buf(xyz.pkt, xyz.len));
294         xyzModem_flush();
295         return xyzModem_frame;
296     }
297     // Verify checksum/CRC
298     if (xyz.crc_mode) {
299         cksum = cyg_crc16(xyz.pkt, xyz.len);
300         if (cksum != ((xyz.crc1 << 8) | xyz.crc2)) {
301             ZM_DEBUG(zm_dprintf("CRC error - recvd: %02x%02x, computed: %x\n", 
302                                 xyz.crc1, xyz.crc2, cksum & 0xFFFF));
303             return xyzModem_cksum;
304         }
305     } else {
306         cksum = 0;
307         for (i = 0;  i < xyz.len;  i++) {
308             cksum += xyz.pkt[i];
309         }
310         if (xyz.crc1 != (cksum & 0xFF)) {
311             ZM_DEBUG(zm_dprintf("Checksum error - recvd: %x, computed: %x\n", xyz.crc1, cksum & 0xFF));
312             return xyzModem_cksum;
313         }
314     }
315     // If we get here, the message passes [structural] muster
316     return 0;
317 }
318
319 int 
320 xyzModem_stream_open(connection_info_t *info, int *err)
321 {
322     int console_chan, stat=0;
323     int retries = xyzModem_MAX_RETRIES;
324     int crc_retries = xyzModem_MAX_RETRIES_WITH_CRC;
325
326 //    ZM_DEBUG(zm_out = zm_out_start);
327 #ifdef xyzModem_zmodem
328     if (info->mode == xyzModem_zmodem) {
329         *err = xyzModem_noZmodem;
330         return -1;
331     }
332 #endif
333
334     // Set up the I/O channel.  Note: this allows for using a different port in the future
335     console_chan = CYGACC_CALL_IF_SET_CONSOLE_COMM(CYGNUM_CALL_IF_SET_COMM_ID_QUERY_CURRENT);
336     if (info->chan >= 0) {
337         CYGACC_CALL_IF_SET_CONSOLE_COMM(info->chan);
338     } else {
339         CYGACC_CALL_IF_SET_CONSOLE_COMM(console_chan);
340     }
341     xyz.__chan = CYGACC_CALL_IF_CONSOLE_PROCS();
342     CYGACC_CALL_IF_SET_CONSOLE_COMM(console_chan);
343     CYGACC_COMM_IF_CONTROL(*xyz.__chan, __COMMCTL_SET_TIMEOUT, xyzModem_CHAR_TIMEOUT);
344     xyz.len = 0;
345     xyz.crc_mode = true;
346     xyz.at_eof = false;
347     xyz.tx_ack = false;
348     xyz.mode = info->mode;
349     xyz.total_retries = 0;
350     xyz.total_SOH = 0;
351     xyz.total_STX = 0;
352     xyz.total_CAN = 0;
353 #ifdef USE_YMODEM_LENGTH
354     xyz.read_length = 0;
355     xyz.file_length = 0;
356 #endif
357     
358     CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
359
360     if (xyz.mode == xyzModem_xmodem) {
361             // X-modem doesn't have an information header - exit here
362             xyz.next_blk = 1;
363             return 0;
364     }
365
366     while (retries-- > 0) {
367         stat = xyzModem_get_hdr();
368         if (stat == 0) {
369             // Y-modem file information header
370             if (xyz.blk == 0) {
371 #ifdef USE_YMODEM_LENGTH
372                 // skip filename
373                 while (*xyz.bufp++);
374                 // get the length
375                 parse_num((char *)xyz.bufp, &xyz.file_length, NULL, " ");
376 #endif
377                 // The rest of the file name data block quietly discarded
378                 xyz.tx_ack = true;
379             }
380             xyz.next_blk = 1;
381             xyz.len = 0;
382             return 0;
383         } else 
384         if (stat == xyzModem_timeout) {
385             if (--crc_retries <= 0) xyz.crc_mode = false;
386             CYGACC_CALL_IF_DELAY_US(5*100000);   // Extra delay for startup
387             CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
388             xyz.total_retries++;
389             ZM_DEBUG(zm_dprintf("NAK (%d)\n", __LINE__));
390         }
391         if (stat == xyzModem_cancel) {
392             break;
393         }
394     }
395     *err = stat;
396     ZM_DEBUG(zm_flush());
397     return -1;
398 }
399
400 int 
401 xyzModem_stream_read(void *buf, int size, int *err)
402 {
403     int stat, total, len;
404     int retries;
405
406     total = 0;
407     stat = xyzModem_cancel;
408     // Try and get 'size' bytes into the buffer
409     while (!xyz.at_eof && (size > 0)) {
410         if (xyz.len == 0) {
411             retries = xyzModem_MAX_RETRIES;
412             while (retries-- > 0) {
413                 stat = xyzModem_get_hdr();
414                 if (stat == 0) {
415                     if (xyz.blk == xyz.next_blk) {
416                         xyz.tx_ack = true;
417                         ZM_DEBUG(zm_dprintf("ACK block %d (%d)\n", xyz.blk, __LINE__));
418                         xyz.next_blk = (xyz.next_blk + 1) & 0xFF;
419
420 #if defined(xyzModem_zmodem) || defined(USE_YMODEM_LENGTH)
421                         if (xyz.mode == xyzModem_xmodem || xyz.file_length == 0) {
422 #else
423                         if (1) {
424 #endif
425                             // Data blocks can be padded with ^Z (EOF) characters
426                             // This code tries to detect and remove them
427                             if ((xyz.bufp[xyz.len-1] == EOF) &&
428                                 (xyz.bufp[xyz.len-2] == EOF) &&
429                                 (xyz.bufp[xyz.len-3] == EOF)) {
430                                 while (xyz.len && (xyz.bufp[xyz.len-1] == EOF)) {
431                                     xyz.len--;
432                                 }
433                             }
434                         }
435
436 #ifdef USE_YMODEM_LENGTH
437                         // See if accumulated length exceeds that of the file.
438                         // If so, reduce size (i.e., cut out pad bytes)
439                         // Only do this for Y-modem (and Z-modem should it ever
440                         // be supported since it can fall back to Y-modem mode).
441                         if (xyz.mode != xyzModem_xmodem && 0 != xyz.file_length) {
442                             xyz.read_length += xyz.len;
443                             if (xyz.read_length > xyz.file_length) {
444                                 xyz.len -= (xyz.read_length - xyz.file_length);
445                             }
446                         }
447 #endif
448                         break;
449                     } else if (xyz.blk == ((xyz.next_blk - 1) & 0xFF)) {
450                         // Just re-ACK this so sender will get on with it
451                         CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
452                         continue;  // Need new header
453                     } else {
454                         stat = xyzModem_sequence;
455                     }
456                 }
457                 if (stat == xyzModem_cancel) {
458                     break;
459                 }
460                 if (stat == xyzModem_eof) {
461                     CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
462                     ZM_DEBUG(zm_dprintf("ACK (%d)\n", __LINE__));
463                     if (xyz.mode == xyzModem_ymodem) {
464                         CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
465                         xyz.total_retries++;
466                         ZM_DEBUG(zm_dprintf("Reading Final Header\n"));
467                         stat = xyzModem_get_hdr();                        
468                         CYGACC_COMM_IF_PUTC(*xyz.__chan, ACK);
469                         ZM_DEBUG(zm_dprintf("FINAL ACK (%d)\n", __LINE__));
470                     }
471                     xyz.at_eof = true;
472                     break;
473                 }
474                 CYGACC_COMM_IF_PUTC(*xyz.__chan, (xyz.crc_mode ? 'C' : NAK));
475                 xyz.total_retries++;
476                 ZM_DEBUG(zm_dprintf("NAK (%d)\n", __LINE__));
477             }
478             if (stat < 0) {
479                 *err = stat;
480                 xyz.len = -1;
481                 return total;
482             }
483         }
484         // Don't "read" data from the EOF protocol package
485         if (!xyz.at_eof) {
486             len = xyz.len;
487             if (size < len) len = size;
488             memcpy(buf, xyz.bufp, len);
489             size -= len;
490             buf = (char *)buf + len;
491             total += len;
492             xyz.len -= len;
493             xyz.bufp += len;
494         }
495     }
496     return total;
497 }
498
499 void
500 xyzModem_stream_close(int *err)
501 {
502     diag_printf("xyzModem - %s mode, %d(SOH)/%d(STX)/%d(CAN) packets, %d retries\n", 
503                 xyz.crc_mode ? "CRC" : "Cksum",
504                 xyz.total_SOH, xyz.total_STX, xyz.total_CAN,
505                 xyz.total_retries);
506 //    ZM_DEBUG(zm_flush());
507 }
508
509 // Need to be able to clean out the input buffer, so have to take the
510 // getc
511 void xyzModem_stream_terminate(bool abort, int (*getc)(void))
512 {
513   int c;
514
515   if (abort) {
516       ZM_DEBUG(zm_dprintf("!!!! TRANSFER ABORT !!!!\n"));
517       switch (xyz.mode) {
518         case xyzModem_xmodem:
519         case xyzModem_ymodem:
520           // The X/YMODEM Spec seems to suggest that multiple CAN followed by an equal
521           // number of Backspaces is a friendly way to get the other end to abort.
522           CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
523           CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
524           CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
525           CYGACC_COMM_IF_PUTC(*xyz.__chan,CAN);
526           CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
527           CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
528           CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
529           CYGACC_COMM_IF_PUTC(*xyz.__chan,BSP);
530           // Now consume the rest of what's waiting on the line.
531           ZM_DEBUG(zm_dprintf("Flushing serial line.\n"));
532           xyzModem_flush();
533           xyz.at_eof = true;
534         break;
535 #ifdef xyzModem_zmodem
536         case xyzModem_zmodem:
537           // Might support it some day I suppose.
538 #endif
539         break;
540       }
541   } else {
542       ZM_DEBUG(zm_dprintf("Engaging cleanup mode...\n"));
543       // Consume any trailing crap left in the inbuffer from
544       // previous recieved blocks. Since very few files are an exact multiple
545       // of the transfer block size, there will almost always be some gunk here.
546       // If we don't eat it now, RedBoot will think the user typed it.
547       ZM_DEBUG(zm_dprintf("Trailing gunk:\n"));
548       while ((c = (*getc)()) > -1) ;
549       ZM_DEBUG(zm_dprintf("\n"));
550       // Make a small delay to give terminal programs like minicom
551       // time to get control again after their file transfer program
552       // exits.
553       CYGACC_CALL_IF_DELAY_US((cyg_int32)250000);
554   }
555 }
556
557 char *
558 xyzModem_error(int err)
559 {
560     switch (err) {
561     case xyzModem_access:
562         return "Can't access file";
563         break;
564     case xyzModem_noZmodem:
565         return "Sorry, zModem not available yet";
566         break;
567     case xyzModem_timeout:
568         return "Timed out";
569         break;
570     case xyzModem_eof:
571         return "End of file";
572         break;
573     case xyzModem_cancel:
574         return "Cancelled";
575         break;
576     case xyzModem_frame:
577         return "Invalid framing";
578         break;
579     case xyzModem_cksum:
580         return "CRC/checksum error";
581         break;
582     case xyzModem_sequence:
583         return "Block sequence error";
584         break;
585     default:
586         return "Unknown error";
587         break;
588     }
589 }
590
591 //
592 // RedBoot interface
593 //
594 GETC_IO_FUNCS(xyzModem_io, xyzModem_stream_open, xyzModem_stream_close,
595               xyzModem_stream_terminate, xyzModem_stream_read, xyzModem_error);
596 RedBoot_load(xmodem, xyzModem_io, false, false, xyzModem_xmodem);
597 RedBoot_load(ymodem, xyzModem_io, false, false, xyzModem_ymodem);