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
1 change: 1 addition & 0 deletions ps2xRuntime/include/runtime/ee_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ class EeScheduler
int setIrqHandlerEnabled(bool dmac, int id, bool enabled);
int setIrqCauseEnabled(bool dmac, uint32_t cause, bool enabled);
void dispatchIrq(bool dmac, uint32_t cause);
void dispatchIrqNow(bool dmac, uint32_t cause);
void setVSyncFlag(uint32_t flagAddress, uint32_t tickAddress);
[[nodiscard]] uint64_t currentVSyncTick() const noexcept;
uint32_t setGsVSyncCallback(uint32_t callback, uint32_t gp, uint32_t sp);
Expand Down
51 changes: 51 additions & 0 deletions ps2xRuntime/src/lib/Kernel/EeScheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,57 @@ void EeScheduler::dispatchIrq(bool dmac, uint32_t cause)
}
}

void EeScheduler::dispatchIrqNow(bool dmac, uint32_t cause)
{
assertExecutor();
const uint32_t mask = dmac ? m_enabledDmacMask : m_enabledIntcMask;
if (cause < 32u && (mask & (1u << cause)) == 0u)
{
return;
}
const auto &handlers = dmac ? m_dmacHandlers : m_intcHandlers;
std::vector<EeIrqHandler> matching;
for (const auto &[id, handler] : handlers)
{
(void)id;
if (handler.enabled && handler.cause == cause && handler.handler != 0u &&
m_runtime.hasFunction(handler.handler))
{
matching.push_back(handler);
}
}
std::sort(matching.begin(), matching.end(), [](const EeIrqHandler &left, const EeIrqHandler &right)
{ return left.order < right.order; });
std::vector<GuestInvocation> invocations;
invocations.reserve(matching.size());
for (const EeIrqHandler &handler : matching)
{
GuestInvocation invocation{};
invocation.kind = GuestInvocationKind::Interrupt;
invocation.context.pc = handler.handler;
SET_GPR_U32(&invocation.context, 4, cause);
SET_GPR_U32(&invocation.context, 5, handler.argument);
SET_GPR_U32(&invocation.context, 28, handler.gp);
SET_GPR_U32(&invocation.context, 29, handler.sp);
SET_GPR_U32(&invocation.context, 31, 0u);
invocations.push_back(std::move(invocation));
}
if (invocations.empty())
{
return;
}

if (m_running.load(std::memory_order_acquire) && currentThread() && !m_insideInterrupt)
{
invokeCurrentSequence(std::move(invocations));
}

for (GuestInvocation &invocation : invocations)
{
queueInvocation(std::move(invocation));
}
}

void EeScheduler::setVSyncFlag(uint32_t flagAddress, uint32_t tickAddress)
{
assertExecutor();
Expand Down
88 changes: 85 additions & 3 deletions ps2xRuntime/src/lib/Kernel/Stubs/SIF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "SIF.h"
#include "../Syscalls/RPC.h"
#include "../../ps2_iop_transport.h"
#include "runtime/ee_scheduler.h"
#include "runtime/ps2_address.h"

#include <algorithm>
Expand Down Expand Up @@ -65,6 +66,7 @@ namespace ps2_stubs
std::array<uint8_t, kIopHeapLimit - kIopHeapBase> g_sifHeapStorage{};
uint32_t g_sifCmdBuffer = 0u;
uint32_t g_sifSysCmdBuffer = 0u;
uint32_t g_sifEeReceiveBuffer = 0u;
bool g_sifCmdInitialized = false;
uint32_t g_sifGetRegLogCount = 0u;
uint32_t g_sifSetRegLogCount = 0u;
Expand All @@ -82,6 +84,7 @@ namespace ps2_stubs
g_sifCmdHandlers.clear();
g_sifCmdBuffer = 0u;
g_sifSysCmdBuffer = 0u;
g_sifEeReceiveBuffer = 0u;
g_sifCmdInitialized = false;
g_sifGetRegLogCount = 0u;
g_sifSetRegLogCount = 0u;
Expand Down Expand Up @@ -815,6 +818,7 @@ namespace ps2_stubs
std::array<Ps2SifDmaTransfer, 32u> pending{};
uint32_t pendingCount = 0u;
bool ok = true;
bool generatedSifReply = false;
for (uint32_t i = 0; i < count; ++i)
{
const uint32_t entryAddr = dmatAddr + (i * static_cast<uint32_t>(sizeof(Ps2SifDmaTransfer)));
Expand Down Expand Up @@ -862,7 +866,10 @@ namespace ps2_stubs
static_cast<uint32_t>(xfer.size),
});
}
if (!copyGuestByteRange(rdram, xfer.dest, xfer.src, static_cast<uint32_t>(xfer.size)))
// Destination zero is the IOP sifcmd receive endpoint, not EE
// address zero. The transport and raw-command HLE consume it.
if (xfer.dest != 0u &&
!copyGuestByteRange(rdram, xfer.dest, xfer.src, static_cast<uint32_t>(xfer.size)))
{
ok = false;
break;
Expand All @@ -880,6 +887,75 @@ namespace ps2_stubs
}
}

