From 102657af582a069e18cce136e55d1782a639f980 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Thu, 14 Aug 2025 09:03:58 +0900 Subject: [PATCH 01/11] Fix XMODEM reception start failure. --- src/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty.c b/src/tty.c index efda859..01c2b1d 100644 --- a/src/tty.c +++ b/src/tty.c @@ -756,7 +756,7 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) tio_printf("Ready to receiving file '%s' ", line); tio_printf("Press any key to abort transfer"); - ret = xymodem_send(device_fd, line, XMODEM_CRC); + ret = xymodem_receive(device_fd, line, XMODEM_CRC); tio_printf("%s", ret < 0 ? "Aborted" : "Done"); } break; From 4aa36e6e9171b191e0c4316e2f22d98c3f174763 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Thu, 14 Aug 2025 13:50:58 +0900 Subject: [PATCH 02/11] Adjust code style to comply with clang-format rules --- .clang-format | 4 + src/main.c | 5 +- src/misc.c | 2 + src/options.c | 12 +- src/readline.c | 2 +- src/script.c | 17 ++- src/tty.c | 36 +++-- src/xymodem.c | 362 +++++++++++++++++++++++++++++++------------------ src/xymodem.h | 4 +- 9 files changed, 285 insertions(+), 159 deletions(-) diff --git a/.clang-format b/.clang-format index 682ae09..d896a25 100644 --- a/.clang-format +++ b/.clang-format @@ -3,3 +3,7 @@ IndentWidth: 4 AllowShortFunctionsOnASingleLine: None KeepEmptyLinesAtTheStartOfBlocks: false BreakBeforeBraces: Allman +IndentCaseLabels: true +ColumnLimit: 144 +SortIncludes: false +SkipMacroDefinitionBody: true diff --git a/src/main.c b/src/main.c index 6676bb3..8434caa 100644 --- a/src/main.c +++ b/src/main.c @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) /* Configure input terminal */ if (isatty(fileno(stdin))) { - stdin_configure(); + stdin_configure(); } else { @@ -110,7 +110,8 @@ int main(int argc, char *argv[]) if (interactive_mode) { tio_printf("Press ctrl-%c q to quit", option.prefix_key); - } else + } + else { tio_printf("Non-interactive mode enabled"); tio_printf("Press ctrl-c to quit"); diff --git a/src/misc.c b/src/misc.c index bd0a429..13d601e 100644 --- a/src/misc.c +++ b/src/misc.c @@ -170,6 +170,7 @@ bool match_patterns(const char *string, const char *patterns) pattern = strtok(patterns_copy, ","); while (pattern != NULL) { + // clang-format off // Check if the string matches the current pattern #ifdef FNM_EXTMATCH if (fnmatch(pattern, string, FNM_EXTMATCH) == 0) @@ -180,6 +181,7 @@ bool match_patterns(const char *string, const char *patterns) free(patterns_copy); return true; } + // clang-format on // Move to the next pattern pattern = strtok(NULL, ","); diff --git a/src/options.c b/src/options.c index acff7ce..beddd90 100644 --- a/src/options.c +++ b/src/options.c @@ -61,6 +61,7 @@ enum opt_t OPT_EXEC, }; +// clang-format off /* Default options */ struct option_t option = { @@ -125,6 +126,7 @@ struct option_t option = .map_i_msb2lsb = false, .map_o_ign_cr = false, }; +// clang-format on void option_print_help(char *argv[]) { @@ -833,12 +835,14 @@ void options_print() tio_printf(" Output line delay: %d", option.output_line_delay); tio_printf(" Automatic connect strategy: %s", option_auto_connect_state_to_string(option.auto_connect)); tio_printf(" Automatic reconnect: %s", option.no_reconnect ? "true" : "false"); + // clang-format off tio_printf(" Pulse duration: DTR=%d RTS=%d CTS=%d DSR=%d DCD=%d RI=%d", option.dtr_pulse_duration, option.rts_pulse_duration, option.cts_pulse_duration, option.dsr_pulse_duration, option.dcd_pulse_duration, option.ri_pulse_duration); + // clang-format on tio_printf(" Input mode: %s", option_input_mode_to_string(option.input_mode)); tio_printf(" Output mode: %s", option_output_mode_to_string(option.output_mode)); tio_printf(" Alert: %s", option_alert_state_to_string(option.alert)); @@ -887,8 +891,9 @@ void options_parse(int argc, char *argv[]) option.vt100 = true; } - while (1) + while (true) { + // clang-format off static struct option long_options[] = { {"baudrate", required_argument, 0, 'b' }, @@ -932,6 +937,7 @@ void options_parse(int argc, char *argv[]) {"complete-profiles", no_argument, 0, OPT_COMPLETE_PROFILES }, {0, 0, 0, 0 } }; + // clang-format on /* getopt_long stores the option index here */ int option_index = 0; @@ -1131,7 +1137,7 @@ void options_parse(int argc, char *argv[]) /* Assume first non-option is the target (tty device, profile, tid) */ if (strcmp(option.target, "")) { - optind++; + optind++; } else if (optind < argc) { @@ -1176,6 +1182,7 @@ void options_parse_final(int argc, char *argv[]) #ifdef __CYGWIN__ unsigned char portnum; char *tty_win; + // clang-format off if ( ((strncmp("COM", option.target, 3) == 0) || (strncmp("com", option.target, 3) == 0) ) && (sscanf(option.target + 3, "%hhu", &portnum) == 1) @@ -1184,5 +1191,6 @@ void options_parse_final(int argc, char *argv[]) asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1); option.target = tty_win; } + // clang-format on #endif } diff --git a/src/readline.c b/src/readline.c index 8176a0b..6cfae00 100644 --- a/src/readline.c +++ b/src/readline.c @@ -90,7 +90,7 @@ static void readline_input_cr(void) rl_history_count++; } else - { + { free(rl_history[0]); memmove(&rl_history[0], &rl_history[1], (RL_HISTORY_MAX - 1) * sizeof(char*)); rl_history[RL_HISTORY_MAX - 1] = strndup(rl_line, rl_line_length); diff --git a/src/script.c b/src/script.c index b69d55b..e050af6 100644 --- a/src/script.c +++ b/src/script.c @@ -45,6 +45,7 @@ static int device_fd; +// clang-format off static char script_init[] = "tio.set = function(arg)\n" " local dtr = arg.DTR or -1\n" @@ -71,6 +72,7 @@ static char script_init[] = "end\n" "tio.alwaysecho = true\n" "setmetatable(tio, tio)\n"; +// clang-format on static bool alwaysecho(lua_State *L) { @@ -100,7 +102,9 @@ static int api_echo(lua_State *L) log_printf("\n[%s] %s", pTimeStampNow, str); } } - } else { + } + else + { for (size_t i=0; i= '0' && c <= '9') + if (c >= '0' && c <= '9') { return c - '0'; } @@ -331,7 +333,7 @@ void *tty_stdin_input_thread(void *arg) pthread_mutex_unlock(&mutex_input_ready); // Input loop for stdin - while (1) + while (true) { /* Input from stdin ready */ byte_count = read(STDIN_FILENO, input_buffer, BUFSIZ); @@ -360,7 +362,8 @@ void *tty_stdin_input_thread(void *arg) for (int i = 0; i 0; size--) { + for (crc = 0; size > 0; size--) + { s = *data++ ^ (crc >> 8); s ^= (s >> 4); crc = (crc << 8) ^ s ^ (s << 5) ^ (s << 12); @@ -81,16 +84,19 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) /* Drain pending characters from serial line. Insist on the * last drained character being 'C'. */ - while(1) { + while (true) + { if (key_hit) return -1; rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { + if (rc == 0) + { if (resp == 'C') break; if (resp == CAN) return ERR; continue; } - else if (rc < 0) { + else if (rc < 0) + { tio_error_print("Read sync from serial failed"); return ERR; } @@ -100,7 +106,8 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) packet.seq = seq; packet.type = STX; - while (len) { + while (len) + { size_t sz, z = 0; char *from, status; @@ -116,11 +123,14 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) /* Send packet */ from = (char *) &packet; sz = sizeof(packet); - while (sz) { + while (sz) + { if (key_hit) return ERR; - if ((rc = write(sio, from, sz)) < 0 ) { - if (errno == EWOULDBLOCK) { + if ((rc = write(sio, from, sz)) < 0 ) + { + if (errno == EWOULDBLOCK) + { usleep(1000); continue; } @@ -138,30 +148,36 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) if (seq == 0 && packet.data[0] == 0) resp = ACK; /* Read receiver response, timeout 1 s */ - for(int n=0; n < 20; n++) { + for (int n = 0; n < 20; n++) + { if (key_hit) return ERR; rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Read ack/nak from serial failed"); return ERR; - } else if(rc > 0) { + } + else if (rc > 0) + { break; } } /* Update "progress bar" */ - switch (resp) { - case NAK: status = 'N'; break; - case ACK: status = '.'; break; - case 'C': status = 'C'; break; - case CAN: status = '!'; return ERR; - default: status = '?'; + switch (resp) + { + case NAK: status = 'N'; break; + case ACK: status = '.'; break; + case 'C': status = 'C'; break; + case CAN: status = '!'; return ERR; + default: status = '?'; } write(STDOUT_FILENO, &status, 1); /* Move to next block after ACK */ - if (resp == ACK) { + if (resp == ACK) + { packet.seq++; len -= z; buf += z; @@ -169,23 +185,29 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) } /* Send EOT at 1 Hz until ACK or CAN received */ - while (seq) { + while (seq) + { if (key_hit) return ERR; - if (write(sio, EOT_STR, 1) < 0) { + if (write(sio, EOT_STR, 1) < 0) + { tio_error_print("Write EOT to serial failed"); return ERR; } write(STDOUT_FILENO, "|", 1); /* 1s timeout */ rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Read from serial failed"); return ERR; - } else if(rc == 0) { + } + else if (rc == 0) + { continue; } - if (resp == ACK || resp == CAN) { + if (resp == ACK || resp == CAN) + { write(STDOUT_FILENO, "\r\n", 2); return (resp == ACK) ? OK : ERR; } @@ -203,16 +225,19 @@ static int xmodem(int sio, const void *data, size_t len) /* Drain pending characters from serial line. Insist on the * last drained character being 'C'. */ - while(1) { + while (true) + { if (key_hit) return -1; rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { + if (rc == 0) + { if (resp == 'C') break; if (resp == CAN) return ERR; continue; } - else if (rc < 0) { + else if (rc < 0) + { tio_error_print("Read sync from serial failed"); return ERR; } @@ -222,7 +247,8 @@ static int xmodem(int sio, const void *data, size_t len) packet.seq = 1; packet.type = SOH; - while (len) { + while (len) + { size_t sz, z = 0; char *from, status; @@ -238,11 +264,14 @@ static int xmodem(int sio, const void *data, size_t len) /* Send packet */ from = (char *) &packet; sz = sizeof(packet); - while (sz) { + while (sz) + { if (key_hit) return ERR; - if ((rc = write(sio, from, sz)) < 0 ) { - if (errno == EWOULDBLOCK) { + if ((rc = write(sio, from, sz)) < 0 ) + { + if (errno == EWOULDBLOCK) + { usleep(1000); continue; } @@ -257,30 +286,36 @@ static int xmodem(int sio, const void *data, size_t len) resp = 0; /* Read receiver response, timeout 1 s */ - for(int n=0; n < 20; n++) { + for (int n = 0; n < 20; n++) + { if (key_hit) return ERR; rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Read ack/nak from serial failed"); return ERR; - } else if(rc > 0) { + } + else if (rc > 0) + { break; } } /* Update "progress bar" */ - switch (resp) { - case NAK: status = 'N'; break; - case ACK: status = '.'; break; - case 'C': status = 'C'; break; - case CAN: status = '!'; return ERR; - default: status = '?'; + switch (resp) + { + case NAK: status = 'N'; break; + case ACK: status = '.'; break; + case 'C': status = 'C'; break; + case CAN: status = '!'; return ERR; + default: status = '?'; } write(STDOUT_FILENO, &status, 1); /* Move to next block after ACK */ - if (resp == ACK) { + if (resp == ACK) + { packet.seq++; len -= z; buf += z; @@ -288,23 +323,29 @@ static int xmodem(int sio, const void *data, size_t len) } /* Send EOT at 1 Hz until ACK or CAN received */ - while (1) { + while (true) + { if (key_hit) return ERR; - if (write(sio, EOT_STR, 1) < 0) { + if (write(sio, EOT_STR, 1) < 0) + { tio_error_print("Write EOT to serial failed"); return ERR; } write(STDOUT_FILENO, "|", 1); /* 1s timeout */ rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Read from serial failed"); return ERR; - } else if(rc == 0) { + } + else if (rc == 0) + { continue; } - if (resp == ACK || resp == CAN) { + if (resp == ACK || resp == CAN) + { write(STDOUT_FILENO, "\r\n", 2); return (resp == ACK) ? OK : ERR; } @@ -325,8 +366,10 @@ int start_receive(int sio) seconds. If nothing is received in that time then return false to indicate that the transfer did not start. */ rc = write(sio, "C", 1); - if (rc < 0) { - if (errno == EWOULDBLOCK) { + if (rc < 0) + { + if (errno == EWOULDBLOCK) + { usleep(1000); continue; } @@ -353,7 +396,7 @@ int start_receive(int sio) return rc; } -uint16_t update_CRC(uint16_t crc, char data_char) +uint16_t update_CRC(uint16_t crc, char data_char) { uint8_t data = data_char; crc = crc ^ ((uint16_t)data << 8); @@ -385,19 +428,25 @@ int receive_packet(int sio, struct xpacket packet, int fd) /* Read seq bytes*/ rc = read_poll(sio, &rxSeq1, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for first seq byte"); return ERR; - } else if (rc < 0) { - tio_error_print("Error reading first seq byte") + } + else if (rc < 0) + { + tio_error_print("Error reading first seq byte"); return ERR_FATAL; } rc = read_poll(sio, &rxSeq2, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for second seq byte"); return ERR; - } else if (rc < 0) { - tio_error_print("Error reading second seq byte") + } + else if (rc < 0) + { + tio_error_print("Error reading second seq byte"); return ERR_FATAL; } if (key_hit) @@ -407,20 +456,24 @@ int receive_packet(int sio, struct xpacket packet, int fd) for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++) { rc = read_poll(sio, &resp, 1, 3000); - /* If the read times out or fails then fail this packet. */ + /* If the read times out or fails then fail this packet. */ if (rc == 0) { tio_error_print("Timeout waiting for next packet char"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); return ERR_FATAL; } return ERR; - } else if (rc < 0) { - tio_error_print("Error reading next packet char") + } + else if (rc < 0) + { + tio_error_print("Error reading next packet char"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; @@ -433,11 +486,14 @@ int receive_packet(int sio, struct xpacket packet, int fd) /* Read CRC */ rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for first CRC byte"); return ERR; - } else if (rc < 0) { - tio_error_print("Error reading first CRC byte") + } + else if (rc < 0) + { + tio_error_print("Error reading first CRC byte"); return ERR_FATAL; } @@ -446,20 +502,23 @@ int receive_packet(int sio, struct xpacket packet, int fd) rxCrc = uresp16 << 8; rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for second CRC byte"); return ERR; - } else if (rc < 0) { - tio_error_print("Error reading second CRC byte") + } + else if (rc < 0) + { + tio_error_print("Error reading second CRC byte"); return ERR_FATAL; } - + uresp = resp; uresp16 = uresp; rxCrc |= uresp16; if (key_hit) - return USER_CAN; + return USER_CAN; /* At this point in the code, there should not be anything in the receive buffer because the sender has just sent a complete packet and is waiting on a response. */ @@ -469,7 +528,8 @@ int receive_packet(int sio, struct xpacket packet, int fd) tio_error_print("%s", strerror(errno)); tio_error_print("Poll check error after packet finish"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; @@ -494,7 +554,8 @@ int receive_packet(int sio, struct xpacket packet, int fd) { /* Resend of previously processed packet. */ rc = write(sio, ACK_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write acknowlegdement packet to serial failed"); return ERR_FATAL; } @@ -520,7 +581,8 @@ int receive_packet(int sio, struct xpacket packet, int fd) { tio_error_print("Problem writing to file"); rc = write(sio, CAN_STR, 1); - if (rc < 0) { + if (rc < 0) + { tio_error_print("Write cancel packet to serial failed"); } return ERR_FATAL; @@ -545,16 +607,20 @@ int xmodem_receive(int sio, int fd) char status; /* Drain pending characters from serial line.*/ - while(1) { + while (true) + { if (key_hit) return -1; rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) { + if (rc == 0) + { if (resp == CAN) return ERR; break; } - else if (rc < 0) { - if (rc != USER_CAN) { + else if (rc < 0) + { + if (rc != USER_CAN) + { tio_error_print("Read sync from serial failed"); } return ERR; @@ -571,79 +637,95 @@ int xmodem_receive(int sio, int fd) { tio_error_print("Timeout waiting for transfer to start"); return ERR; - } else if (rc < 0) { + } + else if (rc < 0) + { tio_error_print("Error starting XMODEM receive"); return ERR; } - while (!complete) { + while (!complete) + { /* Poll for 1 new byte for 3 seconds */ rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) { + if (rc == 0) + { tio_error_print("Timeout waiting for start of next packet"); return ERR; - } else if (rc < 0) { - tio_error_print("Error reading start of next packet") + } + else if (rc < 0) + { + tio_error_print("Error reading start of next packet"); return ERR; } if (key_hit) return USER_CAN; - switch(resp) + switch (resp) { case SOH: - /* Start of a packet */ - rc = receive_packet(sio, packet, fd); - if (rc == OK) { - packet.seq++; - status = '.'; - } else if (rc == ERR) { - rc = write(sio, NAK_STR, 1); - if (rc < 0) { - tio_error_print("Writing not acknowledge packet to serial failed"); + /* Start of a packet */ + rc = receive_packet(sio, packet, fd); + if (rc == OK) + { + packet.seq++; + status = '.'; + } + else if (rc == ERR) + { + rc = write(sio, NAK_STR, 1); + if (rc < 0) + { + tio_error_print("Writing not acknowledge packet to serial failed"); + return ERR; + } + status = 'N'; + } + else if (rc == ERR_FATAL) + { + tio_error_print("Receive cancelled due to fatal error"); return ERR; } - status = 'N'; - } else if (rc == ERR_FATAL) { - tio_error_print("Receive cancelled due to fatal error"); - return ERR; - } else if (rc == USER_CAN) { - rc = write(sio, CAN_STR, 1); - if (rc < 0) { - tio_error_print("Writing cancel to serial failed"); - return ERR; + else if (rc == USER_CAN) + { + rc = write(sio, CAN_STR, 1); + if (rc < 0) + { + tio_error_print("Writing cancel to serial failed"); + return ERR; + } + return USER_CAN; } - return USER_CAN; - } else if (rc == RX_IGNORE) { - status = ':'; - } - break; + else if (rc == RX_IGNORE) + { + status = ':'; + } + break; case EOT: - /* End of Transfer */ - rc = write(sio, ACK_STR, 1); - if (rc < 0) - { - tio_error_print("Write acknowlegdement packet to serial failed"); - return ERR; - } - complete = true; - status = '\0'; - write(STDOUT_FILENO, "|\r\n", 3); - break; + /* End of Transfer */ + rc = write(sio, ACK_STR, 1); + if (rc < 0) + { + tio_error_print("Write acknowlegdement packet to serial failed"); + return ERR; + } + complete = true; + status = '\0'; + write(STDOUT_FILENO, "|\r\n", 3); + break; case CAN: - /* Cancel from sender */ - tio_error_print("Transmission cancelled from sender"); - return ERR; - break; + /* Cancel from sender */ + tio_error_print("Transmission cancelled from sender"); + return ERR; + break; default: - tio_error_print("Unexpected character received waiting for next packet"); - return ERR; - break; + tio_error_print("Unexpected character received waiting for next packet"); + return ERR; + break; } - /* Update "progress bar" */ write(STDOUT_FILENO, &status, 1); @@ -660,14 +742,16 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) /* Open file, map into memory */ fd = open(filename, O_RDONLY); - if (fd < 0) { + if (fd < 0) + { tio_error_print("Could not open file"); return ERR; } fstat(fd, &stat); len = stat.st_size; buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); - if (!buf) { + if (!buf) + { close(fd); tio_error_print("Could not mmap file"); return ERR; @@ -675,15 +759,19 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) /* Do transfer */ key_hit = 0; - if (mode == XMODEM_1K) { + if (mode == XMODEM_1K) + { rc = xmodem_1k(sio, buf, len, 1); } - else if (mode == XMODEM_CRC) { + else if (mode == XMODEM_CRC) + { rc = xmodem(sio, buf, len); } - else { + else + { /* Ymodem: hdr + file + fin */ - while(1) { + while (true) + { char hdr[1024], *p; rc = -1; @@ -694,7 +782,7 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ if (xmodem_1k(sio, buf, len, 1) < 0) break; /* xmodem file */ if (xmodem_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ - rc = 0; break; + rc = 0; break; } } key_hit = 0xff; @@ -712,21 +800,25 @@ int xymodem_receive(int sio, const char *filename, modem_mode_t mode) /* Create new file */ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664); - if (fd < 0) { + if (fd < 0) + { tio_error_print("Could not open file"); return ERR; } /* Do transfer */ key_hit = 0; - if (mode == XMODEM_1K) { + if (mode == XMODEM_1K) + { tio_error_print("Not supported"); rc = -1; } - else if (mode == XMODEM_CRC) { + else if (mode == XMODEM_CRC) + { rc = xmodem_receive(sio, fd); } - else { + else + { tio_error_print("Not supported"); rc = -1; } diff --git a/src/xymodem.h b/src/xymodem.h index 1b46cd7..b5d2b8d 100644 --- a/src/xymodem.h +++ b/src/xymodem.h @@ -21,7 +21,8 @@ #pragma once -typedef enum { +typedef enum +{ XMODEM_1K, XMODEM_CRC, YMODEM, @@ -30,5 +31,4 @@ typedef enum { extern char key_hit; int xymodem_send(int sio, const char *filename, modem_mode_t mode); - int xymodem_receive(int sio, const char *filename, modem_mode_t mode); From b2a8c02d1da4bd5aa146489f6f7ed2f7a7ae1a81 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Thu, 14 Aug 2025 22:18:41 +0900 Subject: [PATCH 03/11] Fix incorrect handling of the return value of the poll system call --- src/misc.c | 7 ++++++- src/xymodem.c | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/misc.c b/src/misc.c index 13d601e..271d266 100644 --- a/src/misc.c +++ b/src/misc.c @@ -98,12 +98,17 @@ int read_poll(int fd, void *data, size_t len, int timeout) if (fds.revents & POLLIN) { // Read ready data + // return value should not be 0 return read(fd, data, len); } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return -1; + } } /* Timeout */ - return ret; + return 0; } // Function to calculate djb2 hash of string diff --git a/src/xymodem.c b/src/xymodem.c index 3b645cc..3b46ef8 100644 --- a/src/xymodem.c +++ b/src/xymodem.c @@ -91,6 +91,9 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) rc = read_poll(sio, &resp, 1, 50); if (rc == 0) { + /* timeout 50 ms + resp has last received character beacuse read_poll() doesn't + destroy resp value in this case. */ if (resp == 'C') break; if (resp == CAN) return ERR; continue; @@ -232,6 +235,9 @@ static int xmodem(int sio, const void *data, size_t len) rc = read_poll(sio, &resp, 1, 50); if (rc == 0) { + /* timeout 50 ms + resp has last received character beacuse read_poll() doesn't + destroy resp value in this case. */ if (resp == 'C') break; if (resp == CAN) return ERR; continue; @@ -389,6 +395,10 @@ int start_receive(int sio) { return rc; } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return -1; + } } if (key_hit) return USER_CAN; @@ -544,6 +554,10 @@ int receive_packet(int sio, struct xpacket packet, int fd) while (read_poll(sio, &dummy, 1, 100) > 0) {} return ERR; } + else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ + { + return ERR; + } } uint8_t tester = 0xff; From 6cc0a58ed87172caeca2366bfb37a78e1e188cb7 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Fri, 15 Aug 2025 23:10:20 +0900 Subject: [PATCH 04/11] Refactoring XYMODEM codes and Fix error code handling Refactoring. - separete routines from xmodem_send_xxx, - xmsend_inirial_handshake - xmsend_wait_response, - xmsend_repeat_eot_and_wait_response - separete routines from xmodem_receive, - xmrecv_drain_pending_chars - separete routines from xymodem_send, - ymodem_send_1k - rename routines. - crc16 --> calculate_crc16 - update_CRC --> update_crc16 - xmodem_1k --> xmodem_send_1k - xmodem_send --> xmodem_send_128b - receive_packet --> xmrecv_receive_packet - rename struct types. - xpacket --> xpacket_128b - separete xpacket's header / footer - xpacket_hdr, xpacket_ftr_crc Fix error code handling. - use 'rc' for system call's return code, and add 'err' for xymodem function's return code (OK, ERR, ...) - add ERR_TMO to return codes - change USER_CAN to ERR_USER_CAN, because it is negative value Fix xmrecv_receive_packet()'s argument packet to pointer type. --- src/xymodem.c | 647 +++++++++++++++++++++++++++----------------------- 1 file changed, 349 insertions(+), 298 deletions(-) diff --git a/src/xymodem.c b/src/xymodem.c index 3b46ef8..cbc7834 100644 --- a/src/xymodem.c +++ b/src/xymodem.c @@ -34,34 +34,42 @@ #define OK 0 #define ERR (-1) #define ERR_FATAL (-2) -#define USER_CAN (-5) +#define ERR_TMO (-3) +#define ERR_USER_CAN (-5) #define RX_IGNORE 5 #define min(a, b) ((a) < (b) ? (a) : (b)) -struct xpacket_1k +struct xpkt_hdr { uint8_t type; uint8_t seq; uint8_t nseq; - uint8_t data[1024]; +} __attribute__((packed)); + +struct xpkt_ftr_crc +{ uint8_t crc_hi; uint8_t crc_lo; } __attribute__((packed)); -struct xpacket +struct xpacket_1k { - uint8_t type; - uint8_t seq; - uint8_t nseq; + struct xpkt_hdr hdr; + uint8_t data[1024]; + struct xpkt_ftr_crc ftr; +} __attribute__((packed)); + +struct xpacket_128b +{ + struct xpkt_hdr hdr; uint8_t data[128]; - uint8_t crc_hi; - uint8_t crc_lo; + struct xpkt_ftr_crc ftr; } __attribute__((packed)); /* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */ -static uint16_t crc16(const uint8_t *data, uint16_t size) +static uint16_t calculate_crc16(const uint8_t *data, uint16_t size) { uint16_t crc, s; @@ -74,27 +82,47 @@ static uint16_t crc16(const uint8_t *data, uint16_t size) return crc; } -static int xmodem_1k(int sio, const void *data, size_t len, int seq) +static uint16_t update_crc16(uint16_t crc, char data_char) { - struct xpacket_1k packet; - const uint8_t *buf = data; - char resp = 0; - int rc, crc; + uint8_t data = data_char; - /* Drain pending characters from serial line. Insist on the - * last drained character being 'C'. - */ + crc = crc ^ ((uint16_t)data << 8); + for (int ix = 0; (ix < 8); ix++) + { + if (crc & 0x8000) + { + crc = (crc << 1) ^ 0x1021; + } + else + { + crc <<= 1; + } + } + return crc; +} + +/* + * Drain pending characters from serial line. Insist on the + * last drained character being initial character 'C':CRC,1K + */ +static int xmsend_initial_handshake(int sio, char init_ch) +{ + int rc; + char resp = 0; + + /* Wait for initial character */ while (true) { if (key_hit) - return -1; + return ERR_USER_CAN; + rc = read_poll(sio, &resp, 1, 50); if (rc == 0) { /* timeout 50 ms resp has last received character beacuse read_poll() doesn't destroy resp value in this case. */ - if (resp == 'C') break; + if (resp == init_ch) break; if (resp == CAN) return ERR; continue; } @@ -104,10 +132,95 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) return ERR; } } + return OK; +} + +/* + * Read receiver response, timeout 1 s + */ +static int xmsend_wait_response(int sio, char *resp, char tmo_resp) +{ + int rc; + + for (int n = 0; n < 20; n++) + { + if (key_hit) + return ERR_USER_CAN; + + rc = read_poll(sio, resp, 1, 50); + if (rc < 0) + { + tio_error_print("Read ack/nak from serial failed"); + return ERR; + } + else if (rc > 0) + { + /* response received */ + return OK; + } + } + /* no response time-out */ + *resp = tmo_resp; + return OK; +} + +/* + * Send EOT at 1 Hz until ACK or CAN received + */ +static int xmsend_repeat_eot_and_wait_response(int sio) +{ + int rc; + char resp; + + while (true) + { + if (key_hit) + return ERR_USER_CAN; + + if (write(sio, EOT_STR, 1) < 0) + { + tio_error_print("Write EOT to serial failed"); + return ERR; + } + write(STDOUT_FILENO, "|", 1); + /* 1s timeout */ + rc = read_poll(sio, &resp, 1, 1000); + if (rc < 0) + { + tio_error_print("Read from serial failed"); + return ERR; + } + else if (rc == 0) + { + continue; + } + if (resp == ACK || resp == CAN) + { + write(STDOUT_FILENO, "\r\n", 2); + return (resp == ACK) ? OK : ERR; + } + } + return OK; /* not reached */ +} + +static int xmodem_send_1k(int sio, const void *data, size_t len, int seq) +{ + struct xpacket_1k packet; + const uint8_t *buf = data; + char resp = 0, tmo_resp; + int rc, crc, err; + + /* Drain pending characters from serial line. + Insist on the last drained character being 'C' */ + err = xmsend_initial_handshake(sio, 'C'); + if (err != OK) + { + return err; + } /* Always work with 1K packets */ - packet.seq = seq; - packet.type = STX; + packet.hdr.seq = seq; + packet.hdr.type = STX; while (len) { @@ -118,10 +231,10 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) z = min(len, sizeof(packet.data)); memcpy(packet.data, buf, z); memset(packet.data + z, 0, sizeof(packet.data) - z); - crc = crc16(packet.data, sizeof(packet.data)); - packet.crc_hi = crc >> 8; - packet.crc_lo = crc; - packet.nseq = 0xff - packet.seq; + crc = calculate_crc16(packet.data, sizeof(packet.data)); + packet.ftr.crc_hi = crc >> 8; + packet.ftr.crc_lo = crc; + packet.hdr.nseq = 0xff - packet.hdr.seq; /* Send packet */ from = (char *) &packet; @@ -129,7 +242,8 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) while (sz) { if (key_hit) - return ERR; + return ERR_USER_CAN; + if ((rc = write(sio, from, sz)) < 0 ) { if (errno == EWOULDBLOCK) @@ -145,26 +259,16 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) } /* Clear response */ - resp = 0; + tmo_resp = 0; /* 'lrzsz' does not ACK ymodem's fin packet */ - if (seq == 0 && packet.data[0] == 0) resp = ACK; + if (seq == 0 && packet.data[0] == 0) tmo_resp = ACK; /* Read receiver response, timeout 1 s */ - for (int n = 0; n < 20; n++) + err = xmsend_wait_response(sio, &resp, tmo_resp); + if (err != OK) { - if (key_hit) - return ERR; - rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) - { - tio_error_print("Read ack/nak from serial failed"); - return ERR; - } - else if (rc > 0) - { - break; - } + return err; } /* Update "progress bar" */ @@ -181,190 +285,178 @@ static int xmodem_1k(int sio, const void *data, size_t len, int seq) /* Move to next block after ACK */ if (resp == ACK) { - packet.seq++; + packet.hdr.seq++; + len -= z; + buf += z; + } + } + + if (seq != 0) + { + /* Send EOT at 1 Hz until ACK or CAN received */ + err = xmsend_repeat_eot_and_wait_response(sio); + if (err != OK) { + return err; + } + } + + return OK; +} + +static int xmodem_send_128b(int sio, const void *data, size_t len) +{ + struct xpacket_128b packet; + const uint8_t *buf = data; + char resp = 0, tmo_resp; + int rc, crc, err; + + /* Drain pending characters from serial line. + Insist on the last drained character being 'C' */ + err = xmsend_initial_handshake(sio, 'C'); + if (err != OK) + return err; + + /* Always work with 128b packets */ + packet.hdr.seq = 1; + packet.hdr.type = SOH; + + while (len) + { + size_t sz, z = 0; + char *from, status; + + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.data)); + memcpy(packet.data, buf, z); + memset(packet.data + z, 0, sizeof(packet.data) - z); + crc = calculate_crc16(packet.data, sizeof(packet.data)); + packet.ftr.crc_hi = crc >> 8; + packet.ftr.crc_lo = crc; + packet.hdr.nseq = 0xff - packet.hdr.seq; + + /* Send packet */ + from = (char *) &packet; + sz = sizeof(packet); + while (sz) + { + if (key_hit) + return ERR_USER_CAN; + + if ((rc = write(sio, from, sz)) < 0 ) + { + if (errno == EWOULDBLOCK) + { + usleep(1000); + continue; + } + tio_error_print("Write packet to serial failed"); + return ERR; + } + from += rc; + sz -= rc; + } + + /* Clear response */ + tmo_resp = 0; + + /* Read receiver response, timeout 1 s */ + err = xmsend_wait_response(sio, &resp, tmo_resp); + if (err != OK) + { + return err; + } + + /* Update "progress bar" */ + switch (resp) + { + case NAK: status = 'N'; break; + case ACK: status = '.'; break; + case 'C': status = 'C'; break; + case CAN: status = '!'; return ERR; + default: status = '?'; + } + write(STDOUT_FILENO, &status, 1); + + /* Move to next block after ACK */ + if (resp == ACK) + { + packet.hdr.seq++; len -= z; buf += z; } } /* Send EOT at 1 Hz until ACK or CAN received */ - while (seq) - { - if (key_hit) - return ERR; - if (write(sio, EOT_STR, 1) < 0) - { - tio_error_print("Write EOT to serial failed"); - return ERR; - } - write(STDOUT_FILENO, "|", 1); - /* 1s timeout */ - rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) - { - tio_error_print("Read from serial failed"); - return ERR; - } - else if (rc == 0) - { - continue; - } - if (resp == ACK || resp == CAN) - { - write(STDOUT_FILENO, "\r\n", 2); - return (resp == ACK) ? OK : ERR; - } - } - return 0; /* not reached */ + err = xmsend_repeat_eot_and_wait_response(sio); + + return err; } -static int xmodem(int sio, const void *data, size_t len) +/* + * Ymodem: hdr + file + fin + */ +static int ymodem_send_1k(int sio, const void *data, size_t len, const char *filename, struct stat *stat) { - struct xpacket packet; - const uint8_t *buf = data; - char resp = 0; - int rc, crc; + int err; + + while (1) + { + char hdr[1024], *p; + + err = ERR; + if (strlen(filename) > 977) break; /* hdr block overrun */ + p = stpncpy(hdr, filename, 1024) + 1; + p += sprintf(p, "%ld %lo %o", len, stat->st_mtime, stat->st_mode); + + if (xmodem_send_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ + if (xmodem_send_1k(sio, data, len, 1) < 0) break; /* xmodem file */ + if (xmodem_send_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ + err = OK; break; + } + return err; +} + +/* + * Drain pending characters from serial line. + */ +static int xmrecv_drain_pending_chars(int sio) +{ + int rc; + char resp; - /* Drain pending characters from serial line. Insist on the - * last drained character being 'C'. - */ while (true) { if (key_hit) - return -1; + return ERR_USER_CAN; + rc = read_poll(sio, &resp, 1, 50); if (rc == 0) { - /* timeout 50 ms - resp has last received character beacuse read_poll() doesn't - destroy resp value in this case. */ - if (resp == 'C') break; - if (resp == CAN) return ERR; - continue; + break; } else if (rc < 0) { tio_error_print("Read sync from serial failed"); return ERR; } - } - - /* Always work with 128b packets */ - packet.seq = 1; - packet.type = SOH; - - while (len) - { - size_t sz, z = 0; - char *from, status; - - /* Build next packet, pad with 0 to full seq */ - z = min(len, sizeof(packet.data)); - memcpy(packet.data, buf, z); - memset(packet.data + z, 0, sizeof(packet.data) - z); - crc = crc16(packet.data, sizeof(packet.data)); - packet.crc_hi = crc >> 8; - packet.crc_lo = crc; - packet.nseq = 0xff - packet.seq; - - /* Send packet */ - from = (char *) &packet; - sz = sizeof(packet); - while (sz) + if (resp == CAN) { - if (key_hit) - return ERR; - if ((rc = write(sio, from, sz)) < 0 ) - { - if (errno == EWOULDBLOCK) - { - usleep(1000); - continue; - } - tio_error_print("Write packet to serial failed"); - return ERR; - } - from += rc; - sz -= rc; - } - - /* Clear response */ - resp = 0; - - /* Read receiver response, timeout 1 s */ - for (int n = 0; n < 20; n++) - { - if (key_hit) - return ERR; - rc = read_poll(sio, &resp, 1, 50); - if (rc < 0) - { - tio_error_print("Read ack/nak from serial failed"); - return ERR; - } - else if (rc > 0) - { - break; - } - } - - /* Update "progress bar" */ - switch (resp) - { - case NAK: status = 'N'; break; - case ACK: status = '.'; break; - case 'C': status = 'C'; break; - case CAN: status = '!'; return ERR; - default: status = '?'; - } - write(STDOUT_FILENO, &status, 1); - - /* Move to next block after ACK */ - if (resp == ACK) - { - packet.seq++; - len -= z; - buf += z; - } - } - - /* Send EOT at 1 Hz until ACK or CAN received */ - while (true) - { - if (key_hit) - return ERR; - if (write(sio, EOT_STR, 1) < 0) - { - tio_error_print("Write EOT to serial failed"); return ERR; } - write(STDOUT_FILENO, "|", 1); - /* 1s timeout */ - rc = read_poll(sio, &resp, 1, 1000); - if (rc < 0) - { - tio_error_print("Read from serial failed"); - return ERR; - } - else if (rc == 0) - { - continue; - } - if (resp == ACK || resp == CAN) - { - write(STDOUT_FILENO, "\r\n", 2); - return (resp == ACK) ? OK : ERR; - } } - return 0; /* not reached */ + return OK; } -int start_receive(int sio) +/* + * Start Receive + */ +static int xmrecv_start_receive(int sio) { int rc; struct pollfd fds; fds.events = POLLIN; fds.fd = sio; + for (int n = 0; n < 20; n++) { /* Send the 'C' byte until the sender of the file responds with @@ -387,44 +479,29 @@ int start_receive(int sio) if (rc < 0) { tio_error_print("%s", strerror(errno)); - return rc; + return ERR; } else if (rc > 0) { if (fds.revents & POLLIN) { - return rc; + return OK; } else /* if (fds.revents & (POLLERR | POLLHUP | POLLNVAL)) */ { - return -1; + return ERR; } } if (key_hit) - return USER_CAN; + return ERR_USER_CAN; } - return rc; + return ERR_TMO; } -uint16_t update_CRC(uint16_t crc, char data_char) -{ - uint8_t data = data_char; - crc = crc ^ ((uint16_t)data << 8); - for (int ix = 0; (ix < 8); ix++) - { - if (crc & 0x8000) - { - crc = (crc << 1) ^ 0x1021; - } - else - { - crc <<= 1; - } - } - return crc; -} - -int receive_packet(int sio, struct xpacket packet, int fd) +/* + * Receive a packet + */ +static int xmrecv_receive_packet(int sio, struct xpacket_128b *packet, int fd) { char rxSeq1, rxSeq2 = 0; char resp = 0; @@ -441,7 +518,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) if (rc == 0) { tio_error_print("Timeout waiting for first seq byte"); - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -452,7 +529,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) if (rc == 0) { tio_error_print("Timeout waiting for second seq byte"); - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -460,10 +537,10 @@ int receive_packet(int sio, struct xpacket packet, int fd) return ERR_FATAL; } if (key_hit) - return USER_CAN; + return ERR_USER_CAN; /* Read packet Data */ - for (unsigned ix = 0; (ix < sizeof(packet.data)); ix++) + for (unsigned ix = 0; (ix < sizeof(packet->data)); ix++) { rc = read_poll(sio, &resp, 1, 3000); /* If the read times out or fails then fail this packet. */ @@ -476,7 +553,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) tio_error_print("Write cancel packet to serial failed"); return ERR_FATAL; } - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -488,10 +565,10 @@ int receive_packet(int sio, struct xpacket packet, int fd) } return ERR_FATAL; } - packet.data[ix] = (uint8_t) resp; - calcCrc = update_CRC(calcCrc, resp); + packet->data[ix] = (uint8_t) resp; + calcCrc = update_crc16(calcCrc, resp); if (key_hit) - return USER_CAN; + return ERR_USER_CAN; } /* Read CRC */ @@ -499,7 +576,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) if (rc == 0) { tio_error_print("Timeout waiting for first CRC byte"); - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -515,7 +592,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) if (rc == 0) { tio_error_print("Timeout waiting for second CRC byte"); - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -528,10 +605,10 @@ int receive_packet(int sio, struct xpacket packet, int fd) rxCrc |= uresp16; if (key_hit) - return USER_CAN; + return ERR_USER_CAN; /* At this point in the code, there should not be anything in the receive buffer - because the sender has just sent a complete packet and is waiting on a response. */ + because the sender has just sent a complete packet and is waiting on a response. */ rc = poll(&fds, 1, 10); if (rc < 0) { @@ -564,7 +641,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) uint8_t seq1 = rxSeq1; uint8_t seq2 = rxSeq2; - if ((calcCrc == rxCrc) && (seq1 == packet.seq - 1) && ((seq1 ^ seq2) == tester)) + if ((calcCrc == rxCrc) && (seq1 == packet->hdr.seq - 1) && ((seq1 ^ seq2) == tester)) { /* Resend of previously processed packet. */ rc = write(sio, ACK_STR, 1); @@ -575,7 +652,7 @@ int receive_packet(int sio, struct xpacket packet, int fd) } return RX_IGNORE; } - else if ((calcCrc != rxCrc) || (seq1 != packet.seq) || ((seq1 ^ seq2) != tester)) + else if ((calcCrc != rxCrc) || (seq1 != packet->hdr.seq) || ((seq1 ^ seq2) != tester)) { /* Fail if the CRC or sequence number is not correct or if the two received sequence numbers are not the complement of one another. */ @@ -583,14 +660,14 @@ int receive_packet(int sio, struct xpacket packet, int fd) tio_debug_printf("CRC read: %u", rxCrc); tio_debug_printf("CRC calculated: %u", calcCrc); tio_debug_printf("Seq read: %hhu", rxSeq1); - tio_debug_printf("Seq should be: %hhu", packet.seq); + tio_debug_printf("Seq should be: %hhu", packet->hdr.seq); tio_debug_printf("inv seq: %hhu", rxSeq2); return ERR; } else { /* The data is good. Process the packet then ACK it to the sender. */ - rc = write(fd, packet.data, sizeof(packet.data)); + rc = write(fd, packet->data, sizeof(packet->data)); if (rc < 0) { tio_error_print("Problem writing to file"); @@ -614,48 +691,36 @@ int receive_packet(int sio, struct xpacket packet, int fd) int xmodem_receive(int sio, int fd) { - struct xpacket packet; + struct xpacket_128b packet; char resp = 0; - int rc; + int rc, err; bool complete = false; char status; /* Drain pending characters from serial line.*/ - while (true) + err = xmrecv_drain_pending_chars(sio); + if (err != OK) { - if (key_hit) - return -1; - rc = read_poll(sio, &resp, 1, 50); - if (rc == 0) - { - if (resp == CAN) return ERR; - break; - } - else if (rc < 0) - { - if (rc != USER_CAN) - { - tio_error_print("Read sync from serial failed"); - } - return ERR; - } + return err; } /* Always work with 128b packets */ - packet.seq = 1; - packet.type = SOH; + packet.hdr.seq = 1; + packet.hdr.type = SOH; /* Start Receive*/ - rc = start_receive(sio); - if (rc == 0) + err = xmrecv_start_receive(sio); + if (err != OK) { - tio_error_print("Timeout waiting for transfer to start"); - return ERR; - } - else if (rc < 0) - { - tio_error_print("Error starting XMODEM receive"); - return ERR; + if (err == ERR_TMO) + { + tio_error_print("Timeout waiting for transfer to start"); + } + else + { + tio_error_print("Error starting XMODEM receive"); + } + return err; } while (!complete) @@ -665,7 +730,7 @@ int xmodem_receive(int sio, int fd) if (rc == 0) { tio_error_print("Timeout waiting for start of next packet"); - return ERR; + return ERR_TMO; } else if (rc < 0) { @@ -673,19 +738,19 @@ int xmodem_receive(int sio, int fd) return ERR; } if (key_hit) - return USER_CAN; + return ERR_USER_CAN; switch (resp) { case SOH: /* Start of a packet */ - rc = receive_packet(sio, packet, fd); - if (rc == OK) + err = xmrecv_receive_packet(sio, &packet, fd); + if (err == OK) { - packet.seq++; + packet.hdr.seq++; status = '.'; } - else if (rc == ERR) + else if (err == ERR || err == ERR_TMO) { rc = write(sio, NAK_STR, 1); if (rc < 0) @@ -695,12 +760,12 @@ int xmodem_receive(int sio, int fd) } status = 'N'; } - else if (rc == ERR_FATAL) + else if (err == ERR_FATAL) { tio_error_print("Receive cancelled due to fatal error"); return ERR; } - else if (rc == USER_CAN) + else if (err == ERR_USER_CAN) { rc = write(sio, CAN_STR, 1); if (rc < 0) @@ -708,9 +773,9 @@ int xmodem_receive(int sio, int fd) tio_error_print("Writing cancel to serial failed"); return ERR; } - return USER_CAN; + return ERR_USER_CAN; } - else if (rc == RX_IGNORE) + else if (err == RX_IGNORE) { status = ':'; } @@ -750,7 +815,7 @@ int xmodem_receive(int sio, int fd) int xymodem_send(int sio, const char *filename, modem_mode_t mode) { size_t len; - int rc, fd; + int err, fd; struct stat stat; const uint8_t *buf; @@ -775,29 +840,15 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) key_hit = 0; if (mode == XMODEM_1K) { - rc = xmodem_1k(sio, buf, len, 1); + err = xmodem_send_1k(sio, buf, len, 1); } else if (mode == XMODEM_CRC) { - rc = xmodem(sio, buf, len); + err = xmodem_send_128b(sio, buf, len); } - else + else /* if (mode == YMODEM) */ { - /* Ymodem: hdr + file + fin */ - while (true) - { - char hdr[1024], *p; - - rc = -1; - if (strlen(filename) > 977) break; /* hdr block overrun */ - p = stpncpy(hdr, filename, 1024) + 1; - p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode); - - if (xmodem_1k(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */ - if (xmodem_1k(sio, buf, len, 1) < 0) break; /* xmodem file */ - if (xmodem_1k(sio, "", 1, 0) < 0) break; /* empty hdr = fin */ - rc = 0; break; - } + err = ymodem_send_1k(sio, buf, len, filename, &stat); } key_hit = 0xff; @@ -805,12 +856,12 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) tcflush(sio, TCIOFLUSH); munmap((void *)buf, len); close(fd); - return rc; + return err; } int xymodem_receive(int sio, const char *filename, modem_mode_t mode) { - int rc, fd; + int err, fd; /* Create new file */ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664); @@ -825,21 +876,21 @@ int xymodem_receive(int sio, const char *filename, modem_mode_t mode) if (mode == XMODEM_1K) { tio_error_print("Not supported"); - rc = -1; + err = ERR; } else if (mode == XMODEM_CRC) { - rc = xmodem_receive(sio, fd); + err = xmodem_receive(sio, fd); } else { tio_error_print("Not supported"); - rc = -1; + err = ERR; } key_hit = 0xff; /* Flush serial and release resources */ tcflush(sio, TCIOFLUSH); close(fd); - return rc; + return err; } From e458b02771b02fe9768c3cd3fda618bd802d047b Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 16 Aug 2025 10:52:31 +0900 Subject: [PATCH 05/11] Add Xmodem-SUM support. --- src/script.c | 7 ++ src/tty.c | 30 ++++++++ src/xymodem.c | 206 ++++++++++++++++++++++++++++++++++++-------------- src/xymodem.h | 1 + 4 files changed, 189 insertions(+), 55 deletions(-) diff --git a/src/script.c b/src/script.c index e050af6..cb104bd 100644 --- a/src/script.c +++ b/src/script.c @@ -241,6 +241,12 @@ static int api_send(lua_State *L) tio_printf("%s", ret < 0 ? "Aborted" : "Done"); break; + case XMODEM_SUM: + tio_printf("Sending file '%s' using XMODEM-SUM", file); + ret = xymodem_send(device_fd, file, XMODEM_SUM); + tio_printf("%s", ret < 0 ? "Aborted" : "Done"); + break; + case YMODEM: tio_printf("Sending file '%s' using YMODEM", file); ret = xymodem_send(device_fd, file, YMODEM); @@ -486,6 +492,7 @@ static void script_set_globals(lua_State *L) script_set_global(L, "toggle", 2); script_set_global(L, "high", 1); script_set_global(L, "low", 0); + script_set_global(L, "XMODEM_SUM", XMODEM_SUM); script_set_global(L, "XMODEM_CRC", XMODEM_CRC); script_set_global(L, "XMODEM_1K", XMODEM_1K); script_set_global(L, "YMODEM", YMODEM); diff --git a/src/tty.c b/src/tty.c index 70c5277..d59c7c1 100644 --- a/src/tty.c +++ b/src/tty.c @@ -767,6 +767,34 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) } break; + case KEY_3: + tio_printf("Send file with XMODEM-SUM"); + tio_printf_raw("Enter file name: "); + if (tio_readln()) + { + int ret; + + tio_printf("Sending file '%s' ", line); + tio_printf("Press any key to abort transfer"); + ret = xymodem_send(device_fd, line, XMODEM_SUM); + tio_printf("%s", ret < 0 ? "Aborted" : "Done"); + } + break; + + case KEY_4: + tio_printf("Receive file with XMODEM-SUM"); + tio_printf_raw("Enter file name: "); + if (tio_readln()) + { + int ret; + + tio_printf("Ready to receiving file '%s' ", line); + tio_printf("Press any key to abort transfer"); + ret = xymodem_receive(device_fd, line, XMODEM_SUM); + tio_printf("%s", ret < 0 ? "Aborted" : "Done"); + } + break; + default: tio_error_print("Invalid protocol option"); break; @@ -1127,6 +1155,8 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward) tio_printf(" (0) XMODEM-1K send"); tio_printf(" (1) XMODEM-CRC send"); tio_printf(" (2) XMODEM-CRC receive"); + tio_printf(" (3) XMODEM-SUM send"); + tio_printf(" (4) XMODEM-SUM receive"); // Process next input character as sub command sub_command = SUBCOMMAND_XMODEM; break; diff --git a/src/xymodem.c b/src/xymodem.c index cbc7834..5190e40 100644 --- a/src/xymodem.c +++ b/src/xymodem.c @@ -54,6 +54,11 @@ struct xpkt_ftr_crc uint8_t crc_lo; } __attribute__((packed)); +struct xpkt_ftr_sum +{ + uint8_t cksum; +} __attribute__((packed)); + struct xpacket_1k { struct xpkt_hdr hdr; @@ -61,13 +66,20 @@ struct xpacket_1k struct xpkt_ftr_crc ftr; } __attribute__((packed)); -struct xpacket_128b +struct xpacket_128b_crc { struct xpkt_hdr hdr; uint8_t data[128]; struct xpkt_ftr_crc ftr; } __attribute__((packed)); +struct xpacket_128b_sum +{ + struct xpkt_hdr hdr; + uint8_t data[128]; + struct xpkt_ftr_sum ftr; +} __attribute__((packed)); + /* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */ static uint16_t calculate_crc16(const uint8_t *data, uint16_t size) { @@ -101,6 +113,15 @@ static uint16_t update_crc16(uint16_t crc, char data_char) return crc; } +static uint8_t calculate_cksum8(const uint8_t *data, uint16_t size) +{ + uint8_t sum; + for (sum = 0; size > 0; size--) { + sum += *data++; + } + return sum; +} + /* * Drain pending characters from serial line. Insist on the * last drained character being initial character 'C':CRC,1K @@ -303,40 +324,75 @@ static int xmodem_send_1k(int sio, const void *data, size_t len, int seq) return OK; } -static int xmodem_send_128b(int sio, const void *data, size_t len) +static int xmodem_send_128b(int sio, const void *data, size_t len, bool use_crc) { - struct xpacket_128b packet; + union + { + struct xpacket_128b_crc c; + struct xpacket_128b_sum s; + } packet; const uint8_t *buf = data; char resp = 0, tmo_resp; int rc, crc, err; /* Drain pending characters from serial line. Insist on the last drained character being 'C' */ - err = xmsend_initial_handshake(sio, 'C'); + if (use_crc) + { + err = xmsend_initial_handshake(sio, 'C'); + } + else + { + err = xmsend_initial_handshake(sio, NAK); + } if (err != OK) return err; /* Always work with 128b packets */ - packet.hdr.seq = 1; - packet.hdr.type = SOH; + if (use_crc) + { + packet.c.hdr.seq = 1; + packet.c.hdr.type = SOH; + } + else + { + packet.s.hdr.seq = 1; + packet.s.hdr.type = SOH; + } while (len) { size_t sz, z = 0; char *from, status; - /* Build next packet, pad with 0 to full seq */ - z = min(len, sizeof(packet.data)); - memcpy(packet.data, buf, z); - memset(packet.data + z, 0, sizeof(packet.data) - z); - crc = calculate_crc16(packet.data, sizeof(packet.data)); - packet.ftr.crc_hi = crc >> 8; - packet.ftr.crc_lo = crc; - packet.hdr.nseq = 0xff - packet.hdr.seq; + if (use_crc) + { + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.c.data)); + memcpy(packet.c.data, buf, z); + memset(packet.c.data + z, 0, sizeof(packet.c.data) - z); + crc = calculate_crc16(packet.c.data, sizeof(packet.c.data)); + packet.c.ftr.crc_hi = crc >> 8; + packet.c.ftr.crc_lo = crc; + packet.c.hdr.nseq = 0xff - packet.c.hdr.seq; + + from = (char *) &packet.c; + sz = sizeof(packet.c); + } + else + { + /* Build next packet, pad with 0 to full seq */ + z = min(len, sizeof(packet.s.data)); + memcpy(packet.s.data, buf, z); + memset(packet.s.data + z, 0, sizeof(packet.s.data) - z); + packet.s.ftr.cksum = calculate_cksum8(packet.s.data, sizeof(packet.s.data)); + packet.s.hdr.nseq = 0xff - packet.s.hdr.seq; + + from = (char *) &packet.s; + sz = sizeof(packet.s); + } /* Send packet */ - from = (char *) &packet; - sz = sizeof(packet); while (sz) { if (key_hit) @@ -380,7 +436,11 @@ static int xmodem_send_128b(int sio, const void *data, size_t len) /* Move to next block after ACK */ if (resp == ACK) { - packet.hdr.seq++; + if (use_crc) + packet.c.hdr.seq++; + else + packet.s.hdr.seq++; + len -= z; buf += z; } @@ -450,7 +510,7 @@ static int xmrecv_drain_pending_chars(int sio) /* * Start Receive */ -static int xmrecv_start_receive(int sio) +static int xmrecv_start_receive(int sio, bool use_crc) { int rc; struct pollfd fds; @@ -463,7 +523,11 @@ static int xmrecv_start_receive(int sio) something. The start character will be sent once a second for a number of seconds. If nothing is received in that time then return false to indicate that the transfer did not start. */ - rc = write(sio, "C", 1); + if (use_crc) + rc = write(sio, "C", 1); + else + rc = write(sio, NAK_STR, 1); + if (rc < 0) { if (errno == EWOULDBLOCK) @@ -501,7 +565,7 @@ static int xmrecv_start_receive(int sio) /* * Receive a packet */ -static int xmrecv_receive_packet(int sio, struct xpacket_128b *packet, int fd) +static int xmrecv_receive_packet(int sio, struct xpacket_128b_crc *packet, int fd, bool use_crc) { char rxSeq1, rxSeq2 = 0; char resp = 0; @@ -566,43 +630,67 @@ static int xmrecv_receive_packet(int sio, struct xpacket_128b *packet, int fd) return ERR_FATAL; } packet->data[ix] = (uint8_t) resp; - calcCrc = update_crc16(calcCrc, resp); + if (use_crc) + calcCrc = update_crc16(calcCrc, resp); + else + calcCrc = (calcCrc + (uint8_t)resp) & 0xff; + if (key_hit) return ERR_USER_CAN; } - /* Read CRC */ - rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) + if (use_crc) { - tio_error_print("Timeout waiting for first CRC byte"); - return ERR_TMO; - } - else if (rc < 0) - { - tio_error_print("Error reading first CRC byte"); - return ERR_FATAL; - } + /* Read CRC */ + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for first CRC byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading first CRC byte"); + return ERR_FATAL; + } - uint8_t uresp = resp; - uint16_t uresp16 = uresp; - rxCrc = uresp16 << 8; + uint8_t uresp = resp; + uint16_t uresp16 = uresp; + rxCrc = uresp16 << 8; - rc = read_poll(sio, &resp, 1, 3000); - if (rc == 0) - { - tio_error_print("Timeout waiting for second CRC byte"); - return ERR_TMO; - } - else if (rc < 0) - { - tio_error_print("Error reading second CRC byte"); - return ERR_FATAL; - } + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for second CRC byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading second CRC byte"); + return ERR_FATAL; + } - uresp = resp; - uresp16 = uresp; - rxCrc |= uresp16; + uresp = resp; + uresp16 = uresp; + rxCrc |= uresp16; + } + else + { + /* Read checksum */ + rc = read_poll(sio, &resp, 1, 3000); + if (rc == 0) + { + tio_error_print("Timeout waiting for checksum byte"); + return ERR_TMO; + } + else if (rc < 0) + { + tio_error_print("Error reading checksum byte"); + return ERR_FATAL; + } + + rxCrc = (uint8_t)resp; + } if (key_hit) return ERR_USER_CAN; @@ -689,9 +777,9 @@ static int xmrecv_receive_packet(int sio, struct xpacket_128b *packet, int fd) return OK; } -int xmodem_receive(int sio, int fd) +int xmodem_receive(int sio, int fd, bool use_crc) { - struct xpacket_128b packet; + struct xpacket_128b_crc packet; char resp = 0; int rc, err; bool complete = false; @@ -709,7 +797,7 @@ int xmodem_receive(int sio, int fd) packet.hdr.type = SOH; /* Start Receive*/ - err = xmrecv_start_receive(sio); + err = xmrecv_start_receive(sio, use_crc); if (err != OK) { if (err == ERR_TMO) @@ -744,7 +832,7 @@ int xmodem_receive(int sio, int fd) { case SOH: /* Start of a packet */ - err = xmrecv_receive_packet(sio, &packet, fd); + err = xmrecv_receive_packet(sio, &packet, fd, use_crc); if (err == OK) { packet.hdr.seq++; @@ -844,7 +932,11 @@ int xymodem_send(int sio, const char *filename, modem_mode_t mode) } else if (mode == XMODEM_CRC) { - err = xmodem_send_128b(sio, buf, len); + err = xmodem_send_128b(sio, buf, len, /* use_crc= */ true); + } + else if (mode == XMODEM_SUM) + { + err = xmodem_send_128b(sio, buf, len, /* use_crc= */ false); } else /* if (mode == YMODEM) */ { @@ -880,7 +972,11 @@ int xymodem_receive(int sio, const char *filename, modem_mode_t mode) } else if (mode == XMODEM_CRC) { - err = xmodem_receive(sio, fd); + err = xmodem_receive(sio, fd, /* use_crc= */ true); + } + else if (mode == XMODEM_SUM) + { + err = xmodem_receive(sio, fd, /* use_crc= */ false); } else { diff --git a/src/xymodem.h b/src/xymodem.h index b5d2b8d..ed239be 100644 --- a/src/xymodem.h +++ b/src/xymodem.h @@ -25,6 +25,7 @@ typedef enum { XMODEM_1K, XMODEM_CRC, + XMODEM_SUM, YMODEM, } modem_mode_t; From 9784d3c277fb0906f9b94e0819ecf58379767a98 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 16 Aug 2025 18:05:56 +0900 Subject: [PATCH 06/11] Fix compile error in MSYS2/Cygwin --- src/tty.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tty.c b/src/tty.c index d59c7c1..6cc0ab0 100644 --- a/src/tty.c +++ b/src/tty.c @@ -2401,7 +2401,7 @@ void tty_wait_for_device(void) // Happens when port unpluged if (errno == EACCES) { - goto error; + break; } #elif defined(__APPLE__) if (errno == EBADF) From 3922ce3447f2885592da596fae8c652655178811 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 20 Sep 2025 10:19:53 +0900 Subject: [PATCH 07/11] Fix output-line-delay behavior If output-line-delay is non-zero and output-delay is any, line delay time set output-line-delay. If output-line-delay is zero and output-delay is non-zero, line delay time set output-delay. It was previously (output-line-delay + output-delay) in any case. Also, since the buffer was flushed after the line delay, there was a possibility that the delay would be reduced or not applied at all. --- src/tty.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tty.c b/src/tty.c index 6cc0ab0..b5e8c10 100644 --- a/src/tty.c +++ b/src/tty.c @@ -283,15 +283,14 @@ ssize_t tty_write(int fd, const void *buffer, size_t count) } bytes_written += retval; + fsync(fd); + tcdrain(fd); + if (option.output_line_delay && *(unsigned char*)buffer == '\n') { delay(option.output_line_delay); } - - fsync(fd); - tcdrain(fd); - - if (option.output_delay) + else if (option.output_delay) { delay(option.output_delay); } From 6a590bd598379192d47021d5378c4967f78a2e0a Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 20 Sep 2025 10:56:11 +0900 Subject: [PATCH 08/11] Fix output-delay and input-mode line combination bug --- src/tty.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tty.c b/src/tty.c index b5e8c10..3dd99ae 100644 --- a/src/tty.c +++ b/src/tty.c @@ -259,22 +259,23 @@ ssize_t tty_write(int fd, const void *buffer, size_t count) { ssize_t retval = 0, bytes_written = 0; size_t i; + unsigned char *cbuf = (unsigned char *)buffer; if (option.map_o_ltu) { // Convert lower case to upper case - for (i = 0; i Date: Sun, 26 Oct 2025 18:50:38 +0900 Subject: [PATCH 09/11] Fix map option's input/output description of tio man page. --- man/tio.1.in | 4 ++-- man/tio.1.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/man/tio.1.in b/man/tio.1.in index eb0338d..aaaf8a5 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -205,8 +205,8 @@ Strip control characters and escape sequences from log. .TP .BR \-m ", " "\-\-map " \fI -Map (replace, translate) characters on input to the serial device or output -from the serial device. The following mapping flags are supported: +Map (replace, translate) characters on input from the serial device or output +to the serial device. The following mapping flags are supported: .RS .TP 12n diff --git a/man/tio.1.txt b/man/tio.1.txt index eeac82c..46afb5e 100644 --- a/man/tio.1.txt +++ b/man/tio.1.txt @@ -158,7 +158,7 @@ OPTIONS -m, --map - Map (replace, translate) characters on input to the serial device or output from the serial device. The following mapping flags are supported: + Map (replace, translate) characters on input from the serial device or output to the serial device. The following mapping flags are supported: ICRNL Map CR to NL on input (unless IGNCR is set) From c11b3a196f4606c7e8996d758ab43a7d65cfb4f7 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 29 Nov 2025 10:11:01 +0900 Subject: [PATCH 10/11] Fix left bracket input in line input mode When the input mode is set to line, the left bracket cannot be input. --- src/readline.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/readline.c b/src/readline.c index 6cfae00..ecb382d 100644 --- a/src/readline.c +++ b/src/readline.c @@ -139,6 +139,7 @@ static void readline_input_left_bracket(void) } else { + readline_input_char('['); rl_escape = 0; } } From 13f86bd18a82dc17a37b3caf3efa6d625ba20394 Mon Sep 17 00:00:00 2001 From: yabu76 Date: Sat, 14 Feb 2026 09:22:12 +0900 Subject: [PATCH 11/11] XMODEM-checksum (aka XMODEM-128) and some fixes Note: XMODEM_SUM refers to the early XMODEM 128bytes/Checksum. --- README.md | 4 ++-- man/tio.1.in | 4 ++-- man/tio.1.txt | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e3ab066..0e33721 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ when used in combination with [tmux](https://tmux.github.io). * Useful for reconnecting when serial device has no serial device by ID * Support for non-standard baud rates * Support for mark and space parity - * X-modem (1K/CRC) and Y-modem file upload + * X-modem (1K/CRC/Checksum) and Y-modem file upload * Support for RS-485 mode * List available serial devices * By device @@ -431,7 +431,7 @@ Returns the `tio` table. Send file using x/y-modem protocol. -Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `YMODEM`. +Protocol can be any of `XMODEM_1K`, `XMODEM_CRC`, `XMODEM_SUM`, `YMODEM`. #### `tio.ttysearch()` diff --git a/man/tio.1.in b/man/tio.1.in index aaaf8a5..8caeefb 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -426,7 +426,7 @@ Toggle line timestamp mode .IP "\fBctrl-t v" Show version .IP "\fBctrl-t x" -Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) +Send file using the XMODEM-1K or XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol) .IP "\fBctrl-t y" Send file using the YMODEM protocol (prompts for file name) .IP "\fBctrl-t ctrl-t" @@ -468,7 +468,7 @@ Returns the tio table. .IP "\fBtio.send(file, protocol)" Send file using x/y-modem protocol. -Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. +Protocol can be any of XMODEM_1K, XMODEM_CRC, XMODEM_SUM, YMODEM. .IP "\fBtio.ttysearch()" Search for serial devices. diff --git a/man/tio.1.txt b/man/tio.1.txt index 46afb5e..c1efbbc 100644 --- a/man/tio.1.txt +++ b/man/tio.1.txt @@ -342,7 +342,7 @@ KEY COMMANDS ctrl-t v Show version - ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC protocol (prompts for file name and protocol) + ctrl-t x Send file using the XMODEM-1K or XMODEM-CRC or XMODEM-SUM protocol (prompts for file name and protocol) ctrl-t y Send file using the YMODEM protocol (prompts for file name) @@ -382,7 +382,7 @@ SCRIPT API send(file, protocol) Send file using x/y-modem protocol. - Protocol can be any of XMODEM_1K, XMODEM_CRC, YMODEM. + Protocol can be any of XMODEM_1K, XMODEM_CRC, XMODEM_SUM, YMODEM. tty_search() Search for serial devices.