From 26094bf73568f0b35dca5e0ec25635cba6d2736a Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 29 Oct 2026 12:57:10 -0700 Subject: [PATCH 1/2] serial: the RX duty-cycle throttle wedged the guest -- availability is the select() truth The A04-22 wedge root-caused: CharIoSocket::rcv_char_available() returned 'no character available' for ~785 of every 800 polls whenever the peer's reply traffic was dense (consecutivechars >= 15), while the guest's NubDriver_WaitForByte spins with MSR[EE]=0 waiting for RR0_RX_CHARACTER_ AVAILABLE (the contradictory flag=44/busy=00 state the wait-clock study measured). The nub's debug transaction never sees its reply, the machine parks, the uncollected ADB packets are the visible wedge (the Cuda starvation flood, the dead cursor, the roll-15 'post-exit wedge', boot 8's repro at emu 250.4 s). The socket buffer already prevents overrun (the kernel holds unread bytes; select() reports them when the guest is ready); the throttle protected nothing. The wedge's instrument-facing consequences die with it: no Cuda flood, no NubDriver_WaitForByte park, no dead cursor. --- devices/serial/chario.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/devices/serial/chario.cpp b/devices/serial/chario.cpp index 611bab3ec3..1487c2c500 100644 --- a/devices/serial/chario.cpp +++ b/devices/serial/chario.cpp @@ -348,12 +348,15 @@ void CharIoSocket::rcv_disable() bool CharIoSocket::rcv_char_available() { - if (consecutivechars >= 15) { - consecutivechars++; - if (consecutivechars >= 800) - consecutivechars = 0; - return 0; - } + // The A04-22 wedge, root-caused 2026-10-29: this throttle returned "no character + // available" for ~785 of every 800 polls whenever the peer's reply traffic was dense + // (consecutivechars >= 15), while the guest's NubDriver_WaitForByte spins with MSR[EE]=0 + // waiting for RR0_RX_CHARACTER_AVAILABLE. The nub's debug transaction then never sees its + // reply, the machine parks in the wait, and the uncollected ADB packets are the visible + // wedge (the Cuda starvation flood). The socket buffer already prevents overrun -- the + // kernel holds unread bytes and select() reports them when the guest is ready -- so this + // duty cycle protected nothing and wedged the guest. Be honest: availability is the + // select() truth. return this->rcv_char_available_now(); } From 1d71da89f3fade9cc62742ef4538ec816346481c Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 29 Oct 2026 13:47:46 -0700 Subject: [PATCH 2/2] serial: never block the CPU thread on the debug channel -- MSG_DONTWAIT in rcv_char A blocking recv(acceptfd) froze the whole emulated machine whenever guest code read the ESCC data register while the peer-side debugger had sent nothing (the silent-peer boots froze at the splash logo, CPU pegged). EAGAIN now means 'no byte pending' and the rx-available bit stays the sole truth the guest polls. --- devices/serial/chario.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/devices/serial/chario.cpp b/devices/serial/chario.cpp index 1487c2c500..fbfceab907 100644 --- a/devices/serial/chario.cpp +++ b/devices/serial/chario.cpp @@ -491,9 +491,16 @@ int CharIoSocket::rcv_char(uint8_t *c) this->rcv_char_available_now(); if (this->acceptfd != -1) { - int received = (int)recv(this->acceptfd, c, 1, 0); + // MSG_DONTWAIT: the emulator's CPU thread must NEVER block on the debug channel. + // A blocking recv here froze the whole machine whenever guest code read the ESCC + // data register while the peer-side debugger had sent nothing (the silent-peer + // boots froze at the splash logo). EAGAIN = no byte pending; the ESCC's + // rx-available bit (rcv_char_available) remains the sole truth the guest polls. + int received = (int)recv(this->acceptfd, c, 1, MSG_DONTWAIT); if (received == -1) { - LOG_F(INFO, "socket accept read err: %s", strerror(errno)); + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG_F(INFO, "socket accept read err: %s", strerror(errno)); + } } else if (received == 1) { /*