Symptom
uci_wait_idle spins indefinitely on the lda UCI_STATUS + fence loop with no retry budget. When upstream code feeds garbage into the UCI command pipe (e.g., the phantom-socket bug — see companion issue #36), the C64 spends hundreds of seconds and hundreds of thousands of register accesses waiting for a state change that never comes, instead of failing cleanly with a wall-clock-bounded timeout.
Discovery context
Surfaced during validation of c64-test-harness issue #90 (JC-000/c64-test-harness#90). Test tools/uci/test_https_local.py reaches the 600 s sentinel timeout because the C64 spends 605 s and 227k UCI register accesses (≈99% of total bus traffic) in this single PC range.
Full forensic analysis: JC-000/c64-test-harness#90 (comment)
Specific code
src/net/uci/uci_cmd.s:69-76 — uci_wait_idle. The unbounded design is intentional per the project's design notes, but for callers driven by host-side DMA/monitor (where wall-clock failure is preferable to indefinite hang) an opt-in bounded variant is appropriate.
DebugCapture trace from a wedged run (/tmp/uci_https_debug/20260510_104617/tail.txt) shows >350k hits each at PCs $2307–$230B — every single one is the lda UCI_STATUS + fence sequence. Interspersed are lone $DF1D writes every ~2k cycles when net_tcp_send's retry path issues a CMD_DATA push. The spin re-enters immediately after each write.
Suggested fix
Two viable shapes:
Option A: opt-in bounded variant (preferred)
Keep the existing unbounded uci_wait_idle for paths where indefinite spin is correct (boot, init). Add a sibling uci_wait_idle_bounded(timeout_jiffies) that returns CARRY-set on timeout. Migrate network-IO retry paths (net_tcp_send, net_tcp_recv, tls_*) to the bounded variant. Bound budget can be a build-time constant (e.g., 60 jiffies = ~1 s).
Option B: global hard cap
Add a single global cycle-counter check inside uci_wait_idle that gates the spin on a configurable upper bound (e.g., 5–10 s wall-clock-equivalent). Smaller diff; less surgical. Risk: changes semantics for paths that legitimately want unbounded spin.
Option A is cleaner — preserves existing behavior, adds a new tool, callers opt in.
Severity
Medium alone — symptomless without an upstream fault. But paired with the phantom-socket bug in #36, this bug is what converts a transient firmware fault into a 600 s test hang. Bounded spin would surface the underlying failure within seconds, making CI feedback and bisection dramatically faster.
Verification
After fix: tools/uci/test_https_local.py should fail in <2 s with a clean error pointing at net_tcp_send (or wherever the bounded uci_wait_idle returns CARRY-set), instead of hanging for 600 s.
Symptom
uci_wait_idlespins indefinitely on thelda UCI_STATUS+ fence loop with no retry budget. When upstream code feeds garbage into the UCI command pipe (e.g., the phantom-socket bug — see companion issue #36), the C64 spends hundreds of seconds and hundreds of thousands of register accesses waiting for a state change that never comes, instead of failing cleanly with a wall-clock-bounded timeout.Discovery context
Surfaced during validation of c64-test-harness issue #90 (JC-000/c64-test-harness#90). Test
tools/uci/test_https_local.pyreaches the 600 s sentinel timeout because the C64 spends 605 s and 227k UCI register accesses (≈99% of total bus traffic) in this single PC range.Full forensic analysis: JC-000/c64-test-harness#90 (comment)
Specific code
src/net/uci/uci_cmd.s:69-76—uci_wait_idle. The unbounded design is intentional per the project's design notes, but for callers driven by host-side DMA/monitor (where wall-clock failure is preferable to indefinite hang) an opt-in bounded variant is appropriate.DebugCapture trace from a wedged run (
/tmp/uci_https_debug/20260510_104617/tail.txt) shows >350k hits each at PCs$2307–$230B— every single one is thelda UCI_STATUS+ fence sequence. Interspersed are lone$DF1Dwrites every ~2k cycles whennet_tcp_send's retry path issues aCMD_DATApush. The spin re-enters immediately after each write.Suggested fix
Two viable shapes:
Option A: opt-in bounded variant (preferred)
Keep the existing unbounded
uci_wait_idlefor paths where indefinite spin is correct (boot, init). Add a siblinguci_wait_idle_bounded(timeout_jiffies)that returns CARRY-set on timeout. Migrate network-IO retry paths (net_tcp_send,net_tcp_recv,tls_*) to the bounded variant. Bound budget can be a build-time constant (e.g., 60 jiffies = ~1 s).Option B: global hard cap
Add a single global cycle-counter check inside
uci_wait_idlethat gates the spin on a configurable upper bound (e.g., 5–10 s wall-clock-equivalent). Smaller diff; less surgical. Risk: changes semantics for paths that legitimately want unbounded spin.Option A is cleaner — preserves existing behavior, adds a new tool, callers opt in.
Severity
Medium alone — symptomless without an upstream fault. But paired with the phantom-socket bug in #36, this bug is what converts a transient firmware fault into a 600 s test hang. Bounded spin would surface the underlying failure within seconds, making CI feedback and bisection dramatically faster.
Verification
After fix:
tools/uci/test_https_local.pyshould fail in <2 s with a clean error pointing atnet_tcp_send(or wherever the boundeduci_wait_idlereturns CARRY-set), instead of hanging for 600 s.