// Emulate the IOP-side sifcmd/sifrpc boot handshake carried in raw
// SIF DMA packets.
if (ok && runtime)
{
auto readGuestDword = [rdram](uint32_t addr)
{
uint8_t bytes[sizeof(uint32_t)]{};
for (uint32_t byte = 0u; byte < sizeof(bytes); ++byte)
{
bytes[byte] = *getConstMemPtr(rdram, addr + byte);
}
uint32_t value = 0u;
std::memcpy(&value, bytes, sizeof(value));
return value;
};
for (uint32_t i = 0; i < pendingCount; ++i)
{
const Ps2SifDmaTransfer &xfer = pending[i];
if (xfer.dest != 0u || static_cast<uint32_t>(xfer.size) < 0x10u ||
isSifIopHeapRange(xfer.src, static_cast<uint32_t>(xfer.size)))
{
continue;
}
if (readGuestDword(xfer.src + 0x08u) != 0x80000002u) // SIF_CMD_INIT_CMD
{
continue;
}
const uint32_t opt = readGuestDword(xfer.src + 0x0Cu);
if (opt == 0u && static_cast<uint32_t>(xfer.size) >= 0x14u)
{
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
g_sifEeReceiveBuffer = readGuestDword(xfer.src + 0x10u);
continue;
}
if (opt != 1u)
{
continue;
}

uint32_t pktbuf = 0u;
{
std::lock_guard<std::mutex> lock(g_sifCmdStateMutex);
pktbuf = g_sifEeReceiveBuffer;
}
if (pktbuf == 0u || isSifIopHeapRange(pktbuf, 24u) ||
!canCopyAddressRange(rdram, pktbuf, 24u))
{
continue;
}
const uint8_t reply[24] = {
0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x80,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00,
};
for (uint32_t byte = 0u; byte < sizeof(reply); ++byte)
{
*getMemPtr(rdram, pktbuf + byte) = reply[byte];
}
generatedSifReply = true;
PS2_IF_AGRESSIVE_LOGS({
std::cerr << "[sifcmd] INIT_CMD boot end: SET_SREG reply to 0x"
<< std::hex << pktbuf << std::dec << std::endl;
});
}
}

if (!ok)
{
static uint32_t warnCount = 0;
Expand All @@ -896,9 +972,15 @@ namespace ps2_stubs
return;
}

ps2_syscalls::dispatchDmacHandlersForCause(rdram, runtime, 5u);

setReturnS32(ctx, static_cast<int32_t>(allocateSifDmaTransferId()));
if (generatedSifReply)
{
runtime->eeScheduler().dispatchIrqNow(true, 5u);
}
else
{
runtime->eeScheduler().dispatchIrq(true, 5u);
}
}

