From c9cfebafda85832284506cf12c4fd6a9ed49f053 Mon Sep 17 00:00:00 2001 From: Christian Werner Date: Sat, 20 Jun 2026 03:11:51 +0200 Subject: [PATCH] fix(kernel): allow command-phase byte reception while ATN is asserted wait_clk() aborted on any asserted ATN, but ATN is held low for the entire command phase by design. Reception therefore aborted on the first poll before clocking a single bit, so the listener never decoded the LISTEN command and always fell through to "not addressed -> IDLE". Make the ATN abort phase-relative via IEC_RX_ATN meaning "ATN changed": in the data phase abort when ATN asserts (C64 interrupts with a new command); in the command phase abort when ATN releases (command sequence finished). The check collapses to `iec_atn_asserted() == data_phase`. Closes #2 Generated by Clanker Co-Authored-By: Claude Opus 4.8 --- kernel/iec_listener.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/kernel/iec_listener.c b/kernel/iec_listener.c index 810ae36..2314d33 100644 --- a/kernel/iec_listener.c +++ b/kernel/iec_listener.c @@ -108,16 +108,26 @@ static void emit_record(u8 kind, u8 value, u8 flags) /* --- busy-poll helper ---------------------------------------------------- */ /* - * Spin until CLK reaches `want_asserted`, or timeout/ATN/RESET. Caller holds - * local IRQs disabled. Uses a microsecond budget rather than a fixed udelay so - * a slightly-fast C64 is handled correctly (research §1.3). + * Spin until CLK reaches `want_asserted`, or timeout/ATN-change/RESET. Caller + * holds local IRQs disabled. Uses a microsecond budget rather than a fixed + * udelay so a slightly-fast C64 is handled correctly (research §1.3). + * + * The ATN abort is phase-relative, and IEC_RX_ATN means "ATN changed, abort": + * - data_phase: ATN is released; abort if it ASSERTS (C64 interrupts data + * transfer with a new command). + * - command phase: ATN is asserted for the whole command byte; abort if it + * RELEASES (the command sequence is over -> hand back to the state machine). + * Treating any asserted ATN as an abort (the previous behaviour) made command + * reception impossible: ATN is legitimately low the entire command phase, so + * the very first poll aborted before a single bit was clocked. */ -static enum iec_rx wait_clk(bool want_asserted, unsigned int timeout_us) +static enum iec_rx wait_clk(bool want_asserted, unsigned int timeout_us, + bool data_phase) { unsigned int waited = 0; while (iec_clk_asserted() != want_asserted) { - if (iec_atn_asserted()) + if (iec_atn_asserted() == data_phase) return IEC_RX_ATN; if (iec_reset_asserted()) return IEC_RX_RESET; @@ -143,7 +153,7 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase) *eoi = false; /* 1. READY-FOR-DATA: wait for talker to release CLK, then release DATA */ - rc = wait_clk(false /* released */, IEC_CLK_TIMEOUT_US); + rc = wait_clk(false /* released */, IEC_CLK_TIMEOUT_US, data_phase); if (rc != IEC_RX_OK) return rc; iec_data_release(); @@ -171,10 +181,12 @@ static enum iec_rx receive_byte(u8 *out, bool *eoi, bool data_phase) /* 3. RECEIVE 8 BITS, LSB first */ for (i = 0; i < 8; i++) { - rc = wait_clk(true /* asserted: data invalid/setup */, IEC_CLK_TIMEOUT_US); + rc = wait_clk(true /* asserted: data invalid/setup */, + IEC_CLK_TIMEOUT_US, data_phase); if (rc != IEC_RX_OK) goto out; - rc = wait_clk(false /* released: data valid -> sample */, IEC_CLK_TIMEOUT_US); + rc = wait_clk(false /* released: data valid -> sample */, + IEC_CLK_TIMEOUT_US, data_phase); if (rc != IEC_RX_OK) goto out; /* released(high) = bit 1, asserted(low) = bit 0 */