Preflight the REU on the UCI test path (closes #97) - #107
Merged
Conversation
A REU-profile PRG on a device with `RAM Expansion Unit: Disabled` does not fail — it computes a wrong X25519 shared secret from DMA that silently no-ops, then spins 65,536 net_polls at ~40 ms looking for a record it can decrypt. That is ~44 minutes on a frozen-looking screen ending `KEYS ENC1 RX`, and it cost an outside contributor a working day (#97) plus a cold power cycle we wrongly sent them after. The prerequisite was documented nowhere on the UCI test path, and it is one a program can check in one REST call. So check it, under the DeviceLock the script already holds, before committing to the run. - tools/uci/_reu_preflight.py: detect the crypto profile from build/labels.txt, and for REU-profile builds read the device setting. Onchip builds are let through with no REST call at all — a blanket check would break the very configuration we recommend as the fix. - Onchip markers are a UNION (LIB_NISTCURVES_REU_BANKS_USED == 0, gen_mul_row, fe_gen_mul_row, sqtab_reserved), verified against both builds, so an upstream rename cannot silently reclassify an onchip build and block it. Absence of all markers fails closed. - Wired into test_https_local.py (which test_https_print_body.py and test_https_local_p384.py delegate to), test_https_bad_finished.py and bench_ecdsa_u64e.py. Exit code 4. Scripts that never touch the REU (boot_check, phase2, phase3_tcp_echo, http_local, http_live) are deliberately unguarded. - It never enables the REU for you. #97 offered that; the U64E is queue-shared and config writes persist until power cycle, so a silent reconfiguration trades a legible error for a mystery two runs later on someone else's branch. C64_SKIP_REU_PREFLIGHT=1 bypasses. Measured on the U64E at 48 MHz, test_https_local.py: build device REU result wall REU Disabled exit 4, both remedies named 2.08 s REU Enabled exit 0 PASS, HTTP 200 128.42 s onchip Disabled exit 0 PASS, HTTP 200 90.99 s Preflight cost in isolation (n=3): REU path 0.073-0.081 s, onchip path 0.001-0.002 s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #97.
The problem, restated
The default
make BACKEND=uciimage is the REU profile:src/crypto/fe25519.sand thelibs/nistcurvesP-256 archive both fetch multiply rows from REU banks by DMA. With no REU that DMA silently does nothing — no fault, rows keepreu_mul_init's residue, every multiply returns a wrong-but-deterministic result. X25519 derives the wrong shared secret, the first encrypted record fails its AEAD tag, andtls13.sspins 65,536net_pollcalls at ~40 ms each: ~44 minutes on a screen frozen atKEYS ENC1 RX.The reporter on #97 lost a working day to this, including a cold power cycle we wrongly sent them after by matching the screen to the unrelated device-wedge signature. Their close-out asked for the README to say the REU is required, and noted that a check at script start would be useful.
What this does
The check is the fix; the README paragraph is secondary. A prerequisite a program can verify should not be left as prose.
New
tools/uci/_reu_preflight.py. Called under the DeviceLock the script already holds, afterenable_uci, before the reset:build/labels.txt, which ld65 emits from the same link as the PRG (every guarded script loads both frombuild/, so they always correspond).C64 and Cartridge Settings / RAM Expansion Unit. Enabled: one line, carry on. Disabled: exit 4 immediately, naming both remedies — enable the REU, ormake BACKEND=uci USE_NISTCURVES_ONCHIP=1, which needs none.C64_SKIP_REU_PREFLIGHT=1bypasses.Detection, and why it is a union
Onchip markers are checked as a union, not a conjunction:
LIB_NISTCURVES_REU_BANKS_USEDgen_mul_rowfe_gen_mul_rowsqtab_reservedVerified against both builds in this worktree (REU PRG
66e37037…/ labelsc4ef5eab…; onchip PRG518ee446…/ labelsf6a30919…). The union matters: the failure mode to avoid is misclassifying an onchip build as REU-profile and blocking it, so no single upstream rename can do that.LIB_NISTCURVES_REU_BANKS_USEDis the semantically exact one — the library declaring its own REU claim through the c64-lib-contract manifest. Absence of every marker fails closed to "REU profile", which is the cheap direction to be wrong in: one skippable error message versus 44 minutes of silence.Scope
Guarded (they exercise a REU-profile crypto path):
test_https_local.py— whichtest_https_print_body.pyandtest_https_local_p384.pyboth delegate to — plustest_https_bad_finished.pyandbench_ecdsa_u64e.py. The bench matters for a different reason: REU-lessfp_mulreturnsa*255*b mod p, so every vector "verifies" as a reject with no diagnostic. A silently wrong benchmark is worse than a slow one.Not guarded, deliberately:
boot_check.py,phase2_check.py,phase3_tcp_echo.py,test_http_local.py,test_http_live.py. None touches the REU, and bolting it on for symmetry would just add a REST call and a failure mode to scripts that work fine without one.It does not auto-enable the REU
#97 offered that as an alternative and it is the wrong trade. The U64E is queue-shared across the c64-* projects, REST config writes persist until the next power cycle, and a test that silently reconfigures someone else's hardware converts a legible error into a mystery two runs later on a different branch. A clear refusal costs seconds; a silent reconfiguration costs trust. So the message tells you the menu path and leaves the write to you.
Acceptance — measured on hardware
U64E at 10.43.23.81, 48 MHz, DeviceLock throughout,
RAM Expansion Unitrestored to its baselineEnabledand confirmed by readback. All three viatools/uci/test_https_local.py:Case C is the one most likely to regress and it passes unblocked, with the preflight reporting
build is the on-chip profile (LIB_NISTCURVES_REU_BANKS_USED=0) — no REU required, skipping device check.Preflight cost measured in isolation, n=3 each:
0.08 s against a 128 s run is not latency worth noticing, and the onchip path adds nothing measurable.
Case A's message:
One note for the record: the first case-B attempt after re-enabling the REU came back
net_last_error=0x88 UCI_ERR_NO_SOCKET— the known post-config-write bridge glitch, not a result. The re-run passed, per the standing rule.Docs
README's "Ultimate 64 Elite Hardware Tests (UCI backend)" section now states the requirement and that the preflight enforces it. CLAUDE.md gets the detection rationale and a pointer from the
KEYS ENC1 RXdiscriminator block added in #106 — if a guarded script reachedRX, the REU cause is already excluded.🤖 Generated with Claude Code