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