mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-28 10:59:16 -04:00
master-runtimefixes
18 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbaebaf37a |
Expose analog input through debug controls
Co-authored-by: douglasjv <vanderveerdouglas@gmail.com> |
||
|
|
9179c1ad5d |
debug: stop observability from perturbing SIO; add fmv_state command
debug_server_record_frame (every vblank) and the sio_state handler read SIO_STAT/CTRL/RX via the GUEST-BUS sio_read(), which has hardware side-effects: sio_tick(0) advances the delayed-IRQ state machine, reading SIO_STAT decrements sio_ack_visible_reads and clears the ACK bit, reading SIO_RX_DATA pops the RX FIFO. So the observer silently mutated the SIO/pad handshake every frame in dev builds (the cardinal observability-alters-state violation the event_ring header itself warns against). Add side-effect-free sio_peek_stat/ctrl/rx_data() and use them on the debug path; the guest bus keeps sio_read() with its real side effects. Also add an fmv_state TCP command reporting the resolved config path + auto_skip_fmv + frame-count-table params + live MDEC/XA/pad state, so the FMV-skip + config state can be observed rather than inferred. Note: this removes a real per-vblank SIO perturbation but is NOT the full fix for the dev-build FMV-skip divergence (residual is the cycle-timing-core class; see precise_irq_slice_state). It is correct observability hygiene. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2d915c3fa9 |
wip(tomba2): precise-IRQ-slicing groundwork — cycles_to_next_event + design spec
Phase 1 of the cycle-budgeted precise interrupt-take-point fix (root cause: compiled blocks take IRQs only at block edges; HW + the dirty-RAM interpreter take them at the exact instruction — oracle-confirmed at the Tomba 2 logo Timer1 debounce). Design validated with ChatGPT; full spec in PRECISE_IRQ_SLICE.md. This commit lands the additive, no-behavior-change foundation (Task #2) plus the prior session's diagnostic take-PC capture, and a build-hygiene dedup: - cycles_to_next_event() (interrupts.c): single source of truth — min guest-cycle distance to the next DELIVERABLE interrupt (source raises its I_STAT bit AND that bit is unmasked in i_mask). Aggregates VBLANK + per-peripheral queries. - timers/cdrom/dma/sio _cycles_to_irq(i_mask): each peripheral owns its own next-IRQ-distance query; all conservative under-estimates (smaller => slice more => never run past an event). - debug_server: cycles_to_next_event command for live validation. - interrupts.c: take-PC capture into the IRQ_DELIVER event-ring aux (prior session's diagnostic; records the architectural take-PC for cross-backend diff). - overlay_loader.c: remove duplicate psx_native_bad_entry definition (HEAD had it twice; would not link cleanly). Live-validated at the logo: returns the VBLANK distance, correctly excludes the three free-running (unarmed/masked) timers and idle CD/DMA/SIO. Runtime-only, no regen. The two-tier executor (Task #3) that consumes this is next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
aeef4b554e |
fix(sio): plain digital pad ignores DualShock config commands (Tomba 2 menu input)
Tomba 2's title menu was unresponsive and the idle attract demo never started. Root cause (proven against the Beetle oracle): the SIO pad state machine answered the DualShock config-mode commands (0x43 enter- config, 0x4D rumble-map, etc.) for EVERY pad, including a plain digital controller. Tomba 2's pad driver probes the controller with 0x43 every frame to detect a DualShock; on real hardware a digital pad (SCPH-1080, id 0x41) ignores 0x43 (hi-z, no ACK), so the driver classifies it as digital-only and just polls with 0x42. Our SM answered 0x43, so the driver went down the DualShock config path and read the 0x00 config- response data bytes as button data -> phantom "all buttons pressed" (pad word 0x0000). That made the menu's edge-detected navigation see nothing newly-pressed (unresponsive) AND reset the idle/attract timer every frame (no attract). Fix: model a plain digital controller. New per-slot pad_supports_config flag (sio_set_pad_config_capable), set from the player pad mode (PAD_MODE_DIGITAL => 0, ANALOG/HYBRID => 1, default 1). When 0, all config-command branches (0x43 / legacy / modern 0x44-0x4F) fall through to the hi-z "no response" path, so a digital pad answers ONLY the 0x42 poll -- byte-for-byte matching the oracle. Validated live: SIO now shows only 0x42 polls (id 0x41, buttons 0xFFFF), idle pad word is clean (no 0x0000 phantom), every injected button reaches the menu var, and START navigates the title menu and starts the game. Gated so only DIGITAL-mode pads change behaviour; HYBRID/ANALOG pads (MMX6, Tomba 1, Ape) are unaffected (config-capable as before) -- but a T1/MMX6/Ape regression pass is still required before any master merge. NEW failure point (next bug in the chain, now reachable): with input fixed, the idle attract demo triggers and loads a gameplay level, which FREEZES -- guest spins in compiled kernel/dispatch code, dirty-interp idle (insns_run flat), vblank stalled, level textures corrupted. This is the untested gameplay-overlay stage, a separate issue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
af303f3454 |
runtime: Tomba "Hybrid" legacy pad-config compatibility (per-game opt-in)
The launcher Hybrid controller mode (built first, for Tomba, to mimic Tomba:
Special Edition) flips the emulated pad's reported type 0x41<->0x73 on
d-pad<->stick. After the real DualShock config state machine was added for MMX6
(
|
||
|
|
5f0654f188 |
launcher: Phase 5 — controller device picker + DualShock toggle
Per-player input routing with a real device dropdown (None / Keyboard /
each plugged-in SDL controller) and a digital-vs-DualShock pad-type toggle,
wired end-to-end through the runtime.
- sio.c/.h: per-slot pad state (pad_buttons[2]) + per-slot pad type. Analog
poll emulation (id 0x73 + 4 stick axes) when a slot is in DualShock mode;
digital (0x41) stays the default so existing games are unaffected. New
setters: sio_set_pad_state_slot / sio_set_pad_analog / sio_set_pad_connected,
getters sio_get_pad_{buttons,connected,analog}_slot.
- config_loader: settings.toml [controller] p1_device/p2_device (none/keyboard/
<SDL GUID>) + p1_analog/p2_analog; load + save round-trip.
- main.cpp: replaced the single auto-opened controller with a two-slot
PlayerInput model. Devices opened by GUID (with a first-free fallback so a
re-paired pad still works), keyboard or controller feeds each PSX port, and
analog stick axes drive the DualShock poll. Hot-plug re-resolves both slots.
Defaults: P1 keyboard/digital, P2 none.
- launcher: enumerates SDL controllers (name+GUID); each player card has a
custom click-to-open dropdown (option list injected via data-rml, the
data-for path being unreliable in this RmlUi build) and a DualShock switch
that drives the pad art (digital/analog) + status line. Zero-config default
selects the first plugged-in pad. A saved-but-unplugged controller is kept
as an "(offline)" option so the selection survives. Choices persist on launch.
- debug_server: pad_status now reports both slots' buttons + connected + analog.
UI polish from review:
- Removed the redundant disc-verdict widget (the green checks already convey it);
Change ISO now anchors to the disc panel instead of floating at the page top.
- Added a prominent "Settings" button in the top bar (renderer/SS/AA/etc. were
only reachable via the easily-missed footer link).
- Fixed the player-card asymmetry on toggle: status text no longer wraps and the
player card body is a fixed height, so the pad art renders at a consistent size
(measured 216x110 analog vs 219x110 digital) regardless of pad type.
Verified: device dropdown enumerates a real DualSense; selection + DualShock
toggle update the card; pad_status confirms per-slot connect + analog routing
(slot0 analog / slot1 digital and the reverse); default keyboard P1 unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6f7fcdc89b |
Remove all psxrecomp-v4 naming — rename to psxrecomp throughout
The "v4" suffix was a holdover from iterative project restarts. There is now one psxrecomp; all identifiers, include guards, cmake variables, function names, log strings, schema tags, and tool paths drop the v4 suffix. Changes: - PSXRECOMP_V4_ROOT → PSXRECOMP_ROOT (cmake) - PSXRECOMP_V4_RUNTIME_SOURCES/_INCLUDE_DIRS/_BIOS_GENERATED → no V4 - psxrecomp_v4_add_runtime_target → psxrecomp_add_runtime_target - PSXRECOMP_V4_*_H include guards → PSXRECOMP_*_H - "psxrecomp-v4" log/window strings → "psxrecomp" - Schema version strings in recompiler JSON output - Tool script hardcoded absolute paths updated Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
44ca157481 |
phase1.0e-e1: peripheral-only sio_advance, wire psx_advance_cycles
Adds sio_advance(uint32_t cycles) — drives ONLY the cycle-paced
shifter+ack scheduler. Does NOT touch the legacy sio_irq_pending /
sio_irq_countdown (that's the unconditional-decrement that drained
pad timing in earlier slices when called from the per-block dispatch).
Wires psx_advance_cycles → sio_advance. Until 1.0e-e2 reroutes the TX
path through the shifter, sio_advance returns immediately at the
g_sio_timing_active gate without doing any work. So this slice is
behavior-neutral by construction:
- No TX path change
- No legacy IRQ countdown change
- pad polling, CROSS, card READ_DATA, "MC" bytes — all baseline
Telemetry counters: sio_get_advance_called(), sio_get_advance_with_work().
Validation against
|
||
|
|
0d15e2bd4e |
phase1.0c-v2: cycle-paced sio_tick + dispatch quantum behind active-guard
Adds the cycle-paced sio_tick body, the sio_tick_quantum() wrapper, and
the pace_state TCP telemetry handler. Crucial change vs. failed 1.0c:
the dispatch-loop quantum tick is gated by g_sio_timing_active (volatile
int), so on the psx_check_interrupts hot path the cost is one load + one
branch when no SIO time is pending — not a full function call into the
cycle-paced loop.
In 1.0c-v2 the TX path is still synchronous and nothing arms shift_active
or pending_ack, so g_sio_timing_active stays 0. Phase 1.0d will set it
when arming the shifter and clear it when the shifter+buffer+ack are all
empty.
Files:
- sio.h: SIO_MODEL_CYCLE_PACED moved to header (default 1); declares
g_sio_timing_active (extern volatile int) and sio_tick_quantum()
- sio.c: defines g_sio_timing_active=0; adds cycle-paced helpers
(sio_consume_ack_event, sio_fire_ack_irq, sio_handle_shift_complete);
sio_tick gains a cycle-paced loop that runs only if shift_active or
pending_ack is set (currently never), then falls through to the legacy
IRQ countdown body which remains the live IRQ delivery path; adds
sio_get_pace_state(uint64_t out[16]) accessor
- interrupts.c: in psx_check_interrupts !in_exception block, calls
sio_tick_quantum only if g_sio_timing_active is set
- debug_server.c: new pace_state TCP handler exposing the snapshot
Validation against
|
||
|
|
5670423d22 |
phase1.0a: sio_tick(int cycles) signature + cycle-paced macro stub
Pure signature change. No behavior change. Foundation for Phase 1.0b/c/d
which will incrementally add cycle-paced SIO byte timing.
- sio_tick(void) -> sio_tick(int cycles)
- legacy body ignores cycles via (void)cycles, runs original access-paced
IRQ countdown verbatim
- SIO_MODEL_CYCLE_PACED macro defined and defaults to 0; reserved for
future cycle-paced model (no #if branches engaged in this commit)
- callers updated to pass 0: sio_read, sio_write end, sio_card_burst_drain
loop body in sio.c; mmio_read32/16/8 I_STAT-read paths in memory.c;
interp_check_interrupts in psx_interpreter.c
Validation against committed baseline
|
||
|
|
8b0fc34714 |
phase4: SELECT-deassert protocol reset + slot-state instrumentation
Real card protocol restarts on every fresh 0x81 — SELECT deassert is
a bus-wide reset signal. Earlier model preserved mc_state across
deassert via mc_save_slot, which caused every subsequent 0x81 to land
on a stale non-IDLE saved state and trigger abort_reselect bookkeeping.
Audit confirmed the BIOS-visible RX was still correct (the abort path
itself reset mc_state), but the retained sector/cmd/counter context
across logically-independent transactions was wrong per no$psx.
Clear volatile protocol state on SELECT deassert (state, cmd, sector,
sector_msb, sector_lsb, data_idx, checksum on both slots). Preserve
mc_slots[].flag (persistent "new card" metadata) and mc_slots[].data
(sector buffer, harmless to retain).
Result on a 4-press boot run: 6 transactions reach READ_DATA (ts=10)
and successfully read the "MC" header magic (RX bytes 0x4D 0x43);
remaining txns continue as 4-byte presence pings. Pre-fix: 0 reads
of card data ever; post-fix: card protocol can complete the setup
phase and enter data phase.
The byte-15 abort that follows is a separate blocker — kernel chain
dispatcher interleaves pad polling into the card data read. Tracked
for next session via the chain dispatcher disassembly.
Also includes:
- Slot-state capture in SioTraceEntry + sio_trace JSON emission
- dirty_ram_interp: replace mid-block fprintf+fflush with a counter
(CLAUDE.md §3 — synchronous Windows console I/O at high rate
starves the dispatch loop)
- Passive Python TCP probes (raw_tcp, trace_audit, trace_range,
txn_summary, txn_dist, txn_bytes, drs_filter, readram_test,
step_audit{,2}, verify_card_buffer, wtrace_filter)
- docs/MEMCARD_AUDIT_2026_04_28.md (prior session's 7-layer audit)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5b3c7dd08d |
phase4: freeze_check diagnostic + accumulated overnight infra
Today's session work
- runtime/src/debug_server.c: freeze_check TCP cmd snapshots all liveness
counters (PC, exception entries, exception_reentry_blocks, dirty_ram
blocks/insns, fn_entry total + top-K histogram, SIO IRQ pending +
countdown, sio_stat/i_stat/i_mask, mc_max_state) for tight-loop
diagnosis when the runtime hangs
- runtime/src/debug_server.c: wtrace slot 12 covering [0x72F0..0x72F4]
(data-byte counter); measured peak=1 confirming setup_data_phase resets
the counter mid-cycle
- runtime/src/interrupts.c: exception_entries_total + exception_reentry_blocks
counters + psx_get_freeze_diag accessor
- runtime/src/sio.c: sio_get_freeze_diag accessor for IRQ pacing internals
- memory entries: phase4_card_gate_setup_writes_2026_04_28 (refutes the
prior \"missing [0x7568+slot] clearer\" framing — setup functions write
LSB=0 directly), phase4_72f0_one_byte_per_vblank_2026_04_28 (peak=1
measurement)
Overnight session work bundled in
- runtime/src/dirty_ram_interp.{c,h}: small MIPS interpreter for
install-at-runtime kernel RAM stubs (Rule 18; e.g., SIO data-byte
handler the BIOS writes to RAM 0xCF0)
- recompiler/src/full_function_emitter.{cpp,h}: 3057 jal/jalr continuation
return labels added to dispatch table (continuation fix); branch
fall-through fix for conditional-branch delay slots
- recompiler/src/strict_translator.cpp: matching translator updates
- generated/: regenerated SCPH1001 dispatch + full C output reflecting
the recompiler changes
- runtime/include/debug_server.h, src/memory.c: card_txn ring + SIO IRQ
ring + EvCB walk ring + per-PC dirty_ram counters + always-on wtrace
coverage for [0x755A], [0x7514], [0x7528], [0x7520], [0x74A4],
[0x75C0..C8], [0x74B8..C0]
- recompiler/tests/, runtime/tests/: SIO/card protocol unit tests
- tools/: 60+ Python diagnostic scripts (chain_trace analysis, SIO trace
diff, EvCB walker, wtrace dumpers, etc.)
- docs/: psx_bios_disasm reference + dynamic_handler_install rationale
- OVERNIGHT_2026_04_28.md, SUMMARY_20260412_2.md: session logs
Phase 4 status (still blocked, root cause better understood)
- BIOS card chain runs through R-handlers correctly (chain_trace shows
R1..R11 cycling 26x); shell IS retriggering _card_read
- Install handler at ROM 0xBFC15F1C increments [0x72F0] correctly per
SIO IRQ; gate at <0x80 / <0x7F is correct
- Bottleneck: SIO IRQ countdown is access-paced, fires ~1× per VBlank
vs ~128× needed; setup_data_phase (called from outer coord on VBlank)
zeroes [0x72F0] before counter reaches 128
- Both attempted fixes today caused freezes:
* VBlank deferral on [0x75C0]==1 — pending VBlank already in i_stat
delivered anyway; tighter dispatch gate also locked up
* Cycle-paced sio_tick at rates 50, 200 (and gated on [0x75C0]==1) —
runtime freezes once data phase activates, root cause unidentified
- delay=5 narrows the threshold (peak=2 vs peak=1, no freeze) but
doesn't get to 0x80
- Real fix likely requires deeper instrumentation of the freeze (call
stack / dispatch state at the moment of TCP starvation)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3971af1cc3 |
debug: pad_status TCP cmd + sio_card_protocol_active() accessor
pad_status reports both runtimes' current pad word side-by-side so input dispatch symmetry can be validated empirically. Verified that the existing `press` cmd already drives Beetle (via psx_oracle_run_frame in main.cpp's per-frame loop); the bouncing-vs- stable behavior the operator observed was downstream of input (shell-state-machine pacing differences), not an input dispatch asymmetry. sio_card_protocol_active() accessor returns 1 if any slot has an in-flight memcard protocol exchange. Building block for a future fix to defer VBlank-driven pad polling while a card read is in progress (root cause of the directory-load failure — see memory phase4_real_root_cause_d3_to_d4 for the byte-level ours-vs-Beetle divergence proof and the failed fix attempts). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7c9ea5f397 |
debug: card txn ring + SIO IRQ ring + EvCB walk ring + diff/nav tools
Three new always-on diagnostic rings, plus tools to drive Beetle in parallel and toggle the SDL window between ours and Beetle for visual validation. Built to support deterministic root-cause analysis of the memcard directory-load failure. Card transaction ring (sio.c, sio.h): one entry per 0x81-rooted card protocol run with full TX/RX bytes, cmd, sector, ack count, terminal state, end_reason classification (success/abort_reselect/abort_reset/ abort_slot/abort_bad_cmd/abort_other). 4096-entry cap. Surfaced via card_txn_dump TCP cmd. Live (in-progress) txn included separately. SIO IRQ #7 delivery ring (sio.c, sio.h): every IRQ #7 fire with byte seq linkage, mc_state at fire, active_device, ctrl, counter_7514, i_stat before/after, source classification (card/pad), slot, applied delay. 4096-entry cap. Surfaced via sio_irq_dump. EvCB walk ring (debug_server.c): captures full kernel EvCB table (class/status/spec/mode/fhandler) on every DeliverEvent entry. Reads ToT layout per nocash: 0x120=ptr, 0x124=size. Exit detection tracked via direct RA-watch (shadow stack unreliable due to psx_restore_state_escape longjmp). Surfaced via evcb_snapshot, evcb_walk_dump, evcb_walk_stats. card_txn_diff (psx_oracle_cmds.c): aligns our structured txn ring against Beetle's 64K SIO ring split into transactions, finds first byte-level divergence, returns context window. SIO_FFD_MAX_BYTES bumped 16K -> 64K so historical Beetle reads remain in scope. emu_press (psx_oracle_cmds.c, beetle_psx_bridge.cpp): drives Beetle's pad input for navigation parity with our `press` cmd. 180-frame cycles verified to navigate Beetle to MC screen. F7 toggle (main.cpp): swaps SDL window display source between ours and Beetle's framebuffer for visual A/B comparison without restart. Hygiene: 162 stale probe artifacts moved into probes/. .gitignore extended for probes/ + root-level *.json/*.png/*.bmp/*.txt scratch. Runtime data (card*.mcd, dummy.*.mcr) intentionally not covered. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3d364091e7 |
fix(memcard): clear FLAG byte after read — the four-day stall
This is the bug. Single line. Four days lost.
# The fuck-up
`runtime/src/sio.c` returned `mc_flag` (init 0x08) on every 0x52 (read
command) but NEVER cleared it. Only writes cleared the flag.
Per no$psx spec: real PSX cards return FLAG=0x08 ONCE — first read
after fresh-battery / power-cycle / write — then clear it to 0x00 for
all subsequent reads.
# Why this stalled directory loading
BIOS state-3 chain handler (func_0000579C / 0xBFC152DC) tests bit 3:
andi $t1, $a0, 0x8
beq $t1, $0, BFC15650 # bit 3 NOT set: success (v0=0, advance)
# bit 3 SET: "fresh card" (v0=-1)
Our card returned 0x08 forever, so EVERY read attempt landed in the
"fresh card" path: state-3 returned v0=-1, dispatcher's $v0 cascade
(BFC1499C) reset the chain counter, infinite loop. The directory
literally could not be read.
Beetle's card sim returns 0x00 in steady-state. That was the
canonical first divergence per recomp-template/NES/PRINCIPLES.md #3.
# Why we couldn't find it for four days
We didn't have a transaction-aligned SIO byte diff. The existing
sio_trace dumps were timing-dependent and didn't survive the
press-X window. We were chasing symptoms (dispatch misses, missing
state-12/13 seeds, chain counter reset paths, RAM divergences at
0x74F8 / 0x7560) — all real but all DOWNSTREAM of this one byte.
Per principle "BUILD TOOLS, NOT GUESSWORK" we built
`sio_first_divergence` (transaction-aligned ours-vs-Beetle byte diff,
splits each ring at 0x81 select, compares same-position TX/RX),
which immediately reported byte 1 of the read transaction:
ours: tx=0x52 rx=0x08
beetle: tx=0x52 rx=0x00
Two-line fix in mc_process_byte's MC_CMD case: clear `mc_flag = 0x00`
after returning it.
# Measured impact
Before:
- chain_trace state-3 returned v0=-1 every cycle
- 0x7514 counter cycled 0->1->2->3->4 then reset (BFC1499C path)
- mc_max_state stuck at 10 with mc_aborts ~30
- Both runtimes stuck at MAIN MENU shell selector
After:
- chain_trace states 1..11 all return v0=0, run 14+ times each
- counter no longer resets at state-3
- Both runtimes navigate to MEMORY CARD screen (matching layout)
- byte-1 SIO RX divergence eliminated (both return 0x00)
Memory cards still don't show save data (state-12/13 read-data
handlers don't fire because outer counter never reaches >=5 to enter
the read-chain path at BFC1588C). That's a separate downstream
divergence which the new diff tool will pinpoint next.
# Other artifacts in this commit
- recompiler/seeds/phase2_ghidra_seeds.json: added state-12 (0x5A58)
and state-13 (0x5AB0) chain entries from no$psx-spec read-data
handler ranges. Necessary for state-12/13 to dispatch when the
outer chain finally enters the read-chain path. Dispatch table
4295 -> 4297 entries, no dispatch misses.
- runtime/src/psx_oracle_cmds.c: new sio_first_divergence TCP cmd —
splits each side's SIO ring into card-protocol transactions
(0x81-start, 0x01 or next 0x81 ends), compares the most recent K
transactions byte-by-byte to find first (matching TX, differing RX)
or transaction-length mismatch.
- runtime/src/debug_server.c: chain_trace ring buffer + mc_status
TCP cmd for chain-state visibility.
- runtime/src/memcard.c + memcard.h: memcard_debug_info() accessor.
- runtime/include/sio.h + sio.c: MC_READ_MSB_ECHO/LSB_ECHO states
per no$psx address-handshake spec.
- generated/SCPH1001_*.c: regen with seed additions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
711d22cff3 |
fix: per-slot card SIO state + card-specific IRQ delay
The SIO model had a single global mc_state shared between both card slots, which caused 100% probe abort rate when the BIOS alternates between slot 0 and slot 1: each new 0x81 probe on the other slot would abort the in-flight state from the previous slot. Changes: - Per-slot McSlotState saved/restored on slot switch via mc_save_slot/mc_load_slot - 0x01 (pad select) preserves card state instead of aborting it - SIO_IRQ_DELAY split into PAD (4) and CARD (8) — card delay tuned so the SIO chain walker can dispatch func_00004D6C before next idle path tick clears the protocol counter Diagnostic tooling: - I_MASK[7] trace ring buffer with bit-7 set/clear counts and caller info - Counter (0x7514) field added to SIO trace so card protocol state machine progress is visible - check_card.py inspects card directory contents Runtime evidence: mc_aborts went from 100% → 0% (when slots don't overlap). mc_max_state advanced from 1 (only MC_CMD) to 8 (MC_READ_DATA). dispatch_check confirms _new_card AND its body 0x5688 are dispatched. Detection callback (func_00005D94 at BFC15894) verified to clear v0=0 before return — continue path of func_00004D6C IS being taken (133 I_MASK[7] sets observed). Remaining: 0x7550 stays NULL despite _new_card being dispatched. Either shell passes NULL buffer or early-return path of _new_card triggers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2767018b46 |
fix: SIO pad detection + input override + CARD driver seeds
Root cause: BIOS pad handler polls I_STAT bit 7 waiting for SIO ACK IRQ. sio_tick() was only called from psx_check_interrupts and SIO register accesses, but I_STAT reads go through memory.c which never called sio_tick(). The IRQ countdown was stuck during the polling loop, causing the BIOS to time out and report "no pad found" (0xFF). Additionally, sio_tick() in psx_check_interrupts fired the IRQ too early — during BIOS delay loops BEFORE I_STAT bit 7 was cleared — so the BIOS cleared it and never saw it re-appear. Fixes: - memory.c: Call sio_tick() on I_STAT reads (32/16/8-bit paths) - interrupts.c: Remove sio_tick() from psx_check_interrupts - debug_server.c: Don't clear input override on TCP disconnect (was wiping override before next VBlank could read it) - debug_server.c: sio_state reports real pad_buttons via getter - sio.c/sio.h: Add sio_get_pad_buttons() for debug server - seeds: Add 20 new seeds for shell input and CARD driver functions Result: Pad buffer at 0x80083E30 shows status=0x00 (pad found), type=0x41 (digital), buttons reflect keyboard/override state. Only 5 COP2/GTE dispatch misses remain (Phase 5). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
fba58d76b3 |
Phase 4 interrupt fixes: RFE, GPR save/restore, DMA IRQ edge-trigger
Three interrupt delivery bugs fixed that were blocking shell progression: 1. RFE after handler exit: The exception handler's normal exit path (jmpbuf longjmp at 0xBFC10944) does not execute RFE. In our model the handler returns to psx_check_interrupts, not to the WaitEvent caller. Without RFE, IEc stays 0 forever after the first interrupt. Fix: apply RFE in psx_check_interrupts if IEc is still 0 after the handler returns. 2. GPR save/restore around handler dispatch: The handler's jmpbuf exit restores callee-saved registers from the kernel jmpbuf (intended for a non-local jump to WaitEvent's caller). In our model this corrupts the interrupted code's registers. Fix: save all GPRs before handler dispatch and restore them after, matching real hardware where the interrupted code's registers are preserved via TCB save/restore. 3. VBlank tick gated on !in_exception: The handler runs thousands of block leaders, each calling psx_check_interrupts. Without gating, dispatch_count advances inside the handler, firing new VBlanks that stack up and starve the main code. Fix: only increment dispatch_count and fire VBlank/timer ticks when not in exception handling. 4. DMA IRQ edge-triggered: update_master_irq() was level-triggered, re-asserting i_stat bit 3 on every call while master_flag=1. Combined with no BIOS callback to acknowledge DMA IRQs, this caused an interrupt storm. Fix: edge-detect master_flag 0->1 transition, and suppress DMA completion IRQs entirely since transfers are synchronous. Also adds GPU debug diagnostics: GP0 command counters (nop/fill/draw/ env/copy), draw area/offset in gpu_state, vram_peek debug command. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |