--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2004 Develer S.r.l. (http://www.develer.com/)
+ * Copyright 1999, 2001 Bernardo Innocenti <bernie@develer.com>
+ * This file is part of DevLib - See devlib/README for information.
+ * -->
+ * \brief X-Modem serial transmission protocol (implementation)
+ *
+ * Suppots the CRC-16 and 1K-blocks variants of the standard.
+ * \see ymodem.txt for the protocol description.
+ *
+ * \version $Id$
+ * \author Bernardo Innocenti <bernie@develer.com>
+ */
+
+/*
+ * $Log$
+ * Revision 1.1 2004/08/11 19:54:22 bernie
+ * Import XModem protocol into DevLib.
+ *
+ */
+
+#include "xmodem.h"
+
+#include <drv/ser.h>
+#include <drv/lcd.h>
+#include <drv/buzzer.h>
+#include <mware/crc.h>
+#include <mware/kfile.h>
+
+
+/*!
+ * \name Protocol control codes
+ * \{
+ */
+#define XM_SOH 0x01 /*!< Start Of Header (128-byte block) */
+#define XM_STX 0x02 /*!< Start Of Header (1024-byte block) */
+#define XM_EOT 0x04 /*!< End Of Transmission */
+#define XM_ACK 0x06 /*!< Acknowledge block */
+#define XM_NAK 0x15 /*!< Negative Acknowledge */
+#define XM_C 0x43 /*!< Request CRC-16 transmission */
+#define XM_CAN 0x18 /*!< CANcel transmission */
+/*\}*/
+
+#define XM_MAXRETRIES 15 /*!< Max retries before giving up */
+#define XM_MAXCRCRETRIES 7 /*!< Max retries before switching to BCC */
+#define XM_BUFSIZE 1024 /*!< Size of block buffer */
+
+
+#if (ARCH & ARCH_BOOT)
+ #include "kbdhw.h"
+ #if (ARCH & ARCH_SLIM)
+ #define CHECK_ABORT KEYPRESSED_STOP
+ #elif (ARCH & ARCH_SARF)
+ #define CHECK_ABORT KEYPRESSED_ESC
+ #endif
+#else
+ #include "kbd.h"
+ #if (ARCH & ARCH_SLIM)
+ #define CHECK_ABORT (kbd_getchar() == K_STOP)
+ #elif (ARCH & ARCH_SARF)
+ #define CHECK_ABORT (kbd_getchar() == K_ESC)
+ #endif
+#endif /* ARCH_BOOT */
+
+
+/*! Buffer to hold a block of data */
+static char block_buffer[XM_BUFSIZE];
+
+
+/*!
+ * Decode serial driver errors and print
+ * them on the display.
+ */
+static void SerialError(int retries)
+{
+ serstatus_t err, status;
+
+ /* Get serial error code and reset it */
+ status = ser_getstatus();
+ ser_setstatus(0);
+
+ /* Mostra tutti gli errori in sequenza */
+ for (err = 0; status != 0; status >>= 1, err++)
+ {
+ /* Se il bit dell'errore e' settato */
+ if (status & 1)
+ {
+ lcd_printf(0, 3, LCD_FILL, "%s %d", serial_errors[err], retries);
+ buz_beep(200);
+ timer_delay(500);
+ }
+ }
+}
+
+
+/*!
+ * Reset previous serial errors and flush the receive buffer
+ * (set a short timeout to speed up purge)
+ */
+static void FlushSerial(void)
+{
+ ser_setstatus(0);
+ ser_settimeouts(200, SER_DEFTXTIMEOUT);
+ while (ser_getchar() != EOF) {}
+ ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
+ ser_setstatus(0);
+}
+
+
+bool xmodem_recv(KFile *fd)
+{
+ int c, i, blocksize;
+ int blocknr = 0, last_block_done = 0, retries = 0;
+ char *buf;
+ uint8_t checksum;
+ uint16_t crc;
+ bool purge = false;
+ bool usecrc = true;
+
+
+ lcd_printf(0, 2, LCD_FILL, "Starting Transfer...");
+ lcd_clear();
+ purge = true;
+ ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
+ ser_setstatus(0);
+
+ /* Send initial NAK to start transmission */
+ for(;;)
+ {
+ if (CHECK_ABORT)
+ {
+ ser_putchar(XM_CAN);
+ ser_putchar(XM_CAN);
+ lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
+ return false;
+ }
+
+ /*
+ * Discard incoming input until a timeout occurs, then send
+ * a NAK to the transmitter.
+ */
+ if (purge)
+ {
+ purge = false;
+
+ if (ser_getstatus())
+ SerialError(retries);
+
+ FlushSerial();
+ retries++;
+
+ if (retries >= XM_MAXRETRIES)
+ {
+ ser_putchar(XM_CAN);
+ ser_putchar(XM_CAN);
+ lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
+ return false;
+ }
+
+ /* Transmission start? */
+ if (blocknr == 0)
+ {
+ if (retries < XM_MAXCRCRETRIES)
+ {
+ lcd_printf(0, 2, LCD_FILL, "Request Tx (CRC)");
+ ser_putchar(XM_C);
+ }
+ else
+ {
+ /* Give up with CRC and fall back to checksum */
+ usecrc = false;
+ lcd_printf(0, 2, LCD_FILL, "Request Tx (BCC)");
+ ser_putchar(XM_NAK);
+ }
+ }
+ else
+ ser_putchar(XM_NAK);
+ }
+
+ switch (ser_getchar())
+ {
+ case XM_STX: /* Start of header (1024-byte block) */
+ blocksize = 1024;
+ goto getblock;
+
+ case XM_SOH: /* Start of header (128-byte block) */
+ blocksize = 128;
+
+ getblock:
+ /* Get block number */
+ c = ser_getchar();
+
+ /* Check complemented block number */
+ if ((~c & 0xff) != ser_getchar())
+ {
+ lcd_printf(0, 3, LCD_FILL, "Bad blk (%d)", c);
+ purge = true;
+ break;
+ }
+
+ /* Determine which block is being sent */
+ if (c == (blocknr & 0xff))
+ /* Last block repeated */
+ lcd_printf(0, 2, LCD_FILL, "Repeat blk %d", blocknr);
+ else if (c == ((blocknr + 1) & 0xff))
+ /* Next block */
+ lcd_printf(0, 2, LCD_FILL, "Recv blk %d", ++blocknr);
+ else
+ {
+ /* Sync lost */
+ lcd_printf(0, 3, LCD_FILL, "Sync lost (%d/%d)", c, blocknr);
+ purge = true;
+ break;
+ }
+
+ buf = block_buffer; /* Reset pointer to start of buffer */
+ checksum = 0;
+ crc = 0;
+ for (i = 0; i < blocksize; i++)
+ {
+ if ((c = ser_getchar()) == EOF)
+ {
+ purge = true;
+ break;
+ }
+
+ /* Store in buffer */
+ *buf++ = (char)c;
+
+ /* Calculate block checksum or CRC */
+ if (usecrc)
+ crc = UPDCRC(c, crc);
+ else
+ checksum += (char)c;
+ }
+
+ if (purge)
+ break;
+
+ /* Get the checksum byte or the CRC-16 MSB */
+ if ((c = ser_getchar()) == EOF)
+ {
+ purge = true;
+ break;
+ }
+
+ if (usecrc)
+ {
+ crc = UPDCRC(c, crc);
+
+ /* Get CRC-16 LSB */
+ if ((c = ser_getchar()) == EOF)
+ {
+ purge = true;
+ break;
+ }
+
+ crc = UPDCRC(c, crc);
+
+ if (crc)
+ {
+ lcd_printf(0, 3, LCD_FILL, "Bad CRC: %04x", crc);
+ purge = true;
+ break;
+ }
+ }
+ /* Compare the checksum */
+ else if (c != checksum)
+ {
+ lcd_printf(0, 3, LCD_FILL, "Bad sum: %04x/%04x", checksum, c);
+ purge = true;
+ break;
+ }
+
+ /*
+ * Avoid flushing the same block twice.
+ * This could happen when the sender does not receive our
+ * acknowledge and resends the same block.
+ */
+ if (last_block_done < blocknr)
+ {
+ /* Call user function to flush the buffer */
+ if (fd->write(fd, block_buffer, blocksize))
+ {
+ /* Acknowledge block and clear error counter */
+ ser_putchar(XM_ACK);
+ retries = 0;
+ last_block_done = blocknr;
+ }
+ else
+ {
+ /* User callback failed: abort transfer immediately */
+ retries = XM_MAXRETRIES;
+ purge = true;
+ }
+ }
+ break;
+
+ case XM_EOT: /* End of transmission */
+ ser_putchar(XM_ACK);
+ lcd_printf(0, 2, LCD_FILL, "Transfer completed");
+ return true;
+
+ case EOF: /* Timeout or serial error */
+ purge = true;
+ break;
+
+ default:
+ lcd_printf(0, 3, LCD_FILL, "Skipping garbage");
+ purge = true;
+ break;
+ }
+ } /* End forever */
+}
+
+
+bool xmodem_send(KFile *fd)
+{
+ size_t size = -1;
+ int blocknr = 1, retries = 0, c, i;
+ bool proceed, usecrc = false;
+ uint16_t crc;
+ uint8_t sum;
+
+
+ ser_settimeouts(SER_DEFRXTIMEOUT, SER_DEFTXTIMEOUT);
+ ser_setstatus(0);
+ FlushSerial();
+ lcd_printf(0, 2, LCD_FILL, "Wait remote host");
+
+ for(;;)
+ {
+ proceed = false;
+ do
+ {
+ if (CHECK_ABORT)
+ return false;
+
+ switch (c = ser_getchar())
+ {
+ case XM_NAK:
+ case XM_C:
+ if (blocknr == 1)
+ {
+ if (c == XM_C)
+ {
+ lcd_printf(0, 2, LCD_FILL, "Tx start (CRC)");
+ usecrc = true;
+ }
+ else
+ lcd_printf(0, 2, LCD_FILL, "Tx start (BCC)");
+
+ /* Call user function to read in one block */
+ size = fd->read(fd, block_buffer, XM_BUFSIZE);
+ }
+ else
+ lcd_printf(0, 2, LCD_FILL, "Resend blk %d", blocknr);
+ proceed = true;
+ break;
+
+ case XM_ACK:
+ /* End of transfer? */
+ if (!size)
+ return true;
+
+ /* Call user function to read in one block */
+ size = fd->read(fd, block_buffer, XM_BUFSIZE);
+ blocknr++;
+ retries = 0;
+ proceed = true;
+ lcd_printf(0, 2, LCD_FILL, "Send blk %d", blocknr);
+ break;
+
+ case EOF:
+ retries++;
+ SerialError(retries);
+ if (retries <= XM_MAXRETRIES)
+ break;
+ /* falling through! */
+
+ case XM_CAN:
+ lcd_printf(0, 2, LCD_FILL, "Transfer aborted");
+ return false;
+
+ default:
+ lcd_printf(0, 3, LCD_FILL, "Skipping garbage");
+ break;
+ }
+ }
+ while (!proceed);
+
+ if (!size)
+ ser_putchar(XM_EOT);
+ else
+ {
+ /* Pad block with 0xFF if it's partially full */
+ if (size < XM_BUFSIZE)
+ for (i = size; i < XM_BUFSIZE; i++)
+ block_buffer[i] = (char)0xFF;
+
+ /* Send block header (STX, blocknr, ~blocknr) */
+ ser_putchar(XM_STX);
+ ser_putchar(blocknr & 0xFF);
+ ser_putchar(~blocknr & 0xFF);
+
+ /* Send block and compute its CRC/checksum */
+ sum = 0;
+ crc = 0;
+ for (i = 0; i < XM_BUFSIZE; i++)
+ {
+ ser_putchar(block_buffer[i]);
+ crc = UPDCRC(block_buffer[i], crc);
+ sum += block_buffer[i];
+ }
+
+ /* Send CRC/Checksum */
+ if (usecrc)
+ {
+ crc = UPDCRC(0, crc);
+ crc = UPDCRC(0, crc);
+ ser_putchar(crc >> 8);
+ ser_putchar(crc & 0xFF);
+ }
+ else
+ ser_putchar(sum);
+ }
+ }
+}