From 7a0518c35b066baf8beda905b87bff44cb9ea032 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Sat, 22 Jun 2024 23:45:08 -0500 Subject: [PATCH] serial_forwarder: serial parsing This removes the dependency on libserialport --- Makefile | 11 ++- event.h | 4 + parse_serial.c | 67 +++++++------ parse_serial.h | 15 +++ serial.c | 50 ++++++++++ serial.h | 3 + serial_forwarder | Bin 0 -> 49432 bytes serial_forwarder.c | 237 ++++++++++++++++++++++++++++++--------------- timer.h | 16 +-- 9 files changed, 286 insertions(+), 117 deletions(-) create mode 100644 event.h create mode 100644 parse_serial.h create mode 100644 serial.c create mode 100644 serial.h create mode 100755 serial_forwarder diff --git a/Makefile b/Makefile index d017ed9..9a222b3 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,8 @@ DEBUG = -g -gdwarf-4 CFLAGS += -Wall -Werror -Wfatal-errors CFLAGS += -std=c2x CFLAGS += -I$(SDL)/include -D_REENTRANT -CFLAGS += -I/usr/local/include CFLAGS += $(shell pkg-config --cflags freetype2) LDFLAGS += -L$(SDL)/build -lSDL3 -Wl,-rpath=$(SDL)/build -LDFLAGS += -Wl,-rpath,/usr/local/lib -L/usr/local/lib -lserialport LDFLAGS += $(shell pkg-config --libs freetype2) DEPFLAGS = -MMD -MP @@ -18,12 +16,17 @@ OPT = -O3 -march=native OBJS = \ timer.o -all: main +SERIAL_FORWARDER_OBJS = \ + serial_forwarder.o \ + serial.o \ + parse_serial.o + +all: serial_forwarder %.o: %.c $(CC) $(CARCH) $(CFLAGS) $(OPT) $(DEBUG) $(DEPFLAGS) -MF ${<}.d -c $< -o $@ -main: $(OBJS) +serial_forwarder: $(SERIAL_FORWARDER_OBJS) $(CC) $(LDFLAGS) $^ -o $@ -include $(shell find -type f -name '*.d') diff --git a/event.h b/event.h new file mode 100644 index 0000000..2c80916 --- /dev/null +++ b/event.h @@ -0,0 +1,4 @@ +enum command { + CMD_NOP, + CMD_STATE_TRANSFER, +}; diff --git a/parse_serial.c b/parse_serial.c index 29dca90..b35d0c6 100644 --- a/parse_serial.c +++ b/parse_serial.c @@ -1,9 +1,10 @@ #include #include #include +#include #include "packet.h" -#include "bufsize.h" +#include "parse_serial.h" uint8_t const * parse_int(uint8_t const * buf, int * length, int * number, int * digits) { @@ -27,8 +28,8 @@ uint8_t const * parse_int(uint8_t const * buf, int * length, int * number, int * case '8': [[fallthrough]]; case '9': *digits += 1; - number *= 10; - number += c - '0'; + *number *= 10; + *number += c - '0'; break; case ' ': break; @@ -49,7 +50,7 @@ int parse_time(uint8_t const * const buf, int length, struct stopwatch_time * ti uint8_t const * bufi = buf; bufi = parse_int(bufi, &length, &time->integer_value, &time->integer_digits); - if (integer_digits == 0) { + if (time->integer_digits == 0) { fprintf(stderr, "invalid integer at `%c`\n", *bufi); return -1; } @@ -62,7 +63,7 @@ int parse_time(uint8_t const * const buf, int length, struct stopwatch_time * ti return -1; bufi = parse_int(bufi, &length, &time->fraction_value, &time->fraction_digits); - if (integer_digits == 0) { + if (time->fraction_digits == 0) { fprintf(stderr, "invalid fraction at `%c`\n", *bufi); return -1; } @@ -74,55 +75,65 @@ int min(int a, int b) { return a < b ? a : b; } -struct parser_state { - uint8_t buf[BUFSIZE]; - int offset; -}; - -bool is_() +bool is_timer_resume(uint8_t const * const buf, int length) { + return false; } -void parse_line(uint8_t * buf, int length, - struct timer_state * timer_state) +bool handle_line(uint8_t const * const buf, int length, + struct timer_state * timer_state) { if (length == 0) { - timer_state->status = COUNTER_RUNNING; + timer_state->status = TIMER_RUNNING; int ret = clock_gettime(CLOCK_MONOTONIC, &timer_state->counter.start); timer_state->counter.offset.tv_sec = 0; timer_state->counter.offset.tv_nsec = 0; assert(ret != -1); - } else if (is_) { + return true; + } else if (is_timer_resume(buf, length)) { + return true; + } else { + int ret = parse_time(buf, length, &timer_state->time); + if (ret == 0) { + timer_state->status = TIMER_STOPPED; + return true; + } } + + return false; } -void handle_parse(uint8_t * read_buf, int length, +bool handle_parse(uint8_t * read_buf, int length, struct parser_state * parser_state, struct timer_state * timer_state) { + bool have_state_change = false; while (length > 0) { - int copy_length = min(length, BUFSIZE - state->offset); + int copy_length = min(length, BUFSIZE - parser_state->offset); length -= copy_length; - memcpy(&state->buf[state->offset], read_buf, copy_length); - state->offset += copy_length; + memcpy(&parser_state->buf[parser_state->offset], read_buf, copy_length); + parser_state->offset += copy_length; - while (state->offset > 0) { + while (parser_state->offset > 0) { int i = 0; - while (i < state->offset) { - if (state->buf[i] == '\r') { - fprintf(stderr, "r at %d %d\n", i, state->offset); - handle_line(state->buf, i); - state->offset -= i + 1; - assert(state->offset >= 0); - memmove(&state->buf[0], &state->buf[i + 1], state->offset); + while (i < parser_state->offset) { + if (parser_state->buf[i] == '\r') { + fprintf(stderr, "r at %d %d\n", i, parser_state->offset); + have_state_change |= handle_line(parser_state->buf, i, + timer_state); + parser_state->offset -= i + 1; + assert(parser_state->offset >= 0); + memmove(&parser_state->buf[0], &parser_state->buf[i + 1], parser_state->offset); break; } i++; - if (i == state->offset) + if (i == parser_state->offset) goto exit; } } exit: continue; } + + return have_state_change; } diff --git a/parse_serial.h b/parse_serial.h new file mode 100644 index 0000000..72af749 --- /dev/null +++ b/parse_serial.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "bufsize.h" +#include "timer.h" + +struct parser_state { + uint8_t buf[BUFSIZE]; + int offset; +}; + +bool handle_parse(uint8_t * read_buf, int length, + struct parser_state * parser_state, + struct timer_state * timer_state); diff --git a/serial.c b/serial.c new file mode 100644 index 0000000..9139eaa --- /dev/null +++ b/serial.c @@ -0,0 +1,50 @@ +#define _DEFAULT_SOURCE 1 + +#include +#include + +#include "serial.h" + +int set_terminal_attributes(int fd) +{ + int ret; + struct termios tty; + ret = tcgetattr(fd, &tty); + if (ret == -1) { + perror("tcgetattr"); + return -1; + } + + tty.c_cflag &= ~PARENB; // no parity + tty.c_cflag &= ~CSTOPB; // one stop bit + tty.c_cflag &= ~CSIZE; // clear size bits + tty.c_cflag |= CS8; // 8 bit + tty.c_cflag &= ~CRTSCTS; // disable RTS/CTS hardware flow control + tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines + + tty.c_lflag &= ~ICANON; + tty.c_lflag &= ~ECHO; // disable echo + tty.c_lflag &= ~ECHOE; // disable erasure + tty.c_lflag &= ~ECHONL; // disable new-line echo + tty.c_lflag &= ~ISIG; // disable interpretation of INTR, QUIT and SUSP + + tty.c_iflag &= ~(IXON|IXOFF|IXANY); // turn off xon/xoff ctrl + tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // disable any special handling of received bytes + + tty.c_oflag &= ~OPOST; // prevent special interpretation of output bytes (e.g. newline chars) + tty.c_oflag &= ~ONLCR; // prevent conversion of newline to carriage return/line feed + + tty.c_cc[VMIN] = 1; + tty.c_cc[VTIME] = 0; + + cfsetospeed(&tty, B1200); + cfsetispeed(&tty, B1200); + + ret = tcsetattr(fd, TCSANOW, &tty); + if (ret == -1) { + perror("tcsetattr"); + return -1; + } + + return 0; +} diff --git a/serial.h b/serial.h new file mode 100644 index 0000000..0a84f04 --- /dev/null +++ b/serial.h @@ -0,0 +1,3 @@ +#pragma once + +int set_terminal_attributes(int fd); diff --git a/serial_forwarder b/serial_forwarder new file mode 100755 index 0000000000000000000000000000000000000000..355dcf94246e8232785da4a8875706711010ab07 GIT binary patch literal 49432 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 literal 0 HcmV?d00001 diff --git a/serial_forwarder.c b/serial_forwarder.c index 761f9b2..b2d75a0 100644 --- a/serial_forwarder.c +++ b/serial_forwarder.c @@ -1,79 +1,23 @@ +#include +#include +#include +#include +#include #include #include -#include -#include -#include #include -#include -#include - -#include +#include +#include +#include #include "bufsize.h" +#include "serial.h" +#include "timer.h" +#include "parse_serial.h" #define PORT 4321 -#define SERIALPORT "/dev/ttyS0" - -int sp_error(const char * s, enum sp_return result) -{ - char * error_message; - switch (result) { - case SP_ERR_ARG: - printf("%s: error: Invalid argument.\n", s); - case SP_ERR_FAIL: - error_message = sp_last_error_message(); - printf("%s: error: Failed: %s\n", error_message, s); - sp_free_error_message(error_message); - case SP_ERR_SUPP: - printf("%s: error: Not supported.\n", s); - case SP_ERR_MEM: - printf("%s: error: Couldn't allocate memory.\n", s); - case SP_OK: [[fallthrough]]; - default: - return result; - } -} - -int init_serial(struct sp_port * port) -{ - enum sp_return sp_ret; - - sp_ret = sp_get_port_by_name(SERIALPORT, &port); - if (sp_ret != SP_OK) { - sp_error("sp_get_port_by_name", sp_ret); - return -1; - } - sp_ret = sp_open(port, SP_MODE_READ_WRITE); - if (sp_ret != SP_OK) { - sp_error("sp_open", sp_ret); - return -1; - } - sp_ret = sp_set_baudrate(port, 1200); - if (sp_ret != SP_OK) { - sp_error("sp_set_baudrate", sp_ret); - return -1; - } - sp_ret = sp_set_bits(port, 8); - if (sp_ret != SP_OK) { - sp_error("sp_set_bits", sp_ret); - return -1; - } - sp_ret = sp_set_parity(port, SP_PARITY_NONE); - if (sp_ret != SP_OK) { - sp_error("sp_set_parity", sp_ret); - return -1; - } - sp_ret = sp_set_stopbits(port, 1); - if (sp_ret != SP_OK) { - sp_error("sp_set_stopbits", sp_ret); - return -1; - } - sp_ret = sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE); - if (sp_ret != SP_OK) { - sp_error("sp_set_flowcontrol", sp_ret); - return -1; - } -} +//#define SERIALPORT "/dev/ttyS0" +#define SERIALPORT "/dev/ttyUSB0" int handle_sock(int sock) { @@ -86,10 +30,10 @@ int handle_sock(int sock) ssize_t recv_len = recvfrom(sock, buf, BUFSIZE, 0, (struct sockaddr *)&src_addr, &addrlen); if (recv_len == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK) { - perror("eagain"); + perror("sock eagain"); return 0; } else { - perror("recvfrom"); + perror("recvfrom sock"); return -1; } } @@ -101,11 +45,85 @@ int handle_sock(int sock) } } +int rearm_timer(int timerfd) +{ + struct itimerspec value; + value.it_value.tv_sec = 1; + value.it_value.tv_nsec = 0; + value.it_interval.tv_sec = 0; + value.it_interval.tv_nsec = 0; + + int ret = timerfd_settime(timerfd, 0, &value, NULL); + if (ret == -1) { + perror("timerfd_settime"); + return -1; + } + + return 0; +} + +int handle_timerfd(int timerfd) +{ + uint64_t expired_count = 0; + while (true) { + ssize_t len = read(timerfd, &expired_count, (sizeof (expired_count))); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + fprintf(stderr, "timerfd eagain\n"); + break; + } else { + perror("read timerfd"); + return -1; + } + } + assert(len == (sizeof (expired_count))); + printf("len %ld\n", len); + } + + rearm_timer(timerfd); + printf("do timer stuff\n"); + + return 0; +} + struct receiver_state { uint64_t seq; uint64_t ack_seq; }; +int handle_serialfd(int timerfd, + struct parser_state * parser_state, + struct timer_state * timer_state) +{ + uint8_t buf[BUFSIZE]; + + while (true) { + ssize_t len = read(timerfd, buf, (sizeof (buf))); + if (len == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + fprintf(stderr, "serialfd eagain\n"); + break; + } else { + perror("read serialfd"); + return -1; + } + } + + /* + for (int i = 0; i < len; i++) { + fprintf(stderr, "%x ", buf[i]); + } + fprintf(stderr, "\n"); + */ + bool have_state_change = handle_parse(buf, len, + parser_state, + timer_state); + printf("have_state_change: %d\n", have_state_change); + } + + return 0; +} + int main(void) { int ret; @@ -127,7 +145,7 @@ int main(void) return -1; } -#define MAX_EVENTS 2 +#define MAX_EVENTS 5 struct epoll_event events[MAX_EVENTS]; int epollfd = epoll_create1(0); @@ -136,19 +154,67 @@ int main(void) return -1; } - struct epoll_event ev; - ev.events = EPOLLIN | EPOLLET; - ev.data.fd = sock; - ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &ev); - if (ret == -1) { - perror("epoll_ctl: sock"); + { + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLET; + ev.data.fd = sock; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &ev); + if (ret == -1) { + perror("epoll_ctl: sock"); + return -1; + } + } + + int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (timerfd == -1) { + perror("timerfd_create"); return -1; } + ret = rearm_timer(timerfd); + if (ret == -1) { + return -1; + } + + if (0) { + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLET; + ev.data.fd = timerfd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, timerfd, &ev); + if (ret == -1) { + perror("epoll_ctl: timerfd"); + return -1; + } + } + + int serialfd = open(SERIALPORT, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (serialfd == -1) { + perror("open: serialport"); + return -1; + } + ret = set_terminal_attributes(serialfd); + if (ret == -1) { + return -1; + } + + { + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLET; + ev.data.fd = serialfd; + ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, serialfd, &ev); + if (ret == -1) { + perror("epoll_ctl: timerfd"); + return -1; + } + } + + struct parser_state parser_state = {0}; + struct timer_state timer_state = {0}; + while (1) { printf("Wait for datagram\n"); - int nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1000); + int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); return -1; @@ -159,6 +225,19 @@ int main(void) ret = handle_sock(sock); if (ret == -1) return -1; + } else if (events[n].data.fd == timerfd) { + ret = handle_timerfd(timerfd); + if (ret == -1) + return -1; + } else if (events[n].data.fd == serialfd) { + fprintf(stderr, "handle_serialfd\n"); + ret = handle_serialfd(serialfd, + &parser_state, + &timer_state); + if (ret == -1) + return -1; + } else { + assert(0); } } diff --git a/timer.h b/timer.h index e3bf079..61df870 100644 --- a/timer.h +++ b/timer.h @@ -1,13 +1,17 @@ +#pragma once + #include -enum counter_status { - COUNTER_STOPPED, - COUNTER_RUNNING, +enum timer_status { + TIMER_STOPPED, + TIMER_RUNNING, }; struct stopwatch_time { - int32_t whole; - int32_t fraction; + int integer_value; + int integer_digits; + int fraction_value; + int fraction_digits; }; struct running_counter { @@ -16,7 +20,7 @@ struct running_counter { }; struct timer_state { - enum counter_status status; + enum timer_status status; struct running_counter counter; struct stopwatch_time time; };