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