diff --git a/system/ymodem/Kconfig b/system/ymodem/Kconfig new file mode 100644 index 000000000..dda519715 --- /dev/null +++ b/system/ymodem/Kconfig @@ -0,0 +1,37 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_YMODEM + tristate "YMODEM" + depends on SERIAL_TERMIOS + ---help--- + Enable support for ymodem. + +if SYSTEM_YMODEM + +config SYSTEM_YMODEM_STACKSIZE + int "ymodem stack size" + default 4096 + ---help--- + The size of stack allocated for the ymodem task. + +config SYSTEM_YMODEM_PRIORITY + int "ymodem priority" + default 255 + ---help--- + The priority of the ymodem task. + +config SYSTEM_YMODEM_DEBUG + bool "ymodem debug" + default false + +if SYSTEM_YMODEM_DEBUG +config SYSTEM_YMODEM_DEBUGFILE_PATH + string "save ymodem debug log path" + default "/tmp/ymodem" + +endif + +endif diff --git a/system/ymodem/Make.defs b/system/ymodem/Make.defs new file mode 100644 index 000000000..200759486 --- /dev/null +++ b/system/ymodem/Make.defs @@ -0,0 +1,24 @@ + +############################################################################ +# apps/system/ymodem/Make.defs +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_YMODEM),) +CONFIGURED_APPS += $(APPDIR)/system/ymodem +endif diff --git a/system/ymodem/Makefile b/system/ymodem/Makefile new file mode 100644 index 000000000..abb7771e0 --- /dev/null +++ b/system/ymodem/Makefile @@ -0,0 +1,32 @@ + +############################################################################ +# apps/system/ymodem/Makefile +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +PROGNAME = sb rb +PRIORITY = $(CONFIG_SYSTEM_YMODEM_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_YMODEM_STACKSIZE) +MODULE = $(CONFIG_SYSTEM_YMODEM) + +CSRCS = ymodem.c +MAINSRC = sb_main.c rb_main.c + +include $(APPDIR)/Application.mk diff --git a/system/ymodem/README.md b/system/ymodem/README.md new file mode 100644 index 000000000..dbde3536f --- /dev/null +++ b/system/ymodem/README.md @@ -0,0 +1,28 @@ +# Introduce + +This is [ymodem protocal](http://pauillac.inria.fr/~doligez/zmodem/ymodem.txt). +According to it, the sb rb application is realized, which is used to send files and receive files respectively + +# Usage + +In the ubuntu system, lszrz needs to be installed, can use `sudo apt install lszrz`. +Use minicom to communicate with the board. + +## Sendfile to pc + +use sb command like this `nsh> sb /tmp/test.c ...`, this command support send multiple files together +then use ` , r` chose `ymodem` to receive board file. + +## Sendfile to board + +use rb cmd like this `nsh> sb`, this command support receive multiple files together +then use ` , s` chose `ymodem`, then chose what file need to send. + +## help + +can use `sb -h` or `rb -h` get help. + +# Debug + +Because the serial port is used for communication, the log is printed to the debug file +you can use `CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH` set debug file path. diff --git a/system/ymodem/rb_main.c b/system/ymodem/rb_main.c new file mode 100644 index 000000000..62a4c891c --- /dev/null +++ b/system/ymodem/rb_main.c @@ -0,0 +1,215 @@ +/**************************************************************************** + * apps/system/ymodem/rb_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include "ymodem.h" + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct ymodem_fd +{ + int file_fd; + char pathname[PATH_MAX]; + size_t file_saved_size; + char *removeperfix; + char *removesuffix; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handler(FAR struct ymodem_ctx *ctx) +{ + char pathname[PATH_MAX + YMODEM_FILE_NAME_LENGTH]; + FAR struct ymodem_fd *ym_fd = ctx->priv; + FAR char *filename; + size_t size; + size_t ret; + + if (ctx->packet_type == YMODEM_FILE_RECV_NAME_PACKET) + { + if (ym_fd->file_fd != 0) + { + close(ym_fd->file_fd); + } + + filename = ctx->file_name; + if (ym_fd->removeperfix) + { + if (strncmp(ctx->file_name, ym_fd->removeperfix, + strlen(ym_fd->removeperfix)) == 0) + { + filename = filename + strlen(ym_fd->removeperfix); + } + } + + if (ym_fd->removesuffix) + { + int len = strlen(filename); + if (len > strlen(ym_fd->removesuffix) && + strcmp(filename + len - strlen(ym_fd->removesuffix), + ym_fd->removesuffix) == 0) + { + filename[len - strlen(ym_fd->removesuffix)] = 0; + } + } + + if (strlen(ym_fd->pathname) != 0) + { + sprintf(pathname, "%s/%s", ym_fd->pathname, filename); + filename = pathname; + } + + ym_fd->file_fd = open(filename, O_CREAT | O_RDWR); + if (ym_fd->file_fd < 0) + { + return -errno; + } + + ym_fd->file_saved_size = 0; + } + else if (ctx->packet_type == YMODEM_RECV_DATA_PACKET) + { + if (ym_fd->file_saved_size + ctx->packet_size > ctx->file_length) + { + size = ctx->file_length - ym_fd->file_saved_size; + } + else + { + size = ctx->packet_size; + } + + ret = write(ym_fd->file_fd, ctx->data, size); + if (ret < 0) + { + return -errno; + } + else if (ret < size) + { + return ERROR; + } + + ym_fd->file_saved_size += ret; + } + + return 0; +} + +static void show_usage(FAR const char *progname, int errcode) +{ + fprintf(stderr, "USAGE: %s [OPTIONS]\n", progname); + fprintf(stderr, "\nWhere:\n"); + fprintf(stderr, "\nand OPTIONS include the following:\n"); + fprintf(stderr, + "\t-d : Communication device to use. Default: stdin & stdout\n"); + fprintf(stderr, + "\t-p : Save remote file path. Default: Current path\n"); + fprintf(stderr, + "\t--removeprefix : Remove save file name prefix\n"); + fprintf(stderr, + "\t--removesuffix : Remove save file name suffix\n"); + exit(errcode); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct ymodem_fd ym_fd; + struct ymodem_ctx ctx; + int index; + int ret; + struct option options[] = + { + {"removeprefix", 1, NULL, 1}, + {"removesuffix", 1, NULL, 2}, + }; + + memset(&ym_fd, 0, sizeof(struct ymodem_fd)); + memset(&ctx, 0, sizeof(struct ymodem_ctx)); + ctx.packet_handler = handler; + ctx.timeout = 200; + ctx.priv = &ym_fd; + ctx.recvfd = 0; + ctx.sendfd = 1; + while ((ret = getopt_long(argc, argv, "p:d:h", options, &index)) + != ERROR) + { + switch (ret) + { + case 1: + ym_fd.removeperfix = optarg; + break; + case 2: + ym_fd.removesuffix = optarg; + break; + case 'p': + strlcpy(ym_fd.pathname, optarg, PATH_MAX); + if (ym_fd.pathname[strlen(ym_fd.pathname)] == '/') + { + ym_fd.pathname[strlen(ym_fd.pathname)] = 0; + } + + break; + case 'd': + ctx.recvfd = open(optarg, O_RDWR | O_NONBLOCK); + if (ctx.recvfd < 0) + { + fprintf(stderr, "ERROR: can't open %s\n", optarg); + } + + ctx.sendfd = ctx.recvfd; + break; + case 'h': + show_usage(argv[0], EXIT_FAILURE); + break; + default: + case '?': + fprintf(stderr, "ERROR: Unrecognized option\n"); + show_usage(argv[0], EXIT_FAILURE); + break; + } + } + + ymodem_recv(&ctx); + if (ctx.recvfd) + { + close(ctx.recvfd); + } + + if (ym_fd.file_fd) + { + close(ym_fd.file_fd); + } + + return 0; +} diff --git a/system/ymodem/sb_main.c b/system/ymodem/sb_main.c new file mode 100644 index 000000000..8c888dc81 --- /dev/null +++ b/system/ymodem/sb_main.c @@ -0,0 +1,162 @@ +/**************************************************************************** + * apps/system/ymodem/sb_main.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "ymodem.h" + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +struct ymodem_fd +{ + int file_fd; + FAR char **filelist; + size_t filenum; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static ssize_t handler(FAR struct ymodem_ctx *ctx) +{ + FAR struct ymodem_fd *ym_fd = ctx->priv; + ssize_t ret = -EINVAL; + FAR char *filename; + struct stat info; + + if (ctx->packet_type == YMODEM_FILE_SEND_NAME_PACKET) + { + if (ym_fd->file_fd != 0) + { + close(ym_fd->file_fd); + } + + filename = ym_fd->filelist[ym_fd->filenum++]; + ret = lstat(filename, &info); + if (ret < 0) + { + return ret; + } + + ym_fd->file_fd = open(filename, O_RDWR); + if (ym_fd->file_fd < 0) + { + return ym_fd->file_fd; + } + + filename = basename(filename); + strncpy(ctx->file_name, filename, YMODEM_FILE_NAME_LENGTH); + ctx->file_length = info.st_size; + } + else if (ctx->packet_type == YMODEM_SEND_DATA_PACKET) + { + ret = read(ym_fd->file_fd, ctx->data, ctx->packet_size); + if (ret < 0) + { + return ret; + } + } + + return ret; +} + +static void show_usage(FAR const char *progname, int errcode) +{ + fprintf(stderr, "USAGE: %s [OPTIONS] [ [ ...]]\n", + progname); + fprintf(stderr, "\nWhere:\n"); + fprintf(stderr, "\t is the local file name\n"); + fprintf(stderr, "\nand OPTIONS include the following:\n"); + fprintf(stderr, + "\t-d : Communication device to use. Default: stdin & stdout\n"); + + exit(errcode); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int main(int argc, FAR char *argv[]) +{ + struct ymodem_fd ym_fd; + struct ymodem_ctx ctx; + int option; + + memset(&ctx, 0, sizeof(struct ymodem_ctx)); + ctx.packet_handler = handler; + ctx.timeout = 3000; + ctx.recvfd = 0; + ctx.sendfd = 1; + ctx.priv = &ym_fd; + while ((option = getopt(argc, argv, "d:h")) != ERROR) + { + switch (option) + { + case 'd': + ctx.recvfd = open(optarg, O_RDWR); + if (ctx.recvfd < 0) + { + fprintf(stderr, "ERROR: can't open %s\n", optarg); + } + + ctx.sendfd = ctx.recvfd; + break; + + case 'h': + show_usage(argv[0], EXIT_FAILURE); + + default: + case '?': + fprintf(stderr, "ERROR: Unrecognized option\n"); + show_usage(argv[0], EXIT_FAILURE); + break; + } + } + + ctx.need_sendfile_num = argc - optind; + ym_fd.file_fd = 0; + ym_fd.filelist = &argv[optind]; + ym_fd.filenum = 0; + + ymodem_send(&ctx); + if (ctx.recvfd) + { + close(ctx.recvfd); + } + + if (ym_fd.file_fd) + { + close(ym_fd.file_fd); + } + + return 0; +} diff --git a/system/ymodem/ymodem.c b/system/ymodem/ymodem.c new file mode 100644 index 000000000..9c77f62aa --- /dev/null +++ b/system/ymodem/ymodem.c @@ -0,0 +1,710 @@ +/**************************************************************************** + * apps/system/ymodem/ymodem.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ymodem.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define SOH 0x01 /* start of 128-byte data packet */ +#define STX 0x02 /* start of 1024-byte data packet */ +#define EOT 0x04 /* end of transmission */ +#define ACK 0x06 /* acknowledge */ +#define NAK 0x15 /* negative acknowledge */ +#define CA 0x18 /* two of these in succession aborts transfer */ +#define CRC16 0x43 /* 'C' == 0x43, request 16-bit CRC */ + +#define MAX_ERRORS 100 + +#define EEOT 200 /* End of transfer */ + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static long get_current_time(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +static ssize_t ymodem_uart_recv(FAR struct ymodem_ctx *ctx, + FAR uint8_t *buf, size_t size, + uint32_t timeout) +{ + ssize_t i = 0; + ssize_t ret; + long base; + struct pollfd pfd = + { + ctx->recvfd, POLLIN, 0 + }; + + base = get_current_time(); + while (i < size && poll(&pfd, 1, timeout) > 0) + { + if (get_current_time() - base >= timeout) + { + ymodem_debug("ymodem_uart_recv timeout\n"); + return -1; + } + + ret = read(ctx->recvfd, buf + i, size - i); + if (ret >= 0) + { + i += ret; + } + else + { + ymodem_debug("ymodem_uart_recv read data error\n"); + return ret; + } + } + + if (i == 0) + { + return -1; + } + + return i; +} + +static ssize_t ymodem_uart_send(FAR struct ymodem_ctx *ctx, + FAR uint8_t *buf, size_t size, + uint32_t timeout) +{ + ssize_t ret = write(ctx->sendfd, buf, size); + if (ret != size) + { + ymodem_debug("ymodem_uart_send error\n"); + return ret; + } + + return ret; +} + +static int ymodem_rcev_packet(FAR struct ymodem_ctx *ctx) +{ + uint32_t timeout = ctx->timeout; + uint16_t packet_size; + uint16_t rcev_crc; + uint16_t cal_crc; + uint8_t crchl[2]; + uint8_t chunk[1]; + int ret; + + ret = ymodem_uart_recv(ctx, chunk, 1, timeout); + if (ret != 1) + { + return ret; + } + + switch (chunk[0]) + { + case SOH: + packet_size = YMODEM_PACKET_SIZE; + break; + case STX: + packet_size = YMODEM_PACKET_1K_SIZE; + break; + case EOT: + return -EEOT; + case CA: + ret = ymodem_uart_recv(ctx, chunk, 1, timeout); + if (ret != 1 && chunk[0] == CA) + { + return -ECANCELED; + } + else + { + return -EBADMSG; + } + + default: + ymodem_debug("rcev_packet: EBADMSG: chunk[0]=%d\n", chunk[0]); + return -EBADMSG; + } + + ret = ymodem_uart_recv(ctx, ctx->seq, 2, timeout); + if (ret != 2) + { + ymodem_debug("rcev_packet: err=%d\n", ret); + return ret; + } + + ret = ymodem_uart_recv(ctx, ctx->data, packet_size, timeout); + if (ret != packet_size) + { + ymodem_debug("rcev_packet: err=%d\n", ret); + return ret; + } + + ret = ymodem_uart_recv(ctx, crchl, 2, timeout); + if (ret != 2) + { + ymodem_debug("rcev_packet: err=%d\n", ret); + return ret; + } + + if ((ctx->seq[0] + ctx->seq[1]) != 0xff) + { + ymodem_debug("rcev_packet: EILSEQ seq[]=%d %d\n", ctx->seq[0], + ctx->seq[1]); + return -EILSEQ; + } + + rcev_crc = (uint16_t)((crchl[0] << 8) + crchl[1]); + cal_crc = crc16(ctx->data, packet_size); + if (rcev_crc != cal_crc) + { + ymodem_debug("rcev_packet: EBADMSG rcev:cal=%x %x\n", + rcev_crc, cal_crc); + return -EBADMSG; + } + + ctx->packet_size = packet_size; + ymodem_debug("rcev_packet:OK: size=%d, seq=%d\n", + packet_size, ctx->seq[0]); + return 0; +} + +static int ymodem_rcev_file(FAR struct ymodem_ctx *ctx) +{ + uint32_t timeout = ctx->timeout; + bool file_start = false; + bool file_done = false; + uint32_t total_seq = 0; + bool canceled = false; + uint8_t chunk[1]; + FAR char *str; + int ret = 0; + int err = 0; + + chunk[0] = CRC16; + ymodem_uart_send(ctx, chunk, 1, timeout); + while (!file_done) + { + ret = ymodem_rcev_packet(ctx); + if (!ret) + { + if ((total_seq & 0xff) != ctx->seq[0]) + { + ymodem_debug("rcev_file: total seq erro:%lu %u\n", + total_seq, ctx->seq[0]); + chunk[0] = CRC16; + ymodem_uart_send(ctx, chunk, 1, timeout); + } + else + { + /* update with the total sequence number */ + + ctx->seq[0] = total_seq; + + /* file name packet */ + + if (total_seq == 0) + { + /* Filename packet is empty, end session */ + + if (ctx->data[0] == '\0') + { + ymodem_debug("rcev_file: session finished\n"); + chunk[0] = ACK; + ymodem_uart_send(ctx, chunk, 1, timeout); + + /* last file done, so the session also finished */ + + file_done = true; + } + else + { + str = (FAR char *)ctx->data; + ctx->packet_type = YMODEM_FILE_RECV_NAME_PACKET; + strncpy(ctx->file_name, str, YMODEM_FILE_NAME_LENGTH); + ctx->file_name[YMODEM_FILE_NAME_LENGTH - 1] = '\0'; + str += strlen(str) + 1; + ctx->file_length = atoi(str); + ymodem_debug("rcev_file: new file %s(%lu) start\n", + ctx->file_name, ctx->file_length); + ret = ctx->packet_handler(ctx); + if (ret) + { + ymodem_debug("rcev_file: handler err for file \ + name packet: ret=%d\n", ret); + canceled = true; + ret = -ENOEXEC; + break; + } + + file_start = true; + chunk[0] = ACK; + ymodem_uart_send(ctx, chunk, 1, timeout); + chunk[0] = CRC16; + ymodem_uart_send(ctx, chunk, 1, timeout); + } + } + else + { + /* data packet */ + + ctx->packet_type = YMODEM_RECV_DATA_PACKET; + ret = ctx->packet_handler(ctx); + if (ret) + { + ymodem_debug("rcev_file: handler err for data \ + packet: ret=%d\n", ret); + canceled = true; + ret = -ENOEXEC; + break; + } + + chunk[0] = ACK; + ymodem_uart_send(ctx, chunk, 1, timeout); + } + + ymodem_debug("rcev_file: packet %lu %s\n", + total_seq, ret ? "failed" : "success"); + + total_seq++; + } + } + else if (ret == -ECANCELED) + { + ymodem_debug("rcev_file: canceled by sender\n"); + canceled = true; + break; + } + else if (ret == -EEOT) + { + chunk[0] = ACK; + ymodem_uart_send(ctx, chunk, 1, timeout); + file_done = true; + ymodem_debug("rcev_file: finished one file transfer\n"); + } + else + { + /* other errors, like ETIME, EILSEQ, EBADMSG... */ + + if (file_start) + { + err++; + } + + if (err > MAX_ERRORS) + { + ymodem_debug("rcev_file: too many errors, cancel!!\n"); + canceled = true; + break; + } + + chunk[0] = CRC16; + ymodem_uart_send(ctx, chunk, 1, timeout); + } + } + + if (canceled) + { + chunk[0] = CA; + ymodem_uart_send(ctx, chunk, 1, timeout); + ymodem_uart_send(ctx, chunk, 1, timeout); + ymodem_debug("rcev_file: cancel command sent to sender\n"); + } + + return ret; +} + +static int ymodem_send_packet(FAR struct ymodem_ctx *ctx) +{ + size_t size; + uint16_t crc; + uint8_t send_crc[2]; + + crc = crc16(ctx->data, ctx->packet_size); + size = ymodem_uart_send(ctx, &ctx->header, ctx->packet_size + 3, + ctx->timeout); + + if (size != ctx->packet_size + 3) + { + ymodem_debug("send packet error\n"); + return -1; + } + + send_crc[0] = crc >> 8; + send_crc[1] = crc & 0x00ff; + size = ymodem_uart_send(ctx, send_crc, 2, ctx->timeout); + if (size != 2) + { + ymodem_debug("send crc16 error\n"); + return -1; + } + + return 0; +} + +static int ymodem_rcev_cmd(FAR struct ymodem_ctx *ctx, uint8_t cmd) +{ + size_t size; + uint8_t chunk[1]; + + size = ymodem_uart_recv(ctx, chunk, 1, ctx->timeout); + if (size != 1) + { + ymodem_debug("recv cmd error\n"); + return -1; + } + + if (chunk[0] == NAK) + { + return -EAGAIN; + } + + if (chunk[0] != cmd) + { + ymodem_debug("recv cmd error, must 0x%x, but receive %d\n", + cmd, chunk[0]); + return -EINVAL; + } + + return 0; +} + +static int ymodem_send_file(FAR struct ymodem_ctx *ctx) +{ + uint8_t chunk[1]; + ssize_t readsize; + ssize_t size; + int err = 0; + int ret; + + if (!ctx || !ctx->packet_handler) + { + ymodem_debug("%s: invalid context config\n", __func__); + return -EINVAL; + } + + if (ctx->need_sendfile_num <= 0) + { + ymodem_debug("need_sendfile_num is %d, no file to send!\n", + ctx->need_sendfile_num); + return -EINVAL; + } + + chunk[0] = 0; + ymodem_debug("waiting handshake\n"); + do + { + size = ymodem_uart_recv(ctx, chunk, 1, ctx->timeout); + } + while (err++ < MAX_ERRORS && chunk[0] != CRC16); + + if (err >= MAX_ERRORS) + { + ymodem_debug("waiting handshake error\n"); + return -ETIMEDOUT; + } + + ymodem_debug("ymodem send file start\n"); +send_start: + ctx->packet_type = YMODEM_FILE_SEND_NAME_PACKET; + ctx->packet_handler(ctx); + if ((ctx->file_name[0] == 0 || ctx->file_length == 0)) + { + ymodem_debug("get fileinfo error\n"); + return -EINVAL; + } + + ymodem_debug("sendfile filename:%s filelength:%lu\n", + ctx->file_name, ctx->file_length); + memset(ctx->data, 0, YMODEM_PACKET_SIZE); + strncpy((FAR char *)ctx->data, ctx->file_name, YMODEM_FILE_NAME_LENGTH); + sprintf((FAR char *)ctx->data + strlen(ctx->file_name) + 1, "%ld", + ctx->file_length); + ctx->header = SOH; + ctx->packet_size = YMODEM_PACKET_SIZE; + ctx->seq[0] = 0x00; + ctx->seq[1] = 0xff; + +send_name: + ret = ymodem_send_packet(ctx); + if (ret < 0) + { + ymodem_debug("send name packet error\n"); + return -EINVAL; + } + + ret = ymodem_rcev_cmd(ctx, ACK); + if (ret == -EAGAIN) + { + ymodem_debug("send name packet recv NAK, need send again\n"); + goto send_name; + } + + if (ret < 0) + { + ymodem_debug("send name packet, recv error cmd\n"); + return ret; + } + + ret = ymodem_rcev_cmd(ctx, CRC16); + if (ret == -EAGAIN) + { + ymodem_debug("send name packet recv NAK, need send again\n"); + goto send_name; + } + + if (ret < 0) + { + ymodem_debug("send name packet, recv error cmd\n"); + return ret; + } + + ctx->packet_type = YMODEM_SEND_DATA_PACKET; +send_packet: + if (ctx->file_length <= YMODEM_PACKET_1K_SIZE) + { + ctx->header = SOH; + ctx->packet_size = YMODEM_PACKET_SIZE; + } + else + { + ctx->header = STX; + ctx->packet_size = YMODEM_PACKET_1K_SIZE; + } + + ymodem_debug("packet_size is %d\n", ctx->packet_size); + ctx->seq[0]++; + ctx->seq[1]--; + readsize = ctx->packet_handler(ctx); + ymodem_debug("%s:%d: readsize is %d\n", __FILE__, __LINE__, readsize); + + if (readsize < 0) + { + return readsize; + } + + if (readsize == 0) + { + goto send_eot; + } + + if (readsize < ctx->packet_size) + { + memset(ctx->data + readsize, 0x1a, ctx->packet_size - readsize); + } + +send_packet_again: + ret = ymodem_send_packet(ctx); + if (ret < 0) + { + ymodem_debug("send data packet error\n"); + return ret; + } + + ret = ymodem_rcev_cmd(ctx, ACK); + if (ret == -EAGAIN) + { + ymodem_debug("send data packet recv NAK, need send again\n"); + goto send_packet_again; + } + + if (ret < 0) + { + ymodem_debug("send data packet, recv error\n"); + return ret; + } + + ctx->file_length -= readsize; + if (ctx->file_length != 0) + { + ymodem_debug("The remain bytes sent are %lu\n", ctx->file_length); + goto send_packet; + } + +send_eot: + chunk[0] = EOT; + size = ymodem_uart_send(ctx, chunk, 1, ctx->timeout); + if (size < 0) + { + ymodem_debug("%s:%d: send EOT error\n", __FILE__, __LINE__); + return -1; + } + + ret = ymodem_rcev_cmd(ctx, ACK); + if (ret == -EAGAIN) + { + ymodem_debug("send EOT recv NAK, need send again\n"); + goto send_eot; + } + + if (ret < 0) + { + ymodem_debug("send EOT, recv ACK error\n"); + return ret; + } + + ret = ymodem_rcev_cmd(ctx, CRC16); + if (ret == -EAGAIN) + { + ymodem_debug("send EOT recv NAK, need send again\n"); + goto send_eot; + } + + if (ret < 0) + { + ymodem_debug("send EOT, recv CRC16 error\n"); + return ret; + } + + if (--ctx->need_sendfile_num != 0) + { + ymodem_debug("need sendfile num is %d, so send file again\n", + ctx->need_sendfile_num); + goto send_start; + } + +send_last: + ctx->header = SOH; + ctx->packet_type = YMODEM_SEND_DATA_PACKET; + ctx->packet_size = YMODEM_PACKET_SIZE; + ctx->seq[0] = 0x00; + ctx->seq[1] = 0xff; + memset(ctx->data, 0, YMODEM_PACKET_SIZE); + ret = ymodem_send_packet(ctx); + if (ret < 0) + { + ymodem_debug("send last packet error\n"); + return -1; + } + + ret = ymodem_rcev_cmd(ctx, ACK); + if (ret == -EAGAIN) + { + ymodem_debug("send last packet, need send again\n"); + goto send_last; + } + + if (ret < 0) + { + ymodem_debug("send last packet, recv error\n"); + return ret; + } + + return 0; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int ymodem_recv(FAR struct ymodem_ctx *ctx) +{ + struct termios saveterm; + struct termios term; + int ret; + + tcgetattr(ctx->recvfd, &saveterm); + tcgetattr(ctx->recvfd, &term); + cfmakeraw(&term); + tcsetattr(ctx->recvfd, TCSANOW, &term); + +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH + ctx->debug_fd = open(CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH, + O_CREAT | O_RDWR); + if (ctx->debug_fd < 0) + { + return -EINVAL; + } +#endif + + if (!ctx || !ctx->packet_handler) + { + ymodem_debug("ymodem: invalid context config\n"); + return -EINVAL; + } + + while (1) + { + ret = ymodem_rcev_file(ctx); + if (ret == -EEOT) + { + continue; + } + + break; + } + +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH + close(ctx->debug_fd); +#endif + + tcsetattr(ctx->recvfd, TCSANOW, &saveterm); + return ret; +} + +int ymodem_send(FAR struct ymodem_ctx *ctx) +{ + struct termios saveterm; + struct termios term; + int ret; + + tcgetattr(ctx->recvfd, &saveterm); + tcgetattr(ctx->recvfd, &term); + cfmakeraw(&term); + tcsetattr(ctx->recvfd, TCSANOW, &term); + +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH + ctx->debug_fd = open(CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH, + O_CREAT | O_RDWR); + if (ctx->debug_fd < 0) + { + return -EINVAL; + } +#endif + + ret = ymodem_send_file(ctx); + if (ret < 0) + { + ymodem_debug("ymodem send file error, ret:%d\n", ret); + } + +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH + close(ctx->debug_fd); +#endif + + tcsetattr(ctx->recvfd, TCSANOW, &saveterm); + return 0; +} diff --git a/system/ymodem/ymodem.h b/system/ymodem/ymodem.h new file mode 100644 index 000000000..5e9459ce6 --- /dev/null +++ b/system/ymodem/ymodem.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * apps/system/ymodem/ymodem.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __APPS_SYSTEM_YMODEM_YMODEM_H +#define __APPS_SYSTEM_YMODEM_YMODEM_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH +# define ymodem_debug(...) dprintf(ctx->debug_fd, ##__VA_ARGS__) +#else +# define ymodem_debug(...) +#endif + +#define YMODEM_PACKET_SIZE 128 +#define YMODEM_PACKET_1K_SIZE 1024 +#define YMODEM_FILE_NAME_LENGTH 64 + +#define YMODEM_FILE_RECV_NAME_PACKET 0 +#define YMODEM_RECV_DATA_PACKET 1 +#define YMODEM_FILE_SEND_NAME_PACKET 2 +#define YMODEM_SEND_DATA_PACKET 3 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct ymodem_ctx +{ + uint8_t header; + uint8_t seq[2]; + uint8_t data[YMODEM_PACKET_1K_SIZE]; + char file_name[YMODEM_FILE_NAME_LENGTH]; + uint16_t packet_size; + uint32_t file_length; + uint32_t timeout; + uint32_t packet_type; + int recvfd; + int sendfd; + CODE ssize_t (*packet_handler)(FAR struct ymodem_ctx *ctx); + FAR void *priv; + uint16_t need_sendfile_num; +#ifdef CONFIG_SYSTEM_YMODEM_DEBUGFILE_PATH + int debug_fd; +#endif +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +int ymodem_recv(FAR struct ymodem_ctx *ctx); +int ymodem_send(FAR struct ymodem_ctx *ctx); + +#endif /* __APPS_SYSTEM_YMODEM_YMODEM_H */