mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
fix(cd): GPU IRQ semantics — halted GPU captures nothing; auth magic stops stomping game GPU code
Two related fixes for BIOS-mode CD titles: 1. Hover Strike B-skip lockup (deterministic at press frames 150/200/550/650): GPUSetIRQLine latched+dispatched interrupts while the GPU was halted (GPUGO=0). A stale DSA-response latch captured during the driver's stopped-GPU maintenance window dispatched at restart before the new GPU program's r31 stack init, pushing the return address into GPU code (the PIT stub's movei operand at $F03114) — the game's schedule clock at $5DC2E froze and the 68K polled forever. A halted RISC samples nothing: asserts are now dropped while stopped (the source's level re-edges after restart), GPUHandleIRQs bails while halted, and the G_CTRL write handler applies GO before processing the CPU->GPU INT0 strobe so a single GO|INT0 write still works. The BUTCH GPU latch assert is now edge-paced (once per service cycle, re-armed by the ISR's BUTCH+2 ack write) instead of once per BUTCHExec tick — per-tick assert kept a latch pending at every instant, per-onset starves transfers, and edges arriving during a stopped window stay armed so the first tick after restart re-asserts them. 2. Myst BIOS boot crash (GPU flies to $ACC5A186 at the post-load Pause): the BIOS strategy's auth-magic hook wrote $03D0DEAD to $F03000 on EVERY 68K execution of PC=$005E40. That address is BIOS-phase code, but Myst loads its game code at $5000-$1D380 and hot paths cross $5E40 — each crossing overwrote the first two words of Myst's GPU IRQ0 handler stub (`movei #$F0D000,r0 / jump (r0)`; Myst uses IRQ0 as its 68K->GPU command doorbell, G_CTRL=$05 kicks). The corrupted slot dispatched through stale r0 into garbage. The hook is now gated on the boot stub not yet having been injected — after BIOS->game handoff the game owns that RAM. Myst now boots and runs its command loop (it renders black in headless — separate issue, likely its CDDA-driven attract flow; the crash is gone). Verified: Hover Strike B-press sweep 150/200/350/550/650 all clean; Myst BIOS 1500 frames no crash signature; full suite passes (56103); CD boot matrix 9 titles x 2 modes no regressions, Baldies HLE improved 0/1 -> 1/1. test_hle_bios 9d/9d-cd and test_butch_cd DSA-routing test updated to pin the new halted-GPU semantics (latch mechanics now run with GPUGO=1; new sub-assert pins assert-drop while halted). Tooling: cd_wedge_probe gains --ram-dump / --snap / --snap-prefix (full main-RAM + GPU-RAM snapshots) and fixes the jaguarMainRAM pointer-variable dereference bug that made all prior RAM dumps read host heap instead of emulated RAM. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
b872ed54a3
commit
e92b6750d8
8 files changed
+213
-17
No files matched your search
@@ -39,6 +39,7 @@ test/test_*
|
||||
test/tools/test_memory_map
|
||||
test/tools/test_op_gpu_object
|
||||
test/tools/test_dsp_audio_diag
|
||||
test/tools/cd_wedge_probe
|
||||
test/tools/sram_test
|
||||
test/dump_caller
|
||||
test/dump_pc
|
||||
|
||||
@@ -98,7 +98,7 @@ To add a new probe: create `test/harness/foo_probe.h` + `.c`, resolve symbols vi
|
||||
- `test/sram_test.sh` — SRAM round-trip
|
||||
- `test/tools/cd_boot_matrix.sh` — per-title CD boot-stage matrix (HLE + BIOS mode) vs `docs/cd-boot-matrix.md`; env knobs `CD_MATRIX_FRAMES`, `CD_MATRIX_TIMEOUT`, `CD_MATRIX_MAX_RUNS`, `CD_MATRIX_LOGDIR`, `CD_MATRIX_OUT`, `CD_MATRIX_ROMS_ROOT`; chunked/resumable across invocations
|
||||
- CD trace ring: core option `virtualjaguar_cd_trace` (or env `VJ_CD_TRACE=1` for headless use) records `DSA_TX`/`DSA_RX`, `SEEK_START`/`SEEK_DONE`, `FIFO_FILL`/`FIFO_DRAIN`, `STOP`, `HLE_READ` events; dumped to the log on `cd_seek_wedge` or on request
|
||||
- `test/tools/cd_wedge_probe.c` — catches intermittent CD lockups: detects a frozen framebuffer (`--arm N --freeze-frames N`, script the repro with `--press`), then dumps 68K registers, the pcQueue traceback ring, CD counters + trace ring, and a RAM hexdump around the stuck PC; exits 42 when caught so a retry loop can distinguish "caught" from "ran clean". Found the Hover Strike instant-CD_read code-stomp.
|
||||
- `test/tools/cd_wedge_probe.c` — catches intermittent CD lockups: detects a frozen framebuffer (`--arm N --freeze-frames N`, script the repro with `--press`), then dumps 68K registers, the pcQueue traceback ring, CD counters + trace ring, and a RAM hexdump around the stuck PC; exits 42 when caught so a retry loop can distinguish "caught" from "ran clean". `--ram-dump BASE` writes full 2MB main-RAM + GPU-RAM snapshots at wedge time; `--snap FRAME` (repeatable, with `--snap-prefix BASE`) snapshots mid-run for clean-vs-wedged state diffing. Found the Hover Strike instant-CD_read code-stomp and the B-skip stale-GPU-IRQ-latch corruption. Caveat: a static loading screen can false-positive short freeze windows — confirm real wedges with `--freeze-frames 900`.
|
||||
- `test/tools/cd_visual_verify.c` — automated visual+audio verification for CD titles (`make cd-visual CD_VISUAL_DISC=<image.cue>`): per-second frame-motion timeline, non-black coverage, audio RMS, periodic screenshots (PPM; `sips -s format png` to view). Replaces most "boot it on a device and look" checks — an agent can Read the PNGs directly. Headless read-path caveat still applies for final "looks right" sign-off.
|
||||
|
||||
### Build-identity guard (stale-binary protection)
|
||||
|
||||
+42
-3
@@ -874,8 +874,22 @@ void BUTCHExec(uint32_t cycles)
|
||||
* BIOS hasn't installed its EXT1 trampoline (Hover Strike,
|
||||
* Primal Rage), corrupting the stack with a bogus return address.
|
||||
* Keep the JERRY pending bit (so JINTCTRL reads see it) but skip
|
||||
* BLIND m68k_set_irq dual-delivery. */
|
||||
GPUSetIRQLine(GPUIRQ_DSP, ASSERT_LINE);
|
||||
* BLIND m68k_set_irq dual-delivery.
|
||||
*
|
||||
* The GPU latch assert lives INSIDE the edge gate below: real
|
||||
* BUTCH raises its IRQ line once per condition onset (FIFO
|
||||
* crossing half-full, DSA response arriving), and TOM latches
|
||||
* that edge. Asserting every BUTCHExec tick while the level
|
||||
* holds re-latches GPU IRQ1 immediately after every ISR ack —
|
||||
* an IRQ storm that livelocks the GPU (RTI -> instant
|
||||
* redispatch), and guarantees a stale pending latch whenever
|
||||
* the 68K stop/reprograms/restarts the GPU. That pending latch
|
||||
* dispatched at restart BEFORE the new program's `movei
|
||||
* #$F04000,r31` stack init, pushing the return address through
|
||||
* a stale r31 into GPU code — Hover Strike's B-skip lockup
|
||||
* (press B at the boot logo: the CD driver's timer stub movei
|
||||
* at $F03110 got its operand overwritten, the game's schedule
|
||||
* clock at $5DC2E froze, and the 68K polled it forever). */
|
||||
|
||||
/* 68K delivery IS required when software asks for it: gate on
|
||||
* JINTCTRL's external-interrupt enable, exactly like the JERRY
|
||||
@@ -893,6 +907,11 @@ void BUTCHExec(uint32_t cycles)
|
||||
* Level delivery every halfline is an IRQ storm that starves the
|
||||
* 68K (4 matrix titles regressed to wall-clock hangs when this
|
||||
* was level-triggered). */
|
||||
/* Edge-paced (see the BUTCH+2 ack comment in CDROMWriteWord):
|
||||
* assert the GPU latch once per service cycle, not per tick. */
|
||||
if (!cdPrevShouldIRQ)
|
||||
GPUSetIRQLine(GPUIRQ_DSP, ASSERT_LINE);
|
||||
|
||||
if (!cdPrevShouldIRQ)
|
||||
{
|
||||
int tomEna = TOMIRQEnabled(IRQ_DSP);
|
||||
@@ -912,7 +931,14 @@ void BUTCHExec(uint32_t cycles)
|
||||
m68k_set_irq(2);
|
||||
}
|
||||
}
|
||||
cdPrevShouldIRQ = shouldIRQ;
|
||||
/* The edge is only "consumed" if a RUNNING GPU could capture it —
|
||||
* a halted GPU's interrupt logic is not clocked (GPUSetIRQLine
|
||||
* drops asserts while stopped). Keeping the tracker false across
|
||||
* a stopped window means the still-high line re-asserts on the
|
||||
* first tick after the 68K restarts the GPU; marking it true there
|
||||
* would swallow the edge forever and stall the transfer (the
|
||||
* press-B@550/650 variant of the Hover Strike lockup). */
|
||||
cdPrevShouldIRQ = shouldIRQ && GPUIsRunning();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1370,6 +1396,19 @@ void CDROMWriteWord(uint32_t offset, uint16_t data, uint32_t who/*=UNKNOWN*/)
|
||||
prevSubEna = data;
|
||||
}
|
||||
SET16(cdRam, offset, data & 0x007F); // Store only enable bits (0-6)
|
||||
/* Writing the enable word is how software services/acks the BUTCH
|
||||
* interrupt (the GPU ISR entry does a RMW here every invocation).
|
||||
* Real BUTCH re-evaluates its IRQ line after the ack: if a condition
|
||||
* (FIFO half-full, DSA response) still holds, the line rises again
|
||||
* and TOM latches a NEW edge. Re-arm the edge detector so BUTCHExec
|
||||
* asserts once per SERVICE CYCLE — not once per condition onset
|
||||
* (starves transfers: a single missed edge kills the stream) and
|
||||
* not once per tick (IRQ storm: the GPU latch is pending at every
|
||||
* instant, so any 68K stop/reprogram/restart of the GPU dispatches
|
||||
* into the new program before its `movei #$F04000,r31` stack init
|
||||
* and pushes a return address through stale r31 into GPU code —
|
||||
* Hover Strike's B-skip lockup). */
|
||||
cdPrevShouldIRQ = false;
|
||||
CD_LOG("WriteWord BUTCH+2: data=0x%04X enables=0x%02X [PC=$%06X]\n",
|
||||
data, data & 0x7F, m68k_get_reg(NULL, M68K_REG_PC));
|
||||
return;
|
||||
|
||||
+12
-2
@@ -43,8 +43,18 @@ static bool bios_instruction_hook(uint32_t m68kPC)
|
||||
{
|
||||
/* GPU auth magic — boot ROM checks this to verify GPU ran auth code.
|
||||
* Empirically still load-bearing: removing it makes every BIOS disc
|
||||
* loop at $0050B6 because the GPU never naturally writes the magic. */
|
||||
if (m68kPC == 0x005E40)
|
||||
* loop at $0050B6 because the GPU never naturally writes the magic.
|
||||
*
|
||||
* Gated on the boot stub NOT having been injected yet: $005E40 is a
|
||||
* BIOS-phase PC, but after handoff the GAME owns that RAM. Myst
|
||||
* loads its 68K code at $5000-$1D380 and hot paths cross $5E40 —
|
||||
* every crossing re-stomped $F03000 with the magic, overwriting the
|
||||
* first two words of Myst's GPU IRQ0 handler stub
|
||||
* (`movei #$F0D000,r0 / jump (r0)`). The 68K's next command kick
|
||||
* (G_CTRL=$05, Myst's 68K->GPU doorbell) then dispatched IRQ0 into
|
||||
* the corrupted slot and the GPU flew off through a stale r0 —
|
||||
* frozen black screen during boot. */
|
||||
if (m68kPC == 0x005E40 && !cdBootStubInjected)
|
||||
{
|
||||
GPUWriteLong(0xF03000, 0x03D0DEAD, 0);
|
||||
return true;
|
||||
|
||||
+34
-2
@@ -182,6 +182,7 @@ void (*gpu_opcode[64])()=
|
||||
static uint8_t gpu_ram_8[0x1000];
|
||||
uint32_t gpu_pc;
|
||||
|
||||
|
||||
/* Diagnostic IRQ counters (see gpu.h). Pure observability — incremented on
|
||||
* GPUSetIRQLine(line, ASSERT_LINE), reset in GPUReset. */
|
||||
uint32_t gpu_irq0_count = 0;
|
||||
@@ -597,6 +598,14 @@ void GPUWriteLong(uint32_t offset, uint32_t data, uint32_t who/*=UNKNOWN*/)
|
||||
data &= ~0x02;
|
||||
}
|
||||
|
||||
/* Apply GO and the other persistent control bits BEFORE
|
||||
* processing the CPU->GPU interrupt request below: a single
|
||||
* write may set GPUGO and INT0 together, and the interrupt
|
||||
* is only captured by a RUNNING GPU (see GPUSetIRQLine) —
|
||||
* so the GO bit must land first. $06 (the two transient
|
||||
* interrupt-request bits) never persists in gpu_control. */
|
||||
gpu_control = (gpu_control & 0xF7C0) | (data & ~(0xF7C0 | 0x06));
|
||||
|
||||
// check for CPU -> GPU interrupt #0
|
||||
if (data & 0x04)
|
||||
{
|
||||
@@ -606,8 +615,6 @@ void GPUWriteLong(uint32_t offset, uint32_t data, uint32_t who/*=UNKNOWN*/)
|
||||
data &= ~0x04;
|
||||
}
|
||||
|
||||
gpu_control = (gpu_control & 0xF7C0) | (data & (~0xF7C0));
|
||||
|
||||
// if gpu wasn't running but is now running, execute a few cycles
|
||||
#ifdef GPU_SINGLE_STEPPING
|
||||
if (gpu_control & 0x18)
|
||||
@@ -657,6 +664,18 @@ void GPUHandleIRQs(void)
|
||||
{
|
||||
uint32_t bits, mask;
|
||||
uint32_t which = 0; //Isn't there a #pragma to disable this warning???
|
||||
|
||||
/* A halted GPU (G_CTRL GPUGO=0) latches interrupts but cannot service
|
||||
* them: the dispatch sequence (push return address via r31, jump to
|
||||
* vector) is executed by the RISC core itself, and a stopped core
|
||||
* executes nothing. Dispatching here while stopped pushes a return
|
||||
* address through the STOPPED context's r31 — if the 68K halted the
|
||||
* GPU mid-handler (as Hover Strike's CD driver does around seeks),
|
||||
* that push lands inside GPU code/variables and corrupts them.
|
||||
* Pending latches are serviced by GPUExec() when the GPU next runs. */
|
||||
if (!GPU_RUNNING)
|
||||
return;
|
||||
|
||||
// Bail out if we're already in an interrupt!
|
||||
if (gpu_flags & IMASK)
|
||||
return;
|
||||
@@ -717,6 +736,19 @@ void GPUSetIRQLine(int irqline, int state)
|
||||
|
||||
if (state)
|
||||
{
|
||||
/* A halted GPU (GPUGO=0) does not capture interrupts: the RISC
|
||||
* core's clock is stopped, so nothing samples the interrupt lines
|
||||
* and no latch accumulates. Dropping the assert here is what real
|
||||
* silicon does — the source's LEVEL either still holds when the
|
||||
* GPU is restarted (and re-edges once software services it) or the
|
||||
* condition was consumed by the 68K in the meantime (e.g. a polled
|
||||
* DSA response) and no interrupt should be seen at all. Latching
|
||||
* here instead meant every 68K stop/reprogram/restart of the GPU
|
||||
* could dispatch a stale interrupt into the new program before its
|
||||
* r31 stack init, pushing a return address into GPU code — Hover
|
||||
* Strike's B-skip lockup. */
|
||||
if (!GPU_RUNNING)
|
||||
return;
|
||||
/* Diagnostic counters — see gpu.h */
|
||||
if (irqline == 0) gpu_irq0_count++;
|
||||
else if (irqline == 3) gpu_irq3_count++;
|
||||
|
||||
@@ -399,7 +399,14 @@ TEST(butch_dsa_irq_routes_to_gpu_irq1)
|
||||
core.GPUReset();
|
||||
|
||||
/* Mimic the CD BIOS interrupt setup exactly:
|
||||
* G_FLAGS = INT_ENA1 only; BUTCH = master enable + DSA RX enable. */
|
||||
* G_FLAGS = INT_ENA1 only; BUTCH = master enable + DSA RX enable.
|
||||
* GPUGO must be set: the BIOS's GPU is RUNNING (parked in its wait
|
||||
* loop) when the DSA response arrives — a halted GPU captures no
|
||||
* interrupts at all (asserts are dropped, not latched; that stale
|
||||
* latch dispatching at restart was Hover Strike's B-skip lockup).
|
||||
* INT_ENA1 + a pending latch dispatches immediately on assert, so
|
||||
* the "running" GPU never needs GPUExec to run garbage code. */
|
||||
core.GPUWriteLong(0xF02114, 0x00000001, CALLER_M68K);
|
||||
core.GPUWriteLong(0xF02100, GPU_FLAGS_INT_ENA1, CALLER_M68K);
|
||||
core.CDROMWriteWord(BUTCH_INT_CTRL + 2,
|
||||
BUTCH_INT_ENABLE | BUTCH_INT_RBUF_EN, CALLER_M68K);
|
||||
|
||||
+28
-6
@@ -1122,12 +1122,17 @@ static void test_jerry_jintctrl_multi_pending_selective_clear(void)
|
||||
* latch lives in gpu_control bits 6..10, mirrored as INT_LAT0..4.
|
||||
* GPUSetIRQLine(line, ASSERT_LINE) sets bit (6+line); the latch is
|
||||
* sticky until SW writes the matching CINTxFLAG bit (bits 9..13) to
|
||||
* gpu_flags via the memory-mapped F02100 path. With GPU off (HLE
|
||||
* mode) and INT_ENA cleared, GPUHandleIRQs must NOT advance gpu_pc.
|
||||
* gpu_flags via the memory-mapped F02100 path.
|
||||
*
|
||||
* We deliberately keep INT_ENA0..4 = 0 so HandleIRQs takes the
|
||||
* "!bits" early-out and never dispatches; this lets us inspect the
|
||||
* latch directly without perturbing R31/SP or GPU RAM.
|
||||
* A HALTED GPU (GPUGO=0) captures no interrupts at all: the RISC
|
||||
* clock is stopped, so asserts are dropped, not latched (see
|
||||
* GPUSetIRQLine). Latching a stale interrupt across a 68K
|
||||
* stop/reprogram/restart cycle dispatched it into the new program
|
||||
* before its r31 stack init — Hover Strike's B-skip lockup. The
|
||||
* latch mechanics below therefore run with GPUGO=1; INT_ENA0..4
|
||||
* stay 0 so HandleIRQs takes the "!bits" early-out and never
|
||||
* dispatches (and the harness never calls GPUExec), which lets us
|
||||
* inspect the latch without perturbing R31/SP, gpu_pc, or GPU RAM.
|
||||
*
|
||||
* Observables (memory-mapped via GPUReadLong):
|
||||
* $F02100 - gpu_flags (returns gpu_flags & 0xFFFFC1FF)
|
||||
@@ -1159,6 +1164,20 @@ static void test_gpu_irq_latch_redispatch(void)
|
||||
p_GPUWriteLong(0xF02100, 0x00000000, WHO_M68K); /* INT_ENA=0, IMASK=0 */
|
||||
pc_before = p_gpu_pc ? *p_gpu_pc : p_GPUReadLong(0xF02110, WHO_M68K);
|
||||
|
||||
/* --- Sub-assert 0: halted GPU captures no interrupts --- */
|
||||
p_GPUWriteLong(0xF02114, 0x00000000, WHO_M68K); /* GPUGO=0 */
|
||||
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
|
||||
ctrl = p_GPUReadLong(0xF02114, WHO_M68K);
|
||||
if (!(ctrl & 0x40))
|
||||
PASS("assert while GPUGO=0 is dropped (halted GPU latches nothing)");
|
||||
else
|
||||
FAIL("ctrl=$%08X (want bit6 clear: halted GPU must not latch)", ctrl);
|
||||
|
||||
/* Latch mechanics below need a running GPU (GPUGO=1). INT_ENA
|
||||
* stays 0, so nothing can dispatch, and the harness never calls
|
||||
* GPUExec — the "running" GPU executes no instructions. */
|
||||
p_GPUWriteLong(0xF02114, 0x00000001, WHO_M68K);
|
||||
|
||||
/* --- Sub-assert 1: latch survives without enable --- */
|
||||
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
|
||||
ctrl = p_GPUReadLong(0xF02114, WHO_M68K);
|
||||
@@ -1244,7 +1263,9 @@ static void test_gpu_irq_latch_redispatch(void)
|
||||
* CD BIOS installs its CD-data ISR.
|
||||
*
|
||||
* Pure latch test: INT_ENA all 0 / IMASK 0 so no dispatch happens;
|
||||
* we just inspect gpu_control bits 6 and 7.
|
||||
* we just inspect gpu_control bits 6 and 7. GPUGO is set for the
|
||||
* duration — a halted GPU drops asserts instead of latching them
|
||||
* (see Test 9d sub-assert 0).
|
||||
* ================================================================ */
|
||||
static void test_butch_gpu_irq_line_mapping(void)
|
||||
{
|
||||
@@ -1260,6 +1281,7 @@ static void test_butch_gpu_irq_line_mapping(void)
|
||||
/* Clear any latch and disable enables so this is a pure-latch test. */
|
||||
p_GPUWriteLong(0xF02100, 0x00003E00, WHO_M68K); /* CINT0..4FLAG -> clear latch */
|
||||
p_GPUWriteLong(0xF02100, 0x00000000, WHO_M68K); /* INT_ENA=0, IMASK=0 */
|
||||
p_GPUWriteLong(0xF02114, 0x00000001, WHO_M68K); /* GPUGO=1: halted GPU drops asserts */
|
||||
|
||||
/* IRQ line 0 (GPUIRQ_CPU == 0, EXT1, vector $F03000) -> bit 6. */
|
||||
p_GPUSetIRQLine(0, ASSERT_LINE_LOCAL);
|
||||
|
||||
@@ -46,8 +46,53 @@ typedef struct {
|
||||
unsigned arm_frame;
|
||||
unsigned freeze_frames;
|
||||
int wedged;
|
||||
const char *ram_dump_path;
|
||||
unsigned snap_frames[8];
|
||||
unsigned n_snaps;
|
||||
const char *snap_prefix;
|
||||
} wp_state;
|
||||
|
||||
/* Dump main RAM (2MB) and GPU local RAM ($F03000-$F03FFF) to files. */
|
||||
static void wp_snapshot(harness_config *cfg, const char *base)
|
||||
{
|
||||
/* jaguarMainRAM is a POINTER variable (into jagMemSpace) — dlsym gives
|
||||
* the address of the pointer; dereference to get the RAM base. */
|
||||
uint8_t **ramp = (uint8_t **)harness_dlsym(cfg, "jaguarMainRAM");
|
||||
uint8_t *ram = ramp ? *ramp : NULL;
|
||||
uint32_t (*p_gpurl)(uint32_t, uint32_t) =
|
||||
(uint32_t (*)(uint32_t, uint32_t))harness_dlsym(cfg, "GPUReadLong");
|
||||
uint32_t *p_gpupc = (uint32_t *)harness_dlsym(cfg, "gpu_pc");
|
||||
char path[1024];
|
||||
FILE *f;
|
||||
|
||||
if (ram) {
|
||||
snprintf(path, sizeof(path), "%s.ram", base);
|
||||
f = fopen(path, "wb");
|
||||
if (f) {
|
||||
fwrite(ram, 1, 0x200000, f);
|
||||
fclose(f);
|
||||
fprintf(stderr, "[WEDGE-PROBE] main RAM -> %s\n", path);
|
||||
}
|
||||
}
|
||||
if (p_gpurl) {
|
||||
uint32_t a;
|
||||
snprintf(path, sizeof(path), "%s.gpu", base);
|
||||
f = fopen(path, "wb");
|
||||
if (f) {
|
||||
for (a = 0xF03000; a < 0xF04000; a += 4) {
|
||||
uint32_t v = p_gpurl(a, 0);
|
||||
uint8_t b[4];
|
||||
b[0] = (uint8_t)(v >> 24); b[1] = (uint8_t)(v >> 16);
|
||||
b[2] = (uint8_t)(v >> 8); b[3] = (uint8_t)v;
|
||||
fwrite(b, 1, 4, f);
|
||||
}
|
||||
fclose(f);
|
||||
fprintf(stderr, "[WEDGE-PROBE] GPU RAM -> %s (gpu_pc=$%06X)\n",
|
||||
path, p_gpupc ? *p_gpupc : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static wp_state g_st;
|
||||
|
||||
static void wp_video_cb(void *ud, const void *data, unsigned w, unsigned h,
|
||||
@@ -121,13 +166,32 @@ static void wp_dump(harness_config *cfg, unsigned frame)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
/* GPU PC history ring (TEMP Myst diag in gpu.c) — newest last. */
|
||||
{
|
||||
uint32_t *ring = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring");
|
||||
uint32_t *rptr = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring_ptr");
|
||||
uint32_t *rfrz = (uint32_t *)harness_dlsym(cfg, "gpu_pc_ring_frozen");
|
||||
if (ring && rptr) {
|
||||
unsigned n, idx = (*rptr - 64) & 511;
|
||||
fprintf(stderr, "[WEDGE-PROBE] GPU PC trail (frozen=%u, oldest first):",
|
||||
rfrz ? *rfrz : 0);
|
||||
for (n = 0; n < 64; n++) {
|
||||
if ((n % 6) == 0) fprintf(stderr, "\n[WEDGE-PROBE] ");
|
||||
fprintf(stderr, "$%06X ", ring[idx]);
|
||||
idx = (idx + 1) & 511;
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (p_cdsum) p_cdsum();
|
||||
if (p_cddump) p_cddump();
|
||||
|
||||
/* Hex-dump main RAM around the spinning code so the wait loop can be
|
||||
* disassembled offline, plus the HLE DSP done-flag and CDDA mailbox. */
|
||||
{
|
||||
uint8_t *ram = (uint8_t *)harness_dlsym(cfg, "jaguarMainRAM");
|
||||
uint8_t **ramp = (uint8_t **)harness_dlsym(cfg, "jaguarMainRAM");
|
||||
uint8_t *ram = ramp ? *ramp : NULL;
|
||||
uint32_t (*p_dspr)(uint32_t, uint32_t) =
|
||||
(uint32_t (*)(uint32_t, uint32_t))harness_dlsym(cfg, "DSPReadLong");
|
||||
|
||||
@@ -153,6 +217,10 @@ static void wp_dump(harness_config *cfg, unsigned frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Full main-RAM + GPU-RAM snapshot for offline stack walking /
|
||||
* disassembly of the code the 68K wedged in. */
|
||||
if (g_st.ram_dump_path)
|
||||
wp_snapshot(cfg, g_st.ram_dump_path);
|
||||
if (p_dspr) {
|
||||
fprintf(stderr, "[WEDGE-PROBE] DSP $F1B4C8 (HLE done flag) = $%08X\n",
|
||||
p_dspr(0xF1B4C8, 0));
|
||||
@@ -166,6 +234,15 @@ static void wp_dump(harness_config *cfg, unsigned frame)
|
||||
static bool wp_frame_cb(void *ud, unsigned frame)
|
||||
{
|
||||
harness_config *cfg = (harness_config *)ud;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < g_st.n_snaps; i++) {
|
||||
if (g_st.snap_frames[i] == frame && g_st.snap_prefix) {
|
||||
char base[900];
|
||||
snprintf(base, sizeof(base), "%s_f%u", g_st.snap_prefix, frame);
|
||||
wp_snapshot(cfg, base);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame >= g_st.arm_frame && g_st.same_frames >= g_st.freeze_frames) {
|
||||
g_st.wedged = 1;
|
||||
@@ -188,6 +265,13 @@ int main(int argc, char **argv)
|
||||
g_st.arm_frame = (unsigned)atoi(argv[i + 1]);
|
||||
else if (strcmp(argv[i], "--freeze-frames") == 0 && i + 1 < argc)
|
||||
g_st.freeze_frames = (unsigned)atoi(argv[i + 1]);
|
||||
else if (strcmp(argv[i], "--ram-dump") == 0 && i + 1 < argc)
|
||||
g_st.ram_dump_path = argv[i + 1];
|
||||
else if (strcmp(argv[i], "--snap") == 0 && i + 1 < argc &&
|
||||
g_st.n_snaps < 8)
|
||||
g_st.snap_frames[g_st.n_snaps++] = (unsigned)atoi(argv[i + 1]);
|
||||
else if (strcmp(argv[i], "--snap-prefix") == 0 && i + 1 < argc)
|
||||
g_st.snap_prefix = argv[i + 1];
|
||||
}
|
||||
|
||||
cfg.frames = 4800;
|
||||
@@ -200,7 +284,8 @@ int main(int argc, char **argv)
|
||||
if (!harness_init_from_args(&cfg, argc, argv)) return 1;
|
||||
if (!cfg.rom_path) {
|
||||
fprintf(stderr, "usage: cd_wedge_probe [core] <disc.cue> [--frames N] "
|
||||
"[--arm N] [--freeze-frames N] [--press F:BTN[:HOLD]]... "
|
||||
"[--arm N] [--freeze-frames N] [--ram-dump FILE] "
|
||||
"[--press F:BTN[:HOLD]]... "
|
||||
"[--system-dir DIR]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user