void sceSifSetIopAddr(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
Expand Down
99 changes: 97 additions & 2 deletions ps2xTest/src/ps2_sif_dma_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ namespace
uint32_t g_dmacHandlerValue = 0u;
uint32_t g_dmacHandlerLastCause = 0u;
uint32_t g_dmacHandlerLastArg = 0u;
uint32_t g_dmacDispatchSequence = 0u;
uint32_t g_dmacHandlerObservedSequence = 0u;
uint32_t g_dmacHandlerReplyAddr = 0u;
int32_t g_sifDmaResult = 0;

constexpr uint32_t kSchedulerSifDmaEntryPc = 0x00101000u;
Expand All @@ -128,9 +131,21 @@ namespace
(void)runtime;
g_dmacHandlerLastCause = ::getRegU32(ctx, 4);
g_dmacHandlerLastArg = ::getRegU32(ctx, 5);
g_dmacHandlerObservedSequence = g_dmacDispatchSequence;
if (g_dmacHandlerWriteAddr != 0u)
{
writeGuestU32(rdram, g_dmacHandlerWriteAddr, g_dmacHandlerValue);
if (g_dmacHandlerReplyAddr == 0u)
{
writeGuestU32(rdram, g_dmacHandlerWriteAddr, g_dmacHandlerValue);
}
else if (readGuestU32(rdram, g_dmacHandlerReplyAddr + 0u) == 24u &&
readGuestU32(rdram, g_dmacHandlerReplyAddr + 8u) == 0x80000001u &&
readGuestU32(rdram, g_dmacHandlerReplyAddr + 16u) == 0u)
{
writeGuestU32(rdram,
g_dmacHandlerWriteAddr,
readGuestU32(rdram, g_dmacHandlerReplyAddr + 20u));
}
}
ctx->pc = 0u;
}
Expand All @@ -147,7 +162,9 @@ namespace
setRegU32(*ctx, 4, kSchedulerSifDmaDescAddr);
setRegU32(*ctx, 5, 1u);
ctx->pc = kSchedulerSifDmaResumePc;
g_dmacDispatchSequence = 1u;
ps2_stubs::sceSifSetDma(rdram, ctx, runtime);
g_dmacDispatchSequence = 2u;
}

void schedulerSifDmaResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
Expand Down Expand Up @@ -199,6 +216,79 @@ void register_ps2_sif_dma_tests()
t.IsTrue(getRegS32(env.ctx, 2) < 0, "sceSifDmaStat should be negative when transfer is complete");
});

tc.Run("raw SIF INIT_CMD replies through the EE receive buffer", [](TestCase &t)
{
TestEnv env;

constexpr uint32_t kDescAddr = 0x00020700u;
constexpr uint32_t kPacketAddr = 0x00020800u;
constexpr uint32_t kReceiveAddr = 0x00020900u;
constexpr uint32_t kInitCmd = 0x80000002u;

const Ps2SifDmaTransfer bootEndDesc{kPacketAddr, 0u, 16, 4};
std::memcpy(env.rdram.data() + kDescAddr, &bootEndDesc, sizeof(bootEndDesc));
const uint32_t bootEndPacket[4] = {16u, 0u, kInitCmd, 1u};
std::memcpy(env.rdram.data() + kPacketAddr, bootEndPacket, sizeof(bootEndPacket));
setRegU32(env.ctx, 4, kDescAddr);
setRegU32(env.ctx, 5, 1u);
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);
t.Equals(readGuestU32(env.rdram.data(), 0u), 0u,
"INIT_CMD without a receive buffer should not overwrite EE address zero");

const Ps2SifDmaTransfer initDesc{kPacketAddr, 0u, 20, 4};
std::memcpy(env.rdram.data() + kDescAddr, &initDesc, sizeof(initDesc));
const uint32_t initPacket[5] = {20u, 0u, kInitCmd, 0u, kReceiveAddr};
std::memcpy(env.rdram.data() + kPacketAddr, initPacket, sizeof(initPacket));
setRegU32(env.ctx, 4, kDescAddr);
setRegU32(env.ctx, 5, 1u);
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);

std::memcpy(env.rdram.data() + kDescAddr, &bootEndDesc, sizeof(bootEndDesc));
std::memcpy(env.rdram.data() + kPacketAddr, bootEndPacket, sizeof(bootEndPacket));
setRegU32(env.ctx, 4, kDescAddr);
setRegU32(env.ctx, 5, 1u);
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);

