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