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