aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/gb.c69
-rw-r--r--src/gb.h6
2 files changed, 72 insertions, 3 deletions
diff --git a/src/gb.c b/src/gb.c
index 2aba2a1..aed51b7 100644
--- a/src/gb.c
+++ b/src/gb.c
@@ -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;
diff --git a/src/gb.h b/src/gb.h
index 7162b4d..33746b4 100644
--- a/src/gb.h
+++ b/src/gb.h
@@ -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