mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-08 17:30:20 -10:00
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:
3 files changed
+101
-22
No files matched your search
+69
-5
@@ -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
@@ -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)
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/Users/jmattiello/Workspace/Provenance/virtualjaguar-libretro/test/roms/private
|
||||
Reference in new issue
Block a user