summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands/Kconfig1
-rw-r--r--commands/Makefile2
-rw-r--r--commands/loadxy.c238
-rw-r--r--include/xymodem.h25
-rw-r--r--lib/Kconfig3
-rw-r--r--lib/Makefile1
-rw-r--r--lib/xymodem.c597
7 files changed, 866 insertions, 1 deletions
diff --git a/commands/Kconfig b/commands/Kconfig
index 16706d36c3..ce04bb2835 100644
--- a/commands/Kconfig
+++ b/commands/Kconfig
@@ -261,6 +261,7 @@ config CMD_LOADB
config CMD_LOADY
select CRC16
+ select XYMODEM
tristate
prompt "loady"
diff --git a/commands/Makefile b/commands/Makefile
index 610be55c6a..2be5f9cf99 100644
--- a/commands/Makefile
+++ b/commands/Makefile
@@ -3,7 +3,7 @@ obj-$(CONFIG_CMD_BOOTM) += bootm.o
obj-$(CONFIG_CMD_UIMAGE) += uimage.o
obj-$(CONFIG_CMD_LINUX16) += linux16.o
obj-$(CONFIG_CMD_LOADB) += loadb.o xyzModem.o
-obj-$(CONFIG_CMD_LOADY) += loadb.o xyzModem.o
+obj-$(CONFIG_CMD_LOADY) += loadb.o xyzModem.o loadxy.o
obj-$(CONFIG_CMD_LOADS) += loads.o
obj-$(CONFIG_CMD_ECHO) += echo.o
obj-$(CONFIG_CMD_MEMORY) += mem.o
diff --git a/commands/loadxy.c b/commands/loadxy.c
new file mode 100644
index 0000000000..141bd7bf9e
--- /dev/null
+++ b/commands/loadxy.c
@@ -0,0 +1,238 @@
+/**
+ * @file
+ * @brief loady and loadx support.
+ *
+ * Provides loadx (over X-Modem) and loady(over Y-Modem) support to download
+ * images.
+ *
+ * FileName: commands/loadxy.c
+ */
+/*
+ * (C) Copyright 2012 Robert Jarzmik <robert.jarzmik@free.fr>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*
+ * Serial up- and download support
+ */
+#include <common.h>
+#include <command.h>
+#include <console.h>
+#include <xymodem.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <malloc.h>
+
+#define DEF_FILE "image.bin"
+
+/**
+ * @brief returns current used console device
+ *
+ * @return console device which is registered with CONSOLE_STDIN and
+ * CONSOLE_STDOUT
+ */
+static struct console_device *get_current_console(void)
+{
+ struct console_device *cdev;
+ /*
+ * Assumption to have BOTH CONSOLE_STDIN AND STDOUT in the
+ * same output console
+ */
+ for_each_console(cdev) {
+ if ((cdev->f_active & (CONSOLE_STDIN | CONSOLE_STDOUT)))
+ return cdev;
+ }
+ return NULL;
+}
+
+static int console_change_speed(struct console_device *cdev, int baudrate)
+{
+ int current_baudrate;
+
+ current_baudrate =
+ (int)simple_strtoul(dev_get_param(&cdev->class_dev,
+ "baudrate"), NULL, 10);
+ if (baudrate && baudrate != current_baudrate) {
+ printf("## Switch baudrate from %d to %d bps and press ENTER ...\n",
+ current_baudrate, baudrate);
+ mdelay(50);
+ cdev->setbrg(cdev, baudrate);
+ mdelay(50);
+ }
+ return current_baudrate;
+}
+
+/**
+ * @brief provide the loady(Y-Modem or Y-Modem/G) support
+ *
+ * @param argc number of arguments
+ * @param argv arguments of loady command
+ *
+ * @return success or failure
+ */
+static int do_loady(int argc, char *argv[])
+{
+ int is_ymodemg = 0, rc = 0, opt, rcode = 0;
+ int load_baudrate = 0, current_baudrate;
+ struct console_device *cdev = NULL;
+
+ while ((opt = getopt(argc, argv, "b:g")) > 0) {
+ switch (opt) {
+ case 'b':
+ load_baudrate = (int)simple_strtoul(optarg, NULL, 10);
+ break;
+ case 'g':
+ is_ymodemg = 1;
+ break;
+ default:
+ perror(argv[0]);
+ return 1;
+ }
+ }
+
+ cdev = get_current_console();
+ if (NULL == cdev) {
+ printf("%s:No console device with STDIN and STDOUT\n", argv[0]);
+ return -ENODEV;
+ }
+
+ current_baudrate = console_change_speed(cdev, load_baudrate);
+ printf("## Ready for binary (ymodem) download at %d bps...\n",
+ load_baudrate ? load_baudrate : current_baudrate);
+
+ if (is_ymodemg)
+ rc = do_load_serial_ymodemg(cdev);
+ else
+ rc = do_load_serial_ymodem(cdev);
+
+ if (rc < 0) {
+ printf("## Binary (ymodem) download aborted (%d)\n", rc);
+ rcode = 1;
+ }
+
+ console_change_speed(cdev, current_baudrate);
+
+ return rcode;
+}
+
+/**
+ * @brief provide the loadx(X-Modem) support
+ *
+ * @param argc number of arguments
+ * @param argv arguments of loadx command
+ *
+ * @return success or failure
+ */
+static int do_loadx(int argc, char *argv[])
+{
+ ulong offset = 0;
+ int load_baudrate = 0, current_baudrate, ofd, opt, rcode = 0;
+ int open_mode = O_WRONLY;
+ char *output_file = NULL;
+ struct console_device *cdev = NULL;
+
+ while ((opt = getopt(argc, argv, "f:b:o:c")) > 0) {
+ switch (opt) {
+ case 'f':
+ output_file = optarg;
+ break;
+ case 'b':
+ load_baudrate = (int)simple_strtoul(optarg, NULL, 10);
+ break;
+ case 'o':
+ offset = (int)simple_strtoul(optarg, NULL, 10);
+ break;
+ case 'c':
+ open_mode |= O_CREAT;
+ break;
+ default:
+ perror(argv[0]);
+ return 1;
+ }
+ }
+
+ cdev = get_current_console();
+ if (NULL == cdev) {
+ printf("%s:No console device with STDIN and STDOUT\n", argv[0]);
+ return -ENODEV;
+ }
+
+ /* Load Defaults */
+ if (NULL == output_file)
+ output_file = DEF_FILE;
+
+ /* File should exist */
+ ofd = open(output_file, open_mode);
+ if (ofd < 0) {
+ perror(argv[0]);
+ return 3;
+ }
+ /* Seek to the right offset */
+ if (offset) {
+ int seek = lseek(ofd, offset, SEEK_SET);
+ if (seek != offset) {
+ close(ofd);
+ ofd = 0;
+ perror(argv[0]);
+ return 4;
+ }
+ }
+
+ current_baudrate = console_change_speed(cdev, load_baudrate);
+ printf("## Ready for binary (X-Modem) download "
+ "to 0x%08lX offset on %s device at %d bps...\n", offset,
+ output_file, load_baudrate);
+ rcode = do_load_serial_ymodem(cdev);
+ if (rcode < 0) {
+ printf("## Binary (kermit) download aborted (%d)\n", rcode);
+ rcode = 1;
+ }
+ console_change_speed(cdev, current_baudrate);
+
+ return rcode;
+}
+
+static const __maybe_unused char cmd_loadx_help[] =
+ "[OPTIONS]\n"
+ " -f file - where to download to - defaults to " DEF_FILE "\n"
+ " -o offset - what offset to download - defaults to 0\n"
+ " -b baud - baudrate at which to download - defaults to "
+ "console baudrate\n"
+ " -c - Create file if it is not present - default disabled";
+
+#ifdef CONFIG_CMD_LOADB
+BAREBOX_CMD_START(loadx)
+ .cmd = do_loadx,
+ .usage = "Load binary file over serial line (X-Modem)",
+BAREBOX_CMD_HELP(cmd_loadx_help)
+BAREBOX_CMD_END
+#endif
+
+static const __maybe_unused char cmd_loady_help[] =
+ "[OPTIONS]\n"
+ " -y - use Y-Modem/G (only for lossless tty as USB)\n"
+ " -b baud - baudrate at which to download - defaults to "
+ "console baudrate\n";
+
+#ifdef CONFIG_CMD_LOADY
+BAREBOX_CMD_START(loady2)
+ .cmd = do_loady,
+ .usage = "Load binary file over serial line (Y-Modem or Y-Modem/G)",
+BAREBOX_CMD_HELP(cmd_loady_help)
+BAREBOX_CMD_END
+#endif
diff --git a/include/xymodem.h b/include/xymodem.h
new file mode 100644
index 0000000000..917cecc003
--- /dev/null
+++ b/include/xymodem.h
@@ -0,0 +1,25 @@
+/*
+ * Handles the X-Modem, Y-Modem and Y-Modem/G protocols
+ *
+ * Copyright (C) 2008 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _XYMODEM_
+#define _XYMODEM_
+struct xyz_ctxt;
+struct console_device;
+
+int do_load_serial_xmodem(struct console_device *cdev, int fd);
+int do_load_serial_ymodem(struct console_device *cdev);
+int do_load_serial_ymodemg(struct console_device *cdev);
+#endif
diff --git a/lib/Kconfig b/lib/Kconfig
index 9882d2d6d8..13ecab0fd6 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -38,6 +38,9 @@ config BITREV
config QSORT
bool
+config XYMODEM
+ bool
+
source lib/gui/Kconfig
endmenu
diff --git a/lib/Makefile b/lib/Makefile
index 41e6a0f92f..eb0af9248e 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_BCH) += bch.o
obj-$(CONFIG_BITREV) += bitrev.o
obj-$(CONFIG_QSORT) += qsort.o
obj-y += gui/
+obj-$(CONFIG_XYMODEM) += xymodem.o
diff --git a/lib/xymodem.c b/lib/xymodem.c
new file mode 100644
index 0000000000..558f6e8cf8
--- /dev/null
+++ b/lib/xymodem.c
@@ -0,0 +1,597 @@
+/*
+ * Handles the X-Modem, Y-Modem and Y-Modem/G protocols
+ *
+ * Copyright (C) 2008 Robert Jarzmik
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * This file provides functions to receive X-Modem or Y-Modem(/G) protocols.
+ *
+ * References:
+ * *-Modem: http://www.techfest.com/hardware/modem/xymodem.htm
+ * XMODEM/YMODEM PROTOCOL REFERENCE, Chuck Forsberg
+ */
+#include <common.h>
+#include <xfuncs.h>
+#include <errno.h>
+#include <crc.h>
+#include <clock.h>
+#include <console.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <kfifo.h>
+#include <linux/byteorder/generic.h>
+
+
+#define xy_dbg(fmt, args...)
+
+/* Values magic to the protocol */
+#define SOH 0x01
+#define STX 0x02
+#define EOT 0x04
+#define ACK 0x06
+#define BSP 0x08
+#define NAK 0x15
+#define CAN 0x18
+
+#define PROTO_XMODEM 0
+#define PROTO_YMODEM 1
+#define PROTO_YMODEM_G 2
+#define MAX_PROTOS 3
+
+#define CRC_NONE 0 /* No CRC checking */
+#define CRC_ADD8 1 /* Add of all data bytes */
+#define CRC_CRC16 2 /* CCCIT CRC16 */
+#define MAX_CRCS 3
+
+#define MAX_RETRIES 10
+#define MAX_RETRIES_WITH_CRC 5
+#define TIMEOUT_READ (1 * SECOND)
+#define TIMEOUT_FLUSH (1 * SECOND)
+#define MAX_CAN_BEFORE_ABORT 5
+#define INPUT_FIFO_SIZE (4 * 1024) /* Should always be > 1029 */
+
+enum proto_state {
+ PROTO_STATE_GET_FILENAME = 0,
+ PROTO_STATE_NEGOCIATE_CRC,
+ PROTO_STATE_RECEIVE_BODY,
+ PROTO_STATE_FINISHED_FILE,
+ PROTO_STATE_FINISHED_XFER,
+};
+
+/**
+ * struct xyz_ctxt - context of a x/y modem (g) transfer
+ *
+ * @cdev: console device to support *MODEM transfer
+ * @fifo: fifo to buffer input from serial line
+ * This is necessary for low hardware FIFOs buffers as UARTs.
+ * @mode: protocol (XMODEM, YMODEM or YMODEM/G)
+ * @crc_mode: CRC_NONE, CRC_ADD8 or CRC_CRC16
+ * @state: protocol state (as in "state machine")
+ * @buf: buffer to store the last tranfered buffer chunk
+ * @filename : filename transmitted by sender (YMODEM* only)
+ * @fd : file descriptor of the current stored file
+ * @file_len: length declared by sender (YMODEM* only)
+ * @nb_received: number of data bytes received since session open
+ * (this doesn't count resends)
+ * @total_SOH: number of SOH frames received (128 bytes chunks)
+ * @total_STX: number of STX frames received (1024 bytes chunks)
+ * @total_CAN: nubmer of CAN frames received (cancel frames)
+ */
+struct xyz_ctxt {
+ struct console_device *cdev;
+ struct kfifo *fifo;
+ int mode;
+ int crc_mode;
+ enum proto_state state;
+ char filename[1024];
+ int fd;
+ int file_len;
+ int nb_received;
+ int next_blk;
+ int total_SOH, total_STX, total_CAN, total_retries;
+};
+
+/**
+ * struct xy_block - one unitary block of x/y modem (g) transfer
+ *
+ * @buf: data buffer
+ * @len: length of data buffer (can only be 128 or 1024)
+ * @seq: block sequence number (as in X/Y/YG MODEM protocol)
+ */
+struct xy_block {
+ unsigned char buf[1024];
+ int len;
+ int seq;
+};
+
+/*
+ * For XMODEM/YMODEM, always try to use the CRC16 versions, called also
+ * XMODEM/CRC and YMODEM.
+ * Only fallback to additive CRC (8 bits) if sender doesn't cope with CRC16.
+ */
+static const char invite_filename_hdr[MAX_PROTOS][MAX_CRCS] = {
+ { 0, NAK, 'C' }, /* XMODEM */
+ { 0, NAK, 'C' }, /* YMODEM */
+ { 0, 'G', 'G' }, /* YMODEM-G */
+};
+
+static const char invite_file_body[MAX_PROTOS][MAX_CRCS] = {
+ { 0, NAK, 'C' }, /* XMODEM */
+ { 0, NAK, 'C' }, /* YMODEM */
+ { 0, 'G', 'G' }, /* YMODEM-G */
+};
+
+static const char block_ack[MAX_PROTOS][MAX_CRCS] = {
+ { 0, ACK, ACK }, /* XMODEM */
+ { 0, ACK, ACK }, /* YMODEM */
+ { 0, 0, 0 }, /* YMODEM-G */
+};
+
+static const char block_nack[MAX_PROTOS][MAX_CRCS] = {
+ { 0, NAK, NAK }, /* XMODEM */
+ { 0, NAK, NAK }, /* YMODEM */
+ { 0, 0, 0 }, /* YMODEM-G */
+};
+
+static int input_fifo_fill(struct console_device *cdev, struct kfifo *fifo)
+{
+ while (cdev->tstc(cdev) && kfifo_len(fifo) < INPUT_FIFO_SIZE)
+ kfifo_putc(fifo, (unsigned char)(cdev->getc(cdev)));
+ return kfifo_len(fifo);
+}
+
+/*
+ * This function is optimized to :
+ * - maximize throughput (ie. read as much as is available in lower layer fifo)
+ * - minimize latencies (no delay or wait timeout if data available)
+ * - have a timeout
+ * This is why standard getc() is not used, and input_fifo_fill() exists.
+ */
+static int xy_gets(struct console_device *cdev, struct kfifo *fifo,
+ unsigned char *buf, int len, uint64_t timeout)
+{
+ int i, rc;
+ uint64_t start = get_time_ns();
+
+ for (i = 0, rc = 0; rc >= 0 && i < len; ) {
+ if (is_timeout(start, timeout)) {
+ rc = -ETIMEDOUT;
+ continue;
+ }
+ if (input_fifo_fill(cdev, fifo))
+ kfifo_getc(fifo, &buf[i++]);
+ }
+
+ return rc < 0 ? rc : i;
+}
+
+static void xy_putc(struct console_device *cdev, unsigned char c)
+{
+ cdev->putc(cdev, c);
+}
+
+static void xy_flush(struct console_device *cdev, struct kfifo *fifo)
+{
+ uint64_t start;
+
+ start = get_time_ns();
+ while (cdev->tstc(cdev) &&
+ !is_timeout(start, TIMEOUT_FLUSH))
+ cdev->getc(cdev);
+ mdelay(250);
+ while (cdev->tstc(cdev) &&
+ !is_timeout(start, TIMEOUT_FLUSH))
+ cdev->getc(cdev);
+ kfifo_reset(fifo);
+}
+
+static int is_xmodem(struct xyz_ctxt *proto)
+{
+ return proto->mode == PROTO_XMODEM;
+}
+
+static void xy_block_ack(struct xyz_ctxt *proto)
+{
+ unsigned char c = block_ack[proto->mode][proto->crc_mode];
+
+ if (c)
+ xy_putc(proto->cdev, c);
+}
+
+static void xy_block_nack(struct xyz_ctxt *proto)
+{
+ unsigned char c = block_nack[proto->mode][proto->crc_mode];
+
+ if (c)
+ xy_putc(proto->cdev, c);
+ proto->total_retries++;
+}
+
+static int check_crc(unsigned char *buf, int len, int crc, int crc_mode)
+{
+ unsigned char crc8 = 0;
+ uint16_t crc16;
+ int i;
+
+ switch (crc_mode) {
+ case CRC_ADD8:
+ for (i = 0; i < len; i++)
+ crc8 += buf[i];
+ return crc8 == crc ? 0 : -EBADMSG;
+ case CRC_CRC16:
+ crc16 = cyg_crc16(buf, len);
+ xy_dbg("crc16: received = %x, calculated=%x\n", crc, crc16);
+ return crc16 == crc ? 0 : -EBADMSG;
+ case CRC_NONE:
+ return 0;
+ default:
+ return -EBADMSG;
+ }
+}
+
+/**
+ * xy_read_block - read a X-Modem or Y-Modem(G) block
+ * @proto: protocol control structure
+ * @blk: block read
+ * @timeout: maximal time to get data
+ *
+ * This is the pivotal function for block receptions. It attempts to receive one
+ * block, ie. one 128 bytes or one 1024 bytes block. The received data can also
+ * be an end of transmission, or a cancel.
+ *
+ * Returns :
+ * >0 : size of the received block
+ * 0 : last block, ie. end of transmission, ie. EOT
+ * -EBADMSG : malformed message (ie. sequence bi-bytes are not
+ * complementary), or CRC check error
+ * -EILSEQ : block sequence number error wrt previously received block
+ * -ETIMEDOUT : block not received before timeout passed
+ * -ECONNABORTED : transfer aborted by sender, ie. CAN
+ */
+static ssize_t xy_read_block(struct xyz_ctxt *proto, struct xy_block *blk,
+ uint64_t timeout)
+{
+ ssize_t rc, data_len = 0;
+ unsigned char hdr, seqs[2], crcs[2];
+ int crc = 0;
+ bool hdr_found = 0;
+ uint64_t start = get_time_ns();
+
+ while (!hdr_found) {
+ rc = xy_gets(proto->cdev, proto->fifo, &hdr, 1, timeout);
+ xy_dbg("read 0x%x(%c) -> %d\n", hdr, hdr, rc);
+ if (rc < 0)
+ goto out;
+ if (is_timeout(start, timeout))
+ goto timeout;
+ switch (hdr) {
+ case SOH:
+ data_len = 128;
+ hdr_found = 1;
+ proto->total_SOH++;
+ break;
+ case STX:
+ data_len = 1024;
+ hdr_found = 1;
+ proto->total_STX++;
+ break;
+ case CAN:
+ rc = -ECONNABORTED;
+ if (proto->total_CAN++ > MAX_CAN_BEFORE_ABORT)
+ goto out;
+ break;
+ case EOT:
+ rc = 0;
+ blk->len = 0;
+ goto out;
+ default:
+ break;
+ }
+ }
+
+ blk->seq = 0;
+ rc = xy_gets(proto->cdev, proto->fifo, seqs, 2, timeout);
+ if (rc < 0)
+ goto out;
+ blk->seq = seqs[0];
+ if (255 - seqs[0] != seqs[1])
+ return -EBADMSG;
+
+ rc = xy_gets(proto->cdev, proto->fifo, blk->buf, data_len, timeout);
+ if (rc < 0)
+ goto out;
+ blk->len = rc;
+
+ switch (proto->crc_mode) {
+ case CRC_ADD8:
+ rc = xy_gets(proto->cdev, proto->fifo, crcs, 1, timeout);
+ crc = crcs[0];
+ break;
+ case CRC_CRC16:
+ rc = xy_gets(proto->cdev, proto->fifo, crcs, 2, timeout);
+ crc = (crcs[0] << 8) + crcs[1];
+ break;
+ case CRC_NONE:
+ rc = 0;
+ break;
+ }
+ if (rc < 0)
+ goto out;
+
+ rc = check_crc(blk->buf, data_len, crc, proto->crc_mode);
+ if (rc < 0)
+ goto out;
+ return data_len;
+timeout:
+ return -ETIMEDOUT;
+out:
+ return rc;
+}
+
+static int check_blk_seq(struct xyz_ctxt *proto, struct xy_block *blk,
+ int read_rc)
+{
+ if (blk->seq == ((proto->next_blk - 1) % 256))
+ return -EALREADY;
+ if (blk->seq != proto->next_blk)
+ return -EILSEQ;
+ return read_rc;
+}
+
+static int parse_first_block(struct xyz_ctxt *proto, struct xy_block *blk)
+{
+ int filename_len;
+ char *str_num;
+
+ filename_len = strlen(blk->buf);
+ if (filename_len > blk->len)
+ return -EINVAL;
+ strlcpy(proto->filename, blk->buf, sizeof(proto->filename));
+ str_num = blk->buf + filename_len + 1;
+ strsep(&str_num, " ");
+ proto->file_len = simple_strtoul(blk->buf + filename_len + 1, NULL, 10);
+ return 1;
+}
+
+static int xy_get_file_header(struct xyz_ctxt *proto)
+{
+ struct xy_block blk;
+ int tries, rc = 0;
+
+ memset(&blk, 0, sizeof(blk));
+ proto->state = PROTO_STATE_GET_FILENAME;
+ proto->crc_mode = CRC_CRC16;
+ for (tries = 0; tries < MAX_RETRIES; tries++) {
+ xy_putc(proto->cdev,
+ invite_filename_hdr[proto->mode][proto->crc_mode]);
+ rc = xy_read_block(proto, &blk, 3 * SECOND);
+ xy_dbg("read block returned %d\n", rc);
+ switch (rc) {
+ case -ECONNABORTED:
+ goto fail;
+ case -ETIMEDOUT:
+ case -EBADMSG:
+ if (proto->mode != PROTO_YMODEM_G)
+ xy_flush(proto->cdev, proto->fifo);
+ break;
+ case -EALREADY:
+ default:
+ proto->next_blk = 1;
+ xy_block_ack(proto);
+ proto->state = PROTO_STATE_NEGOCIATE_CRC;
+ rc = parse_first_block(proto, &blk);
+ return rc;
+ }
+
+ if (rc < 0 && tries++ >= MAX_RETRIES_WITH_CRC)
+ proto->crc_mode = CRC_ADD8;
+ }
+ rc = -ETIMEDOUT;
+fail:
+ proto->total_retries += tries;
+ return rc;
+}
+
+static int xy_await_header(struct xyz_ctxt *proto)
+{
+ int rc;
+
+ rc = xy_get_file_header(proto);
+ if (rc < 0)
+ return rc;
+ proto->state = PROTO_STATE_NEGOCIATE_CRC;
+ xy_dbg("header received, filename=%s, file length=%d\n",
+ proto->filename, proto->file_len);
+ if (proto->filename[0])
+ proto->fd = open(proto->filename, O_WRONLY | O_CREAT);
+ else
+ proto->state = PROTO_STATE_FINISHED_XFER;
+ proto->nb_received = 0;
+ return rc;
+}
+
+static void xy_finish_file(struct xyz_ctxt *proto)
+{
+ close(proto->fd);
+ proto->fd = 0;
+ proto->state = PROTO_STATE_FINISHED_FILE;
+}
+
+static struct xyz_ctxt *xymodem_open(struct console_device *cdev,
+ int proto_mode, int xmodem_fd)
+{
+ struct xyz_ctxt *proto;
+
+ proto = xzalloc(sizeof(struct xyz_ctxt));
+ proto->fifo = kfifo_alloc(INPUT_FIFO_SIZE);
+ proto->mode = proto_mode;
+ proto->cdev = cdev;
+ proto->crc_mode = CRC_CRC16;
+
+ if (is_xmodem(proto)) {
+ proto->fd = xmodem_fd;
+ proto->state = PROTO_STATE_NEGOCIATE_CRC;
+ } else {
+ proto->state = PROTO_STATE_GET_FILENAME;
+ }
+ xy_flush(proto->cdev, proto->fifo);
+ return proto;
+}
+
+static int xymodem_handle(struct xyz_ctxt *proto)
+{
+ int rc = 0, xfer_max, len = 0, again = 1, remain;
+ int crc_tries = 0, same_blk_retries = 0;
+ unsigned char invite;
+ struct xy_block blk;
+
+ while (again) {
+ switch (proto->state) {
+ case PROTO_STATE_GET_FILENAME:
+ crc_tries = 0;
+ rc = xy_await_header(proto);
+ if (rc < 0)
+ goto fail;
+ continue;
+ case PROTO_STATE_FINISHED_FILE:
+ if (is_xmodem(proto))
+ proto->state = PROTO_STATE_FINISHED_XFER;
+ else
+ proto->state = PROTO_STATE_GET_FILENAME;
+ xy_putc(proto->cdev, ACK);
+ continue;
+ case PROTO_STATE_FINISHED_XFER:
+ again = 0;
+ rc = 0;
+ goto out;
+ case PROTO_STATE_NEGOCIATE_CRC:
+ invite = invite_file_body[proto->mode][proto->crc_mode];
+ proto->next_blk = 1;
+ if (crc_tries++ > MAX_RETRIES_WITH_CRC)
+ proto->crc_mode = CRC_ADD8;
+ xy_putc(proto->cdev, invite);
+ /* Fall through */
+ case PROTO_STATE_RECEIVE_BODY:
+ rc = xy_read_block(proto, &blk, 3 * SECOND);
+ if (rc > 0) {
+ rc = check_blk_seq(proto, &blk, rc);
+ proto->state = PROTO_STATE_RECEIVE_BODY;
+ }
+ break;
+ }
+
+ if (proto->state != PROTO_STATE_RECEIVE_BODY)
+ continue;
+
+ switch (rc) {
+ case -ECONNABORTED:
+ goto fail;
+ case -ETIMEDOUT:
+ if (proto->mode == PROTO_YMODEM_G)
+ goto fail;
+ xy_flush(proto->cdev, proto->fifo);
+ xy_block_nack(proto);
+ break;
+ case -EBADMSG:
+ case -EILSEQ:
+ if (proto->mode == PROTO_YMODEM_G)
+ goto fail;
+ xy_flush(proto->cdev, proto->fifo);
+ xy_block_nack(proto);
+ break;
+ case -EALREADY:
+ xy_block_ack(proto);
+ break;
+ case 0:
+ xy_finish_file(proto);
+ break;
+ default:
+ remain = proto->file_len - proto->nb_received;
+ if (is_xmodem(proto))
+ xfer_max = blk.len;
+ else
+ xfer_max = min(blk.len, remain);
+ rc = write(proto->fd, blk.buf, xfer_max);
+ proto->next_blk = ((blk.seq + 1) % 256);
+ proto->nb_received += rc;
+ len += rc;
+ xy_block_ack(proto);
+ break;
+ }
+ if (rc < 0)
+ same_blk_retries++;
+ else
+ same_blk_retries = 0;
+ if (same_blk_retries > MAX_RETRIES)
+ goto fail;
+ }
+out:
+ return rc;
+fail:
+ if (proto->fd)
+ close(proto->fd);
+ return rc;
+}
+
+static void xymodem_close(struct xyz_ctxt *proto)
+{
+ xy_flush(proto->cdev, proto->fifo);
+ printf("\nxyModem - %d(SOH)/%d(STX)/%d(CAN) packets,"
+ " %d retries\n",
+ proto->total_SOH, proto->total_STX,
+ proto->total_CAN, proto->total_retries);
+ kfifo_free(proto->fifo);
+}
+
+int do_load_serial_xmodem(struct console_device *cdev, int fd)
+{
+ struct xyz_ctxt *proto;
+ int rc;
+
+ proto = xymodem_open(cdev, PROTO_XMODEM, fd);
+ do {
+ rc = xymodem_handle(proto);
+ } while (rc > 0);
+ xymodem_close(proto);
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(do_load_serial_xmodem);
+
+int do_load_serial_ymodem(struct console_device *cdev)
+{
+ struct xyz_ctxt *proto;
+ int rc;
+
+ proto = xymodem_open(cdev, PROTO_YMODEM, 0);
+ do {
+ rc = xymodem_handle(proto);
+ } while (rc > 0);
+ xymodem_close(proto);
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(do_load_serial_ymodem);
+
+int do_load_serial_ymodemg(struct console_device *cdev)
+{
+ struct xyz_ctxt *proto;
+ int rc;
+
+ proto = xymodem_open(cdev, PROTO_YMODEM_G, 0);
+ do {
+ rc = xymodem_handle(proto);
+ } while (rc > 0);
+ xymodem_close(proto);
+ return rc < 0 ? rc : 0;
+}
+EXPORT_SYMBOL(do_load_serial_ymodemg);