Firmware: --e0thresh patch — gate the E0 error display (default N=5)
Stock $D5F2 repaints the cockpit display to the E0 counter readout on the FIRST increment of $3187/$3184/$3185, so one benign give-up shows E0000001 forever. New opt-in make_patch.py edit 5 hijacks the render's LDX #$2038 into a 30-byte cave at $E000: all three counters below N -> exit via the routine's own epilogue (registers restored, display untouched); any >= N -> resume the render. Counters still accumulate. Built + disassembly-verified (not yet burned): RIOv4_2_patched_e0t5.bin (9600) and RIOv4_2_patched_31250v2_e0t5.bin (FastRIO). Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -459,3 +459,30 @@ detail in [`docs/hardware/display-board-1408.md`](../docs/hardware/display-board
|
||||
- **RUN LED = buffered `AS*`** (address strobe): it indicates "clock
|
||||
alive", not "software alive" — it stays lit even inside the `dEAd`
|
||||
loop. TX/RX LEDs are buffered `SER_OUT`/`SER_IN`.
|
||||
|
||||
### E0-display threshold patch (edit 5, `--e0thresh[=N]`)
|
||||
|
||||
Stock firmware has **no threshold**: the first increment of any of the
|
||||
three displayed counters repaints the cockpit display from `F0000000` to
|
||||
the `E0` readout and it never reverts — so a single benign reply
|
||||
give-up (e.g. one lost ACK healed by host-side stop-and-wait) leaves a
|
||||
permanent `E0000001`. `make_patch.py --e0thresh=N` (default 5) gates the
|
||||
renderer: `$D5F4`'s `LDX #$2038` becomes `JMP $E000`, and a 30-byte cave
|
||||
at `$E000` (erased region, clear of the `$DFF0` wedge stub) compares
|
||||
`$3187`/`$3184`/`$3185` against N — all below ⇒ exit through the
|
||||
routine's own epilogue (`$D61D`, registers restored, display untouched);
|
||||
any at/above ⇒ resume the render at `$D5F7`. Counters still accumulate
|
||||
regardless, so the diagnostic history is intact — the display just stays
|
||||
quiet until real trouble (N events since power-on).
|
||||
|
||||
Built variants (wedge fix + threshold 5):
|
||||
|
||||
| image | config | sha256 (first 12) |
|
||||
|---|---|---|
|
||||
| `RIOv4_2_patched_e0t5.bin` | 9600, native-game compatible | `9c21ac7199fb` |
|
||||
| `RIOv4_2_patched_31250v2_e0t5.bin` | 31250 + widened ACK-wait (FastRIO) | `b43032b016d7` |
|
||||
|
||||
**Not yet burned or bench-verified.** To verify on hardware: burn, then
|
||||
force reply give-ups (e.g. kill the host mid-poll repeatedly) — the
|
||||
display must stay `F0000000` through the 4th event and flip to
|
||||
`E00000 05` on the 5th; `RIO_TAP`/mash regression as usual.
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,13 @@ WIDEN_ACKWAIT = "--widen-ackwait" in sys.argv or BAUD_NAME in ("62500", "125000"
|
||||
assert not ("--widen-ackwait" in sys.argv) or selected, \
|
||||
"--widen-ackwait only makes sense with a baud retune (the wait is byte-scaled)"
|
||||
|
||||
# --e0thresh[=N]: gate the E0 error display (see edit 5). Default N=5.
|
||||
E0T = None
|
||||
for a in sys.argv:
|
||||
if a.startswith("--e0thresh"):
|
||||
E0T = int(a.split("=", 1)[1]) if "=" in a else 5
|
||||
assert E0T is None or 1 <= E0T <= 255, f"--e0thresh out of range: {E0T}"
|
||||
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
||||
SRC = args[0] if len(args) > 0 else "RIOv4_2.bin"
|
||||
if len(args) > 1:
|
||||
@@ -50,6 +57,8 @@ elif BAUD_NAME:
|
||||
DST = f"RIOv4_2_patched_{BAUD_NAME}.bin"
|
||||
else:
|
||||
DST = "RIOv4_2_patched.bin"
|
||||
if E0T is not None and len(args) <= 1:
|
||||
DST = DST[:-4] + f"_e0t{E0T}.bin"
|
||||
|
||||
d = bytearray(open(SRC, "rb").read())
|
||||
assert len(d) == 0x10000, f"expected 64KB image, got {len(d)}"
|
||||
@@ -107,6 +116,31 @@ if WIDEN_ACKWAIT:
|
||||
"expected LDAA $317B ; CMPA #imm at $D9E3"
|
||||
patch(0xD9E7, [0x04], [ACKWAIT_VAL])
|
||||
|
||||
# --- edit 5 (--e0thresh=N): threshold-gate the E0 error display -----------
|
||||
# Stock behavior: every increment of $3187 (TX-ring overflow), $3184 (reply
|
||||
# retransmit) or $3185 (reply teardown/give-up) immediately calls $D5F2,
|
||||
# which paints 'E0'+counters over the F0 banner — so ONE benign give-up
|
||||
# shows E0000001 forever. Gate the render: skip unless any counter >= N.
|
||||
# $D5F2 prologue (PSHX/PSHA) has already run when we take over its
|
||||
# LDX #$2038; the cave may clobber A/X because both exits restore them —
|
||||
# below-threshold leaves via the routine's own epilogue at $D61D
|
||||
# (PULA/PULX/RTS), at-or-above resumes the render at $D5F7.
|
||||
if E0T is not None:
|
||||
patch(0xD5F4, [0xCE, 0x20, 0x38], [0x7E, 0xE0, 0x00]) # LDX #$2038 -> JMP $E000
|
||||
cave = [0xB6, 0x31, 0x87, # $E000 LDAA $3187 (ring overflows)
|
||||
0x81, E0T, # $E003 CMPA #N
|
||||
0x24, 0x11, # $E005 BCC $E018 (>=N -> render)
|
||||
0xB6, 0x31, 0x84, # $E007 LDAA $3184 (reply retransmits)
|
||||
0x81, E0T, # $E00A CMPA #N
|
||||
0x24, 0x0A, # $E00C BCC $E018
|
||||
0xB6, 0x31, 0x85, # $E00E LDAA $3185 (teardowns/give-ups)
|
||||
0x81, E0T, # $E011 CMPA #N
|
||||
0x24, 0x03, # $E013 BCC $E018
|
||||
0x7E, 0xD6, 0x1D, # $E015 JMP $D61D (below threshold: epilogue)
|
||||
0xCE, 0x20, 0x38, # $E018 LDX #$2038 (displaced instruction)
|
||||
0x7E, 0xD5, 0xF7] # $E01B JMP $D5F7 (resume render)
|
||||
patch(0xE000, [0xFF] * len(cave), cave)
|
||||
|
||||
open(DST, "wb").write(d)
|
||||
new_sha = hashlib.sha256(d).hexdigest()
|
||||
# byte-diff report
|
||||
|
||||
Reference in New Issue
Block a user