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