Write little test for serial.
[bertos.git] / 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  *#* $Log$
50  *#* Revision 1.12  2007/10/05 09:14:15  batt
51  *#* Fix some debug strings; suppress warning if XM_BUFSIZE < 1024.
52  *#*
53  *#* Revision 1.11  2007/06/07 09:10:44  batt
54  *#* Fix some todos.
55  *#*
56  *#* Revision 1.10  2006/07/19 12:56:28  bernie
57  *#* Convert to new Doxygen style.
58  *#*
59  *#* Revision 1.9  2005/11/04 16:20:02  bernie
60  *#* Fix reference to README.devlib in header.
61  *#*
62  *#* Revision 1.8  2004/08/25 14:12:09  rasky
63  *#* Aggiornato il comment block dei log RCS
64  *#*
65  *#* Revision 1.7  2004/08/15 06:30:06  bernie
66  *#* Make the buffer a local variable, as documented.
67  *#*
68  *#* Revision 1.6  2004/08/15 05:31:46  bernie
69  *#* Add an #error to spread some FUD about the quality of this module;
70  *#* Add a few TODOs from Rasky's review;
71  *#* Update to the new drv/ser.c API;
72  *#* Move FlushSerial() to drv/ser.c and generalize.
73  *#*
74  *#* Revision 1.5  2004/08/12 23:46:21  bernie
75  *#* Remove extra indentation level in switch statements.
76  *#*
77  *#* Revision 1.4  2004/08/12 23:35:50  bernie
78  *#* Replace a handmade loop with memset().
79  *#*
80  *#* Revision 1.3  2004/08/12 23:34:36  bernie
81  *#* Replace if/else with continue to reduce indentation level.
82  *#*
83  *#* Revision 1.2  2004/08/12 23:24:07  bernie
84  *#* Rename UPDCRC() to UPDCRC16().
85  *#*
86  *#* Revision 1.1  2004/08/11 19:54:22  bernie
87  *#* Import XModem protocol into DevLib.
88  *#*
89  *#*/
90
91 #include "xmodem.h"
92
93 #include <appconfig.h>
94 #include <string.h> /* for memset() */
95 #include <drv/ser.h>
96 #include <algos/crc.h>
97 #include <cfg/debug.h>
98
99
100
101
102 /**
103  * \name Protocol control codes
104  * \{
105  */
106 #define XM_SOH  0x01  /**< Start Of Header (128-byte block) */
107 #define XM_STX  0x02  /**< Start Of Header (1024-byte block) */
108 #define XM_EOT  0x04  /**< End Of Transmission */
109 #define XM_ACK  0x06  /**< Acknowledge block */
110 #define XM_NAK  0x15  /**< Negative Acknowledge */
111 #define XM_C    0x43  /**< Request CRC-16 transmission */
112 #define XM_CAN  0x18  /**< CANcel transmission */
113 /*\}*/
114
115 #define XM_MAXRETRIES     15  /**< Max retries before giving up */
116 #define XM_MAXCRCRETRIES   7  /**< Max retries before switching to BCC */
117
118 #if CONFIG_XMODEM_1KCRC == 1
119         #define XM_BUFSIZE       1024  /**< 1024 bytes of block buffer */
120 #else
121         #define XM_BUFSIZE       128   /**< 128 bytes of block buffer */
122 #endif
123
124
125 #if CONFIG_XMODEM_RECV
126 /**
127  * \brief Receive a file using the XModem protocol.
128  *
129  * \param port Serial port to use for transfer
130  * \param fd Destination file
131  *
132  * \note This function allocates a large amount of stack (\see XM_BUFSIZE).
133  */
134 bool xmodem_recv(struct Serial *port, KFile *fd)
135 {
136         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
137         int c, i, blocksize;
138         int blocknr = 0, last_block_done = 0, retries = 0;
139         char *buf;
140         uint8_t checksum;
141         uint16_t crc;
142         bool purge = false;
143         bool usecrc = true;
144
145
146         XMODEM_PROGRESS("Starting Transfer...\n");
147         purge = true;
148         ser_setstatus(port, 0);
149
150         /* Send initial NAK to start transmission */
151         for(;;)
152         {
153                 if (XMODEM_CHECK_ABORT)
154                 {
155                         ser_putchar(XM_CAN, port);
156                         ser_putchar(XM_CAN, port);
157                         XMODEM_PROGRESS("Transfer aborted\n");
158                         return false;
159                 }
160
161                 /*
162                  * Discard incoming input until a timeout occurs, then send
163                  * a NAK to the transmitter.
164                  */
165                 if (purge)
166                 {
167                         purge = false;
168
169                         if (ser_getstatus(port))
170                                 XMODEM_PROGRESS("Retries %d\n", retries);
171
172                         ser_resync(port, 200);
173                         retries++;
174
175                         if (retries >= XM_MAXRETRIES)
176                         {
177                                 ser_putchar(XM_CAN, port);
178                                 ser_putchar(XM_CAN, port);
179                                 XMODEM_PROGRESS("Transfer aborted\n");
180                                 return false;
181                         }
182
183                         /* Transmission start? */
184                         if (blocknr == 0)
185                         {
186                                 if (retries < XM_MAXCRCRETRIES)
187                                 {
188                                         XMODEM_PROGRESS("Request Tx (CRC)\n");
189                                         ser_putchar(XM_C, port);
190                                 }
191                                 else
192                                 {
193                                         /* Give up with CRC and fall back to checksum */
194                                         usecrc = false;
195                                         XMODEM_PROGRESS("Request Tx (BCC)\n");
196                                         ser_putchar(XM_NAK, port);
197                                 }
198                         }
199                         else
200                                 ser_putchar(XM_NAK, port);
201                 }
202
203                 switch (ser_getchar(port))
204                 {
205                 #if XM_BUFSIZE >= 1024
206                 case XM_STX:  /* Start of header (1024-byte block) */
207                         blocksize = 1024;
208                         goto getblock;
209                 #endif
210
211                 case XM_SOH:  /* Start of header (128-byte block) */
212                         blocksize = 128;
213                         /* Needed to avoid warning if XM_BUFSIZE < 1024 */
214                         goto getblock;
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                                 XMODEM_PROGRESS("Bad blk (%d)\n", 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                                 XMODEM_PROGRESS("Repeat blk %d\n", blocknr);
231                         else if (c == ((blocknr + 1) & 0xff))
232                                 /* Next block */
233                                 XMODEM_PROGRESS("Recv blk %d\n", ++blocknr);
234                         else
235                         {
236                                 /* Sync lost */
237                                 XMODEM_PROGRESS("Sync lost (%d/%d)\n", 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                                         XMODEM_PROGRESS("Bad CRC: %04x\n", crc);
289                                         purge = true;
290                                         break;
291                                 }
292                         }
293                         /* Compare the checksum */
294                         else if (c != checksum)
295                         {
296                                 XMODEM_PROGRESS("Bad sum: %04x/%04x\n", 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                         XMODEM_PROGRESS("Transfer completed\n");
328                         return true;
329
330                 case EOF: /* Timeout or serial error */
331                         purge = true;
332                         break;
333
334                 default:
335                         XMODEM_PROGRESS("Skipping garbage\n");
336                         purge = true;
337                         break;
338                 }
339         } /* End forever */
340 }
341 #endif
342
343
344 #if CONFIG_XMODEM_SEND
345 /**
346  * \brief Transmit some data using the XModem protocol.
347  *
348  * \param port Serial port to use for transfer
349  * \param fd Source file
350  *
351  * \note This function allocates a large amount of stack for
352  *       the XModem transfer buffer (\see XM_BUFSIZE).
353  */
354 bool xmodem_send(struct Serial *port, KFile *fd)
355 {
356         char block_buffer[XM_BUFSIZE]; /* Buffer to hold a block of data */
357         size_t size = -1;
358         int blocknr = 1, retries = 0, c, i;
359         bool proceed, usecrc = false;
360         uint16_t crc;
361         uint8_t sum;
362
363         /*
364          * Reading a block can be very slow, so we read the first block early
365          * to avoid receiving double XM_C char.
366          * This could happen if we check for XM_C and then read the block, giving
367          * the receiving device time to send another XM_C char misinterpretating
368          * the blocks sent.
369          */
370         size = fd->read(fd, block_buffer, XM_BUFSIZE);
371
372         ser_setstatus(port, 0);
373         ser_purge(port);
374         XMODEM_PROGRESS("Wait remote host\n");
375
376         for(;;)
377         {
378                 proceed = false;
379                 do
380                 {
381                         if (XMODEM_CHECK_ABORT)
382                                 return false;
383
384                         switch (c = ser_getchar(port))
385                         {
386                         case XM_NAK:
387                                 XMODEM_PROGRESS("Resend blk %d\n", blocknr);
388                                 proceed = true;
389                                 break;
390
391                         case XM_C:
392                                 if (c == XM_C)
393                                 {
394                                         XMODEM_PROGRESS("Tx start (CRC)\n");
395                                         usecrc = true;
396                                 }
397                                 else
398                                         XMODEM_PROGRESS("Tx start (BCC)\n");
399
400                                 proceed = true;
401                                 break;
402
403                         case XM_ACK:
404                                 /* End of transfer? */
405                                 if (!size)
406                                         return true;
407
408                                 /* Call user function to read in one block */
409                                 size = fd->read(fd, block_buffer, XM_BUFSIZE);
410                                 XMODEM_PROGRESS("Send blk %d\n", blocknr);
411                                 blocknr++;
412                                 retries = 0;
413                                 proceed = true;
414                                 break;
415
416                         case EOF:
417                                 ser_setstatus(port, 0);
418                                 retries++;
419                                 XMODEM_PROGRESS("Retries %d\n", retries);
420                                 if (retries <= XM_MAXRETRIES)
421                                         break;
422                                 /* falling through! */
423
424                         case XM_CAN:
425                                 XMODEM_PROGRESS("Transfer aborted\n");
426                                 return false;
427
428                         default:
429                                 XMODEM_PROGRESS("Skipping garbage\n");
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                 #if XM_BUFSIZE == 128
446                         ser_putchar(XM_SOH, port);
447                 #else
448                         ser_putchar(XM_STX, port);
449                 #endif
450                 ser_putchar(blocknr & 0xFF, port);
451                 ser_putchar(~blocknr & 0xFF, port);
452
453                 /* Send block and compute its CRC/checksum */
454                 sum = 0;
455                 crc = 0;
456                 for (i = 0; i < XM_BUFSIZE; i++)
457                 {
458                         ser_putchar(block_buffer[i], port);
459                         crc = UPDCRC16(block_buffer[i], crc);
460                         sum += block_buffer[i];
461                 }
462
463                 /* Send CRC/Checksum */
464                 if (usecrc)
465                 {
466                         crc = UPDCRC16(0, crc);
467                         crc = UPDCRC16(0, crc);
468                         ser_putchar(crc >> 8, port);
469                         ser_putchar(crc & 0xFF, port);
470                 }
471                 else
472                         ser_putchar(sum, port);
473         }
474 }
475 #endif