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