Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 66 additions & 6 deletions CLAUDE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -132,11 +132,23 @@ into `uci_host_buf` (256 bytes in UCI_BSS); `net_tcp_connect` passes
it to firmware. Dotted-quad IP literals work because firmware passes
them through.

### Firmware quirk — per-byte NEXT_DATA ACK
### Firmware quirk — FPGA register timing (delay-loop fence)

Per-byte `NEXT_DATA` ACK truncates multi-byte responses on the current
U64E firmware revision. The read path uses a tight-poll pattern instead
(read `$DF1E` until `DATA_AV` clears). Documented in `uci_cmd.s`.
The U64E's UCI FPGA needs **~38 us** between consecutive register
accesses regardless of CPU clock speed. At stock 1 MHz the bus cycle
time naturally satisfies this. At turbo speeds (4-48 MHz) the CPU
outruns the FPGA, causing double-latched writes and stale reads that
corrupt the UCI command protocol.

**Fix:** A nested delay-loop macro `uci_fence` (defined in
`src/net/uci/uci_regs.inc`) is inserted after every read/write to UCI
registers `$DF1C-$DF1F`. Parameters: `UCI_FENCE_OUTER = 5`,
`UCI_FENCE_INNER = 100`, yielding ~2525 cycles (~52 us at 48 MHz,
35% safety margin). 14 bytes per fence site, 24 fence sites total
(11 write + 13 read). At 1 MHz the same loop costs ~2.5 ms per
access — negligible for networking.

48 MHz turbo is fully supported and verified on real U64E hardware.

### Memory layout under UCI

Expand All@@ -160,11 +172,30 @@ Scripts under `tools/uci/` require a U64E at 192.168.1.81 and use
- `test_http_local.py` — HTTP GET against a local test server
- `test_http_live.py` — HTTP GET against a real internet host (requires
internet access from the U64E)
- `test_https_local.py` — HTTPS e2e scaffolding against a local TLS 1.3
listener (ECDSA-P256 cert from
`tools/https_e2e/certs/`). DMAs a 6502 stub
that calls `http_get`, flips the U64E to 48
MHz turbo, and captures full diagnostics on
pass or timeout. `DEBUG_CAPTURE=1` enables a
bounded 6510 bus stream for post-mortem.
Each run writes a timestamped artifact dir
under `$UCI_DEBUG_DIR` (default
`/tmp/uci_https_debug/<ISO>/`) containing:
packed raw trace (`trace.bin` + meta sidecar),
derived `summary.txt` / `tail.txt` /
`uci_accesses.txt`, the full 4 KB ring
(`ring.bin` + `ring_meta.json`), a DMA-read
TLS state snapshot (`tls_state_dump.json`),
the listener's `server_result.json`, and
`run_info.txt`. Rotation keeps the last 5
dirs; `UCI_DEBUG_KEEP_ON_PASS=1` preserves
PASS runs. Currently still fails inside
`tls_handle_certificate` (X.509 parsing) —
see Known issues below.

### Known issues

- Ring buffer needs explicit zeroing before `http_get_plain` calls
(stale data from auto-init polling).
- `http_status` parsing is garbled on large responses because the
poll-timeout counter in `http.s` expires before all headers are
consumed under UCI's slower `net_poll` round-trip. Body arrives
Expand All@@ -174,6 +205,35 @@ Scripts under `tools/uci/` require a U64E at 192.168.1.81 and use
- Boot banner line 03 still says "rr-net" under ip65 build even
though Phase 2 made it backend-aware — this is correct/expected
behavior. Under UCI it says "ULTIMATE 64 ELITE (UCI)".
- The delay-loop fence adds ~2.5 ms overhead per UCI register access
at 1 MHz (negligible for networking, but visible in tight loops).
- TLS 1.3 handshake currently stalls inside `tls_handle_certificate`
(`src/tls_cert.s`) during X.509 parsing. Two upstream bugs that
used to mask this were fixed in the current branch: (a) a `net_poll`
entry gate that spun forever on post-drain residual STATE bits
(fixed by swapping `uci_wait_idle` → `uci_wait_not_busy` at the
`net_poll` preamble only — other call sites remain on wait_idle),
and (b) `tls_transcript_hash` was defined in `src/tls_transcript.s`
but never called, so handshake + application key derivation fed
32 zero bytes into HKDF-Expand-Label as the transcript context.
After the fix, handshake AEAD decryption succeeds, EncryptedExt
processes, and the 352 B Certificate record decrypts into
`tls_hs_buf` — stall moved forward from ENCRYPTED_EXT (0x03) to
CERTIFICATE (0x04). DHCP and plain HTTP are unaffected at all
speeds.

### Design note — bounded timeouts must use wall-clock time

