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