diff options
| -rw-r--r-- | src/gb.c | 69 | ||||
| -rw-r--r-- | src/gb.h | 6 |
2 files changed, 72 insertions, 3 deletions
@@ -6,6 +6,59 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <time.h> +#include <sys/socket.h> +#include <sys/un.h> + +// ---------- link socket resilience (--serial-sock) ---------- +// The link/network bridge socket can die under us (hub restarted, peer +// killed). A dead link must not be fatal or silent: mark it down, then retry +// a background reconnect at most every 500ms from the serial poll path. RX +// stays "pending" while down (serial_no_eof), so the guest just sees a quiet +// link that comes back. + +static double mono_sec(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1e9; +} + +static void serial_mark_dead(GB *gb) { + if (!gb->serial_sock_path || gb->serial_sock_dead) return; + fprintf(stderr, "[sl0pboy] link socket lost (%s); reconnecting...\n", + gb->serial_sock_path); + if (gb->serial_in_fd >= 0) close(gb->serial_in_fd); + gb->serial_in_fd = gb->serial_out_fd = -1; + gb->serial_sock_dead = true; + gb->serial_retry_at = mono_sec() + 0.5; +} + +// One reconnect attempt (rate-limited). Returns true if the link is up. +static bool serial_check(GB *gb) { + if (!gb->serial_sock_dead) return gb->serial_in_fd >= 0; + double now = mono_sec(); + if (now < gb->serial_retry_at) return false; + gb->serial_retry_at = now + 0.5; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) return false; + struct sockaddr_un a; + memset(&a, 0, sizeof a); + a.sun_family = AF_UNIX; + strncpy(a.sun_path, gb->serial_sock_path, sizeof(a.sun_path) - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) != 0) { + close(fd); + return false; + } + int fl = fcntl(fd, F_GETFL, 0); + if (fl != -1) fcntl(fd, F_SETFL, fl | O_NONBLOCK); + gb->serial_in_fd = gb->serial_out_fd = fd; + gb->serial_sock_dead = false; + fprintf(stderr, "[sl0pboy] link socket reconnected (%s)\n", + gb->serial_sock_path); + return true; +} void gb_request_interrupt(GB *gb, u8 flag) { gb->iff |= flag; @@ -129,9 +182,13 @@ void gb_write(GB *gb, u16 addr, u8 val) { // internal clock (GB is master): transmit gb->sb. Completes now. u8 outb = gb->sb; if (gb->serial_log) { fputc(outb, stderr); fflush(stderr); } + if (gb->serial_sock_path && gb->serial_sock_dead) serial_check(gb); if (gb->serial_out_fd >= 0) { unsigned char c = outb; - (void)!write(gb->serial_out_fd, &c, 1); + ssize_t w = write(gb->serial_out_fd, &c, 1); + if (w < 0 && (errno == EPIPE || errno == ECONNRESET || + errno == EBADF || errno == ENOTCONN)) + serial_mark_dead(gb); } gb->sb = 0xFF; // nothing shifted in on a pure TX gb->sc &= ~0x80; @@ -141,6 +198,7 @@ void gb_write(GB *gb, u16 addr, u8 val) { // Complete only when an input byte is available - and do NOT echo // it to stdout - so a program can poll for input without emitting // junk. Stays pending (bit7 set) while stdin has nothing. + if (gb->serial_sock_path && gb->serial_sock_dead) serial_check(gb); if (gb->serial_in_fd >= 0) { unsigned char c; ssize_t n = read(gb->serial_in_fd, &c, 1); @@ -149,13 +207,18 @@ void gb_write(GB *gb, u16 addr, u8 val) { gb->sc &= ~0x80; gb_request_interrupt(gb, INT_SERIAL); } else if (n == 0) { - if (!gb->serial_no_eof) { + if (gb->serial_sock_path) { + serial_mark_dead(gb); // peer closed: reconnect, stay pending + } else if (!gb->serial_no_eof) { gb->sb = 0x04; // EOF -> EOT (Ctrl-D) gb->sc &= ~0x80; gb_request_interrupt(gb, INT_SERIAL); } // else: stay pending (OSK provides input) + } else if (errno == ECONNRESET || errno == EBADF || + errno == ENOTCONN) { + serial_mark_dead(gb); } - // n < 0 (EAGAIN on a live tty): stay pending, poll again + // other n < 0 (EAGAIN on a live link): stay pending, poll again } } return; @@ -117,6 +117,12 @@ struct GB { bool serial_log; // print serial output to stderr (blargg tests) int serial_out_fd; // if >=0, transmitted serial bytes written here (stdout) int serial_in_fd; // if >=0, received serial bytes read here (stdin), else 0xFF + // Link-over-unix-socket resilience: when serial_sock_path is set, a dead + // socket (EOF / EPIPE / ECONNRESET) is closed and reconnected in the + // background instead of going silently dead forever. See serial_check(). + const char *serial_sock_path; // --serial-sock path (NULL = stdio serial) + bool serial_sock_dead; // link socket lost; reconnect pending + double serial_retry_at; // CLOCK_MONOTONIC time of next reconnect try bool poweroff; // set by the $ED opcode: request a clean emulator exit // Boot ROM (BIOS). When boot_rom is non-NULL, gb_reset starts execution |
