Merge pull request #212 from libretro/fix/m68k-group0-frame

fix(m68k,gpu): 68000 group-0 exception frame + GPU STOREP phrase alignment
This commit is contained in:
Joe Mattiello authored and GitHub committed 2026-07-29 22:18:47 -04:00
commit 0b153773a3
3 files changed
+101 -22

No files matched your search

+69 -5
View File
@@ -97,6 +97,7 @@ NB: Seems that when an address exception occurs, it doesn't get handled properly
void Exception(int nr, uint32_t oldpc, int ExceptionSource)
{
uint32_t currpc = m68k_getpc(), newpc;
int faultWasSuper = regs.s;
MakeSR();
@@ -108,11 +109,74 @@ void Exception(int nr, uint32_t oldpc, int ExceptionSource)
regs.s = 1;
}
// Create 68000 style stack frame
m68k_areg(regs, 7) -= 4; // Push PC on stack
m68k_write_memory_32(m68k_areg(regs, 7), currpc);
m68k_areg(regs, 7) -= 2; // Push SR on stack
m68k_write_memory_16(m68k_areg(regs, 7), regs.sr);
if (nr == 2 || nr == 3)
{
/* Group 0 (bus error / address error): the 68000 pushes a
14-byte frame, not the 6-byte frame used by every other
exception:
A7+ 0 special status word
A7+ 2 address that caused the fault (long)
A7+ 6 first word of the faulting instruction
A7+ 8 status register
A7+10 program counter (long)
Games install recovery stubs that step over the extra 8
bytes and resume — "ADDQ.L #8,A7 ; RTE" is the standard
idiom, and Pitfall: The Mayan Adventure has exactly that at
vectors 2 and 3. Pushing a short 6-byte frame here made the
stub's ADDQ overshoot the saved SR/PC, so its RTE loaded
garbage and the 68K fell into a permanent
fault-RTE-refault loop (issue #138).
The special status word reports data space at the privilege
level in force when the access faulted. The R/W bit is
reported as "read" unconditionally because the generated CPU
core does not record the direction of the faulting access;
no known title inspects it — handlers only skip these
8 bytes. */
uint16_t ssw = (uint16_t)((faultWasSuper ? 5 : 1) | 0x10);
/* Resume PC. A real 68000 saves a PC "3 to 5 words beyond"
the faulting instruction, i.e. architecturally imprecise, so
there is no single spec-correct value to push here. Use the
address gencpu recorded for this instruction
(last_addr_for_exception_3), which is what the UAE-derived
core computes for exactly this purpose; empirically Pitfall
takes five consecutive address errors, returns to the same
A7 each time and carries on, so the value is one the game
survives.
The generated core records last_*_for_exception_3 only for
address errors, so every field sourced from them is gated on
nr == 3; a bus error would otherwise push stale metadata
from an earlier address error. Nothing in this core raises
exception 2 today (see m68kinterface.c:
"EXCEPTION_BUS_ERROR ... This one is not emulated!"), so
that path is unreachable — the gate is there so it stays
correct if bus errors are ever implemented. */
uint32_t framePC = (nr == 3 ? last_addr_for_exception_3 : currpc);
uint16_t frameOp = (nr == 3 ? last_op_for_exception_3 : 0);
uint32_t frameFault = (nr == 3 ? last_fault_for_exception_3 : 0);
m68k_areg(regs, 7) -= 4; // Push PC on stack
m68k_write_memory_32(m68k_areg(regs, 7), framePC);
m68k_areg(regs, 7) -= 2; // Push SR on stack
m68k_write_memory_16(m68k_areg(regs, 7), regs.sr);
m68k_areg(regs, 7) -= 2; // Faulting instruction word
m68k_write_memory_16(m68k_areg(regs, 7), frameOp);
m68k_areg(regs, 7) -= 4; // Faulting access address
m68k_write_memory_32(m68k_areg(regs, 7), frameFault);
m68k_areg(regs, 7) -= 2; // Special status word
m68k_write_memory_16(m68k_areg(regs, 7), ssw);
}
else
{
// Create 68000 style stack frame
m68k_areg(regs, 7) -= 4; // Push PC on stack
m68k_write_memory_32(m68k_areg(regs, 7), currpc);
m68k_areg(regs, 7) -= 2; // Push SR on stack
m68k_write_memory_16(m68k_areg(regs, 7), regs.sr);
}
// LOG_TRACE(TRACE_CPU_EXCEPTION, "cpu exception %d currpc %x buspc %x newpc %x fault_e3 %x op_e3 %hx addr_e3 %x\n",
// nr, currpc, BusErrorPC, get_long(4 * nr), last_fault_for_exception_3, last_op_for_exception_3, last_addr_for_exception_3);
+31 -17
View File
@@ -714,6 +714,12 @@ void GPUHandleIRQs(void)
// subqt #4,r31 ; pre-decrement stack pointer
// move pc,r30 ; address of interrupted code
// store r30,(r31) ; store return address
/* That "store r30,(r31)" is an ordinary RISC LONG store, so hardware
* ignores address bits 1-0 (see the alignment note on gpu_opcode_storep).
* A game that takes a GPU interrupt while r31 still holds an unaligned
* value pushes the return address into the longword containing r31 -- it
* cannot straddle two longwords and corrupt the neighbour, which is what
* an unmasked push here would do. */
gpu_reg[31] -= 4;
if (gpu_in_delay_slot)
{
@@ -723,11 +729,11 @@ void GPUHandleIRQs(void)
* address is the pending BRANCH TARGET, not gpu_pc (which still
* points just past the delay slot). Flag the in-flight jump opcode
* so it does not overwrite gpu_pc (the vector) afterwards. */
GPUWriteLong(gpu_reg[31], gpu_ds_branch_target - 2, GPU);
GPUWriteLong(gpu_reg[31] & 0xFFFFFFFC, gpu_ds_branch_target - 2, GPU);
gpu_ds_irq_dispatched = 1;
}
else
GPUWriteLong(gpu_reg[31], gpu_pc - 2, GPU);
GPUWriteLong(gpu_reg[31] & 0xFFFFFFFC, gpu_pc - 2, GPU);
// movei #service_address,r30 ; pointer to ISR entry
// jump (r30) ; jump to ISR
@@ -1521,23 +1527,31 @@ INLINE static void gpu_opcode_store(void)
}
/* STOREP moves a whole phrase, so the memory controller sees a phrase
* address: bits 2-0 carry no meaning for a 64-bit transfer and hardware
* rounds them away. The Jaguar bus has no cycle that writes eight bytes
* starting at an arbitrary byte offset, so an unaligned STOREP can never
* smear the transfer forward into the following phrase. The address was
* already masked for GPU local RAM; external addresses were passed through
* raw, which is the bug.
*
* Pitfall: The Mayan Adventure depends on this. Its GPU code keeps a
* two-longword mailbox at main RAM $0-$7 and reaches it with STOREP through a
* register that sometimes holds $7 rather than $0 (the low bits are junk the
* hardware discards). Unmasked, that wrote longwords at $7 and $B; the
* second landed on $C-$E and replaced the 68K address-error vector. The game
* installs a deliberate "ADDQ.L #8,A7 ; RTE" address-error recovery stub at
* $400 and takes real address errors during play, so a few frames later it
* vectored into garbage and the 68K never came back (issue #138).
*
* The same argument applies to the plain LONG stores (bits 1-0), which still
* only mask GPU local RAM. Masking those too is left alone deliberately: it
* is not needed for #138 and it does perturb Ruiner Pinball and Super Burnout,
* which needs its own before/after evidence rather than riding along here. */
INLINE static void gpu_opcode_storep(void)
{
#ifdef GPU_CORRECT_ALIGNMENT
if ((RM >= 0xF03000) && (RM <= 0xF03FFF))
{
GPUWriteLong((RM & 0xFFFFFFF8) + 0, gpu_hidata, GPU);
GPUWriteLong((RM & 0xFFFFFFF8) + 4, RN, GPU);
}
else
{
GPUWriteLong(RM + 0, gpu_hidata, GPU);
GPUWriteLong(RM + 4, RN, GPU);
}
#else
GPUWriteLong(RM + 0, gpu_hidata, GPU);
GPUWriteLong(RM + 4, RN, GPU);
#endif
GPUWriteLong((RM & 0xFFFFFFF8) + 0, gpu_hidata, GPU);
GPUWriteLong((RM & 0xFFFFFFF8) + 4, RN, GPU);
}
INLINE static void gpu_opcode_loadb(void)
+1
View File
@@ -0,0 +1 @@
/Users/jmattiello/Workspace/Provenance/virtualjaguar-libretro/test/roms/private