From 2a38aa83f78d54e81f887bddc1eb77451628f830 Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Thu, 8 Nov 2012 22:16:40 +0100 Subject: [PATCH] commands: change Y-Modem implementation The current Y-Modem implementation has some limitations: - Y-Modem/G protocol is not supported - Multiple files (aka. batch) transfers are not supported - Transfer speed over fast lines (USB console) is slow - Code is not trivial to maintain (personnal opinion) This implementation tries to address all these points by introducing loady2 command. The effects are : - transfer speed for Y-Modem over USB jumps from 2kBytes/s to 180kBytes/s - transfer speed for Y-Modem/G jumps to 200kBytes/s - multiple file transfers are possible This command was tested on a USB console and UART 9600bps serial line : - NAKs (and retransmissions) were tested for faulty serial lines - multiple file transfers were tested - Y-Modem, Y-Modem/G and X-Modem transfers were tested Signed-off-by: Robert Jarzmik Tested-by: Antony Pavlov Signed-off-by: Sascha Hauer --- commands/Kconfig | 1 + commands/Makefile | 2 +- commands/loadxy.c | 238 ++++++++++++++++++ include/xymodem.h | 25 ++ lib/Kconfig | 3 + lib/Makefile | 1 + lib/xymodem.c | 597 ++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 866 insertions(+), 1 deletion(-) create mode 100644 commands/loadxy.c create mode 100644 include/xymodem.h create mode 100644 lib/xymodem.c diff --git a/commands/Kconfig b/commands/Kconfig index 16706d36c..ce04bb283 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 610be55c6..2be5f9cf9 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 000000000..141bd7bf9 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 000000000..917cecc00 --- /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 9882d2d6d..13ecab0fd 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 41e6a0f92..eb0af9248 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 000000000..558f6e8cf --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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);