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