Skip to content

fix(runtime): mirror D_ENABLEW into D_ENABLER and reset it to 0x1201 - #284

Open
luisbraganca wants to merge 1 commit into
ran-j:mainfrom
luisbraganca:fix/dmac-enable-register-mirror
Open

luisbraganca wants to merge 1 commit into
ran-j:mainfrom
luisbraganca:fix/dmac-enable-register-mirror

Conversation

@luisbraganca

@luisbraganca luisbraganca commented Oct 10, 2026 •

Copy link
Copy Markdown

Closes #257

Problem

D_ENABLEW (0x1000F590) and D_ENABLER (0x1000F520) are the write and read ports of the single DMAC enable register. PS2Memory kept them as two unrelated m_ioRegisters slots, so a guest that suspends the DMAC and polls for the CPND bit never sees it:

sw   $a1, D_ENABLEW      ; request suspend (bit 16)
lw   $v0, D_ENABLER      ; read it back -> always 0 here
and  $v0, 0x10000
beq  -> retry forever

Ridge Racer V does exactly this around its DMAC interrupt handler and frame kick (0x0021FDD8 / 0x0021FE30), so it froze after loading its IOP modules and clearing both framebuffers.

Change

ps2xRuntime/src/lib/ps2_memory.cpp

  • writeIORegister: a store to D_ENABLEW lands in both slots. Every access width reaches it: write8/16 merge into the 32-bit slot and call writeIORegister, write64/128 split into write32, and a recompiled sw goes through PS2Runtime::Store32 because the I/O range is a special address.
  • initialize: both slots start at 0x1201, the value the hardware reports at boot; bit 16 is clear, so the DMAC starts enabled.

Nothing else changes. Channel kicks are still performed immediately while CPND is set: the runtime has no queued-DMA model (a kick with D_CTRL.DMAE = 0 is dropped today), and suspend should not start dropping transfers. Stores to D_ENABLER keep the generic I/O-register behaviour, as in PCSX2.

References

  • PCSX2 pcsx2/Dmac.cpp, dmacWrite32, case DMAC_ENABLEW: psHu32(DMAC_ENABLEW) = value; psHu32(DMAC_ENABLER) = value;
  • PCSX2 pcsx2/Hw.cpp, hwReset: psHu32(DMAC_ENABLEW) = 0x1201; psHu32(DMAC_ENABLER) = 0x1201;
  • PCSX2 gates DMA on psHu8(DMAC_ENABLER + 2) == 1 (byte 2 = bit 16), which is why the test also checks the byte read.
  • ps2tek, EE DMAC registers: 1000F520h D_ENABLER - DMAC disabled status (R), 1000F590h D_ENABLEW - DMAC disable (W), bit 16 "DMAC disabled", "SCPH-39001 seems to expect D_ENABLER to be set to 1201h upon boot".

Verification

ps2xTest/src/ps2_memory_tests.cpp, suite PS2Memory, three new cases:

  • DMAC D_ENABLER reads 0x1201 after reset with CPND clear
  • DMAC D_ENABLEW writes are visible at D_ENABLER — sw, then sh to the upper half, then lb of byte 2
  • DMAC suspend and resume polls through D_ENABLEW and D_ENABLER terminate — the Ridge Racer V loops with a poll cap

On main (2c5fbb9) with only the tests applied: 440 total, 437 passed, 3 failed — the three cases above. With the fix: ps2x_tests 440/440.

Host: macOS on Apple Silicon, AppleClang, Release, -DPS2X_ENABLE_FFMPEG=OFF -DPS2X_BUILD_STUDIO=OFF. No game ELF is available here, so the Ridge Racer V spin is reproduced by the third test rather than by running the title.

D_ENABLEW (0x1000F590) and D_ENABLER (0x1000F520) are the write and read ports of the single DMAC enable register, but PS2Memory kept them as two unrelated I/O slots, so a write to one was never visible at the other. Any guest that suspends the DMAC and polls for the CPND bit therefore spun forever; Ridge Racer V does this around its DMAC interrupt handler and frame kick and froze after loading its IOP modules.

writeIORegister now stores a D_ENABLEW write into both slots, which covers every access width (write8/16 merge into the 32-bit slot, write64/128 split into write32, recompiled stores arrive through PS2Runtime::Store32). initialize seeds both slots with 0x1201, the value the hardware reports at boot, with CPND clear. Matches PCSX2's dmacWrite32 and hwReset.

Channel kicks are still executed immediately while CPND is set: the runtime has no queued-DMA model, and a kick with D_CTRL.DMAE clear is dropped today, so gating on suspend would lose transfers.

Adds three PS2Memory cases: the reset value, sw/sh writes to D_ENABLEW reading back at D_ENABLER (including the byte read PCSX2 polls), and the suspend/resume spin loops terminating. They fail on main.

ps2x_tests: 440 passed, 0 failed (437 before).

Fixes ran-j#257
@luisbraganca

Copy link
Copy Markdown
Author

Hi @ran-j,

This touches ps2_memory.cpp and ps2_memory_tests.cpp, both of which are in #285, so it'll conflict with your IOP and performance work. Happy to rebase onto that once it's in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime: writes to D_ENABLEW (0x1000F590) are never visible at D_ENABLER (0x1000F520), hanging any game that suspends the DMAC

1 participant