/* * 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 #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 xy_gets(struct console_device *cdev, struct kfifo *fifo, unsigned char *buf, int len, uint64_t timeout) { int rc; rc = console_drain(cdev, fifo, buf, len, timeout); if (rc != len) return -ETIMEDOUT; return len; } 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 = crc_itu_t(0, 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);