5861ac9acc9a07890116d1d462c3c9e1b13ec04b
[bertos.git] / mware / xmodem.c
1 /*!
2  * \file
3  * <!--
4  * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
5  * Copyright 1999, 2001 Bernardo Innocenti <bernie@develer.com>
6  * This file is part of DevLib - See devlib/README for information.
7  * -->
8  * \brief X-Modem serial transmission protocol (implementation)
9  *
10  * Suppots the CRC-16 and 1K-blocks variants of the standard.
11  * \see ymodem.txt for the protocol description.
12  *
13  * \version $Id$
14  * \author Bernardo Innocenti <bernie@develer.com>
15  */
16
17 /*
18  * $Log$
19  * Revision 1.5  2004/08/12 23:46:21  bernie
20  * Remove extra indentation level in switch statements.
21  *
22  * Revision 1.4  2004/08/12 23:35:50  bernie
23  * Replace a handmade loop with memset().
24  *
25  * Revision 1.3  2004/08/12 23:34:36  bernie
26  * Replace if/else with continue to reduce indentation level.
27  *
28  * Revision 1.2  2004/08/12 23:24:07  bernie
29  * Rename UPDCRC() to UPDCRC16().
30  *
31  * Revision 1.1  2004/08/11 19:54:22  bernie
32  * Import XModem protocol into DevLib.
33  *
34  */
35
36 #include "xmodem.h"
37
38 #include <drv/ser.h>
39 #include <drv/lcd.h>
40 #include <drv/buzzer.h>
41 #include <mware/crc.h>
42 #include <mware/kfile.h>
43
44 #include <string.h> /* for memset() */
45
46
47 /*!
48  * \name Protocol control codes
49  * \{
50  */
51 #define XM_SOH  0x01  /*!< Start Of Header (128-byte block) */
52 #define XM_STX  0x02  /*!< Start Of Header (1024-byte block) */
53 #define XM_EOT  0x04  /*!< End Of Transmission */
54 #define XM_ACK  0x06  /*!< Acknowledge block */
55 #define XM_NAK  0x15  /*!< Negative Acknowledge */
56 #define XM_C    0x43  /*!< Request CRC-16 transmission */
57 #define XM_CAN  0x18  /*!< CANcel transmission */
58 /*\}*/
59
60 #define XM_MAXRETRIES     15  /*!< Max retries before giving up */
61 #define XM_MAXCRCRETRIES   7  /*!< Max retries before switching to BCC */
62 #define XM_BUFSIZE      1024  /*!< Size of block buffer */
63
64
65 #if (ARCH & ARCH_BOOT)
66         #include "kbdhw.h"
67         #if (ARCH & ARCH_SLIM)
68                 #define CHECK_ABORT             KEYPRESSED_STOP
69         #elif (ARCH & ARCH_SARF)
70                 #define CHECK_ABORT             KEYPRESSED_ESC
71         #endif
72 #else
73         #include "kbd.h"
74         #if (ARCH & ARCH_SLIM)
75                 #define CHECK_ABORT             (kbd_getchar() == K_STOP)
76         #elif (ARCH & ARCH_SARF)
77                 #define CHECK_ABORT             (kbd_getchar() == K_ESC)
78         #endif
79 #endif /* ARCH_BOOT */
80
81
82 /*! Buffer to hold a block of data */
83 static char block_buffer[XM_BUFSIZE];
84
85
86 /*!
87  * Decode serial driver errors and print
88  * them on the display.
89  */
90 static void SerialError(int retries)
91 {
92         serstatus_t err, status;
93
94         /* Get serial error code and reset it */
95         status = ser_getstatus();
96         ser_setstatus(0);
97
98         /* Mostra tutti gli errori in sequenza */
99         for (err = 0; status != 0; status >>= 1, err++)
100         {
101                 /* Se il bit dell'errore e' settato */
102                 if (status & 1)
103                 {
104                         lcd_printf(0, 3, LCD_FILL, "%s %d", serial_errors[err], retries);
105                         buz_beep(200);
106                         timer_delay(500);
107                 }
108         }
109 }
110
111
112 /*!
113  * Reset previous serial errors and flush the receive buffer
114  * (set a short timeout to speed up purge)
115  */
116 static void FlushSerial(void)
117 {
118         ser_setstatus(0);
119         ser_settimeouts(200, SER_DEFTXTIMEOUT);
120         while (ser_getchar() != EOF) {}
121         ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
122         ser_setstatus(0);
123 }
124
125
126 bool xmodem_recv(KFile *fd)
127 {
128         int c, i, blocksize;
129         int blocknr = 0, last_block_done = 0, retries = 0;
130         char *buf;
131         uint8_t checksum;
132         uint16_t crc;
133         bool purge = false;
134         bool usecrc = true;
135
136
137         lcd_printf(0, 2, LCD_FILL, "Starting Transfer...");
138         lcd_clear();
139         purge = true;
140         ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
141         ser_setstatus(0);
142
143         /* Send initial NAK to start transmission */
144         for(;;)
145         {
146                 if (CHECK_ABORT)
147                 {
148                         ser_putchar(XM_CAN);
149                         ser_putchar(XM_CAN);
150                         lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
151                         return false;
152                 }
153
154                 /*
155                  * Discard incoming input until a timeout occurs, then send
156                  * a NAK to the transmitter.
157                  */
158                 if (purge)
159                 {
160                         purge = false;
161
162                         if (ser_getstatus())
163                                 SerialError(retries);
164
165                         FlushSerial();
166                         retries++;
167
168                         if (retries >= XM_MAXRETRIES)
169                         {
170                                 ser_putchar(XM_CAN);
171                                 ser_putchar(XM_CAN);
172                                 lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
173                                 return false;
174                         }
175
176                         /* Transmission start? */
177                         if (blocknr == 0)
178                         {
179                                 if (retries < XM_MAXCRCRETRIES)
180                                 {
181                                         lcd_printf(0, 2, LCD_FILL, "Request Tx (CRC)");
182                                         ser_putchar(XM_C);
183                                 }
184                                 else
185                                 {
186                                         /* Give up with CRC and fall back to checksum */
187                                         usecrc = false;
188                                         lcd_printf(0, 2, LCD_FILL, "Request Tx (BCC)");
189                                         ser_putchar(XM_NAK);
190                                 }
191                         }
192                         else
193                                 ser_putchar(XM_NAK);
194                 }
195
196                 switch (ser_getchar())
197                 {
198                 case XM_STX:  /* Start of header (1024-byte block) */
199                         blocksize = 1024;
200                         goto getblock;
201
202                 case XM_SOH:  /* Start of header (128-byte block) */
203                         blocksize = 128;
204
205                 getblock:
206                         /* Get block number */
207                         c = ser_getchar();
208
209                         /* Check complemented block number */
210                         if ((~c & 0xff) != ser_getchar())
211                         {
212                                 lcd_printf(0, 3, LCD_FILL, "Bad blk (%d)", c);
213                                 purge = true;
214                                 break;
215                         }
216
217                         /* Determine which block is being sent */
218                         if (c == (blocknr & 0xff))
219                                 /* Last block repeated */
220                                 lcd_printf(0, 2, LCD_FILL, "Repeat blk %d", blocknr);
221                         else if (c == ((blocknr + 1) & 0xff))
222                                 /* Next block */
223                                 lcd_printf(0, 2, LCD_FILL, "Recv blk %d", ++blocknr);
224                         else
225                         {
226                                 /* Sync lost */
227                                 lcd_printf(0, 3, LCD_FILL, "Sync lost (%d/%d)", c, blocknr);
228                                 purge = true;
229                                 break;
230                         }
231
232                         buf = block_buffer;     /* Reset pointer to start of buffer */
233                         checksum = 0;
234                         crc = 0;
235                         for (i = 0; i < blocksize; i++)
236                         {
237                                 if ((c = ser_getchar()) == EOF)
238                                 {
239                                         purge = true;
240                                         break;
241                                 }
242
243                                 /* Store in buffer */
244                                 *buf++ = (char)c;
245
246                                 /* Calculate block checksum or CRC */
247                                 if (usecrc)
248                                         crc = UPDCRC16(c, crc);
249                                 else
250                                         checksum += (char)c;
251                         }
252
253                         if (purge)
254                                 break;
255
256                         /* Get the checksum byte or the CRC-16 MSB */
257                         if ((c = ser_getchar()) == EOF)
258                         {
259                                 purge = true;
260                                 break;
261                         }
262
263                         if (usecrc)
264                         {
265                                 crc = UPDCRC16(c, crc);
266
267                                 /* Get CRC-16 LSB */
268                                 if ((c = ser_getchar()) == EOF)
269                                 {
270                                         purge = true;
271                                         break;
272                                 }
273
274                                 crc = UPDCRC16(c, crc);
275
276                                 if (crc)
277                                 {
278                                         lcd_printf(0, 3, LCD_FILL, "Bad CRC: %04x", crc);
279                                         purge = true;
280                                         break;
281                                 }
282                         }
283                         /* Compare the checksum */
284                         else if (c != checksum)
285                         {
286                                 lcd_printf(0, 3, LCD_FILL, "Bad sum: %04x/%04x", checksum, c);
287                                 purge = true;
288                                 break;
289                         }
290
291                         /*
292                          * Avoid flushing the same block twice.
293                          * This could happen when the sender does not receive our
294                          * acknowledge and resends the same block.
295                          */
296                         if (last_block_done < blocknr)
297                         {
298                                 /* Call user function to flush the buffer */
299                                 if (fd->write(fd, block_buffer, blocksize))
300                                 {
301                                         /* Acknowledge block and clear error counter */
302                                         ser_putchar(XM_ACK);
303                                         retries = 0;
304                                         last_block_done = blocknr;
305                                 }
306                                 else
307                                 {
308                                         /* User callback failed: abort transfer immediately */
309                                         retries = XM_MAXRETRIES;
310                                         purge = true;
311                                 }
312                         }
313                         break;
314
315                 case XM_EOT:    /* End of transmission */
316                         ser_putchar(XM_ACK);
317                         lcd_printf(0, 2, LCD_FILL, "Transfer completed");
318                         return true;
319
320                 case EOF: /* Timeout or serial error */
321                         purge = true;
322                         break;
323
324                 default:
325                         lcd_printf(0, 3, LCD_FILL, "Skipping garbage");
326                         purge = true;
327                         break;
328                 }
329         } /* End forever */
330 }
331
332
333 bool xmodem_send(KFile *fd)
334 {
335         size_t size = -1;
336         int blocknr = 1, retries = 0, c, i;
337         bool proceed, usecrc = false;
338         uint16_t crc;
339         uint8_t sum;
340
341
342         ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
343         ser_setstatus(0);
344         FlushSerial();
345         lcd_printf(0, 2, LCD_FILL, "Wait remote host");
346
347         for(;;)
348         {
349                 proceed = false;
350                 do
351                 {
352                         if (CHECK_ABORT)
353                                 return false;
354
355                         switch (c = ser_getchar())
356                         {
357                         case XM_NAK:
358                         case XM_C:
359                                 if (blocknr == 1)
360                                 {
361                                         if (c == XM_C)
362                                         {
363                                                 lcd_printf(0, 2, LCD_FILL, "Tx start (CRC)");
364                                                 usecrc = true;
365                                         }
366                                         else
367                                                 lcd_printf(0, 2, LCD_FILL, "Tx start (BCC)");
368
369                                         /* Call user function to read in one block */
370                                         size = fd->read(fd, block_buffer, XM_BUFSIZE);
371                                 }
372                                 else
373                                         lcd_printf(0, 2, LCD_FILL, "Resend blk %d", blocknr);
374                                 proceed = true;
375                                 break;
376
377                         case XM_ACK:
378                                 /* End of transfer? */
379                                 if (!size)
380                                         return true;
381
382                                 /* Call user function to read in one block */
383                                 size = fd->read(fd, block_buffer, XM_BUFSIZE);
384                                 blocknr++;
385                                 retries = 0;
386                                 proceed = true;
387                                 lcd_printf(0, 2, LCD_FILL, "Send blk %d", blocknr);
388                                 break;
389
390                         case EOF:
391                                 retries++;
392                                 SerialError(retries);
393                                 if (retries <= XM_MAXRETRIES)
394                                         break;
395                                 /* falling through! */
396
397                         case XM_CAN:
398                                 lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
399                                 return false;
400
401                         default:
402                                 lcd_printf(0, 3, LCD_FILL, "Skipping garbage");
403                                 break;
404                         }
405                 }
406                 while (!proceed);
407
408                 if (!size)
409                 {
410                         ser_putchar(XM_EOT);
411                         continue;
412                 }
413
414                 /* Pad block with 0xFF if it's partially full */
415                 memset(block_buffer + size, 0xFF, XM_BUFSIZE - size);
416
417                 /* Send block header (STX, blocknr, ~blocknr) */
418                 ser_putchar(XM_STX);
419                 ser_putchar(blocknr & 0xFF);
420                 ser_putchar(~blocknr & 0xFF);
421
422                 /* Send block and compute its CRC/checksum */
423                 sum = 0;
424                 crc = 0;
425                 for (i = 0; i < XM_BUFSIZE; i++)
426                 {
427                         ser_putchar(block_buffer[i]);
428                         crc = UPDCRC16(block_buffer[i], crc);
429                         sum += block_buffer[i];
430                 }
431
432                 /* Send CRC/Checksum */
433                 if (usecrc)
434                 {
435                         crc = UPDCRC16(0, crc);
436                         crc = UPDCRC16(0, crc);
437                         ser_putchar(crc >> 8);
438                         ser_putchar(crc & 0xFF);
439                 }
440                 else
441                         ser_putchar(sum);
442         }
443 }