X-Git-Url: http://rtime.felk.cvut.cz/gitweb/sojka/sterm.git/blobdiff_plain/47c1c50b804985913e7ca235af7bac9754354fb9..HEAD:/sterm.c diff --git a/sterm.c b/sterm.c index 3e1a573..13701e7 100644 --- a/sterm.c +++ b/sterm.c @@ -1,7 +1,7 @@ /* * Simple serial terminal * - * Copyright 2014, 2015 Michal Sojka + * Copyright 2014, 2015, 2016, 2017, 2019, 2020, 2021 Michal Sojka * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -30,6 +30,8 @@ */ #define _BSD_SOURCE +#define _DEFAULT_SOURCE +#define _GNU_SOURCE #include #include #include @@ -40,9 +42,15 @@ #include #include #include +#include #include #include +#ifdef HAVE_LOCKDEV #include +#endif +#include +#include +#include #define STRINGIFY(val) #val #define TOSTRING(val) STRINGIFY(val) @@ -52,6 +60,8 @@ #define VERBOSE(format, ...) do { if (verbose) fprintf(stderr, "sterm: " format, ##__VA_ARGS__); } while (0) +#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y)) + bool verbose = false; bool exit_on_escape = true; @@ -71,10 +81,12 @@ void restore_stdin_term() tcsetattr(0, TCSANOW, &stdin_tio_backup); } +#ifdef HAVE_LOCKDEV void unlock() { dev_unlock(dev, getpid()); } +#endif void sighandler(int arg) { @@ -102,12 +114,35 @@ int dtr_rts_arg(const char option, const char *optarg) return val; } +// See DSR and CPR at +// https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_output_sequences +bool is_cpr_control_seq(char c) +{ + static enum state { CSI_ESC, CSI_BRACKET, PAR_N, PAR_M, FIN_R } state; + + switch (state) { + case CSI_ESC: state = (c == 0x1b) ? CSI_BRACKET : CSI_ESC; break; + case CSI_BRACKET: state = (c == '[') ? PAR_N : CSI_ESC; break; + case PAR_N: state = (c == ';') ? PAR_M : (c >= '0' && c <= '9' ? PAR_N : CSI_ESC); break; + case PAR_M: state = (c == 'R') ? FIN_R : (c >= '0' && c <= '9' ? PAR_M : CSI_ESC); break; + case FIN_R: break; + } + if (state == FIN_R) { + state = CSI_ESC; + return true; + + } else + return state != CSI_ESC; +} + void exit_on_escapeseq(const char *buf, int len) { static const char escseq[] = "\r~."; static const char *state = escseq+1; int i; for (i = 0; i < len; i++) { + if (is_cpr_control_seq(buf[i])) + continue; if (buf[i] == *state) { state++; if (*state == 0) @@ -120,23 +155,29 @@ void exit_on_escapeseq(const char *buf, int len) } } -void usage(const char* argv0) +void print_usage_and_exit(const char* argv0, int exit_code) { - fprintf(stderr, "Usage: %s [options] \n", argv0); - fprintf(stderr, + fprintf(exit_code == 0 ? stdout : stderr, "Usage: %s [options] \n", argv0); + fprintf(exit_code == 0 ? stdout : stderr, "Options:\n" + " -b send break signal\n" " -c enter command mode\n" " -d[PULSE] make pulse on DTR\n" " -e ignore '~.' escape sequence\n" - " -n do not switch the device to raw mode\n" + " -h, --help print help and exit\n" + " -n do not switch stdin TTY to raw mode\n" " -r[PULSE] make pulse on RTS\n" " -s \n" + " -t minimum delay between two transmitted characters\n" + " -v verbose mode\n" + " -V, --version print version and exit\n" "\n" "PULSE is a number specifying the pulse. Absolute value defines the\n" "length of the pulse in milliseconds, sign determines the polarity of\n" "the pulse. Alternatively, PULSE can be either '+' or '-', which\n" - "corresponds to +1 or -1.\n" + "corresponds to '+1' or '-1'.\n" ); + exit(exit_code); } void pulse(int fd, int dtr, int rts) @@ -167,6 +208,7 @@ void handle_commands(int fd) while (!go) { char *p1 = NULL; + int num; if (fgets(command, sizeof(command), stdin) == NULL) { if (!feof(stdin)) perror("Command read"); @@ -176,8 +218,12 @@ void handle_commands(int fd) pulse(fd, dtr_rts_arg('d', p1), 0); else if (sscanf(command, "rts %ms", &p1) == 1) pulse(fd, 0, dtr_rts_arg('r', p1)); + else if (sscanf(command, "break %d", &num) == 1) + CHECK(tcsendbreak(fd, num)); else if (strcmp(command, "go\n") == 0) break; + else if (strcmp(command, "exit\n") == 0) + exit(0); else { fprintf(stderr, "Unknown command: %s\n", command); exit(1); @@ -187,6 +233,13 @@ void handle_commands(int fd) } } +static int64_t now_us() +{ + struct timespec ts; + CHECK(clock_gettime(CLOCK_MONOTONIC, &ts)); + return ts.tv_sec * 1000000 + ts.tv_nsec / 1000; +} + int main(int argc, char *argv[]) { int fd; @@ -197,14 +250,24 @@ int main(int argc, char *argv[]) bool stdin_tty; bool raw = true; bool cmd = false; + int break_dur = -1; + int tx_delay_ms = 0; if ((stdin_tty = isatty(0))) { CHECK(tcgetattr(0, &stdin_tio_backup)); atexit(restore_stdin_term); } - while ((opt = getopt(argc, argv, "cnd::er::s:v")) != -1) { + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"version", no_argument, 0, 'V' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "b:cnd::er::s:vVt:h", + long_options, NULL)) != -1) { switch (opt) { + case 'b': break_dur = atoi(optarg); break; case 'c': cmd = true; break; case 'd': dtr = dtr_rts_arg(opt, optarg); break; case 'e': exit_on_escape = false; break; @@ -233,6 +296,18 @@ int main(int argc, char *argv[]) S(57600); S(115200); S(230400); + S(460800); + S(500000); + S(576000); + S(921600); + S(1000000); + S(1152000); + S(1500000); + S(2000000); + S(2500000); + S(3000000); + S(3500000); + S(4000000); #undef S default: fprintf(stderr, "Unknown baud rate %d\n", s); @@ -240,12 +315,19 @@ int main(int argc, char *argv[]) } break; } + case 't': + tx_delay_ms = atoi(optarg); + break; case 'v': verbose = true; break; + case 'V': + printf("sterm version %s\n", strlen(STERM_VERSION) > 0 ? STERM_VERSION : "unknown"); + return 0; + case 'h': + /* fallthrough */ default: /* '?' */ - usage(argv[0]); - exit(1); + print_usage_and_exit(argv[0], opt == 'h' ? 0 : 1); } } @@ -254,23 +336,29 @@ int main(int argc, char *argv[]) if (!dev) { fprintf(stderr, "No device specified\n"); - usage(argv[0]); - exit(1); + print_usage_and_exit(argv[0], 1); } signal(SIGINT, sighandler); signal(SIGTERM, sighandler); signal(SIGHUP, sighandler); +#ifdef HAVE_LOCKDEV pid_t pid = dev_lock(dev); if (pid > 0) { fprintf(stderr, "%s is used by PID %d\n", dev, pid); exit(1); } else if (pid < 0) { - perror("dev_lock()"); + char *msg; + asprintf(&msg, "dev_lock('%s')", dev); /* No free() because we exit() immediately */ + if (errno) + perror(msg); + else + fprintf(stderr, "%s: Error\n", msg); exit(1); } atexit(unlock); +#endif /* O_NONBLOCK is needed to not wait for the CDC signal. See tty_ioctl(4). */ if ((fd = open(dev, O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0) { @@ -281,6 +369,8 @@ int main(int argc, char *argv[]) int n = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, n & ~O_NDELAY); + flock(fd, LOCK_EX); + if (isatty(fd)) { CHECK(ioctl(fd, TIOCEXCL, NULL)); @@ -296,6 +386,9 @@ int main(int argc, char *argv[]) if (dtr || rts) pulse(fd, dtr, rts); + if (break_dur != -1) + CHECK(tcsendbreak(fd, break_dur)); + /* Disable flow control */ tio.c_cflag &= ~(CRTSCTS); tio.c_iflag &= ~(IXON|IXOFF); @@ -311,11 +404,12 @@ int main(int argc, char *argv[]) if (cmd) handle_commands(fd); + + enum { STDIN, DEV }; struct pollfd fds[2] = { - { .fd = 0, .events = POLLIN }, - { .fd = fd, .events = POLLIN }, + [STDIN] = { .fd = STDIN_FILENO, .events = POLLIN }, + [DEV] = { .fd = fd, .events = POLLIN }, }; - char buf[4096]; if (stdin_tty) { tio = stdin_tio_backup; @@ -327,32 +421,63 @@ int main(int argc, char *argv[]) if (exit_on_escape) VERBOSE("Use '~.' sequence to exit.\r\n"); + char buf2dev[4096]; + int buf_len = 0, buf_idx = 0; + int64_t last_tx_us = 0; while (1) { - int r1, r2; - CHECK(poll(fds, 2, -1)); - if (fds[0].revents & POLLIN) { - r1 = CHECK(read(0, buf, sizeof(buf))); - if (r1 == 0) { + int timeout = (tx_delay_ms == 0 || buf_len == 0) + ? -1 + : MAX(0, tx_delay_ms - (now_us() - last_tx_us) / 1000); + + CHECK(poll(fds, 2, timeout)); + if (fds[STDIN].revents & POLLIN && buf_len == 0) { + buf_len = CHECK(read(STDIN_FILENO, buf2dev, sizeof(buf2dev))); + if (buf_len == 0) { VERBOSE("EOF on stdin\r\n"); break; } + buf_idx = 0; if (exit_on_escape) - exit_on_escapeseq(buf, r1); - r2 = CHECK(write(fd, buf, r1)); - if (r1 != r2) { - fprintf(stderr, "Not all data written to %s (%d/%d)\n", dev, r1, r2); + exit_on_escapeseq(buf2dev, buf_len); + } + if (buf_len > 0) { + int wlen = 0; + bool short_write = false; + if (tx_delay_ms == 0) { + wlen = CHECK(write(fd, buf2dev, buf_len)); + short_write = wlen != buf_len; + + } else { + int64_t now = now_us(); + if (now - last_tx_us >= tx_delay_ms * 1000) { + wlen = CHECK(write(fd, &buf2dev[buf_idx], 1)); + short_write = wlen != 1; + last_tx_us = now; + } + } + if (short_write) { + fprintf(stderr, "Not all data written to %s (%d/%d)\n", dev, buf_len, wlen); exit(1); } + buf_idx += wlen; + if (buf_idx >= buf_len) + buf_len = 0; + } + if (fds[STDIN].revents & POLLHUP) { + VERBOSE("HUP on stdin\r\n"); + break; } - if (fds[1].revents & POLLIN) { - r1 = CHECK(read(fd, buf, sizeof(buf))); - if (r1 == 0) { + if (fds[DEV].revents & POLLIN) { + char buf[1024]; + int rlen, wlen; + rlen = CHECK(read(fd, buf, sizeof(buf))); + if (rlen == 0) { VERBOSE("EOF on %s\r\n", dev); break; } - r2 = CHECK(write(1, buf, r1)); - if (r1 != r2) { - fprintf(stderr, "Not all data written to stdout (%d/%d)\n", r1, r2); + wlen = CHECK(write(STDOUT_FILENO, buf, rlen)); + if (rlen != wlen) { + fprintf(stderr, "Not all data written to stdout (%d/%d)\n", wlen, rlen); exit(1); } }