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