c3410cafed8db3b8581d248100ac816d73c00c34
[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.11  2007/06/07 09:10:44  batt
26  *#* Fix some todos.
27  *#*
28  *#* Revision 1.10  2006/07/19 12:56:28  bernie
29  *#* Convert to new Doxygen style.
30  *#*
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 <string.h> /* for memset() */
67 #include <algos/crc.h>
68
69
70 /**
71  * \name Protocol control codes
72  * \{
73  */
74 #define XM_SOH  0x01  /**< Start Of Header (128-byte block) */
75 #define XM_STX  0x02  /**< Start Of Header (1024-byte block) */
76 #define XM_EOT  0x04  /**< End Of Transmission */
77 #define XM_ACK  0x06  /**< Acknowledge block */
78 #define XM_NAK  0x15  /**< Negative Acknowledge */
79 #define XM_C    0x43  /**< Request CRC-16 transmission */
80 #define XM_CAN  0x18  /**< CANcel transmission */
81 /*\}*/
82
83 #define XM_MAXRETRIES     15  /**< Max retries before giving up */
84 #define XM_MAXCRCRETRIES   7  /**< Max retries before switching to BCC */
85
86 #if CONFIG_XMODEM_1KCRC == 1
87         #define XM_BUFSIZE       1024  /**< 1024 bytes of block buffer */
88 #else
89         #define XM_BUFSIZE       128   /**< 128 bytes of block buffer */
90 #endif
91
92
93 #if CONFIG_XMODEM_RECV
94 /**
95  * \brief Receive a file using the XModem protocol.
96  *
97  * \param port Serial port to use for transfer
98  * \param fd Destination file
99  *
100  * \note This function allocates a large amount of stack (\see XM_BUFSIZE).
101  */
102 bool xmodem_recv(struct Serial *port, KFile *fd)
103 {
104         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
105         int c, i, blocksize;
106         int blocknr = 0, last_block_done = 0, retries = 0;
107         char *buf;
108         uint8_t checksum;
109         uint16_t crc;
110         bool purge = false;
111         bool usecrc = true;
112
113
114         XMODEM_PROGRESS("Starting Transfer...\n");
115         purge = true;
116         ser_setstatus(port, 0);
117
118         /* Send initial NAK to start transmission */
119         for(;;)
120         {
121                 if (XMODEM_CHECK_ABORT)
122                 {
123                         ser_putchar(XM_CAN, port);
124                         ser_putchar(XM_CAN, port);
125                         XMODEM_PROGRESS("Transfer aborted\n");
126                         return false;
127                 }
128
129                 /*
130                  * Discard incoming input until a timeout occurs, then send
131                  * a NAK to the transmitter.
132                  */
133                 if (purge)
134                 {
135                         purge = false;
136
137                         if (ser_getstatus(port))
138                                 XMODEM_PROGRESS("Retries %d\n", retries);
139
140                         ser_resync(port, 200);
141                         retries++;
142
143                         if (retries >= XM_MAXRETRIES)
144                         {
145                                 ser_putchar(XM_CAN, port);
146                                 ser_putchar(XM_CAN, port);
147                                 XMODEM_PROGRESS("Transfer aborted\n");
148                                 return false;
149                         }
150
151                         /* Transmission start? */
152                         if (blocknr == 0)
153                         {
154                                 if (retries < XM_MAXCRCRETRIES)
155                                 {
156                                         XMODEM_PROGRESS("Request Tx (CRC)\n");
157                                         ser_putchar(XM_C, port);
158                                 }
159                                 else
160                                 {
161                                         /* Give up with CRC and fall back to checksum */
162                                         usecrc = false;
163                                         XMODEM_PROGRESS("Request Tx (BCC)");
164                                         ser_putchar(XM_NAK, port);
165                                 }
166                         }
167                         else
168                                 ser_putchar(XM_NAK, port);
169                 }
170
171                 switch (ser_getchar(port))
172                 {
173                 #if XM_BUFSIZE >= 1024
174                 case XM_STX:  /* Start of header (1024-byte block) */
175                         blocksize = 1024;
176                         goto getblock;
177                 #endif
178
179                 case XM_SOH:  /* Start of header (128-byte block) */
180                         blocksize = 128;
181
182                 getblock:
183                         /* Get block number */
184                         c = ser_getchar(port);
185
186                         /* Check complemented block number */
187                         if ((~c & 0xff) != ser_getchar(port))
188                         {
189                                 XMODEM_PROGRESS("Bad blk (%d)\n", c);
190                                 purge = true;
191                                 break;
192                         }
193
194                         /* Determine which block is being sent */
195                         if (c == (blocknr & 0xff))
196                                 /* Last block repeated */
197                                 XMODEM_PROGRESS("Repeat blk %d\n", blocknr);
198                         else if (c == ((blocknr + 1) & 0xff))
199                                 /* Next block */
200                                 XMODEM_PROGRESS("Recv blk %d\n", ++blocknr);
201                         else
202                         {
203                                 /* Sync lost */
204                                 XMODEM_PROGRESS("Sync lost (%d/%d)\n", c, blocknr);
205                                 purge = true;
206                                 break;
207                         }
208
209                         buf = block_buffer;     /* Reset pointer to start of buffer */
210                         checksum = 0;
211                         crc = 0;
212                         for (i = 0; i < blocksize; i++)
213                         {
214                                 if ((c = ser_getchar(port)) == EOF)
215                                 {
216                                         purge = true;
217                                         break;
218                                 }
219
220                                 /* Store in buffer */
221                                 *buf++ = (char)c;
222
223                                 /* Calculate block checksum or CRC */
224                                 if (usecrc)
225                                         crc = UPDCRC16(c, crc);
226                                 else
227                                         checksum += (char)c;
228                         }
229
230                         if (purge)
231                                 break;
232
233                         /* Get the checksum byte or the CRC-16 MSB */
234                         if ((c = ser_getchar(port)) == EOF)
235                         {
236                                 purge = true;
237                                 break;
238                         }
239
240                         if (usecrc)
241                         {
242                                 crc = UPDCRC16(c, crc);
243
244                                 /* Get CRC-16 LSB */
245                                 if ((c = ser_getchar(port)) == EOF)
246                                 {
247                                         purge = true;
248                                         break;
249                                 }
250
251                                 crc = UPDCRC16(c, crc);
252
253                                 if (crc)
254                                 {
255                                         XMODEM_PROGRESS("Bad CRC: %04x\n", crc);
256                                         purge = true;
257                                         break;
258                                 }
259                         }
260                         /* Compare the checksum */
261                         else if (c != checksum)
262                         {
263                                 XMODEM_PROGRESS("Bad sum: %04x/%04x\n", checksum, c);
264                                 purge = true;
265                                 break;
266                         }
267
268                         /*
269                          * Avoid flushing the same block twice.
270                          * This could happen when the sender does not receive our
271                          * acknowledge and resends the same block.
272                          */
273                         if (last_block_done < blocknr)
274                         {
275                                 /* Call user function to flush the buffer */
276                                 if (fd->write(fd, block_buffer, blocksize))
277                                 {
278                                         /* Acknowledge block and clear error counter */
279                                         ser_putchar(XM_ACK, port);
280                                         retries = 0;
281                                         last_block_done = blocknr;
282                                 }
283                                 else
284                                 {
285                                         /* User callback failed: abort transfer immediately */
286                                         retries = XM_MAXRETRIES;
287                                         purge = true;
288                                 }
289                         }
290                         break;
291
292                 case XM_EOT:    /* End of transmission */
293                         ser_putchar(XM_ACK, port);
294                         XMODEM_PROGRESS("Transfer completed");
295                         return true;
296
297                 case EOF: /* Timeout or serial error */
298                         purge = true;
299                         break;
300
301                 default:
302                         XMODEM_PROGRESS("Skipping garbage");
303                         purge = true;
304                         break;
305                 }
306         } /* End forever */
307 }
308 #endif
309
310
311 #if CONFIG_XMODEM_SEND
312 /**
313  * \brief Transmit some data using the XModem protocol.
314  *
315  * \param port Serial port to use for transfer
316  * \param fd Source file
317  *
318  * \note This function allocates a large amount of stack for
319  *       the XModem transfer buffer (\see XM_BUFSIZE).
320  */
321 bool xmodem_send(struct Serial *port, KFile *fd)
322 {
323         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
324         size_t size = -1;
325         int blocknr = 1, retries = 0, c, i;
326         bool proceed, usecrc = false;
327         uint16_t crc;
328         uint8_t sum;
329
330         /*
331          * Reading a block can be very slow, so we read the first block early
332          * to avoid receiving double XM_C char.
333          * This could happen if we check for XM_C and then read the block, giving
334          * the receiving device time to send another XM_C char misinterpretating
335          * the blocks sent.
336          */
337         size = fd->read(fd, block_buffer, XM_BUFSIZE);
338
339         ser_setstatus(port, 0);
340         ser_purge(port);
341         XMODEM_PROGRESS("Wait remote host\n");
342
343         for(;;)
344         {
345                 proceed = false;
346                 do
347                 {
348                         if (XMODEM_CHECK_ABORT)
349                                 return false;
350
351                         switch (c = ser_getchar(port))
352                         {
353                         case XM_NAK:
354                                 XMODEM_PROGRESS("Resend blk %d\n", blocknr);
355                                 proceed = true;
356                                 break;
357
358                         case XM_C:
359                                 if (c == XM_C)
360                                 {
361                                         XMODEM_PROGRESS("Tx start (CRC)\n");
362                                         usecrc = true;
363                                 }
364                                 else
365                                         XMODEM_PROGRESS("Tx start (BCC)\n");
366
367                                 proceed = true;
368                                 break;
369
370                         case XM_ACK:
371                                 /* End of transfer? */
372                                 if (!size)
373                                         return true;
374
375                                 /* Call user function to read in one block */
376                                 size = fd->read(fd, block_buffer, XM_BUFSIZE);
377                                 XMODEM_PROGRESS("Send blk %d\n", blocknr);
378                                 blocknr++;
379                                 retries = 0;
380                                 proceed = true;
381                                 break;
382
383                         case EOF:
384                                 ser_setstatus(port, 0);
385                                 retries++;
386                                 XMODEM_PROGRESS("Retries %d\n", retries);
387                                 if (retries <= XM_MAXRETRIES)
388                                         break;
389                                 /* falling through! */
390
391                         case XM_CAN:
392                                 XMODEM_PROGRESS("Transfer aborted\n");
393                                 return false;
394
395                         default:
396                                 XMODEM_PROGRESS("Skipping garbage\n");
397                                 break;
398                         }
399                 }
400                 while (!proceed);
401
402                 if (!size)
403                 {
404                         ser_putchar(XM_EOT, port);
405                         continue;
406                 }
407
408                 /* Pad block with 0xFF if it's partially full */
409                 memset(block_buffer + size, 0xFF, XM_BUFSIZE - size);
410
411                 /* Send block header (STX, blocknr, ~blocknr) */
412                 #if XM_BUFSIZE == 128
413                         ser_putchar(XM_SOH, port);
414                 #else
415                         ser_putchar(XM_STX, port);
416                 #endif
417                 ser_putchar(blocknr & 0xFF, port);
418                 ser_putchar(~blocknr & 0xFF, port);
419
420                 /* Send block and compute its CRC/checksum */
421                 sum = 0;
422                 crc = 0;
423                 for (i = 0; i < XM_BUFSIZE; i++)
424                 {
425                         ser_putchar(block_buffer[i], port);
426                         crc = UPDCRC16(block_buffer[i], crc);
427                         sum += block_buffer[i];
428                 }
429
430                 /* Send CRC/Checksum */
431                 if (usecrc)
432                 {
433                         crc = UPDCRC16(0, crc);
434                         crc = UPDCRC16(0, crc);
435                         ser_putchar(crc >> 8, port);
436                         ser_putchar(crc & 0xFF, port);
437                 }
438                 else
439                         ser_putchar(sum, port);
440         }
441 }
442 #endif