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