Any future robustness work on the UCI adapter's spin-wait helpers
(`uci_wait_idle`, `uci_push_wait`, etc.) MUST use a wall-clock time
source — CIA timer on stock C64, TOD clock on U64E — rather than a
cycle-counted iteration budget. The fences around every UCI register
access make per-iteration cost scale with CPU speed: a budget that is
ample at 1 MHz collapses to far too short at 48 MHz because turbo
scales CPU cycles but not the FPGA's wire-level operation durations.
A prior attempt on branch `feat/net-drain-abi` split waits into
fast/long tiers with cycle-count budgets and broke DHCP at turbo for
exactly this reason; the branch was abandoned.

## Memory layout

Expand Down
32 changes: 25 additions & 7 deletions src/net/uci/net.s
Original file line numberDiff line numberDiff line change
Expand Up@@ -54,6 +54,7 @@
; --- primitives from uci_cmd.s ---
.import uci_abort
.import uci_wait_idle
.import uci_wait_not_busy
.import uci_begin_cmd
.import uci_put_byte
.import uci_push_wait
Expand DownExpand Up@@ -91,6 +92,7 @@ net_init:
jsr uci_abort

lda UCI_ID
uci_fence ; settle before comparing ID
cmp #UCI_ID_VALUE
beq @present

Expand DownExpand Up@@ -136,7 +138,7 @@ net_poll:
beq @do_poll
rts
@do_poll:
jsr uci_wait_idle
jsr uci_wait_not_busy

lda #UCI_TARGET_NETWORK
jsr uci_begin_cmd
Expand DownExpand Up@@ -175,16 +177,23 @@ net_poll:
; SMC dst. Loop style matches uci_read_resp_bytes — tight-poll
; DATA_AV and read UCI_RESP_DATA; the firmware FIFO auto-advances
; on read (Phase 2 finding), so NO per-byte NEXT_DATA.
uci_fence ; give firmware time to stage response
ldy #$00
@hdr_loop:
lda UCI_STATUS
uci_fence ; settle before testing DATA_AV
and #UCI_STAT_DATA_AV
beq @hdr_done_short
bne @hdr_got ; branch past trampoline
jmp @hdr_done_short ; long branch: fence too wide for BEQ
@hdr_got:
lda UCI_RESP_DATA
uci_fence ; settle before storing header byte
sta uci_read_hdr,y
iny
cpy #2
bcc @hdr_loop
bcs @hdr_got2 ; branch past trampoline (inverted BCC)
jmp @hdr_loop ; long branch back: fence too wide for BCC
@hdr_got2:
jmp @hdr_done

@hdr_done_short:
Expand DownExpand Up@@ -234,13 +243,15 @@ net_poll:
bne @not_full
lda uci_next_hi
cmp tcp_recv_head+1
beq @done_data ; ring full — drop the rest
bne @not_full
jmp @done_data ; ring full — drop the rest

@not_full:
; Wait for DATA_AV — the firmware streams data in bursts; if the
; FIFO drained mid-record we bail (shouldn't happen if firmware
; honored actual_len but we defend anyway).
lda UCI_STATUS
uci_fence ; settle before testing DATA_AV
and #UCI_STAT_DATA_AV
bne @have_byte
jmp @done_data
Expand All@@ -256,6 +267,7 @@ net_poll:
sta @rb_store+2

lda UCI_RESP_DATA
uci_fence ; settle before storing data byte
@rb_store:
sta $ffff ; SMC: patched each byte

Expand DownExpand Up@@ -406,13 +418,18 @@ net_tcp_connect:
ldy #$00
@host_loop:
lda uci_host_buf,y
beq @host_done
bne @host_push ; branch past trampoline
jmp @host_done ; long branch: fence too wide for BEQ
@host_push:
sta UCI_CMD_DATA
uci_fence ; heavy fence: hostname bytes at 48 MHz
iny
bne @host_loop ; bounded by 256 B (and by null before that)
beq @host_done ; Y wrapped to 0 — stop (bounded by 256 B)
jmp @host_loop ; long branch back: fence too wide for BNE
@host_done:
lda #$00
sta UCI_CMD_DATA ; explicit null terminator
uci_fence

jsr uci_push_wait

Expand DownExpand Up@@ -531,6 +548,7 @@ net_tcp_send:
@sb_load:
lda $ffff,y ; SMC: source base patched above
sta UCI_CMD_DATA
uci_fence ; heavy fence: FIFO overruns at 48 MHz with standard fence
iny
bne @sb_nohi
inc @sb_load+2 ; advance base high byte
Expand DownExpand Up@@ -820,7 +838,7 @@ net_recv_byte:
.segment "RODATA"

net_banner_str:
.byte "ULTIMATE 64 ELITE (UCI)"
.byte "UCI NETWORKING"
.byte $0d, 0

; =============================================================================
Expand Down
Loading