mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
EE counters: clamp T_COUNT reads until the boundary interrupt is delivered
NFL 2K5 (SLUS-20919) hangs at the boot logo in a 64-bit divide-by-repeated- subtraction with a huge unsigned dividend. Its 64-bit clock is an overflow- ISR-maintained wrap accumulator plus a live T0_COUNT read (bus/16, OVFE), reconciled lock-free with double reads — airtight on hardware, where the count wrap and the overflow interrupt are the same edge and the ISR preempts before any later read. Under the JIT the guest can observe the wrap while the ISR's effects are still pending, in two phases: (1) the count (derived from the live cpuRegs.cycle) crosses the boundary before the scheduled rcntUpdate event runs; (2) rcntUpdate has wrapped the count and raised the INTC, but the exception waits for the next event test — which our static-linked / short-block tails defer past the reader's entire load sequence (traced live: the wrap event fires at the reader's own block-entry event test, and delivery lands at its jr-ra exit, 30 cycles too late). Either way the game reads stale-accumulator + wrapped-count, time goes backwards one wrap period, and the divide runs ~2^48 iterations. Clamp the read to just-before-the-boundary until the interrupt has actually been delivered. The deliverability guard (INTC pending & unmasked & Status EIE/IE, no EXL/ERL) makes this exact: inside the handler or with the source masked (e.g. the game's DisableIntc reader, which reconciles the raw wrap itself) the wrapped count stays observable, as on hardware. Pinned by EeTimerCountReadRace.* in recompiler_tests. Verified live: cold fastboot reaches attract; previously parked at the divide loop within ~20s.
This commit is contained in:
@@ -958,6 +958,41 @@ __fi u32 rcntRcount(int index)
|
||||
|
||||
ret = counters[index].count;
|
||||
|
||||
// Never expose a boundary crossing (wrap or target-reset) to the guest
|
||||
// before the corresponding interrupt has actually been DELIVERED. On
|
||||
// hardware the boundary and the interrupt are the same edge, and with
|
||||
// interrupts enabled the handler preempts before any later read can
|
||||
// execute — a wrapped count paired with the pre-overflow ISR state is an
|
||||
// impossible observation. Under the JITs that window is real and spans
|
||||
// two phases:
|
||||
// 1. The count (derived from the live cpuRegs.cycle) has crossed the
|
||||
// boundary but the scheduled rcntUpdate event hasn't run yet.
|
||||
// 2. rcntUpdate has processed the crossing (count wrapped, OVFF/EQUF
|
||||
// set, INTC raised) but the exception is still waiting for the next
|
||||
// event test to be dispatched, so the guest's ISR hasn't run.
|
||||
// NFL 2K5's lock-free 64-bit clock (overflow-ISR-maintained wrap
|
||||
// accumulator + T0_COUNT) reads time going backwards in that window and
|
||||
// hangs in a runaway divide at the boot logo. Clamp the read to
|
||||
// just-before-the-boundary until delivery. The deliverability guard makes
|
||||
// this exact: with interrupts blocked (DI/EXL — including inside the
|
||||
// handler itself) or the INTC source masked, the guest legitimately
|
||||
// observes the wrapped count, as on hardware.
|
||||
const u32 target = counters[index].target & 0xffff;
|
||||
const bool intc_pending_delivery =
|
||||
(psHu32(INTC_STAT) & psHu32(INTC_MASK) & (1u << counters[index].interrupt)) &&
|
||||
(cpuRegs.CP0.n.Status.val & 0x400) &&
|
||||
cpuRegs.CP0.n.Status.b.EIE && cpuRegs.CP0.n.Status.b.IE &&
|
||||
!cpuRegs.CP0.n.Status.b.EXL && !cpuRegs.CP0.n.Status.b.ERL;
|
||||
if (counters[index].mode.ZeroReturn)
|
||||
{
|
||||
if (target != 0 && (ret >= target || (counters[index].mode.TargetReached && intc_pending_delivery)))
|
||||
ret = target - 1;
|
||||
}
|
||||
else if (ret > 0xffff || (counters[index].mode.OverflowReached && intc_pending_delivery))
|
||||
{
|
||||
ret = 0xffff;
|
||||
}
|
||||
|
||||
// Spams the Console.
|
||||
EECNT_LOG("EE Counter[%d] readCount32 = %x", index, ret);
|
||||
return (u16)ret;
|
||||
|
||||
Reference in New Issue
Block a user