t.Equals(readGuestU32(env.rdram.data(), 0u), 0u,
"raw SIF commands should not overwrite EE address zero");
t.Equals(readGuestU32(env.rdram.data(), kReceiveAddr + 0u), 24u,
"SET_SREG reply should carry a 24-byte packet size");
t.Equals(readGuestU32(env.rdram.data(), kReceiveAddr + 8u), 0x80000001u,
"reply should use SIF_CMD_SET_SREG");
t.Equals(readGuestU32(env.rdram.data(), kReceiveAddr + 16u), 0u,
"reply should select SREG 0");
t.Equals(readGuestU32(env.rdram.data(), kReceiveAddr + 20u), 1u,
"reply should mark RPC initialization complete");

constexpr uint32_t kSregAddr = 0x00020A00u;
g_dmacHandlerWriteAddr = kSregAddr;
g_dmacHandlerValue = 0u;
g_dmacHandlerLastCause = 0u;
g_dmacHandlerLastArg = 0u;
g_dmacDispatchSequence = 0u;
g_dmacHandlerObservedSequence = 0u;
g_dmacHandlerReplyAddr = kReceiveAddr;
g_sifDmaResult = 0;
env.runtime.registerFunction(kSchedulerSifDmaEntryPc, schedulerSifDmaEntry);
env.runtime.registerFunction(kSchedulerSifDmaResumePc, schedulerSifDmaResume);
env.runtime.registerFunction(kSchedulerSifDmaHandlerPc, testDmacHandler);

std::memset(env.rdram.data() + kReceiveAddr, 0, 24u);
std::memcpy(env.rdram.data() + kSchedulerSifDmaDescAddr, &bootEndDesc, sizeof(bootEndDesc));
std::memcpy(env.rdram.data() + kPacketAddr, bootEndPacket, sizeof(bootEndPacket));

R5900Context mainContext{};
mainContext.pc = kSchedulerSifDmaEntryPc;
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
env.runtime.eeScheduler().run();

t.IsTrue(g_sifDmaResult > 0, "raw INIT_CMD should still report DMA success");
t.Equals(readGuestU32(env.rdram.data(), kSregAddr), 1u,
"DMAC handler should consume the SET_SREG reply");
t.Equals(g_dmacHandlerObservedSequence, 1u,
"SET_SREG reply should preempt before sceSifSetDma returns");
});

tc.Run("IOP heap DMA uses private backing instead of aliasing EE RDRAM", [](TestCase &t)
{
TestEnv env;
Expand Down Expand Up @@ -306,7 +396,7 @@ void register_ps2_sif_dma_tests()
t.Equals(getRegS32(env.ctx, 2), 0, "isceSifSetDChain should mirror sceSifSetDChain");
});

tc.Run("sceSifSetDma dispatches enabled DMAC handlers for cause 5", [](TestCase &t)
tc.Run("ordinary sceSifSetDma queues enabled DMAC handlers for cause 5", [](TestCase &t)
{
TestEnv env;

Expand All @@ -318,6 +408,9 @@ void register_ps2_sif_dma_tests()
g_dmacHandlerValue = 0xCAFEBABEu;
g_dmacHandlerLastCause = 0u;
g_dmacHandlerLastArg = 0u;
g_dmacDispatchSequence = 0u;
g_dmacHandlerObservedSequence = 0u;
g_dmacHandlerReplyAddr = 0u;
g_sifDmaResult = 0;
env.runtime.registerFunction(kSchedulerSifDmaEntryPc, schedulerSifDmaEntry);
env.runtime.registerFunction(kSchedulerSifDmaResumePc, schedulerSifDmaResume);
Expand Down Expand Up @@ -348,6 +441,8 @@ void register_ps2_sif_dma_tests()
t.Equals(g_dmacHandlerLastCause, 5u, "DMAC handler should observe cause 5");
t.Equals(g_dmacHandlerLastArg, kSchedulerSifDmaHandlerArg,
"DMAC handler should receive registered argument");
t.Equals(g_dmacHandlerObservedSequence, 2u,
"ordinary DMA handler should remain queued until sceSifSetDma returns");
});

tc.Run("sceSifSetDma acknowledges DTX work-buffer transfers by advancing the EE footer ticket", [](TestCase &t)
Expand Down