Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ps2xRuntime/src/lib/ps2_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ namespace

constexpr uint32_t kGsCsrRegOffset = 0x1000u;

// D_ENABLER (read port) and D_ENABLEW (write port) are the one DMAC enable
// register; bit 16 (CPND) suspends every channel. The hardware reads 0x1201
// at boot (PCSX2 hwReset, ps2tek).
constexpr uint32_t kDEnableR = 0x1000F520u;
constexpr uint32_t kDEnableW = 0x1000F590u;
constexpr uint32_t kDEnableResetValue = 0x1201u;

// Atomically apply a 32-bit write to one half (off=0 low dword, off=4 high
// dword) of the GS CSR register. Bits 0..1 of the low dword (SIGNAL/FINISH) are
// write-one-to-clear; everything else is a plain merge. Uses compare_exchange
Expand Down Expand Up @@ -355,6 +362,8 @@ bool PS2Memory::initialize(size_t ramSize)

// Initialize I/O registers
m_ioRegisters.clear();
m_ioRegisters[kDEnableR] = kDEnableResetValue;
m_ioRegisters[kDEnableW] = kDEnableResetValue;

// Initialize GS registers
memset(&gs_regs, 0, sizeof(gs_regs));
Expand Down Expand Up @@ -1216,6 +1225,15 @@ bool PS2Memory::writeIORegister(uint32_t address, uint32_t value)
return true;
}

if (address == kDEnableW)
{
// Writes to D_ENABLEW must read back at D_ENABLER: guests suspend the DMAC
// with `sw D_ENABLEW` and spin on `lw D_ENABLER` until CPND is set (#257).
m_ioRegisters[kDEnableW] = value;
m_ioRegisters[kDEnableR] = value;
return true;
}

m_ioRegisters[address] = value;

if (address >= 0x10003C00u && address < 0x10003E00u)
Expand Down
67 changes: 67 additions & 0 deletions ps2xTest/src/ps2_memory_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,73 @@ void register_ps2_memory_tests()
}
});

tc.Run("DMAC D_ENABLER reads 0x1201 after reset with CPND clear", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");

constexpr uint32_t kDEnableR = 0x1000F520u;
constexpr uint32_t kCpnd = 1u << 16; // DMAC suspended

// The hardware reports 0x1201 at boot (PCSX2 hwReset, ps2tek); bit 16 is
// clear, so the DMAC starts enabled.
t.Equals(mem.read32(kDEnableR), 0x1201u, "D_ENABLER should read 0x1201 after reset");
t.IsTrue((mem.read32(kDEnableR) & kCpnd) == 0u, "CPND should be clear after reset");
});

tc.Run("DMAC D_ENABLEW writes are visible at D_ENABLER", [](TestCase &t)
{
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");

constexpr uint32_t kDEnableR = 0x1000F520u;
constexpr uint32_t kDEnableW = 0x1000F590u;
constexpr uint32_t kCpnd = 1u << 16;

mem.write32(kDEnableW, kCpnd);
t.Equals(mem.read32(kDEnableR), kCpnd, "sw D_ENABLEW should be readable at D_ENABLER");

mem.write32(kDEnableW, 0u);
t.Equals(mem.read32(kDEnableR), 0u, "clearing D_ENABLEW should clear D_ENABLER");

// Narrow accesses merge into the same 32-bit register: PCSX2 polls the
// suspend state with a byte read of D_ENABLER+2.
mem.write16(kDEnableW + 2u, 0x1u);
t.Equals(mem.read32(kDEnableR), kCpnd, "sh to the upper half of D_ENABLEW should set CPND at D_ENABLER");
t.Equals(mem.read8(kDEnableR + 2u), static_cast<uint8_t>(1u), "lb of D_ENABLER byte 2 should expose CPND");
});

tc.Run("DMAC suspend and resume polls through D_ENABLEW and D_ENABLER terminate", [](TestCase &t)
{
// Ridge Racer V brackets its DMAC interrupt handler and frame kick with
// while ((D_ENABLER & 0x10000) == 0) D_ENABLEW = 0x10000;
// while ((D_ENABLER & 0x10000) != 0) D_ENABLEW = 0;
// which spins forever unless the two ports are one register.
PS2Memory mem;
t.IsTrue(mem.initialize(), "PS2Memory initialize should succeed");

constexpr uint32_t kDEnableR = 0x1000F520u;
constexpr uint32_t kDEnableW = 0x1000F590u;
constexpr uint32_t kCpnd = 1u << 16;
constexpr uint32_t kMaxPolls = 8u;

uint32_t suspendPolls = 0u;
while ((mem.read32(kDEnableR) & kCpnd) == 0u && suspendPolls < kMaxPolls)
{
mem.write32(kDEnableW, kCpnd);
++suspendPolls;
}
t.Equals(suspendPolls, 1u, "the suspend request should be visible on the first poll");

uint32_t resumePolls = 0u;
while ((mem.read32(kDEnableR) & kCpnd) != 0u && resumePolls < kMaxPolls)
{
mem.write32(kDEnableW, 0u);
++resumePolls;
}
t.Equals(resumePolls, 1u, "the resume request should be visible on the first poll");
});

tc.Run("DMAC SPR_FROM copies scratchpad to RDRAM and completes channel 8", [](TestCase &t)
{
PS2Memory mem;
Expand Down