Refactor BeRTOS to be in his own directory.
[bertos.git] / bertos / mware / xmodem.c
1 /**
2  * \file
3  * <!--
4  * This file is part of BeRTOS.
5  *
6  * Bertos is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  * As a special exception, you may use this file as part of a free software
21  * library without restriction.  Specifically, if other files instantiate
22  * templates or use macros or inline functions from this file, or you compile
23  * this file and link it with other files to produce an executable, this
24  * file does not by itself cause the resulting executable to be covered by
25  * the GNU General Public License.  This exception does not however
26  * invalidate any other reasons why the executable file might be covered by
27  * the GNU General Public License.
28  *
29  * Copyright 2004, 2005, 2006, 2007 Develer S.r.l. (http://www.develer.com/)
30  * Copyright 1999, 2001 Bernardo Innocenti <bernie@develer.com>
31  *
32  * -->
33  * \brief X-Modem serial transmission protocol (implementation)
34  *
35  * Supports the CRC-16 and 1K-blocks variants of the standard.
36  * \see ymodem.txt for the protocol description.
37  *
38  * \todo Break xmodem_send() and xmodem_recv() in smaller functions.
39  *
40  * \todo Maybe convert drv/ser.c to the KFile interface for symmetry and
41  *       flexibility.
42  *
43  * \version $Id$
44  * \author Bernardo Innocenti <bernie@develer.com>
45  * \author Francesco Sacchi <batt@develer.com>
46  */
47
48
49 #include "xmodem.h"
50
51 #include <appconfig.h>
52 #include <string.h> /* for memset() */
53 #include <drv/ser.h>
54 #include <algo/crc.h>
55 #include <cfg/debug.h>
56
57
58
59
60 /**
61  * \name Protocol control codes
62  * \{
63  */
64 #define XM_SOH  0x01  /**< Start Of Header (128-byte block) */
65 #define XM_STX  0x02  /**< Start Of Header (1024-byte block) */
66 #define XM_EOT  0x04  /**< End Of Transmission */
67 #define XM_ACK  0x06  /**< Acknowledge block */
68 #define XM_NAK  0x15  /**< Negative Acknowledge */
69 #define XM_C    0x43  /**< Request CRC-16 transmission */
70 #define XM_CAN  0x18  /**< CANcel transmission */
71 /*\}*/
72
73 #define XM_MAXRETRIES     15  /**< Max retries before giving up */
74 #define XM_MAXCRCRETRIES   7  /**< Max retries before switching to BCC */
75
76 #if CONFIG_XMODEM_1KCRC == 1
77         #define XM_BUFSIZE       1024  /**< 1024 bytes of block buffer */
78 #else
79         #define XM_BUFSIZE       128   /**< 128 bytes of block buffer */
80 #endif
81
82
83 #if CONFIG_XMODEM_RECV
84 /**
85  * \brief Receive a file using the XModem protocol.
86  *
87  * \param port Serial port to use for transfer
88  * \param fd Destination file
89  *
90  * \note This function allocates a large amount of stack (\see XM_BUFSIZE).
91  */
92 bool xmodem_recv(struct Serial *port, KFile *fd)
93 {
94         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
95         int c, i, blocksize;
96         int blocknr = 0, last_block_done = 0, retries = 0;
97         char *buf;
98         uint8_t checksum;
99         uint16_t crc;
100         bool purge = false;
101         bool usecrc = true;
102
103
104         XMODEM_PROGRESS("Starting Transfer...\n");
105         purge = true;
106         ser_setstatus(port, 0);
107
108         /* Send initial NAK to start transmission */
109         for(;;)
110         {
111                 if (XMODEM_CHECK_ABORT)
112                 {
113                         ser_putchar(XM_CAN, port);
114                         ser_putchar(XM_CAN, port);
115                         XMODEM_PROGRESS("Transfer aborted\n");
116                         return false;
117                 }
118
119                 /*
120                  * Discard incoming input until a timeout occurs, then send
121                  * a NAK to the transmitter.
122                  */
123                 if (purge)
124                 {
125                         purge = false;
126
127                         if (ser_getstatus(port))
128                                 XMODEM_PROGRESS("Retries %d\n", retries);
129
130                         ser_resync(port, 200);
131                         retries++;
132
133                         if (retries >= XM_MAXRETRIES)
134                         {
135                                 ser_putchar(XM_CAN, port);
136                                 ser_putchar(XM_CAN, port);
137                                 XMODEM_PROGRESS("Transfer aborted\n");
138                                 return false;
139                         }
140
141                         /* Transmission start? */
142                         if (blocknr == 0)
143                         {
144                                 if (retries < XM_MAXCRCRETRIES)
145                                 {
146                                         XMODEM_PROGRESS("Request Tx (CRC)\n");
147                                         ser_putchar(XM_C, port);
148                                 }
149                                 else
150                                 {
151                                         /* Give up with CRC and fall back to checksum */
152                                         usecrc = false;
153                                         XMODEM_PROGRESS("Request Tx (BCC)\n");
154                                         ser_putchar(XM_NAK, port);
155                                 }
156                         }
157                         else
158                                 ser_putchar(XM_NAK, port);
159                 }
160
161                 switch (ser_getchar(port))
162                 {
163                 #if XM_BUFSIZE >= 1024
164                 case XM_STX:  /* Start of header (1024-byte block) */
165                         blocksize = 1024;
166                         goto getblock;
167                 #endif
168
169                 case XM_SOH:  /* Start of header (128-byte block) */
170                         blocksize = 128;
171                         /* Needed to avoid warning if XM_BUFSIZE < 1024 */
172                         goto getblock;
173                 getblock:
174                         /* Get block number */
175                         c = ser_getchar(port);
176
177                         /* Check complemented block number */
178                         if ((~c & 0xff) != ser_getchar(port))
179                         {
180                                 XMODEM_PROGRESS("Bad blk (%d)\n", c);
181                                 purge = true;
182                                 break;
183                         }
184
185                         /* Determine which block is being sent */
186                         if (c == (blocknr & 0xff))
187                                 /* Last block repeated */
188                                 XMODEM_PROGRESS("Repeat blk %d\n", blocknr);
189                         else if (c == ((blocknr + 1) & 0xff))
190                                 /* Next block */
191                                 XMODEM_PROGRESS("Recv blk %d\n", ++blocknr);
192                         else
193                         {
194                                 /* Sync lost */
195                                 XMODEM_PROGRESS("Sync lost (%d/%d)\n", c, blocknr);
196                                 purge = true;
197                                 break;
198                         }
199
200                         buf = block_buffer;     /* Reset pointer to start of buffer */
201                         checksum = 0;
202                         crc = 0;
203                         for (i = 0; i < blocksize; i++)
204                         {
205                                 if ((c = ser_getchar(port)) == EOF)
206                                 {
207                                         purge = true;
208                                         break;
209                                 }
210
211                                 /* Store in buffer */
212                                 *buf++ = (char)c;
213
214                                 /* Calculate block checksum or CRC */
215                                 if (usecrc)
216                                         crc = UPDCRC16(c, crc);
217                                 else
218                                         checksum += (char)c;
219                         }
220
221                         if (purge)
222                                 break;
223
224                         /* Get the checksum byte or the CRC-16 MSB */
225                         if ((c = ser_getchar(port)) == EOF)
226                         {
227                                 purge = true;
228                                 break;
229                         }
230
231                         if (usecrc)
232                         {
233                                 crc = UPDCRC16(c, crc);
234
235                                 /* Get CRC-16 LSB */
236                                 if ((c = ser_getchar(port)) == EOF)
237                                 {
238                                         purge = true;
239                                         break;
240                                 }
241
242                                 crc = UPDCRC16(c, crc);
243
244                                 if (crc)
245                                 {
246                                         XMODEM_PROGRESS("Bad CRC: %04x\n", crc);
247                                         purge = true;
248                                         break;
249                                 }
250                         }
251                         /* Compare the checksum */
252                         else if (c != checksum)
253                         {
254                                 XMODEM_PROGRESS("Bad sum: %04x/%04x\n", checksum, c);
255                                 purge = true;
256                                 break;
257                         }
258
259                         /*
260                          * Avoid flushing the same block twice.
261                          * This could happen when the sender does not receive our
262                          * acknowledge and resends the same block.
263                          */
264                         if (last_block_done < blocknr)
265                         {
266                                 /* Call user function to flush the buffer */
267                                 if (fd->write(fd, block_buffer, blocksize))
268                                 {
269                                         /* Acknowledge block and clear error counter */
270                                         ser_putchar(XM_ACK, port);
271                                         retries = 0;
272                                         last_block_done = blocknr;
273                                 }
274                                 else
275                                 {
276                                         /* User callback failed: abort transfer immediately */
277                                         retries = XM_MAXRETRIES;
278                                         purge = true;
279                                 }
280                         }
281                         break;
282
283                 case XM_EOT:    /* End of transmission */
284                         ser_putchar(XM_ACK, port);
285                         XMODEM_PROGRESS("Transfer completed\n");
286                         return true;
287
288                 case EOF: /* Timeout or serial error */
289                         purge = true;
290                         break;
291
292                 default:
293                         XMODEM_PROGRESS("Skipping garbage\n");
294                         purge = true;
295                         break;
296                 }
297         } /* End forever */
298 }
299 #endif
300
301
302 #if CONFIG_XMODEM_SEND
303 /**
304  * \brief Transmit some data using the XModem protocol.
305  *
306  * \param port Serial port to use for transfer
307  * \param fd Source file
308  *
309  * \note This function allocates a large amount of stack for
310  *       the XModem transfer buffer (\see XM_BUFSIZE).
311  */
312 bool xmodem_send(struct Serial *port, KFile *fd)
313 {
314         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
315         size_t size = -1;
316         int blocknr = 1, retries = 0, c, i;
317         bool proceed, usecrc = false;
318         uint16_t crc;
319         uint8_t sum;
320
321         /*
322          * Reading a block can be very slow, so we read the first block early
323          * to avoid receiving double XM_C char.
324          * This could happen if we check for XM_C and then read the block, giving
325          * the receiving device time to send another XM_C char misinterpretating
326          * the blocks sent.
327          */
328         size = fd->read(fd, block_buffer, XM_BUFSIZE);
329
330         ser_setstatus(port, 0);
331         ser_purge(port);
332         XMODEM_PROGRESS("Wait remote host\n");
333
334         for(;;)
335         {
336                 proceed = false;
337                 do
338                 {
339                         if (XMODEM_CHECK_ABORT)
340                                 return false;
341
342                         switch (c = ser_getchar(port))
343                         {
344                         case XM_NAK:
345                                 XMODEM_PROGRESS("Resend blk %d\n", blocknr);
346                                 proceed = true;
347                                 break;
348
349                         case XM_C:
350                                 if (c == XM_C)
351                                 {
352                                         XMODEM_PROGRESS("Tx start (CRC)\n");
353                                         usecrc = true;
354                                 }
355                                 else
356                                         XMODEM_PROGRESS("Tx start (BCC)\n");
357
358                                 proceed = true;
359                                 break;
360
361                         case XM_ACK:
362                                 /* End of transfer? */
363                                 if (!size)
364                                         return true;
365
366                                 /* Call user function to read in one block */
367                                 size = fd->read(fd, block_buffer, XM_BUFSIZE);
368                                 XMODEM_PROGRESS("Send blk %d\n", blocknr);
369                                 blocknr++;
370                                 retries = 0;
371                                 proceed = true;
372                                 break;
373
374                         case EOF:
375                                 ser_setstatus(port, 0);
376                                 retries++;
377                                 XMODEM_PROGRESS("Retries %d\n", retries);
378                                 if (retries <= XM_MAXRETRIES)
379                                         break;
380                                 /* falling through! */
381
382                         case XM_CAN:
383                                 XMODEM_PROGRESS("Transfer aborted\n");
384                                 return false;
385
386                         default:
387                                 XMODEM_PROGRESS("Skipping garbage\n");
388                                 break;
389                         }
390                 }
391                 while (!proceed);
392
393                 if (!size)
394                 {
395                         ser_putchar(XM_EOT, port);
396                         continue;
397                 }
398
399                 /* Pad block with 0xFF if it's partially full */
400                 memset(block_buffer + size, 0xFF, XM_BUFSIZE - size);
401
402                 /* Send block header (STX, blocknr, ~blocknr) */
403                 #if XM_BUFSIZE == 128
404                         ser_putchar(XM_SOH, port);
405                 #else
406                         ser_putchar(XM_STX, port);
407                 #endif
408                 ser_putchar(blocknr & 0xFF, port);
409                 ser_putchar(~blocknr & 0xFF, port);
410
411                 /* Send block and compute its CRC/checksum */
412                 sum = 0;
413                 crc = 0;
414                 for (i = 0; i < XM_BUFSIZE; i++)
415                 {
416                         ser_putchar(block_buffer[i], port);
417                         crc = UPDCRC16(block_buffer[i], crc);
418                         sum += block_buffer[i];
419                 }
420
421                 /* Send CRC/Checksum */
422                 if (usecrc)
423                 {
424                         crc = UPDCRC16(0, crc);
425                         crc = UPDCRC16(0, crc);
426                         ser_putchar(crc >> 8, port);
427                         ser_putchar(crc & 0xFF, port);
428                 }
429                 else
430                         ser_putchar(sum, port);
431         }
432 }
433 #endif