mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-01 02:17:14 -04:00
refactor: from guest threads to EE scheduler (#184)
* refactor: from guest threads to EE scheduler * feat: bad wip mpeg fix for code veronica * feat: cheap copy from host feat: small perf o vsync tick * feat: added EE clock Hz fix: fix MPEG out of sync with new EE refactor * fix: fix lotr tests
This commit is contained in:
@@ -94,6 +94,9 @@ namespace ps2x::iop
|
||||
bool signalCompletion = false;
|
||||
CallbackPolicy callbackPolicy = CallbackPolicy::RuntimeDefault;
|
||||
ServerDispatchPolicy serverDispatchPolicy = ServerDispatchPolicy::RuntimeDefault;
|
||||
uint32_t guestFunction = 0;
|
||||
uint32_t guestArguments[4]{};
|
||||
uint32_t guestDefaultResultAddress = 0;
|
||||
};
|
||||
|
||||
enum class SifTransferKind : uint32_t
|
||||
|
||||
@@ -133,48 +133,26 @@ namespace ps2x::iop::detail
|
||||
const bool hasUrpcHandler = isUrpc && command < 64u && urpcFunction != 0u;
|
||||
if (hasUrpcHandler && request.serverFunction != 0u)
|
||||
{
|
||||
uint32_t serverResult = 0u;
|
||||
if (m_host.invokeGuestFunction(request.callToken,
|
||||
request.serverFunction,
|
||||
request.function,
|
||||
request.serverBuffer,
|
||||
request.send.size,
|
||||
0u,
|
||||
&serverResult))
|
||||
{
|
||||
result.handled = true;
|
||||
result.resultAddress = serverResult;
|
||||
if (result.resultAddress == 0u && request.serverBuffer != 0u)
|
||||
{
|
||||
result.resultAddress = request.serverBuffer;
|
||||
}
|
||||
if (result.resultAddress == 0u && request.receive.address != 0u)
|
||||
{
|
||||
result.resultAddress = request.receive.address;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result.guestFunction = request.serverFunction;
|
||||
result.guestArguments[0] = request.function;
|
||||
result.guestArguments[1] = request.serverBuffer;
|
||||
result.guestArguments[2] = request.send.size;
|
||||
result.guestDefaultResultAddress = request.serverBuffer != 0u
|
||||
? request.serverBuffer
|
||||
: request.receive.address;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hasUrpcHandler &&
|
||||
request.send.address != 0u &&
|
||||
request.send.size > 0u)
|
||||
{
|
||||
uint32_t dispatcherResult = 0u;
|
||||
if (m_host.invokeGuestFunction(request.callToken,
|
||||
m_bindings.dispatcherFunctionAddress,
|
||||
request.function,
|
||||
request.send.address,
|
||||
request.send.size,
|
||||
0u,
|
||||
&dispatcherResult))
|
||||
{
|
||||
result.handled = true;
|
||||
result.resultAddress = dispatcherResult != 0u
|
||||
? dispatcherResult
|
||||
: request.send.address;
|
||||
return result;
|
||||
}
|
||||
result.guestFunction = m_bindings.dispatcherFunctionAddress;
|
||||
result.guestArguments[0] = request.function;
|
||||
result.guestArguments[1] = request.send.address;
|
||||
result.guestArguments[2] = request.send.size;
|
||||
result.guestDefaultResultAddress = request.send.address;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (request.function == 2u &&
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace ps2recomp
|
||||
m_ss << fmt::format("{}ctx->pc = 0x{:X}u;\n", indent, target);
|
||||
if (target <= sourcePc && !isCallLikeEdge())
|
||||
{
|
||||
m_ss << fmt::format("{}if (runtime->shouldPreemptGuestExecution()) {{\n", indent);
|
||||
m_ss << fmt::format("{}if (runtime->eeCheckpointDue()) {{\n", indent);
|
||||
m_ss << fmt::format("{} return;\n", indent);
|
||||
m_ss << fmt::format("{}}}\n", indent);
|
||||
}
|
||||
@@ -296,10 +296,16 @@ namespace ps2recomp
|
||||
const std::string_view handlerName = isSyscall ? resolvedSyscallName : resolvedStubName;
|
||||
|
||||
m_ss << indent << "{\n";
|
||||
m_ss << indent << " const uint32_t __entryPc = ctx->pc;\n";
|
||||
if (kind == StaticBranchKind::Call)
|
||||
{
|
||||
m_ss << fmt::format("{} ctx->pc = 0x{:X}u;\n", indent, fallthroughPc());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ss << indent << " ctx->pc = getRegU32(ctx, 31);\n";
|
||||
}
|
||||
m_ss << indent << " " << (isSyscall ? "ps2_syscalls::" : "ps2_stubs::")
|
||||
<< handlerName << "(rdram, ctx, runtime);\n";
|
||||
m_ss << indent << " if (ctx->pc == __entryPc) { ctx->pc = getRegU32(ctx, 31); }\n";
|
||||
m_ss << indent << "}\n";
|
||||
|
||||
if (kind == StaticBranchKind::Jump)
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
// Fallthrough with no terminating branch: advance ctx->pc past the function so dispatchLoop doesn't re-call it forever.
|
||||
// Fallthrough with no terminating branch: publish the next PC so the EE dispatcher does not re-enter this function.
|
||||
if (!instructions.empty() && !lastInstructionWasControlFlow)
|
||||
{
|
||||
ss << " ctx->pc = 0x" << std::hex << function.end << "u;\n"
|
||||
|
||||
@@ -1153,7 +1153,7 @@ namespace ps2recomp
|
||||
stub << "#ifdef _DEBUG\n";
|
||||
stub << " PS_LOG_ENTRY(\"" << generatedName << "\");\n";
|
||||
stub << "#endif\n";
|
||||
stub << " const uint32_t __entryPc = ctx->pc;\n"
|
||||
stub << " ctx->pc = getRegU32(ctx, 31);\n"
|
||||
<< " ";
|
||||
|
||||
if (function.isSkipped)
|
||||
@@ -1190,12 +1190,7 @@ namespace ps2recomp
|
||||
}
|
||||
}
|
||||
|
||||
stub << "\n"
|
||||
<< " if (ctx->pc == __entryPc)\n"
|
||||
<< " {\n"
|
||||
<< " ctx->pc = getRegU32(ctx, 31);\n"
|
||||
<< " }\n"
|
||||
<< "}";
|
||||
stub << "\n}";
|
||||
m_generatedStubs[function.start] = stub.str();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ namespace ps2recomp
|
||||
case SPECIAL_JALR:
|
||||
return fmt::format("// JALR ${}, ${} - Handled by branch logic", inst.rd, inst.rs);
|
||||
case SPECIAL_SYSCALL:
|
||||
return fmt::format("runtime->handleSyscall(rdram, ctx, 0x{:X}u);", (inst.raw >> 6) & 0xFFFFFu);
|
||||
return fmt::format("ctx->pc = 0x{:X}u;\nruntime->handleSyscall(rdram, ctx, 0x{:X}u);",
|
||||
inst.address + 4u,
|
||||
(inst.raw >> 6) & 0xFFFFFu);
|
||||
case SPECIAL_BREAK:
|
||||
return fmt::format("runtime->handleBreak(rdram, ctx);");
|
||||
case SPECIAL_SYNC:
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ps2_log.h"
|
||||
#include "runtime/ps2_address.h"
|
||||
@@ -40,6 +39,8 @@ namespace ps2x::iop
|
||||
|
||||
class PS2IopHostAdapter;
|
||||
class PS2IopTransport;
|
||||
class EeScheduler;
|
||||
struct EeEvent;
|
||||
|
||||
enum PS2Exception
|
||||
{
|
||||
@@ -321,46 +322,6 @@ public:
|
||||
SkipCallDebug = 3,
|
||||
};
|
||||
|
||||
class GuestExecutionScope
|
||||
{
|
||||
public:
|
||||
explicit GuestExecutionScope(PS2Runtime *runtime) noexcept;
|
||||
~GuestExecutionScope();
|
||||
|
||||
GuestExecutionScope(const GuestExecutionScope &) = delete;
|
||||
GuestExecutionScope &operator=(const GuestExecutionScope &) = delete;
|
||||
|
||||
private:
|
||||
PS2Runtime *m_runtime = nullptr;
|
||||
};
|
||||
|
||||
class GuestExecutionReleaseScope
|
||||
{
|
||||
public:
|
||||
explicit GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept;
|
||||
~GuestExecutionReleaseScope();
|
||||
|
||||
GuestExecutionReleaseScope(const GuestExecutionReleaseScope &) = delete;
|
||||
GuestExecutionReleaseScope &operator=(const GuestExecutionReleaseScope &) = delete;
|
||||
|
||||
private:
|
||||
PS2Runtime *m_runtime = nullptr;
|
||||
uint32_t m_depth = 0u;
|
||||
};
|
||||
|
||||
class DeferredGuestYieldScope
|
||||
{
|
||||
public:
|
||||
explicit DeferredGuestYieldScope(bool &pendingOut) noexcept;
|
||||
~DeferredGuestYieldScope();
|
||||
|
||||
DeferredGuestYieldScope(const DeferredGuestYieldScope &) = delete;
|
||||
DeferredGuestYieldScope &operator=(const DeferredGuestYieldScope &) = delete;
|
||||
|
||||
private:
|
||||
bool &m_pendingOut;
|
||||
};
|
||||
|
||||
bool replaceFunction(uint32_t address, RecompiledFunction func);
|
||||
// TODO remove this later need to update all tests
|
||||
bool registerFunction(uint32_t address, RecompiledFunction func);
|
||||
@@ -413,31 +374,28 @@ public:
|
||||
uint32_t guestHeapLimit() const;
|
||||
uint32_t reserveAsyncCallbackStack(uint32_t size, uint32_t alignment = 16u);
|
||||
|
||||
void dispatchLoop(uint8_t *rdram, R5900Context *ctx);
|
||||
|
||||
void drainCompletedDmacHandlers(uint8_t *rdram);
|
||||
|
||||
bool shouldPreemptGuestExecution();
|
||||
void yieldGuestExecutionAfterWake();
|
||||
void waitForGuestExecutionHandoff();
|
||||
void waitForGuestExecutionHandoff(uint64_t baselineEpoch);
|
||||
uint64_t guestExecutionHandoffEpochSnapshot() const
|
||||
{
|
||||
return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void requestStop();
|
||||
bool isStopRequested() const;
|
||||
|
||||
uint32_t guestExecutionWaiterCountForTesting() const
|
||||
{
|
||||
return m_guestExecutionWaiters.load(std::memory_order_acquire);
|
||||
}
|
||||
EeScheduler &eeScheduler();
|
||||
const EeScheduler &eeScheduler() const;
|
||||
void postEeEvent(EeEvent event);
|
||||
bool eeCheckpointDue(uint32_t cycles = 32u) noexcept;
|
||||
[[noreturn]] void eeWaitVSyncTicks(uint32_t ticks, uint32_t resumePc);
|
||||
|
||||
uint64_t guestExecutionHandoffTimeouts() const
|
||||
struct EeExitHandlerRegistration
|
||||
{
|
||||
return m_guestExecutionHandoffTimeouts.load(std::memory_order_relaxed);
|
||||
}
|
||||
uint32_t function = 0;
|
||||
uint32_t argument = 0;
|
||||
};
|
||||
void addEeExitHandler(int threadId, uint32_t function, uint32_t argument);
|
||||
std::vector<EeExitHandlerRegistration> takeEeExitHandlers(int threadId);
|
||||
void removeEeExitHandlers(int threadId);
|
||||
bool findEeSyscallOverride(uint32_t syscallNumber, uint32_t &handler) const;
|
||||
void setEeSyscallOverride(uint8_t *rdram, uint32_t syscallNumber, uint32_t handler);
|
||||
void initializeEeKernelState(uint8_t *rdram);
|
||||
|
||||
uint8_t Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
|
||||
uint16_t Load16(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr);
|
||||
@@ -502,12 +460,6 @@ private:
|
||||
uint32_t allocateGuestBlockLocked(uint32_t size, uint32_t alignment);
|
||||
void freeGuestBlockLocked(uint32_t guestAddr);
|
||||
void coalesceGuestHeapLocked();
|
||||
void enterGuestExecution();
|
||||
void leaveGuestExecution();
|
||||
uint32_t releaseGuestExecution();
|
||||
void reacquireGuestExecution(uint32_t depth);
|
||||
void markGuestExecutionAcquired();
|
||||
|
||||
void HandleIntegerOverflow(R5900Context *ctx);
|
||||
|
||||
[[nodiscard]] ps2x::iop::RpcAbi selectIopRpcAbi(const ps2x::iop::RpcAbiRequest &request) const;
|
||||
@@ -515,9 +467,8 @@ private:
|
||||
void notifyIopSifTransfer(uint8_t *rdram, const ps2x::iop::SifTransfer &transfer);
|
||||
void resetIop();
|
||||
|
||||
friend class GuestExecutionScope;
|
||||
friend class GuestExecutionReleaseScope;
|
||||
friend class PS2IopTransport;
|
||||
friend class EeScheduler;
|
||||
|
||||
private:
|
||||
PS2Memory m_memory;
|
||||
@@ -530,12 +481,11 @@ private:
|
||||
VU1Interpreter m_vu0{VU1Interpreter::Unit::VU0};
|
||||
VU1Interpreter m_vu1{VU1Interpreter::Unit::VU1};
|
||||
R5900Context m_cpuContext;
|
||||
mutable std::recursive_mutex m_guestExecutionMutex;
|
||||
mutable std::atomic<uint32_t> m_guestExecutionWaiters{0u};
|
||||
mutable std::mutex m_guestExecutionHandoffMutex;
|
||||
mutable std::condition_variable m_guestExecutionHandoffCv;
|
||||
std::atomic<uint64_t> m_guestExecutionHandoffEpoch{0u};
|
||||
std::atomic<uint64_t> m_guestExecutionHandoffTimeouts{0u};
|
||||
std::unique_ptr<EeScheduler> m_eeScheduler;
|
||||
mutable std::mutex m_eeKernelStateMutex;
|
||||
std::unordered_map<int, std::vector<EeExitHandlerRegistration>> m_eeExitHandlers;
|
||||
std::unordered_map<uint32_t, uint32_t> m_eeSyscallOverrides;
|
||||
std::unordered_set<uint32_t> m_eeSyscallMirrorAddresses;
|
||||
mutable std::mutex m_guestHeapMutex;
|
||||
mutable std::mutex m_asyncCallbackStackMutex;
|
||||
std::vector<GuestHeapBlock> m_guestHeapBlocks;
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
std::string translatePs2Path(const char *ps2Path);
|
||||
|
||||
extern std::atomic<int> g_activeThreads;
|
||||
|
||||
inline std::mutex g_sys_fd_mutex;
|
||||
|
||||
namespace ps2_syscalls
|
||||
@@ -29,15 +27,10 @@ namespace ps2_syscalls
|
||||
|
||||
bool dispatchNumericSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause);
|
||||
void initializeGuestKernelState(uint8_t *rdram);
|
||||
void initializeGuestKernelState(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
|
||||
void notifyRuntimeStop();
|
||||
void joinAllGuestHostThreads();
|
||||
void detachAllGuestHostThreads();
|
||||
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick();
|
||||
uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick(PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, int fixedResult = -1);
|
||||
}
|
||||
|
||||
#endif // PS2_SYSCALLS_H
|
||||
|
||||
@@ -0,0 +1,447 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2_runtime.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
// This exception is the EE equivalent of a longjmp to the dispatcher. It is
|
||||
// not an error and must only be caught at EeScheduler::run().
|
||||
struct EeDispatcherTransfer final
|
||||
{
|
||||
};
|
||||
|
||||
enum class EeThreadStatus : uint8_t
|
||||
{
|
||||
Running,
|
||||
Ready,
|
||||
Waiting,
|
||||
WaitingSuspended,
|
||||
Suspended,
|
||||
Dormant,
|
||||
};
|
||||
|
||||
enum class EeWaitReason : uint8_t
|
||||
{
|
||||
None,
|
||||
Sleep,
|
||||
Semaphore,
|
||||
EventFlag,
|
||||
VSync,
|
||||
External,
|
||||
Mpeg,
|
||||
};
|
||||
|
||||
struct EeSemaphoreWait
|
||||
{
|
||||
int id = 0;
|
||||
};
|
||||
|
||||
struct EeEventFlagWait
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t bits = 0;
|
||||
uint32_t mode = 0;
|
||||
uint32_t resultAddress = 0;
|
||||
};
|
||||
|
||||
struct EeVSyncWait
|
||||
{
|
||||
uint64_t afterTick = 0;
|
||||
int fixedResult = -1;
|
||||
};
|
||||
|
||||
struct EeExternalWait
|
||||
{
|
||||
uint32_t type = 0;
|
||||
uint64_t token = 0;
|
||||
};
|
||||
|
||||
using EeWaitPayload = std::variant<std::monostate,
|
||||
EeSemaphoreWait,
|
||||
EeEventFlagWait,
|
||||
EeVSyncWait,
|
||||
EeExternalWait>;
|
||||
|
||||
struct EeWaitState
|
||||
{
|
||||
EeWaitReason reason = EeWaitReason::None;
|
||||
EeWaitPayload payload{};
|
||||
std::function<void(R5900Context &)> completion;
|
||||
};
|
||||
|
||||
enum class GuestInvocationKind : uint8_t
|
||||
{
|
||||
Interrupt,
|
||||
Alarm,
|
||||
GsCallback,
|
||||
RpcCallback,
|
||||
SyscallOverride,
|
||||
ExitHandler,
|
||||
HleCall,
|
||||
};
|
||||
|
||||
struct GuestInvocation
|
||||
{
|
||||
GuestInvocationKind kind = GuestInvocationKind::Interrupt;
|
||||
uint64_t sequence = 0;
|
||||
uint64_t tag = 0;
|
||||
R5900Context context{};
|
||||
std::function<void(const R5900Context &, R5900Context &)> onComplete;
|
||||
};
|
||||
|
||||
struct GuestThread
|
||||
{
|
||||
int id = 0;
|
||||
R5900Context context{};
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t arg = 0;
|
||||
int initialPriority = 0;
|
||||
int currentPriority = 0;
|
||||
EeThreadStatus status = EeThreadStatus::Dormant;
|
||||
int suspendCount = 0;
|
||||
uint32_t wakeupCount = 0;
|
||||
bool ownsStack = false;
|
||||
uint32_t tlsBase = 0;
|
||||
EeWaitState wait{};
|
||||
std::function<void(R5900Context &)> resumeCompletion;
|
||||
std::vector<GuestInvocation> invocations;
|
||||
|
||||
[[nodiscard]] R5900Context &activeContext()
|
||||
{
|
||||
return invocations.empty() ? context : invocations.back().context;
|
||||
}
|
||||
|
||||
[[nodiscard]] const R5900Context &activeContext() const
|
||||
{
|
||||
return invocations.empty() ? context : invocations.back().context;
|
||||
}
|
||||
};
|
||||
|
||||
struct EeSemaphore
|
||||
{
|
||||
int id = 0;
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
int initCount = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
std::deque<int> waiters;
|
||||
};
|
||||
|
||||
struct EeEventFlag
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t initBits = 0;
|
||||
uint32_t bits = 0;
|
||||
std::deque<int> waiters;
|
||||
};
|
||||
|
||||
struct EeAlarm
|
||||
{
|
||||
int id = 0;
|
||||
uint16_t ticks = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t argument = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
};
|
||||
|
||||
struct EeIrqHandler
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t cause = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t argument = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
bool enabled = true;
|
||||
int order = 0;
|
||||
};
|
||||
|
||||
struct EeThreadSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t pc = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
int initialPriority = 0;
|
||||
int currentPriority = 0;
|
||||
EeThreadStatus status = EeThreadStatus::Dormant;
|
||||
EeWaitReason waitReason = EeWaitReason::None;
|
||||
int waitId = 0;
|
||||
int suspendCount = 0;
|
||||
uint32_t wakeupCount = 0;
|
||||
};
|
||||
|
||||
struct EeSemaphoreSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
uint32_t waiters = 0;
|
||||
};
|
||||
|
||||
struct EeEventFlagSnapshot
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t bits = 0;
|
||||
uint32_t initBits = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t waiters = 0;
|
||||
};
|
||||
|
||||
struct EeKernelSnapshot
|
||||
{
|
||||
uint64_t sequence = 0;
|
||||
uint64_t eeCycle = 0;
|
||||
uint64_t sliceEndCycle = 0;
|
||||
uint64_t nextEventCycle = 0;
|
||||
int runningThreadId = 0;
|
||||
std::vector<EeThreadSnapshot> threads;
|
||||
std::vector<EeSemaphoreSnapshot> semaphores;
|
||||
std::vector<EeEventFlagSnapshot> eventFlags;
|
||||
};
|
||||
|
||||
enum class EeEventType : uint8_t
|
||||
{
|
||||
Stop,
|
||||
VBlankStart,
|
||||
VBlankEnd,
|
||||
Dmac,
|
||||
ExternalWake,
|
||||
Alarm,
|
||||
};
|
||||
|
||||
struct EeEvent
|
||||
{
|
||||
EeEventType type = EeEventType::ExternalWake;
|
||||
uint32_t id = 0;
|
||||
uint64_t value = 0;
|
||||
};
|
||||
|
||||
struct EeThreadCreateParams
|
||||
{
|
||||
uint32_t attr = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
int priority = 0;
|
||||
uint32_t option = 0;
|
||||
};
|
||||
|
||||
class EeScheduler
|
||||
{
|
||||
public:
|
||||
static constexpr int kMainThreadId = 1;
|
||||
static constexpr int kFirstThreadId = 2;
|
||||
static constexpr int kLastThreadId = 255;
|
||||
static constexpr int kPriorityCount = 128;
|
||||
static constexpr uint64_t kEeClockHz = 294912000ull;
|
||||
static constexpr uint32_t kGeneratedCheckpointCycles = 32u;
|
||||
static constexpr uint32_t kGuestDispatchCycles = 8u;
|
||||
static constexpr uint64_t kDefaultTimeSliceCycles = 65536ull;
|
||||
|
||||
explicit EeScheduler(PS2Runtime &runtime);
|
||||
~EeScheduler();
|
||||
|
||||
EeScheduler(const EeScheduler &) = delete;
|
||||
EeScheduler &operator=(const EeScheduler &) = delete;
|
||||
|
||||
void reset(uint8_t *rdram, const R5900Context &mainContext);
|
||||
void run();
|
||||
void requestStop();
|
||||
void postEvent(EeEvent event);
|
||||
[[nodiscard]] bool checkpointDue(uint32_t cycles = kGeneratedCheckpointCycles) noexcept;
|
||||
void accountCycles(uint32_t cycles) noexcept;
|
||||
[[nodiscard]] bool isExecutingGuest() const noexcept;
|
||||
|
||||
// Kernel object API. All calls except postEvent/requestStop execute on the
|
||||
// EE executor and therefore need no host synchronization.
|
||||
int createThread(const EeThreadCreateParams ¶ms);
|
||||
int deleteThread(int id, uint32_t &ownedStack);
|
||||
int startThread(int id, uint32_t arg, const R5900Context &caller, bool interruptSafe);
|
||||
[[noreturn]] void exitCurrent(bool deleteThread);
|
||||
int terminateThread(int id, uint32_t &ownedStack, bool interruptSafe);
|
||||
int suspendThread(int id, bool interruptSafe);
|
||||
int resumeThread(int id, bool interruptSafe);
|
||||
void sleepCurrent();
|
||||
int wakeupThread(int id, bool interruptSafe);
|
||||
int cancelWakeup(int id);
|
||||
int changePriority(int id, int priority, bool interruptSafe, int &oldPriority);
|
||||
int rotateReadyQueue(int priority, bool interruptSafe);
|
||||
int releaseWait(int id, bool interruptSafe);
|
||||
void transferIfRequested(bool interruptSafe);
|
||||
|
||||
int createSemaphore(int initCount, int maxCount, uint32_t attr, uint32_t option);
|
||||
int deleteSemaphore(int id, bool interruptSafe);
|
||||
int signalSemaphore(int id, bool interruptSafe);
|
||||
int pollSemaphore(int id);
|
||||
void waitSemaphore(int id);
|
||||
|
||||
int createEventFlag(uint32_t initialBits, uint32_t attr, uint32_t option);
|
||||
int deleteEventFlag(int id, bool interruptSafe);
|
||||
int setEventFlag(int id, uint32_t bits, bool interruptSafe);
|
||||
int clearEventFlag(int id, uint32_t mask);
|
||||
int pollEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t &observedBits);
|
||||
void waitEventFlag(int id, uint32_t bits, uint32_t mode, uint32_t resultAddress);
|
||||
|
||||
int setAlarm(uint16_t ticks, uint32_t handler, uint32_t argument, uint32_t gp, uint32_t sp);
|
||||
int cancelAlarm(int id);
|
||||
void queueInvocation(GuestInvocation invocation);
|
||||
[[noreturn]] void invokeCurrent(GuestInvocation invocation);
|
||||
[[noreturn]] void invokeCurrentSequence(std::vector<GuestInvocation> invocations);
|
||||
[[nodiscard]] bool hasInvocation(GuestInvocationKind kind, uint64_t tag) const;
|
||||
[[nodiscard]] uint32_t invocationStackTop();
|
||||
|
||||
int addIrqHandler(bool dmac,
|
||||
uint32_t cause,
|
||||
uint32_t handler,
|
||||
bool append,
|
||||
uint32_t argument,
|
||||
uint32_t gp,
|
||||
uint32_t sp);
|
||||
int removeIrqHandler(bool dmac, uint32_t cause, int id);
|
||||
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 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);
|
||||
|
||||
[[noreturn]] void waitVSync(uint64_t afterTick, int fixedResult = -1, std::function<void(R5900Context &)> completion = {});
|
||||
void completeVSync(uint64_t tick);
|
||||
void completeExternalWait(uint32_t type, uint64_t token, int result);
|
||||
[[noreturn]] void waitExternal(EeWaitReason reason, uint32_t type, uint64_t token, std::function<void(R5900Context &)> completion = {});
|
||||
|
||||
[[nodiscard]] GuestThread *thread(int id);
|
||||
[[nodiscard]] const GuestThread *thread(int id) const;
|
||||
[[nodiscard]] EeSemaphore *semaphore(int id);
|
||||
[[nodiscard]] const EeSemaphore *semaphore(int id) const;
|
||||
[[nodiscard]] EeEventFlag *eventFlag(int id);
|
||||
[[nodiscard]] const EeEventFlag *eventFlag(int id) const;
|
||||
[[nodiscard]] GuestThread *currentThread();
|
||||
[[nodiscard]] const GuestThread *currentThread() const;
|
||||
[[nodiscard]] int currentThreadId() const noexcept;
|
||||
[[nodiscard]] R5900Context *currentContext();
|
||||
[[nodiscard]] uint8_t *rdram() const noexcept;
|
||||
|
||||
// Direct syscall tests use the same main-thread record without starting a
|
||||
// second executor. Production execution calls reset() before run().
|
||||
void bindMainContextForSyscall(R5900Context &ctx, uint8_t *rdram);
|
||||
|
||||
[[nodiscard]] EeKernelSnapshot snapshot() const;
|
||||
void publishSnapshot();
|
||||
|
||||
private:
|
||||
struct ScheduledEvent
|
||||
{
|
||||
uint64_t deadlineCycle = 0;
|
||||
std::chrono::steady_clock::time_point hostDeadline{};
|
||||
EeEvent event{};
|
||||
uint64_t sequence = 0;
|
||||
};
|
||||
|
||||
void assertExecutor() const;
|
||||
[[nodiscard]] int allocateThreadId();
|
||||
GuestThread &acquireInvocationThread();
|
||||
void enqueueReady(GuestThread &thread, bool front = false);
|
||||
void removeReady(GuestThread &thread);
|
||||
[[nodiscard]] GuestThread *selectReady();
|
||||
void makeRunning(GuestThread &thread);
|
||||
void makeDormant(GuestThread &thread);
|
||||
void removeFromWaitObject(GuestThread &thread);
|
||||
[[noreturn]] void blockCurrent(EeWaitState wait);
|
||||
void makeReady(GuestThread &thread, int result, bool interruptSafe);
|
||||
void requestPreemptionIfHigher(const GuestThread &readyThread, bool interruptSafe);
|
||||
void applyPendingPreemption();
|
||||
void processPendingEvents();
|
||||
void processDueDeadlines();
|
||||
void processEvent(const EeEvent &event);
|
||||
void finishEventWaiters(EeEventFlag &flag, bool interruptSafe);
|
||||
[[nodiscard]] static bool eventCondition(uint32_t current, uint32_t requested, uint32_t mode);
|
||||
static int waitObjectId(const EeWaitState &wait);
|
||||
void writeGuestU32(uint32_t address, uint32_t value);
|
||||
void waitForEvent();
|
||||
void scheduleEvent(uint64_t deadlineCycle, std::chrono::steady_clock::time_point hostDeadline, EeEvent event);
|
||||
void updateNextDeadline();
|
||||
[[nodiscard]] bool hasReadyAtOrAbovePriority(int priority) const;
|
||||
void renewTimeSlice();
|
||||
void copyMainContextToRuntime();
|
||||
|
||||
PS2Runtime &m_runtime;
|
||||
uint8_t *m_rdram = nullptr;
|
||||
std::array<std::deque<int>, kPriorityCount> m_readyQueues{};
|
||||
std::unordered_map<int, GuestThread> m_threads;
|
||||
std::unordered_map<int, EeSemaphore> m_semaphores;
|
||||
std::unordered_map<int, EeEventFlag> m_eventFlags;
|
||||
std::unordered_map<int, EeAlarm> m_alarms;
|
||||
std::unordered_map<int, EeIrqHandler> m_intcHandlers;
|
||||
std::unordered_map<int, EeIrqHandler> m_dmacHandlers;
|
||||
int m_nextThreadId = kFirstThreadId;
|
||||
int m_nextInvocationThreadId = -1;
|
||||
int m_nextSemaphoreId = 1;
|
||||
int m_nextEventFlagId = 1;
|
||||
int m_nextAlarmId = 1;
|
||||
int m_nextIntcHandlerId = 1;
|
||||
int m_nextDmacHandlerId = 1;
|
||||
int m_intcHeadOrder = 0;
|
||||
int m_intcTailOrder = 1000;
|
||||
int m_dmacHeadOrder = 0;
|
||||
int m_dmacTailOrder = 1000;
|
||||
uint32_t m_enabledIntcMask = 0xFFFFFFFFu;
|
||||
uint32_t m_enabledDmacMask = 0xFFFFFFFFu;
|
||||
int m_currentThreadId = 0;
|
||||
bool m_rescheduleRequested = false;
|
||||
bool m_timeSliceExpired = false;
|
||||
bool m_insideInterrupt = false;
|
||||
uint64_t m_eeCycle = 0;
|
||||
uint64_t m_sliceEndCycle = kDefaultTimeSliceCycles;
|
||||
std::thread::id m_executorThread{};
|
||||
std::atomic<bool> m_running{false};
|
||||
std::atomic<bool> m_guestExecuting{false};
|
||||
std::atomic<bool> m_stopRequested{false};
|
||||
std::atomic<bool> m_checkpointPending{false};
|
||||
uint32_t m_debugPublishCountdown = 0u;
|
||||
|
||||
mutable std::mutex m_eventMutex;
|
||||
std::condition_variable m_eventCv;
|
||||
std::deque<EeEvent> m_events;
|
||||
std::vector<ScheduledEvent> m_deadlines;
|
||||
std::deque<GuestInvocation> m_pendingInvocations;
|
||||
uint64_t m_eventSequence = 0;
|
||||
uint64_t m_invocationSequence = 0;
|
||||
uint64_t m_vsyncTick = 0;
|
||||
uint32_t m_vsyncFlagAddress = 0;
|
||||
uint32_t m_vsyncTickAddress = 0;
|
||||
uint32_t m_gsVSyncCallback = 0;
|
||||
uint32_t m_gsVSyncCallbackGp = 0;
|
||||
uint32_t m_gsVSyncCallbackSp = 0;
|
||||
std::unordered_map<uint64_t, uint32_t> m_invocationStackTops;
|
||||
std::atomic<uint64_t> m_nextDeadlineCycle{0};
|
||||
|
||||
mutable std::mutex m_snapshotMutex;
|
||||
EeKernelSnapshot m_snapshot;
|
||||
uint64_t m_snapshotSequence = 0;
|
||||
};
|
||||
@@ -364,7 +364,14 @@ public:
|
||||
|
||||
private:
|
||||
void snapshotVRAM();
|
||||
void writeRegisterUnlocked(uint8_t regAddr, uint64_t value);
|
||||
void writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi);
|
||||
void uploadImageNativeUnlocked(uint64_t bitbltbuf,
|
||||
uint64_t trxpos,
|
||||
uint64_t trxreg,
|
||||
uint64_t trxdir,
|
||||
const uint8_t *data,
|
||||
uint32_t sizeBytes);
|
||||
void vertexKick(bool drawing);
|
||||
void latchHostPresentationFrameUnlocked();
|
||||
|
||||
@@ -396,6 +403,7 @@ private:
|
||||
uint32_t m_vramSize = 0;
|
||||
struct GSRegisters *m_privRegs = nullptr;
|
||||
mutable std::recursive_mutex m_stateMutex;
|
||||
mutable std::mutex m_presentationMutex;
|
||||
|
||||
GSContext m_ctx[2];
|
||||
GSPrimReg m_prim{};
|
||||
|
||||
@@ -204,20 +204,14 @@ struct GSRegisters
|
||||
uint64_t extdata; // External data
|
||||
uint64_t extwrite; // External write
|
||||
uint64_t bgcolor; // Background color
|
||||
// Status. Concurrency contract: the vsync worker thread toggles the FIELD bit
|
||||
// (bit 13) once per tick; guest threads issue write-one-to-clear writes against
|
||||
// the SIGNAL/FINISH status bits (0..1) via the MMIO path; the GIF sets SIGNAL
|
||||
// and FINISH from yet another thread. All three interleave, so this register
|
||||
// must be updated with atomic RMWs only (no load-then-store pairs anywhere).
|
||||
std::atomic<uint64_t> csr;
|
||||
std::atomic<uint64_t> vsyncTick;
|
||||
uint64_t imr; // Interrupt mask
|
||||
uint64_t busdir; // Bus direction
|
||||
uint64_t siglblid; // Signal label ID
|
||||
};
|
||||
static_assert(sizeof(GSRegisters) == (19u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
|
||||
static_assert(sizeof(GSRegisters) == (20u * sizeof(uint64_t)), "GSRegisters layout changed unexpectedly");
|
||||
static_assert(alignof(GSRegisters) == alignof(uint64_t), "GSRegisters alignment must remain 64-bit");
|
||||
// CSR is written by the vsync worker while guest threads concurrently read/write it
|
||||
// (MMIO) and the GIF sets SIGNAL/FINISH; a lock-free atomic keeps that path wait-free.
|
||||
static_assert(std::atomic<uint64_t>::is_always_lock_free, "GS CSR atomic must be lock-free on all supported targets");
|
||||
|
||||
// PS2 VIF (VPU Interface) registers
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,173 @@
|
||||
#include "Common.h"
|
||||
#include "CD.h"
|
||||
#include "MPEG.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint32_t g_cdStReadTraceCount = 0u;
|
||||
}
|
||||
constexpr uint32_t kCdStreamBlocking = 1u;
|
||||
constexpr uint32_t kDvdSectorsPerSecondX1 = 675u;
|
||||
constexpr uint32_t kDvdSectorsPerSecondX4 = kDvdSectorsPerSecondX1 * 4u;
|
||||
constexpr uint64_t kNtScFieldsPerSecondNumerator = 60000u;
|
||||
constexpr uint64_t kNtScFieldsPerSecondDenominator = 1001u;
|
||||
|
||||
struct CdStreamTimingState
|
||||
{
|
||||
bool initialized = false;
|
||||
bool active = false;
|
||||
bool paused = false;
|
||||
uint32_t capacitySectors = 64u;
|
||||
uint32_t bankCount = 4u;
|
||||
uint32_t sectorsPerBank = 16u;
|
||||
uint32_t sectorsPerSecond = kDvdSectorsPerSecondX4;
|
||||
uint64_t producedSectors = 0u;
|
||||
uint64_t consumedSectors = 0u;
|
||||
uint64_t productionRemainder = 0u;
|
||||
uint64_t lastVSyncTick = 0u;
|
||||
};
|
||||
|
||||
uint32_t g_cdStReadTraceCount = 0u;
|
||||
CdStreamTimingState g_cdStreamTiming;
|
||||
|
||||
uint64_t currentCdStreamTick(PS2Runtime *runtime)
|
||||
{
|
||||
return runtime != nullptr ? runtime->eeScheduler().currentVSyncTick() : 0u;
|
||||
}
|
||||
|
||||
uint32_t dvdStreamSectorsPerSecond(uint8_t spindleControl)
|
||||
{
|
||||
switch (spindleControl)
|
||||
{
|
||||
case 2u: // SCECdSpinX1
|
||||
return kDvdSectorsPerSecondX1;
|
||||
case 3u: // SCECdSpinX2
|
||||
return kDvdSectorsPerSecondX1 * 2u;
|
||||
case 11u: // SCECdSpin1p6
|
||||
return 1080u;
|
||||
case 4u: // SCECdSpinX4
|
||||
case 0u: // SCECdSpinStm / max
|
||||
case 1u: // optimized
|
||||
case 20u: // max
|
||||
default:
|
||||
return kDvdSectorsPerSecondX4;
|
||||
}
|
||||
}
|
||||
|
||||
void resetCdStreamProduction(PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamTiming.producedSectors = 0u;
|
||||
g_cdStreamTiming.consumedSectors = 0u;
|
||||
g_cdStreamTiming.productionRemainder = 0u;
|
||||
g_cdStreamTiming.lastVSyncTick = currentCdStreamTick(runtime);
|
||||
}
|
||||
|
||||
uint64_t totalCdStreamSectors()
|
||||
{
|
||||
if (g_cdStreamingEndLbn == 0xFFFFFFFFu || g_cdStreamingEndLbn < g_cdStreamingLbn)
|
||||
{
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
return g_cdStreamTiming.consumedSectors + static_cast<uint64_t>(g_cdStreamingEndLbn - g_cdStreamingLbn);
|
||||
}
|
||||
|
||||
void updateCdStreamProduction(PS2Runtime *runtime)
|
||||
{
|
||||
if (!g_cdStreamTiming.active || g_cdStreamTiming.paused || runtime == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t tick = currentCdStreamTick(runtime);
|
||||
if (tick <= g_cdStreamTiming.lastVSyncTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t elapsedTicks = tick - g_cdStreamTiming.lastVSyncTick;
|
||||
g_cdStreamTiming.lastVSyncTick = tick;
|
||||
|
||||
// 59.94 Hz field clock: sectors = fields * sectors/s * 1001 / 60000.
|
||||
const uint64_t unitsPerTick = static_cast<uint64_t>(g_cdStreamTiming.sectorsPerSecond) * kNtScFieldsPerSecondDenominator;
|
||||
const uint64_t accumulated = g_cdStreamTiming.productionRemainder + elapsedTicks * unitsPerTick;
|
||||
const uint64_t elapsedProduction = accumulated / kNtScFieldsPerSecondNumerator;
|
||||
g_cdStreamTiming.productionRemainder = accumulated % kNtScFieldsPerSecondNumerator;
|
||||
if (elapsedProduction == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t buffered = g_cdStreamTiming.producedSectors - g_cdStreamTiming.consumedSectors;
|
||||
const uint64_t effectiveCapacity = std::max<uint32_t>(1u, g_cdStreamTiming.capacitySectors);
|
||||
const uint64_t space = buffered < effectiveCapacity ? effectiveCapacity - buffered : 0u;
|
||||
uint64_t newlyProduced = std::min(elapsedProduction, space);
|
||||
|
||||
const uint64_t streamTotal = totalCdStreamSectors();
|
||||
if (streamTotal != std::numeric_limits<uint64_t>::max())
|
||||
{
|
||||
const uint64_t remainingToProduce = streamTotal > g_cdStreamTiming.producedSectors
|
||||
? streamTotal - g_cdStreamTiming.producedSectors
|
||||
: 0u;
|
||||
newlyProduced = std::min(newlyProduced, remainingToProduce);
|
||||
}
|
||||
|
||||
g_cdStreamTiming.producedSectors += newlyProduced;
|
||||
if (elapsedProduction > newlyProduced)
|
||||
{
|
||||
g_cdStreamTiming.productionRemainder = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bufferedCdStreamSectors(PS2Runtime *runtime)
|
||||
{
|
||||
updateCdStreamProduction(runtime);
|
||||
const uint64_t buffered = g_cdStreamTiming.producedSectors - g_cdStreamTiming.consumedSectors;
|
||||
return static_cast<uint32_t>(std::min<uint64_t>(buffered, std::numeric_limits<uint32_t>::max()));
|
||||
}
|
||||
|
||||
uint32_t readableCdStreamSectors(PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t buffered = bufferedCdStreamSectors(runtime);
|
||||
if (buffered == 0u)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const uint64_t streamTotal = totalCdStreamSectors();
|
||||
if (streamTotal != std::numeric_limits<uint64_t>::max() && g_cdStreamTiming.producedSectors >= streamTotal)
|
||||
{
|
||||
return buffered;
|
||||
}
|
||||
|
||||
const uint32_t bank = std::max(1u, g_cdStreamTiming.sectorsPerBank);
|
||||
return (buffered / bank) * bank;
|
||||
}
|
||||
|
||||
uint64_t cdStreamWakeTickForSectors(PS2Runtime *runtime, uint32_t sectorsNeeded)
|
||||
{
|
||||
const uint64_t now = currentCdStreamTick(runtime);
|
||||
if (sectorsNeeded == 0u || g_cdStreamTiming.sectorsPerSecond == 0u)
|
||||
{
|
||||
return now;
|
||||
}
|
||||
|
||||
const uint64_t requiredUnits = static_cast<uint64_t>(sectorsNeeded) * kNtScFieldsPerSecondNumerator;
|
||||
const uint64_t remainingUnits = requiredUnits > g_cdStreamTiming.productionRemainder
|
||||
? requiredUnits - g_cdStreamTiming.productionRemainder
|
||||
: 0u;
|
||||
const uint64_t unitsPerTick = static_cast<uint64_t>(g_cdStreamTiming.sectorsPerSecond) * kNtScFieldsPerSecondDenominator;
|
||||
const uint64_t ticks = std::max<uint64_t>(1u, (remainingUnits + unitsPerTick - 1u) / unitsPerTick);
|
||||
return now + ticks;
|
||||
}
|
||||
|
||||
void restartCdStreamAt(uint32_t lbn, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamingLbn = lbn;
|
||||
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(lbn);
|
||||
resetCdStreamProduction(runtime);
|
||||
}
|
||||
}
|
||||
|
||||
CdDebugSnapshot getCdDebugSnapshot()
|
||||
{
|
||||
@@ -41,9 +200,7 @@ namespace ps2_stubs
|
||||
snapshot.files.push_back(std::move(row));
|
||||
}
|
||||
std::sort(snapshot.files.begin(), snapshot.files.end(), [](const CdDebugFileEntry &a, const CdDebugFileEntry &b)
|
||||
{
|
||||
return a.baseLbn < b.baseLbn;
|
||||
});
|
||||
{ return a.baseLbn < b.baseLbn; });
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -241,6 +398,7 @@ namespace ps2_stubs
|
||||
{
|
||||
g_cdInitialized = true;
|
||||
g_lastCdError = 0;
|
||||
g_cdStreamTiming = {};
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
@@ -492,8 +650,7 @@ namespace ps2_stubs
|
||||
|
||||
void sceCdSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamingLbn = getRegU32(ctx, 4);
|
||||
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
|
||||
restartCdStreamAt(getRegU32(ctx, 4), runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
@@ -509,6 +666,23 @@ namespace ps2_stubs
|
||||
|
||||
void sceCdStInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t bufferSectors = getRegU32(ctx, 4);
|
||||
const uint32_t bankCount = getRegU32(ctx, 5);
|
||||
const uint32_t bufferAddr = getRegU32(ctx, 6);
|
||||
|
||||
if (bufferSectors == 0u || bankCount == 0u || bufferAddr == 0u || bufferSectors / bankCount == 0u)
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
g_cdStreamTiming.initialized = true;
|
||||
g_cdStreamTiming.active = false;
|
||||
g_cdStreamTiming.paused = false;
|
||||
g_cdStreamTiming.capacitySectors = bufferSectors;
|
||||
g_cdStreamTiming.bankCount = bankCount;
|
||||
g_cdStreamTiming.sectorsPerBank = std::max(1u, bufferSectors / bankCount);
|
||||
resetCdStreamProduction(runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
@@ -519,90 +693,216 @@ namespace ps2_stubs
|
||||
|
||||
void sceCdStPause(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
updateCdStreamProduction(runtime);
|
||||
g_cdStreamTiming.paused = true;
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct CdStReadContinuation
|
||||
{
|
||||
uint32_t requestedSectors = 0u;
|
||||
uint32_t buffer = 0u;
|
||||
uint32_t errorAddress = 0u;
|
||||
uint32_t sectorsRead = 0u;
|
||||
};
|
||||
|
||||
void finishCdStRead(uint8_t *rdram, R5900Context *ctx, const CdStReadContinuation &state, int32_t error)
|
||||
{
|
||||
if (int32_t *errorOut = reinterpret_cast<int32_t *>(getMemPtr(rdram, state.errorAddress)); errorOut)
|
||||
{
|
||||
*errorOut = error;
|
||||
}
|
||||
setReturnS32(ctx, static_cast<int32_t>(state.sectorsRead));
|
||||
}
|
||||
|
||||
void continueCdStRead(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
PS2Runtime *runtime,
|
||||
CdStReadContinuation state)
|
||||
{
|
||||
if (!g_cdStreamTiming.active || state.requestedSectors == 0u)
|
||||
{
|
||||
finishCdStRead(rdram, ctx, state, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
uint32_t remaining = state.requestedSectors - state.sectorsRead;
|
||||
bool atEnd = false;
|
||||
if (g_cdStreamingEndLbn != 0xFFFFFFFFu)
|
||||
{
|
||||
if (g_cdStreamingLbn >= g_cdStreamingEndLbn)
|
||||
{
|
||||
remaining = 0u;
|
||||
atEnd = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t streamRemaining = g_cdStreamingEndLbn - g_cdStreamingLbn;
|
||||
if (remaining > streamRemaining)
|
||||
{
|
||||
remaining = streamRemaining;
|
||||
atEnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining == 0u)
|
||||
{
|
||||
if (atEnd || (g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn))
|
||||
{
|
||||
notifyMpegCdStreamEof(runtime);
|
||||
}
|
||||
finishCdStRead(rdram, ctx, state, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t available = readableCdStreamSectors(runtime);
|
||||
if (runtime == nullptr)
|
||||
{
|
||||
available = remaining;
|
||||
}
|
||||
|
||||
if (available == 0u)
|
||||
{
|
||||
if (!runtime)
|
||||
{
|
||||
finishCdStRead(rdram, ctx, state, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t bank = std::max(1u, g_cdStreamTiming.sectorsPerBank);
|
||||
const uint32_t wakeSectors = std::min(remaining, bank);
|
||||
const uint32_t buffered = bufferedCdStreamSectors(runtime);
|
||||
const uint32_t needed = wakeSectors > buffered ? wakeSectors - buffered : 1u;
|
||||
const uint64_t wakeTick = cdStreamWakeTickForSectors(runtime, needed);
|
||||
runtime->eeScheduler().waitVSync(
|
||||
wakeTick - 1u,
|
||||
-1,
|
||||
[rdram, runtime, state](R5900Context &resumeContext)
|
||||
{
|
||||
if (static_cast<int32_t>(getRegU32(&resumeContext, 2)) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
continueCdStRead(rdram, &resumeContext, runtime, state);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t sectors = std::min(remaining, available);
|
||||
const uint64_t destination64 = static_cast<uint64_t>(state.buffer) + static_cast<uint64_t>(state.sectorsRead) * kCdSectorSize;
|
||||
const uint32_t destination = static_cast<uint32_t>(destination64);
|
||||
const uint32_t offset = destination & PS2_RAM_MASK;
|
||||
const size_t maxBytes = PS2_RAM_SIZE - offset;
|
||||
sectors = std::min<uint32_t>(sectors, static_cast<uint32_t>(maxBytes / kCdSectorSize));
|
||||
|
||||
if (sectors == 0u)
|
||||
{
|
||||
g_lastCdError = -1;
|
||||
finishCdStRead(rdram, ctx, state, g_lastCdError);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t readLbn = g_cdStreamingLbn;
|
||||
const size_t readBytes = static_cast<size_t>(sectors) * kCdSectorSize;
|
||||
if (!readCdSectors(readLbn, sectors, rdram + offset, readBytes))
|
||||
{
|
||||
finishCdStRead(rdram, ctx, state, g_lastCdError);
|
||||
return;
|
||||
}
|
||||
|
||||
g_cdStreamingLbn += sectors;
|
||||
if (runtime == nullptr)
|
||||
{
|
||||
g_cdStreamTiming.producedSectors += sectors;
|
||||
}
|
||||
g_cdStreamTiming.consumedSectors += sectors;
|
||||
state.sectorsRead += sectors;
|
||||
|
||||
const bool hitStreamEnd = g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn;
|
||||
notifyMpegCdStreamDataProduced(static_cast<uint32_t>(readBytes), hitStreamEnd);
|
||||
|
||||
if (g_cdStReadTraceCount < 32u)
|
||||
{
|
||||
std::cerr << "[sceCdStRead] requested=" << state.requestedSectors
|
||||
<< " accumulated=" << state.sectorsRead
|
||||
<< " chunk=" << sectors
|
||||
<< " buffered=" << readableCdStreamSectors(runtime)
|
||||
<< " lbn=0x" << std::hex << readLbn
|
||||
<< " end=0x" << g_cdStreamingEndLbn
|
||||
<< std::dec << std::endl;
|
||||
++g_cdStReadTraceCount;
|
||||
}
|
||||
|
||||
if (state.sectorsRead >= state.requestedSectors || hitStreamEnd)
|
||||
{
|
||||
finishCdStRead(rdram, ctx, state, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// STMBLK is implemented by repeatedly consuming the stream ring.
|
||||
// If a complete bank is still available, keep draining it before
|
||||
// yielding. Otherwise the next iteration parks on the producer.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sceCdStRead(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t requestedSectors = getRegU32(ctx, 4);
|
||||
uint32_t sectors = requestedSectors;
|
||||
uint32_t buf = getRegU32(ctx, 5);
|
||||
uint32_t errAddr = getRegU32(ctx, 7);
|
||||
const uint32_t requestedSectors = getRegU32(ctx, 4);
|
||||
const uint32_t buffer = getRegU32(ctx, 5);
|
||||
const uint32_t mode = getRegU32(ctx, 6);
|
||||
const uint32_t errorAddress = getRegU32(ctx, 7);
|
||||
|
||||
uint32_t offset = buf & PS2_RAM_MASK;
|
||||
size_t requestedBytes = static_cast<size_t>(requestedSectors) * kCdSectorSize;
|
||||
const size_t maxBytes = PS2_RAM_SIZE - offset;
|
||||
if (requestedBytes > maxBytes)
|
||||
CdStReadContinuation state{};
|
||||
state.requestedSectors = requestedSectors;
|
||||
state.buffer = buffer;
|
||||
state.errorAddress = errorAddress;
|
||||
|
||||
if (int32_t *errorOut = reinterpret_cast<int32_t *>(getMemPtr(rdram, errorAddress)); errorOut)
|
||||
{
|
||||
requestedBytes = maxBytes;
|
||||
*errorOut = 0;
|
||||
}
|
||||
|
||||
bool hitStreamEnd = false;
|
||||
if (!g_cdStreamTiming.active || requestedSectors == 0u)
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == kCdStreamBlocking)
|
||||
{
|
||||
continueCdStRead(rdram, ctx, runtime, state);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t remaining = requestedSectors;
|
||||
if (g_cdStreamingEndLbn != 0xFFFFFFFFu)
|
||||
{
|
||||
if (g_cdStreamingLbn >= g_cdStreamingEndLbn)
|
||||
{
|
||||
sectors = 0u;
|
||||
hitStreamEnd = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32_t remaining = g_cdStreamingEndLbn - g_cdStreamingLbn;
|
||||
if (sectors > remaining)
|
||||
{
|
||||
sectors = remaining;
|
||||
hitStreamEnd = true;
|
||||
}
|
||||
}
|
||||
remaining = g_cdStreamingLbn < g_cdStreamingEndLbn
|
||||
? std::min(remaining, g_cdStreamingEndLbn - g_cdStreamingLbn)
|
||||
: 0u;
|
||||
}
|
||||
|
||||
size_t bytes = static_cast<size_t>(sectors) * kCdSectorSize;
|
||||
if (bytes > maxBytes)
|
||||
const uint32_t available = runtime != nullptr
|
||||
? readableCdStreamSectors(runtime)
|
||||
: remaining;
|
||||
const uint32_t sectors = std::min(remaining, available);
|
||||
if (sectors == 0u)
|
||||
{
|
||||
bytes = maxBytes;
|
||||
}
|
||||
|
||||
const uint32_t readLbn = g_cdStreamingLbn;
|
||||
const bool ok = (sectors > 0u) && readCdSectors(readLbn, sectors, rdram + offset, bytes);
|
||||
if (ok)
|
||||
{
|
||||
g_cdStreamingLbn += sectors;
|
||||
if (requestedBytes > bytes)
|
||||
if (g_cdStreamingEndLbn != 0xFFFFFFFFu && g_cdStreamingLbn >= g_cdStreamingEndLbn)
|
||||
{
|
||||
std::memset(rdram + offset + bytes, 0, requestedBytes - bytes);
|
||||
notifyMpegCdStreamEof(runtime);
|
||||
}
|
||||
if (hitStreamEnd || g_cdStreamingLbn == g_cdStreamingEndLbn)
|
||||
{
|
||||
notifyMpegCdStreamEof();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestedBytes > 0u)
|
||||
{
|
||||
std::memset(rdram + offset, 0, requestedBytes);
|
||||
}
|
||||
notifyMpegCdStreamEof();
|
||||
setReturnS32(ctx, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (int32_t *err = reinterpret_cast<int32_t *>(getMemPtr(rdram, errAddr)); err)
|
||||
{
|
||||
*err = ok ? 0 : g_lastCdError;
|
||||
}
|
||||
|
||||
if (g_cdStReadTraceCount < 32u)
|
||||
{
|
||||
std::cerr << "[sceCdStRead] sectors=" << requestedSectors
|
||||
<< " read=" << sectors
|
||||
<< " buf=0x" << std::hex << buf
|
||||
<< " lbn=0x" << readLbn
|
||||
<< " end=0x" << g_cdStreamingEndLbn
|
||||
<< std::dec << " ok=" << ok
|
||||
<< " bytes=" << bytes << std::endl;
|
||||
++g_cdStReadTraceCount;
|
||||
}
|
||||
|
||||
setReturnS32(ctx, ok ? static_cast<int32_t>(sectors) : 0);
|
||||
state.requestedSectors = sectors;
|
||||
continueCdStRead(rdram, ctx, runtime, state);
|
||||
}
|
||||
|
||||
void sceCdStream(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -612,44 +912,63 @@ namespace ps2_stubs
|
||||
|
||||
void sceCdStResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamTiming.paused = false;
|
||||
g_cdStreamTiming.lastVSyncTick = currentCdStreamTick(runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
void sceCdStSeek(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamingLbn = getRegU32(ctx, 4);
|
||||
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
|
||||
restartCdStreamAt(getRegU32(ctx, 4), runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
void sceCdStSeekF(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamingLbn = getRegU32(ctx, 4);
|
||||
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
|
||||
restartCdStreamAt(getRegU32(ctx, 4), runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
void sceCdStStart(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_cdStreamingLbn = getRegU32(ctx, 4);
|
||||
g_cdStreamingEndLbn = cdStreamingEndLbnForStart(g_cdStreamingLbn);
|
||||
const uint32_t lbn = getRegU32(ctx, 4);
|
||||
const uint32_t modeAddr = getRegU32(ctx, 5);
|
||||
uint8_t spindleControl = 0u;
|
||||
if (const uint8_t *mode = getConstMemPtr(rdram, modeAddr); mode)
|
||||
{
|
||||
spindleControl = mode[1u];
|
||||
}
|
||||
|
||||
restartCdStreamAt(lbn, runtime);
|
||||
g_cdStreamTiming.active = true;
|
||||
g_cdStreamTiming.paused = false;
|
||||
g_cdStreamTiming.sectorsPerSecond = dvdStreamSectorsPerSecond(spindleControl);
|
||||
g_cdStReadTraceCount = 0u;
|
||||
|
||||
notifyMpegCdStreamStart();
|
||||
notifyMpegCdStreamStart(runtime);
|
||||
|
||||
std::cerr << "[sceCdStStart] lbn=0x" << std::hex << g_cdStreamingLbn
|
||||
<< " endLbn=0x" << g_cdStreamingEndLbn << std::dec << std::endl;
|
||||
<< " endLbn=0x" << g_cdStreamingEndLbn << std::dec
|
||||
<< " rate=" << g_cdStreamTiming.sectorsPerSecond << " sectors/s"
|
||||
<< " buffer=" << g_cdStreamTiming.capacitySectors << " sectors"
|
||||
<< " bank=" << g_cdStreamTiming.sectorsPerBank << " sectors"
|
||||
<< std::endl;
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
void sceCdStStat(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
setReturnS32(ctx, 0);
|
||||
const uint32_t buffered = bufferedCdStreamSectors(runtime);
|
||||
const uint32_t bankSize = std::max(1u, g_cdStreamTiming.sectorsPerBank);
|
||||
setReturnS32(ctx, static_cast<int32_t>((buffered / bankSize) * bankSize));
|
||||
}
|
||||
|
||||
void sceCdStStop(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
notifyMpegCdStreamEof();
|
||||
updateCdStreamProduction(runtime);
|
||||
g_cdStreamTiming.active = false;
|
||||
g_cdStreamTiming.paused = false;
|
||||
notifyMpegCdStreamEof(runtime);
|
||||
setReturnS32(ctx, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,12 @@
|
||||
#include "ps2_log.h"
|
||||
#include "runtime/ps2_gs_common.h"
|
||||
#include "runtime/ps2_gs_psmct16.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::mutex g_gs_sync_v_mutex;
|
||||
uint64_t g_gs_sync_v_base_tick = 0u;
|
||||
std::mutex g_gs_sync_v_callback_mutex;
|
||||
uint32_t g_gs_sync_v_callback_func = 0u;
|
||||
uint32_t g_gs_sync_v_callback_gp = 0u;
|
||||
uint32_t g_gs_sync_v_callback_sp = 0u;
|
||||
uint32_t g_gs_sync_v_callback_stack_base = 0u;
|
||||
uint32_t g_gs_sync_v_callback_stack_top = 0u;
|
||||
uint32_t g_gs_sync_v_callback_bad_pc_logs = 0u;
|
||||
uint64_t makeClearPrim(bool useContext2)
|
||||
{
|
||||
return static_cast<uint64_t>(GS_PRIM_SPRITE) |
|
||||
@@ -598,175 +590,6 @@ namespace ps2_stubs
|
||||
setReturnU32(ctx, terminatePacketBuilderState(rdram, ctx, runtime));
|
||||
}
|
||||
|
||||
static void resetGsSyncVState()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_mutex);
|
||||
g_gs_sync_v_base_tick = ps2_syscalls::GetCurrentVSyncTick();
|
||||
}
|
||||
|
||||
static int32_t getGsSyncVFieldForTick(uint64_t tick)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_mutex);
|
||||
if (tick <= g_gs_sync_v_base_tick)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int32_t>((tick - g_gs_sync_v_base_tick - 1u) & 1u);
|
||||
}
|
||||
|
||||
void resetGsSyncVCallbackState()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
g_gs_sync_v_callback_func = 0u;
|
||||
g_gs_sync_v_callback_gp = 0u;
|
||||
g_gs_sync_v_callback_sp = 0u;
|
||||
g_gs_sync_v_callback_stack_base = 0u;
|
||||
g_gs_sync_v_callback_stack_top = 0u;
|
||||
g_gs_sync_v_callback_bad_pc_logs = 0u;
|
||||
}
|
||||
resetGsSyncVState();
|
||||
}
|
||||
|
||||
void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick)
|
||||
{
|
||||
if (!rdram || !runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t callback = 0u;
|
||||
uint32_t gp = 0u;
|
||||
uint32_t callbackStackTop = 0u;
|
||||
const uint64_t callbackTick = (tick != 0u) ? tick : ps2_syscalls::GetCurrentVSyncTick();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
callback = g_gs_sync_v_callback_func;
|
||||
gp = g_gs_sync_v_callback_gp;
|
||||
callbackStackTop = g_gs_sync_v_callback_stack_top;
|
||||
if (callback == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!runtime->hasFunction(callback))
|
||||
{
|
||||
static uint32_t s_missingCallbackLogCount = 0u;
|
||||
if (s_missingCallbackLogCount < 32u)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[sceGsSyncVCallback:missing] cb=0x" << std::hex << callback
|
||||
<< " gp=0x" << gp
|
||||
<< " tick=0x" << callbackTick
|
||||
<< std::dec << std::endl;
|
||||
});
|
||||
++s_missingCallbackLogCount;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (callbackStackTop == 0u)
|
||||
{
|
||||
constexpr uint32_t kCallbackStackSize = 0x4000u;
|
||||
const uint32_t stackTop = runtime->reserveAsyncCallbackStack(kCallbackStackSize, 16u);
|
||||
if (stackTop != 0u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
if (g_gs_sync_v_callback_stack_top == 0u)
|
||||
{
|
||||
g_gs_sync_v_callback_stack_base = stackTop - (kCallbackStackSize - 0x10u);
|
||||
g_gs_sync_v_callback_stack_top = stackTop;
|
||||
}
|
||||
callbackStackTop = g_gs_sync_v_callback_stack_top;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
R5900Context callbackCtx{};
|
||||
SET_GPR_U32(&callbackCtx, 28, gp);
|
||||
SET_GPR_U32(&callbackCtx, 29, (callbackStackTop != 0u) ? callbackStackTop : (PS2_RAM_SIZE - 0x10u));
|
||||
SET_GPR_U32(&callbackCtx, 31, 0u);
|
||||
SET_GPR_U32(&callbackCtx, 4, static_cast<uint32_t>(callbackTick));
|
||||
callbackCtx.pc = callback;
|
||||
|
||||
static uint32_t s_dispatchLogCount = 0u;
|
||||
const bool shouldLogDispatch = (s_dispatchLogCount < 64u);
|
||||
if (shouldLogDispatch)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
RUNTIME_LOG("[sceGsSyncVCallback:dispatch] cb=0x" << std::hex << callback
|
||||
<< " gp=0x" << gp
|
||||
<< " sp=0x" << getRegU32(&callbackCtx, 29)
|
||||
<< " tick=0x" << callbackTick
|
||||
<< std::dec << std::endl);
|
||||
});
|
||||
}
|
||||
|
||||
uint32_t steps = 0u;
|
||||
bool reschedulePending = false;
|
||||
uint64_t handoffBaseline = 0u;
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
PS2Runtime::DeferredGuestYieldScope deferYield(reschedulePending);
|
||||
|
||||
while (callbackCtx.pc != 0u && !runtime->isStopRequested() && steps < 1024u)
|
||||
{
|
||||
if (!runtime->hasFunction(callbackCtx.pc))
|
||||
{
|
||||
if (g_gs_sync_v_callback_bad_pc_logs < 16u)
|
||||
{
|
||||
std::cerr << "[sceGsSyncVCallback:bad-pc] pc=0x" << std::hex << callbackCtx.pc
|
||||
<< " ra=0x" << getRegU32(&callbackCtx, 31)
|
||||
<< " sp=0x" << getRegU32(&callbackCtx, 29)
|
||||
<< " gp=0x" << getRegU32(&callbackCtx, 28)
|
||||
<< std::dec << std::endl;
|
||||
++g_gs_sync_v_callback_bad_pc_logs;
|
||||
}
|
||||
callbackCtx.pc = 0u;
|
||||
break;
|
||||
}
|
||||
|
||||
auto step = runtime->lookupFunction(callbackCtx.pc);
|
||||
if (!step)
|
||||
{
|
||||
break;
|
||||
}
|
||||
++steps;
|
||||
step(rdram, &callbackCtx, runtime);
|
||||
}
|
||||
handoffBaseline = runtime->guestExecutionHandoffEpochSnapshot();
|
||||
}
|
||||
if (reschedulePending && !runtime->isStopRequested())
|
||||
{
|
||||
runtime->waitForGuestExecutionHandoff(handoffBaseline);
|
||||
}
|
||||
|
||||
if (shouldLogDispatch)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
RUNTIME_LOG("[sceGsSyncVCallback:return] cb=0x" << std::hex << callback
|
||||
<< " finalPc=0x" << callbackCtx.pc
|
||||
<< " ra=0x" << getRegU32(&callbackCtx, 31)
|
||||
<< " steps=0x" << steps
|
||||
<< std::dec << std::endl);
|
||||
});
|
||||
++s_dispatchLogCount;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
static uint32_t warnCount = 0u;
|
||||
if (warnCount < 8u)
|
||||
{
|
||||
std::cerr << "[sceGsSyncVCallback] callback exception: " << e.what() << std::endl;
|
||||
++warnCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sceGsExecLoadImage(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t imgAddr = getRegU32(ctx, 4);
|
||||
@@ -979,8 +802,6 @@ namespace ps2_stubs
|
||||
g_gparam.omode = static_cast<uint8_t>(omode & 0xFF);
|
||||
g_gparam.ffmode = static_cast<uint8_t>(ffmode & 0x1);
|
||||
writeGsGParamToScratch(runtime);
|
||||
resetGsSyncVState();
|
||||
|
||||
uint64_t pmode = makePmode(1, 0, 0, 0, 0, 0x80);
|
||||
uint64_t smode2 = (interlace & 0x1) | ((ffmode & 0x1) << 1);
|
||||
uint64_t dispfb = makeDispFb(0, 10, 0, 0, 0);
|
||||
@@ -1459,14 +1280,10 @@ namespace ps2_stubs
|
||||
|
||||
void sceGsSyncV(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint64_t tick = ps2_syscalls::WaitForNextVSyncTick(rdram, runtime);
|
||||
if (g_gparam.interlace != 0u)
|
||||
{
|
||||
setReturnS32(ctx, getGsSyncVFieldForTick(tick));
|
||||
return;
|
||||
}
|
||||
|
||||
setReturnS32(ctx, 1);
|
||||
ps2_syscalls::WaitVSyncTick(rdram,
|
||||
ctx,
|
||||
runtime,
|
||||
g_gparam.interlace != 0u ? -1 : 1);
|
||||
}
|
||||
|
||||
void sceGsSyncVCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -1477,17 +1294,9 @@ namespace ps2_stubs
|
||||
const uint32_t gp = getRegU32(ctx, 28);
|
||||
const uint32_t sp = getRegU32(ctx, 29);
|
||||
|
||||
uint32_t oldCallback = 0u;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_gs_sync_v_callback_mutex);
|
||||
oldCallback = g_gs_sync_v_callback_func;
|
||||
g_gs_sync_v_callback_func = newCallback;
|
||||
if (newCallback != 0u)
|
||||
{
|
||||
g_gs_sync_v_callback_gp = gp;
|
||||
g_gs_sync_v_callback_sp = sp;
|
||||
}
|
||||
}
|
||||
EeScheduler &ee = runtime->eeScheduler();
|
||||
ee.bindMainContextForSyscall(*ctx, rdram);
|
||||
const uint32_t oldCallback = ee.setGsVSyncCallback(newCallback, gp, sp);
|
||||
|
||||
static uint32_t s_syncVCallbackLogCount = 0u;
|
||||
if (s_syncVCallbackLogCount < 128u)
|
||||
@@ -1504,11 +1313,6 @@ namespace ps2_stubs
|
||||
++s_syncVCallbackLogCount;
|
||||
}
|
||||
|
||||
if (newCallback != 0u)
|
||||
{
|
||||
ps2_syscalls::EnsureVSyncWorkerRunning(rdram, runtime);
|
||||
}
|
||||
|
||||
setReturnU32(ctx, oldCallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
void resetGsSyncVCallbackState();
|
||||
void dispatchGsSyncVCallback(uint8_t *rdram, PS2Runtime *runtime, uint64_t tick);
|
||||
void sceGifPkAddGsAD(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceGifPkAddGsData(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceGifPkCloseGifTag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -1,47 +1,19 @@
|
||||
#include "Common.h"
|
||||
#include "IPU.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
namespace ps2_stubs
|
||||
namespace
|
||||
{
|
||||
void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
constexpr uint32_t REG_IPU_CTRL = 0x10002010u;
|
||||
constexpr uint32_t REG_IPU_CMD = 0x10002000u;
|
||||
constexpr uint32_t REG_IPU_IN_FIFO = 0x10007010u;
|
||||
constexpr uint32_t IQVAL_BASE = 0x1721e0u;
|
||||
constexpr uint32_t VQVAL_BASE = 0x172230u;
|
||||
constexpr uint32_t SETD4_CHCR_ENTRY = 0x126428u;
|
||||
|
||||
void completeIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
static constexpr uint32_t REG_IPU_CTRL = 0x10002010u;
|
||||
static constexpr uint32_t REG_IPU_CMD = 0x10002000u;
|
||||
static constexpr uint32_t REG_IPU_IN_FIFO = 0x10007010u;
|
||||
static constexpr uint32_t IQVAL_BASE = 0x1721e0u;
|
||||
static constexpr uint32_t VQVAL_BASE = 0x172230u;
|
||||
static constexpr uint32_t SETD4_CHCR_ENTRY = 0x126428u;
|
||||
|
||||
if (!runtime)
|
||||
return;
|
||||
|
||||
if (!runtime->memory().getRDRAM())
|
||||
{
|
||||
if (!runtime->memory().initialize())
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!runtime->syncCoreSubsystems())
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
PS2Memory &mem = runtime->memory();
|
||||
|
||||
if (runtime->hasFunction(SETD4_CHCR_ENTRY))
|
||||
{
|
||||
auto setD4 = runtime->lookupFunction(SETD4_CHCR_ENTRY);
|
||||
ctx->r[4] = _mm_set_epi64x(0, 1);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
setD4(rdram, ctx, runtime);
|
||||
}
|
||||
}
|
||||
|
||||
mem.write32(REG_IPU_CTRL, 0x40000000u);
|
||||
mem.write32(REG_IPU_CMD, 0u);
|
||||
|
||||
@@ -70,12 +42,54 @@ namespace ps2_stubs
|
||||
|
||||
mem.write32(REG_IPU_CMD, 0x60000000u);
|
||||
mem.write32(REG_IPU_CMD, 0x90000000u);
|
||||
|
||||
mem.write32(REG_IPU_CTRL, 0x40000000u);
|
||||
mem.write32(REG_IPU_CMD, 0u);
|
||||
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace ps2_stubs
|
||||
{
|
||||
void sceIpuInit(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
if (!runtime)
|
||||
return;
|
||||
|
||||
if (!runtime->memory().getRDRAM())
|
||||
{
|
||||
if (!runtime->memory().initialize())
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!runtime->syncCoreSubsystems())
|
||||
{
|
||||
setReturnS32(ctx, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (runtime->hasFunction(SETD4_CHCR_ENTRY))
|
||||
{
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.bindMainContextForSyscall(*ctx, rdram);
|
||||
GuestInvocation invocation{};
|
||||
invocation.kind = GuestInvocationKind::HleCall;
|
||||
invocation.context = *ctx;
|
||||
invocation.context.pc = SETD4_CHCR_ENTRY;
|
||||
SET_GPR_U32(&invocation.context, 4, 1u);
|
||||
SET_GPR_U32(&invocation.context, 29, 0u);
|
||||
SET_GPR_U32(&invocation.context, 31, 0u);
|
||||
invocation.onComplete = [rdram, runtime](const R5900Context &, R5900Context &parent)
|
||||
{
|
||||
completeIpuInit(rdram, &parent, runtime);
|
||||
};
|
||||
scheduler.invokeCurrent(std::move(invocation));
|
||||
}
|
||||
|
||||
completeIpuInit(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void sceIpuRestartDMA(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,10 @@
|
||||
namespace ps2_stubs
|
||||
{
|
||||
void resetMpegStubState();
|
||||
void notifyMpegCdStreamStart();
|
||||
void notifyMpegCdStreamEof();
|
||||
void enqueueMpegDecodedFrameForTesting(uint32_t mpegAddr);
|
||||
void notifyMpegCdStreamStart(PS2Runtime *runtime = nullptr);
|
||||
void notifyMpegCdStreamDataProduced(uint32_t byteCount, bool endOfStream);
|
||||
void notifyMpegCdStreamEof(PS2Runtime *runtime = nullptr);
|
||||
void sceMpegFlush(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceMpegAddBs(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceMpegAddCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_log.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include <iostream>
|
||||
@@ -14,7 +15,6 @@
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
@@ -35,10 +35,51 @@ std::string translatePs2Path(const char *ps2Path);
|
||||
#include "Helpers/Loader.h"
|
||||
#include "Helpers/Runtime.h"
|
||||
|
||||
namespace ps2_syscalls
|
||||
inline bool resolveEeGuestRange(uint32_t address, size_t size, uint32_t &offset, bool &scratch)
|
||||
{
|
||||
inline void yieldGuestExecutionAfterWake(PS2Runtime *runtime)
|
||||
scratch = ps2IsScratchpadAddress(address);
|
||||
if (scratch)
|
||||
{
|
||||
runtime->yieldGuestExecutionAfterWake();
|
||||
offset = ps2ScratchpadOffset(address);
|
||||
return size <= PS2_SCRATCHPAD_SIZE && offset <= PS2_SCRATCHPAD_SIZE - size;
|
||||
}
|
||||
|
||||
if (address < PS2_RAM_SIZE)
|
||||
{
|
||||
offset = address;
|
||||
}
|
||||
else if ((address >= 0x20000000u && address < 0x40000000u) ||
|
||||
(address >= 0x80000000u && address < 0xC0000000u))
|
||||
{
|
||||
offset = address & 0x1FFFFFFFu;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return size <= PS2_RAM_SIZE && offset <= PS2_RAM_SIZE - size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline const T *getEeGuestStruct(const uint8_t *rdram, uint32_t address)
|
||||
{
|
||||
uint32_t offset = 0;
|
||||
bool scratch = false;
|
||||
if (!rdram || (address & (alignof(T) - 1u)) != 0u ||
|
||||
!resolveEeGuestRange(address, sizeof(T), offset, scratch))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (scratch)
|
||||
{
|
||||
const uint8_t *base = ps2GetScratchpadHostPtr();
|
||||
return base ? reinterpret_cast<const T *>(base + offset) : nullptr;
|
||||
}
|
||||
return reinterpret_cast<const T *>(rdram + offset);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T *getEeGuestStruct(uint8_t *rdram, uint32_t address)
|
||||
{
|
||||
return const_cast<T *>(getEeGuestStruct<T>(static_cast<const uint8_t *>(rdram), address));
|
||||
}
|
||||
|
||||
@@ -1,153 +1,3 @@
|
||||
struct ThreadExitException final : public std::exception
|
||||
{
|
||||
const char *what() const noexcept override
|
||||
{
|
||||
return "PS2 Thread Exit";
|
||||
}
|
||||
};
|
||||
|
||||
static void throwIfTerminated(const std::shared_ptr<ThreadInfo> &info)
|
||||
{
|
||||
if (info && info->terminated.load())
|
||||
{
|
||||
throw ThreadExitException();
|
||||
}
|
||||
}
|
||||
|
||||
// Condition-variable waits in the EE runtime must release the global guest
|
||||
// execution mutex, but must not reacquire it while still holding the local
|
||||
// wait-object mutex. Reacquiring guest execution while holding a semaphore,
|
||||
// thread, event-flag, or vsync mutex can create an ABBA deadlock:
|
||||
//
|
||||
// awakened thread: local wait mutex -> waiting for GuestExecutionScope
|
||||
// running thread: GuestExecutionScope -> waiting for local wait mutex
|
||||
//
|
||||
// This helper keeps guest execution released for the whole host wait and for
|
||||
// any post-wake bookkeeping that needs the local mutex, then unlocks the local
|
||||
// mutex before the GuestExecutionReleaseScope destructor reacquires guest code.
|
||||
template <typename Lock, typename WaitFn, typename FinishFn>
|
||||
static void waitWithGuestExecutionReleasedUntilUnlocked(PS2Runtime *runtime,
|
||||
Lock &lock,
|
||||
WaitFn waitFn,
|
||||
FinishFn finishFn)
|
||||
{
|
||||
auto releaseGuestExecution = std::make_unique<PS2Runtime::GuestExecutionReleaseScope>(runtime);
|
||||
|
||||
waitFn();
|
||||
finishFn();
|
||||
|
||||
if (lock.owns_lock())
|
||||
{
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
releaseGuestExecution.reset();
|
||||
}
|
||||
|
||||
template <typename Lock, typename WaitFn>
|
||||
static void waitWithGuestExecutionReleasedUntilUnlocked(PS2Runtime *runtime, Lock &lock, WaitFn waitFn)
|
||||
{
|
||||
waitWithGuestExecutionReleasedUntilUnlocked(runtime, lock, waitFn, []() {});
|
||||
}
|
||||
|
||||
static void waitWhileSuspended(const std::shared_ptr<ThreadInfo> &info, PS2Runtime *runtime = nullptr)
|
||||
{
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
std::unique_lock<std::mutex> lock(info->m);
|
||||
if (info->suspendCount > 0)
|
||||
{
|
||||
info->status = THS_SUSPEND;
|
||||
info->waitType = TSW_NONE;
|
||||
info->waitId = 0;
|
||||
|
||||
bool terminated = false;
|
||||
waitWithGuestExecutionReleasedUntilUnlocked(
|
||||
runtime,
|
||||
lock,
|
||||
[&]()
|
||||
{
|
||||
info->cv.wait(lock, [&]()
|
||||
{ return info->suspendCount == 0 || info->terminated.load(); });
|
||||
},
|
||||
[&]()
|
||||
{
|
||||
terminated = info->terminated.load();
|
||||
if (!terminated)
|
||||
{
|
||||
info->status = THS_RUN;
|
||||
}
|
||||
});
|
||||
|
||||
if (terminated)
|
||||
{
|
||||
throw ThreadExitException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<ThreadInfo> lookupThreadInfo(int tid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
|
||||
auto it = g_threads.find(tid);
|
||||
if (it != g_threads.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::shared_ptr<ThreadInfo> ensureCurrentThreadInfo(R5900Context *ctx)
|
||||
{
|
||||
const int tid = g_currentThreadId;
|
||||
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
|
||||
auto it = g_threads.find(tid);
|
||||
if (it != g_threads.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto info = std::make_shared<ThreadInfo>();
|
||||
info->started = true;
|
||||
info->status = THS_RUN;
|
||||
info->currentPriority = info->priority;
|
||||
info->suspendCount = 0;
|
||||
if (ctx)
|
||||
{
|
||||
info->entry = ctx->pc;
|
||||
info->stack = getRegU32(ctx, 29);
|
||||
info->gp = getRegU32(ctx, 28);
|
||||
}
|
||||
info->waitType = TSW_NONE;
|
||||
info->waitId = 0;
|
||||
|
||||
g_threads.emplace(tid, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
static std::shared_ptr<SemaInfo> lookupSemaInfo(int sid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
|
||||
auto it = g_semas.find(sid);
|
||||
if (it != g_semas.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::shared_ptr<EventFlagInfo> lookupEventFlagInfo(int eid)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_event_flag_map_mutex);
|
||||
auto it = g_eventFlags.find(eid);
|
||||
if (it != g_eventFlags.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void setRegU32(R5900Context *ctx, int reg, uint32_t value)
|
||||
{
|
||||
if (!ctx || reg < 0 || reg > 31)
|
||||
@@ -155,104 +5,6 @@ static void setRegU32(R5900Context *ctx, int reg, uint32_t value)
|
||||
SET_GPR_U32(ctx, reg, value);
|
||||
}
|
||||
|
||||
static std::chrono::microseconds alarmTicksToDuration(uint16_t ticks)
|
||||
{
|
||||
constexpr uint64_t kAlarmTickUsec = 64u; // Approximate EE H-SYNC tick period.
|
||||
const uint64_t clampedTicks = (ticks == 0u) ? 1u : static_cast<uint64_t>(ticks);
|
||||
return std::chrono::microseconds(clampedTicks * kAlarmTickUsec);
|
||||
}
|
||||
|
||||
static void ensureAlarmWorkerRunning()
|
||||
{
|
||||
std::call_once(g_alarm_worker_once, []()
|
||||
{ std::thread([]()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
std::shared_ptr<AlarmInfo> readyAlarm;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_alarm_mutex);
|
||||
while (!readyAlarm)
|
||||
{
|
||||
if (g_alarms.empty())
|
||||
{
|
||||
g_alarm_cv.wait(lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nextIt = std::min_element(g_alarms.begin(), g_alarms.end(),
|
||||
[](const auto &a, const auto &b)
|
||||
{
|
||||
return a.second->dueAt < b.second->dueAt;
|
||||
});
|
||||
if (nextIt == g_alarms.end())
|
||||
{
|
||||
g_alarm_cv.wait(lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (nextIt->second->dueAt > now)
|
||||
{
|
||||
g_alarm_cv.wait_until(lock, nextIt->second->dueAt);
|
||||
continue;
|
||||
}
|
||||
|
||||
readyAlarm = nextIt->second;
|
||||
g_alarms.erase(nextIt);
|
||||
}
|
||||
}
|
||||
|
||||
if (!readyAlarm || !readyAlarm->runtime || !readyAlarm->rdram || !readyAlarm->handler)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!readyAlarm->runtime->hasFunction(readyAlarm->handler))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
constexpr uint32_t kAlarmCallbackStackSize = 0x4000u;
|
||||
thread_local PS2Runtime *s_alarmStackRuntime = nullptr;
|
||||
thread_local uint32_t s_alarmStackTop = 0u;
|
||||
if (s_alarmStackRuntime != readyAlarm->runtime || s_alarmStackTop == 0u)
|
||||
{
|
||||
s_alarmStackRuntime = readyAlarm->runtime;
|
||||
s_alarmStackTop = readyAlarm->runtime->reserveAsyncCallbackStack(kAlarmCallbackStackSize, 16u);
|
||||
}
|
||||
|
||||
R5900Context callbackCtx{};
|
||||
setRegU32(&callbackCtx, 28, readyAlarm->gp);
|
||||
setRegU32(&callbackCtx, 29,
|
||||
(s_alarmStackTop != 0u) ? s_alarmStackTop : (PS2_RAM_SIZE - 0x10u));
|
||||
setRegU32(&callbackCtx, 31, 0);
|
||||
setRegU32(&callbackCtx, 4, static_cast<uint32_t>(readyAlarm->id));
|
||||
setRegU32(&callbackCtx, 5, static_cast<uint32_t>(readyAlarm->ticks));
|
||||
setRegU32(&callbackCtx, 6, readyAlarm->commonArg);
|
||||
setRegU32(&callbackCtx, 7, 0);
|
||||
callbackCtx.pc = readyAlarm->handler;
|
||||
|
||||
PS2Runtime::RecompiledFunction func = readyAlarm->runtime->lookupFunction(readyAlarm->handler);
|
||||
func(readyAlarm->rdram, &callbackCtx, readyAlarm->runtime);
|
||||
}
|
||||
catch (const ThreadExitException &)
|
||||
{
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
static int alarmExceptionLogs = 0;
|
||||
if (alarmExceptionLogs < 8)
|
||||
{
|
||||
std::cerr << "[SetAlarm] callback exception: " << e.what() << std::endl;
|
||||
++alarmExceptionLogs;
|
||||
}
|
||||
}
|
||||
} })
|
||||
.detach(); });
|
||||
}
|
||||
|
||||
static void rpcCopyToRdram(uint8_t *rdram, uint32_t dst, uint32_t src, size_t size)
|
||||
{
|
||||
if (!rdram || size == 0)
|
||||
@@ -331,146 +83,6 @@ static bool readStackU32(uint8_t *rdram, uint32_t sp, uint32_t offset, uint32_t
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class RpcInvokeExitReason
|
||||
{
|
||||
Returned,
|
||||
NullPc,
|
||||
MissingFunction,
|
||||
StepLimit,
|
||||
SamePcLimit
|
||||
};
|
||||
|
||||
static const char *rpcInvokeExitReasonName(RpcInvokeExitReason reason)
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case RpcInvokeExitReason::Returned:
|
||||
return "returned";
|
||||
case RpcInvokeExitReason::NullPc:
|
||||
return "null-pc";
|
||||
case RpcInvokeExitReason::MissingFunction:
|
||||
return "missing-function";
|
||||
case RpcInvokeExitReason::StepLimit:
|
||||
return "step-limit";
|
||||
case RpcInvokeExitReason::SamePcLimit:
|
||||
return "same-pc-limit";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static bool rpcInvokeFunction(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime,
|
||||
uint32_t funcAddr, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t *outV0)
|
||||
{
|
||||
if (!runtime || !ctx || !funcAddr || !runtime->hasFunction(funcAddr))
|
||||
return false;
|
||||
|
||||
constexpr uint32_t kRpcInvokeStackSize = 0x4000u;
|
||||
constexpr uint32_t kRpcInvokeReturnSentinel = 0x00FFF000u;
|
||||
constexpr uint32_t kRpcInvokeMaxSteps = 0x8000u;
|
||||
|
||||
R5900Context tmp = *ctx;
|
||||
setRegU32(&tmp, 4, a0);
|
||||
setRegU32(&tmp, 5, a1);
|
||||
setRegU32(&tmp, 6, a2);
|
||||
setRegU32(&tmp, 7, a3);
|
||||
|
||||
thread_local uint32_t s_rpcInvokeStackBase = 0u;
|
||||
thread_local uint32_t s_rpcInvokeStackTop = 0u;
|
||||
if (s_rpcInvokeStackTop == 0u)
|
||||
{
|
||||
const uint32_t stackBase = runtime->guestMalloc(kRpcInvokeStackSize, 16u);
|
||||
if (stackBase != 0u)
|
||||
{
|
||||
s_rpcInvokeStackBase = stackBase;
|
||||
s_rpcInvokeStackTop = (stackBase + kRpcInvokeStackSize) & ~0xFu;
|
||||
}
|
||||
}
|
||||
if (s_rpcInvokeStackTop != 0u)
|
||||
{
|
||||
setRegU32(&tmp, 29, s_rpcInvokeStackTop);
|
||||
}
|
||||
(void)s_rpcInvokeStackBase;
|
||||
|
||||
setRegU32(&tmp, 31, kRpcInvokeReturnSentinel);
|
||||
tmp.pc = funcAddr;
|
||||
|
||||
uint32_t steps = 0u;
|
||||
uint32_t lastPc = 0xFFFFFFFFu;
|
||||
uint32_t samePcCount = 0u;
|
||||
RpcInvokeExitReason exitReason = RpcInvokeExitReason::MissingFunction;
|
||||
while (tmp.pc != 0u &&
|
||||
tmp.pc != kRpcInvokeReturnSentinel &&
|
||||
runtime->hasFunction(tmp.pc) &&
|
||||
steps < kRpcInvokeMaxSteps)
|
||||
{
|
||||
const uint32_t pc = tmp.pc;
|
||||
if (pc == lastPc)
|
||||
{
|
||||
++samePcCount;
|
||||
if (samePcCount > 0x2000u)
|
||||
{
|
||||
exitReason = RpcInvokeExitReason::SamePcLimit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastPc = pc;
|
||||
samePcCount = 0u;
|
||||
}
|
||||
|
||||
PS2Runtime::RecompiledFunction func = runtime->lookupFunction(pc);
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
func(rdram, &tmp, runtime);
|
||||
}
|
||||
++steps;
|
||||
}
|
||||
|
||||
if (outV0)
|
||||
{
|
||||
*outV0 = getRegU32(&tmp, 2);
|
||||
}
|
||||
|
||||
if (tmp.pc == kRpcInvokeReturnSentinel)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tmp.pc == 0u)
|
||||
{
|
||||
exitReason = RpcInvokeExitReason::NullPc;
|
||||
}
|
||||
else if (steps >= kRpcInvokeMaxSteps)
|
||||
{
|
||||
exitReason = RpcInvokeExitReason::StepLimit;
|
||||
}
|
||||
else if (!runtime->hasFunction(tmp.pc))
|
||||
{
|
||||
exitReason = RpcInvokeExitReason::MissingFunction;
|
||||
}
|
||||
|
||||
static std::atomic<uint32_t> s_rpcInvokeFailureLogs{0u};
|
||||
constexpr uint32_t kMaxRpcInvokeFailureLogs = 64u;
|
||||
const uint32_t logIndex = s_rpcInvokeFailureLogs.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < kMaxRpcInvokeFailureLogs)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[SyscallOverride:invoke-failed]"
|
||||
<< " func=0x" << std::hex << funcAddr
|
||||
<< " exitPc=0x" << tmp.pc
|
||||
<< " ra=0x" << getRegU32(&tmp, 31)
|
||||
<< std::dec
|
||||
<< " steps=" << steps
|
||||
<< " reason=" << rpcInvokeExitReasonName(exitReason)
|
||||
<< std::endl;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t rpcAllocPacketAddr(uint8_t *rdram)
|
||||
{
|
||||
if (kRpcPacketPoolCount == 0)
|
||||
@@ -493,28 +105,6 @@ static uint32_t rpcAllocServerAddr(uint8_t *rdram)
|
||||
return addr;
|
||||
}
|
||||
|
||||
struct IrqHandlerInfo
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t cause = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t arg = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
bool enabled = true;
|
||||
int order = 0;
|
||||
};
|
||||
|
||||
static std::unordered_map<int, IrqHandlerInfo> g_intcHandlers;
|
||||
static std::unordered_map<int, IrqHandlerInfo> g_dmacHandlers;
|
||||
static int g_nextIntcHandlerId = 1;
|
||||
static int g_nextDmacHandlerId = 1;
|
||||
|
||||
static int g_intc_head_order = 0;
|
||||
static int g_intc_tail_order = 1000;
|
||||
static int g_dmac_head_order = 0;
|
||||
static int g_dmac_tail_order = 1000;
|
||||
|
||||
inline std::string translatePs2Path(const char *ps2Path)
|
||||
{
|
||||
if (!ps2Path || !*ps2Path)
|
||||
|
||||
@@ -2,40 +2,10 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
inline std::unordered_map<int, FILE *> g_fileDescriptors;
|
||||
inline int g_nextFd = 3; // Start after stdin, stdout, stderr
|
||||
|
||||
struct ThreadInfo
|
||||
{
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t priority = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t arg = 0;
|
||||
bool started = false;
|
||||
bool ownsStack = false;
|
||||
uint32_t tlsBase = 0;
|
||||
|
||||
// Thread Status
|
||||
int status = 0x10; // THS_DORMANT
|
||||
int waitType = 0; // TSW_NONE
|
||||
int waitId = 0;
|
||||
int wakeupCount = 0;
|
||||
int currentPriority = 0;
|
||||
int suspendCount = 0;
|
||||
std::atomic<uint32_t> currentPc{0};
|
||||
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
std::atomic<bool> forceRelease{false};
|
||||
std::atomic<bool> terminated{false};
|
||||
};
|
||||
|
||||
// Thread status
|
||||
#define THS_RUN 0x01
|
||||
#define THS_READY 0x02
|
||||
@@ -138,6 +108,24 @@ struct ee_thread_status_t
|
||||
uint32_t wakeupCount; // 0x2C
|
||||
};
|
||||
|
||||
// PS2SDK EE kernel.h t_ee_thread. CreateThread consumes this full 0x24-byte
|
||||
// descriptor; it is not the attr-first IOP thread descriptor.
|
||||
struct ee_thread_t
|
||||
{
|
||||
int status;
|
||||
uint32_t func;
|
||||
uint32_t stack;
|
||||
int stack_size;
|
||||
uint32_t gp_reg;
|
||||
int initial_priority;
|
||||
int current_priority;
|
||||
uint32_t attr;
|
||||
uint32_t option;
|
||||
};
|
||||
|
||||
static_assert(sizeof(ee_thread_t) == 0x24u);
|
||||
static_assert(sizeof(ee_thread_status_t) == 0x30u);
|
||||
|
||||
struct ee_sema_t
|
||||
{
|
||||
int count;
|
||||
@@ -148,43 +136,7 @@ struct ee_sema_t
|
||||
uint32_t option;
|
||||
};
|
||||
|
||||
struct SemaInfo
|
||||
{
|
||||
int count = 0;
|
||||
int maxCount = 0;
|
||||
int initCount = 0;
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
int waiters = 0;
|
||||
bool deleted = false;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
struct EventFlagInfo
|
||||
{
|
||||
uint32_t attr = 0;
|
||||
uint32_t option = 0;
|
||||
uint32_t initBits = 0;
|
||||
uint32_t bits = 0;
|
||||
int waiters = 0;
|
||||
bool deleted = false;
|
||||
std::mutex m;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
struct AlarmInfo
|
||||
{
|
||||
int id = 0;
|
||||
uint16_t ticks = 0;
|
||||
uint32_t handler = 0;
|
||||
uint32_t commonArg = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t sp = 0;
|
||||
uint8_t *rdram = nullptr;
|
||||
PS2Runtime *runtime = nullptr;
|
||||
std::chrono::steady_clock::time_point dueAt;
|
||||
};
|
||||
static_assert(sizeof(ee_sema_t) == 0x18u);
|
||||
|
||||
struct io_stat_t
|
||||
{
|
||||
@@ -204,136 +156,8 @@ static constexpr uint32_t kFioSoIROth = 0x0004;
|
||||
static constexpr uint32_t kFioSoIWOth = 0x0002;
|
||||
static constexpr uint32_t kFioSoIXOth = 0x0001;
|
||||
|
||||
inline std::unordered_map<int, std::shared_ptr<ThreadInfo>> g_threads;
|
||||
inline int g_nextThreadId = 2; // Reserve 1 for the main thread
|
||||
inline thread_local int g_currentThreadId = 1;
|
||||
inline std::mutex g_thread_map_mutex;
|
||||
inline std::unordered_map<int, std::thread> g_hostThreads;
|
||||
inline std::mutex g_host_thread_mutex;
|
||||
|
||||
inline std::unordered_map<int, std::shared_ptr<SemaInfo>> g_semas;
|
||||
inline int g_nextSemaId = 1;
|
||||
inline std::mutex g_sema_map_mutex;
|
||||
inline std::unordered_map<int, std::shared_ptr<EventFlagInfo>> g_eventFlags;
|
||||
inline int g_nextEventFlagId = 1;
|
||||
inline std::mutex g_event_flag_map_mutex;
|
||||
inline std::unordered_map<int, std::shared_ptr<AlarmInfo>> g_alarms;
|
||||
inline int g_nextAlarmId = 1;
|
||||
inline std::mutex g_alarm_mutex;
|
||||
inline std::condition_variable g_alarm_cv;
|
||||
inline std::once_flag g_alarm_worker_once;
|
||||
inline std::atomic<int> g_activeThreads{0};
|
||||
inline std::mutex g_fd_mutex;
|
||||
|
||||
static void registerHostThread(int tid, std::thread worker)
|
||||
{
|
||||
std::thread stale;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_host_thread_mutex);
|
||||
auto it = g_hostThreads.find(tid);
|
||||
if (it != g_hostThreads.end())
|
||||
{
|
||||
stale = std::move(it->second);
|
||||
g_hostThreads.erase(it);
|
||||
}
|
||||
g_hostThreads.emplace(tid, std::move(worker));
|
||||
}
|
||||
|
||||
if (stale.joinable())
|
||||
{
|
||||
if (stale.get_id() == std::this_thread::get_id())
|
||||
{
|
||||
stale.detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
stale.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void joinHostThreadById(int tid)
|
||||
{
|
||||
std::thread worker;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_host_thread_mutex);
|
||||
auto it = g_hostThreads.find(tid);
|
||||
if (it != g_hostThreads.end())
|
||||
{
|
||||
worker = std::move(it->second);
|
||||
g_hostThreads.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (!worker.joinable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (worker.get_id() == std::this_thread::get_id())
|
||||
{
|
||||
worker.detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
static void joinAllHostThreads()
|
||||
{
|
||||
std::vector<std::thread> workers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_host_thread_mutex);
|
||||
workers.reserve(g_hostThreads.size());
|
||||
const std::thread::id selfId = std::this_thread::get_id();
|
||||
for (auto it = g_hostThreads.begin(); it != g_hostThreads.end();)
|
||||
{
|
||||
std::thread &worker = it->second;
|
||||
if (worker.joinable() && worker.get_id() == selfId)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
workers.push_back(std::move(worker));
|
||||
it = g_hostThreads.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &worker : workers)
|
||||
{
|
||||
if (!worker.joinable())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
static void detachAllHostThreads()
|
||||
{
|
||||
std::vector<std::thread> workers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_host_thread_mutex);
|
||||
workers.reserve(g_hostThreads.size());
|
||||
for (auto &entry : g_hostThreads)
|
||||
{
|
||||
workers.push_back(std::move(entry.second));
|
||||
}
|
||||
g_hostThreads.clear();
|
||||
}
|
||||
|
||||
for (auto &worker : workers)
|
||||
{
|
||||
if (!worker.joinable())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
worker.detach();
|
||||
}
|
||||
}
|
||||
|
||||
struct RpcServerState
|
||||
{
|
||||
uint32_t sid = 0;
|
||||
@@ -397,24 +221,11 @@ inline uint32_t g_rpc_next_id = 1;
|
||||
inline uint32_t g_rpc_packet_index = 0;
|
||||
inline uint32_t g_rpc_server_index = 0;
|
||||
inline uint32_t g_rpc_active_queue = 0;
|
||||
struct ExitHandlerEntry
|
||||
{
|
||||
uint32_t func = 0;
|
||||
uint32_t arg = 0;
|
||||
};
|
||||
|
||||
inline std::mutex g_exit_handler_mutex;
|
||||
inline std::unordered_map<int, std::vector<ExitHandlerEntry>> g_exit_handlers;
|
||||
|
||||
inline std::mutex g_bootmode_mutex;
|
||||
inline bool g_bootmode_initialized = false;
|
||||
inline uint32_t g_bootmode_pool_offset = 0;
|
||||
inline std::unordered_map<uint8_t, uint32_t> g_bootmode_addresses;
|
||||
|
||||
inline std::mutex g_syscall_override_mutex;
|
||||
inline std::unordered_map<uint32_t, uint32_t> g_syscall_overrides;
|
||||
inline std::unordered_set<uint32_t> g_syscall_mirror_addrs;
|
||||
|
||||
static constexpr uint32_t kGuestSyscallTableGuestBase = 0x80011F80u;
|
||||
static constexpr uint32_t kGuestSyscallTablePhysBase = kGuestSyscallTableGuestBase & 0x1FFFFFFFu;
|
||||
static constexpr uint32_t kGuestSyscallMirrorLimit = 0x00080000u;
|
||||
|
||||
@@ -1,538 +1,103 @@
|
||||
#include "Common.h"
|
||||
#include "Interrupt.h"
|
||||
#include "ps2_log.h"
|
||||
#include "Stubs/GS.h"
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
namespace interrupt_state
|
||||
namespace
|
||||
{
|
||||
constexpr uint32_t kIntcVblankStart = 2u;
|
||||
constexpr uint32_t kIntcVblankEnd = 3u;
|
||||
constexpr auto kVblankPeriod = std::chrono::microseconds(16667);
|
||||
constexpr int kMaxCatchupTicks = 4;
|
||||
constexpr uint32_t kMaxIrqHandlerSteps = 4096u;
|
||||
|
||||
std::mutex g_irq_handler_mutex;
|
||||
std::mutex g_irq_worker_mutex;
|
||||
std::condition_variable g_irq_worker_cv;
|
||||
std::mutex g_vsync_flag_mutex;
|
||||
std::condition_variable g_vsync_cv;
|
||||
std::atomic<bool> g_irq_worker_stop{false};
|
||||
std::atomic<bool> g_irq_worker_running{false};
|
||||
uint32_t g_enabled_intc_mask = 0xFFFFFFFFu;
|
||||
uint32_t g_enabled_dmac_mask = 0xFFFFFFFFu;
|
||||
uint64_t g_vsync_tick_counter = 0u;
|
||||
VSyncFlagRegistration g_vsync_registration{};
|
||||
}
|
||||
|
||||
using namespace interrupt_state;
|
||||
|
||||
static void writeGuestU32NoThrow(uint8_t *rdram, uint32_t addr, uint32_t value)
|
||||
{
|
||||
if (addr == 0u)
|
||||
EeScheduler &scheduler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
return;
|
||||
EeScheduler &result = runtime->eeScheduler();
|
||||
result.bindMainContextForSyscall(*ctx, rdram);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t *dst = getMemPtr(rdram, addr);
|
||||
if (!dst)
|
||||
void setCauseEnabled(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
PS2Runtime *runtime,
|
||||
bool dmac,
|
||||
bool enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::memcpy(dst, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static void writeGuestU64NoThrow(uint8_t *rdram, uint32_t addr, uint64_t value)
|
||||
{
|
||||
if (addr == 0u)
|
||||
{
|
||||
return;
|
||||
setReturnS32(ctx,
|
||||
scheduler(rdram, ctx, runtime)
|
||||
.setIrqCauseEnabled(dmac, getRegU32(ctx, 4), enabled));
|
||||
}
|
||||
|
||||
uint8_t *dst = getMemPtr(rdram, addr);
|
||||
if (!dst)
|
||||
void addHandler(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
PS2Runtime *runtime,
|
||||
bool dmac)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::memcpy(dst, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static uint32_t readGuestU32NoThrow(uint8_t *rdram, uint32_t addr)
|
||||
{
|
||||
if (addr == 0u)
|
||||
{
|
||||
return 0u;
|
||||
const int id = scheduler(rdram, ctx, runtime)
|
||||
.addIrqHandler(dmac,
|
||||
getRegU32(ctx, 4),
|
||||
getRegU32(ctx, 5),
|
||||
getRegU32(ctx, 6) != 0u,
|
||||
getRegU32(ctx, 7),
|
||||
getRegU32(ctx, 28),
|
||||
getRegU32(ctx, 29));
|
||||
setReturnS32(ctx, id);
|
||||
}
|
||||
|
||||
uint8_t *src = getMemPtr(rdram, addr);
|
||||
if (!src)
|
||||
void removeHandler(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
PS2Runtime *runtime,
|
||||
bool dmac)
|
||||
{
|
||||
return 0u;
|
||||
setReturnS32(ctx,
|
||||
scheduler(rdram, ctx, runtime)
|
||||
.removeIrqHandler(dmac,
|
||||
getRegU32(ctx, 4),
|
||||
static_cast<int>(getRegU32(ctx, 5))));
|
||||
}
|
||||
|
||||
uint32_t value = 0u;
|
||||
std::memcpy(&value, src, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
static uint32_t getAsyncHandlerStackTop(PS2Runtime *runtime)
|
||||
{
|
||||
constexpr uint32_t kAsyncHandlerStackSize = 0x4000u;
|
||||
thread_local PS2Runtime *s_cachedRuntime = nullptr;
|
||||
thread_local uint32_t s_cachedStackTop = 0u;
|
||||
|
||||
if (runtime == nullptr)
|
||||
void setHandlerEnabled(uint8_t *rdram,
|
||||
R5900Context *ctx,
|
||||
PS2Runtime *runtime,
|
||||
bool dmac,
|
||||
bool enabled)
|
||||
{
|
||||
return PS2_RAM_SIZE - 0x10u;
|
||||
}
|
||||
|
||||
if (s_cachedRuntime != runtime || s_cachedStackTop == 0u)
|
||||
{
|
||||
s_cachedRuntime = runtime;
|
||||
s_cachedStackTop = runtime->reserveAsyncCallbackStack(kAsyncHandlerStackSize, 16u);
|
||||
}
|
||||
|
||||
return (s_cachedStackTop != 0u) ? s_cachedStackTop : (PS2_RAM_SIZE - 0x10u);
|
||||
}
|
||||
|
||||
static void dispatchIntcHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause)
|
||||
{
|
||||
if (!rdram || !runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<IrqHandlerInfo> handlers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (cause < 32u && (g_enabled_intc_mask & (1u << cause)) == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handlers.reserve(g_intcHandlers.size());
|
||||
for (const auto &[id, info] : g_intcHandlers)
|
||||
{
|
||||
(void)id;
|
||||
if (!info.enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (info.cause != cause)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (info.handler == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
handlers.push_back(info);
|
||||
}
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
|
||||
{ return a.order < b.order; });
|
||||
}
|
||||
|
||||
for (const IrqHandlerInfo &info : handlers)
|
||||
{
|
||||
if (!runtime->hasFunction(info.handler))
|
||||
{
|
||||
if (cause == kIntcVblankStart)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
static std::atomic<uint32_t> s_missingHandlerLogCount{0u};
|
||||
const uint32_t logIndex = s_missingHandlerLogCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < 32u)
|
||||
{
|
||||
auto flags = std::cout.flags();
|
||||
std::cout << "[INTC:missing] cause=" << cause
|
||||
<< " handler=0x" << std::hex << info.handler
|
||||
<< std::dec
|
||||
<< " id=" << info.id
|
||||
<< std::endl;
|
||||
std::cout.flags(flags);
|
||||
}
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
R5900Context irqCtx{};
|
||||
SET_GPR_U32(&irqCtx, 28, info.gp);
|
||||
SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
|
||||
SET_GPR_U32(&irqCtx, 31, 0u);
|
||||
SET_GPR_U32(&irqCtx, 4, cause);
|
||||
SET_GPR_U32(&irqCtx, 5, info.arg);
|
||||
SET_GPR_U32(&irqCtx, 6, 0u);
|
||||
SET_GPR_U32(&irqCtx, 7, 0u);
|
||||
irqCtx.pc = info.handler;
|
||||
|
||||
bool reschedulePending = false;
|
||||
uint64_t handoffBaseline = 0u;
|
||||
uint32_t steps = 0u;
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
PS2Runtime::DeferredGuestYieldScope deferYield(reschedulePending);
|
||||
|
||||
while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested() && steps < kMaxIrqHandlerSteps)
|
||||
{
|
||||
PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc);
|
||||
if (!step)
|
||||
{
|
||||
break;
|
||||
}
|
||||
step(rdram, &irqCtx, runtime);
|
||||
++steps;
|
||||
}
|
||||
handoffBaseline = runtime->guestExecutionHandoffEpochSnapshot();
|
||||
}
|
||||
if (steps >= kMaxIrqHandlerSteps)
|
||||
{
|
||||
static uint32_t s_stepLimitLogCount = 0u;
|
||||
if (s_stepLimitLogCount < 16u)
|
||||
{
|
||||
std::cerr << "[INTC:step-limit] handler=0x" << std::hex << info.handler << " pc=0x" << irqCtx.pc << std::dec << std::endl;
|
||||
++s_stepLimitLogCount;
|
||||
}
|
||||
}
|
||||
if (reschedulePending && !runtime->isStopRequested())
|
||||
{
|
||||
runtime->waitForGuestExecutionHandoff(handoffBaseline);
|
||||
}
|
||||
}
|
||||
catch (const ThreadExitException &)
|
||||
{
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
static uint32_t warnCount = 0;
|
||||
if (warnCount < 8u)
|
||||
{
|
||||
std::cerr << "[INTC] handler 0x" << std::hex << info.handler
|
||||
<< " threw exception: " << e.what() << std::dec << std::endl;
|
||||
++warnCount;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx,
|
||||
scheduler(rdram, ctx, runtime)
|
||||
.setIrqHandlerEnabled(dmac,
|
||||
static_cast<int>(getRegU32(ctx, 5)),
|
||||
enabled));
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause)
|
||||
void dispatchDmacHandlersForCause(uint8_t *, PS2Runtime *runtime, uint32_t cause)
|
||||
{
|
||||
if (!rdram || !runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<IrqHandlerInfo> handlers;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (cause < 32u && (g_enabled_dmac_mask & (1u << cause)) == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
handlers.reserve(g_dmacHandlers.size());
|
||||
for (const auto &[id, info] : g_dmacHandlers)
|
||||
{
|
||||
(void)id;
|
||||
if (!info.enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (info.cause != cause)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (info.handler == 0u)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
handlers.push_back(info);
|
||||
}
|
||||
std::sort(handlers.begin(), handlers.end(), [](const IrqHandlerInfo &a, const IrqHandlerInfo &b)
|
||||
{ return a.order < b.order; });
|
||||
}
|
||||
|
||||
for (const IrqHandlerInfo &info : handlers)
|
||||
{
|
||||
if (!runtime->hasFunction(info.handler))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
R5900Context irqCtx{};
|
||||
SET_GPR_U32(&irqCtx, 28, info.gp);
|
||||
SET_GPR_U32(&irqCtx, 29, getAsyncHandlerStackTop(runtime));
|
||||
SET_GPR_U32(&irqCtx, 31, 0u);
|
||||
SET_GPR_U32(&irqCtx, 4, cause);
|
||||
SET_GPR_U32(&irqCtx, 5, info.arg);
|
||||
SET_GPR_U32(&irqCtx, 6, 0u);
|
||||
SET_GPR_U32(&irqCtx, 7, 0u);
|
||||
irqCtx.pc = info.handler;
|
||||
|
||||
bool reschedulePending = false;
|
||||
uint64_t handoffBaseline = 0u;
|
||||
uint32_t steps = 0u;
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
PS2Runtime::DeferredGuestYieldScope deferYield(reschedulePending);
|
||||
|
||||
while (irqCtx.pc != 0u && runtime && !runtime->isStopRequested() &&
|
||||
steps < kMaxIrqHandlerSteps)
|
||||
{
|
||||
PS2Runtime::RecompiledFunction step = runtime->lookupFunction(irqCtx.pc);
|
||||
if (!step)
|
||||
{
|
||||
break;
|
||||
}
|
||||
step(rdram, &irqCtx, runtime);
|
||||
++steps;
|
||||
}
|
||||
handoffBaseline = runtime->guestExecutionHandoffEpochSnapshot();
|
||||
}
|
||||
if (steps >= kMaxIrqHandlerSteps)
|
||||
{
|
||||
static uint32_t s_stepLimitLogCount = 0u;
|
||||
if (s_stepLimitLogCount < 16u)
|
||||
{
|
||||
std::cerr << "[DMAC:step-limit] handler=0x" << std::hex << info.handler
|
||||
<< " pc=0x" << irqCtx.pc << std::dec << std::endl;
|
||||
++s_stepLimitLogCount;
|
||||
}
|
||||
}
|
||||
if (reschedulePending && !runtime->isStopRequested())
|
||||
{
|
||||
runtime->waitForGuestExecutionHandoff(handoffBaseline);
|
||||
}
|
||||
}
|
||||
catch (const ThreadExitException &)
|
||||
{
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
static uint32_t warnCount = 0;
|
||||
if (warnCount < 8u)
|
||||
{
|
||||
std::cerr << "[DMAC] handler 0x" << std::hex << info.handler
|
||||
<< " threw exception: " << e.what() << std::dec << std::endl;
|
||||
++warnCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
runtime->eeScheduler().dispatchIrq(true, cause);
|
||||
}
|
||||
|
||||
static void updateGsCsrFieldForVSync(PS2Runtime *runtime, uint64_t tickValue)
|
||||
uint64_t GetCurrentVSyncTick(PS2Runtime *runtime)
|
||||
{
|
||||
if (!runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr uint64_t kGsCsrFieldMask = 0x2000ull;
|
||||
std::atomic<uint64_t> &csr = runtime->memory().gs().csr;
|
||||
if (tickValue & 1ull)
|
||||
{
|
||||
csr.fetch_or(kGsCsrFieldMask);
|
||||
}
|
||||
else
|
||||
{
|
||||
csr.fetch_and(~kGsCsrFieldMask);
|
||||
}
|
||||
return runtime->eeScheduler().currentVSyncTick();
|
||||
}
|
||||
|
||||
static uint64_t signalVSyncFlag(uint8_t *rdram, PS2Runtime *runtime)
|
||||
void WaitVSyncTick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, int fixedResult)
|
||||
{
|
||||
VSyncFlagRegistration reg{};
|
||||
uint64_t tickValue = 0u;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
|
||||
reg = g_vsync_registration;
|
||||
g_vsync_registration = {};
|
||||
tickValue = ++g_vsync_tick_counter;
|
||||
}
|
||||
|
||||
g_vsync_cv.notify_all();
|
||||
updateGsCsrFieldForVSync(runtime, tickValue);
|
||||
|
||||
if (reg.flagAddr != 0u)
|
||||
{
|
||||
writeGuestU32NoThrow(rdram, reg.flagAddr, 1u);
|
||||
}
|
||||
if (reg.tickAddr != 0u)
|
||||
{
|
||||
writeGuestU64NoThrow(rdram, reg.tickAddr, tickValue);
|
||||
}
|
||||
return tickValue;
|
||||
}
|
||||
|
||||
static void interruptWorkerMain(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
g_currentThreadId = -1;
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
auto nextTick = clock::now() + kVblankPeriod;
|
||||
|
||||
while (runtime != nullptr && !runtime->isStopRequested())
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
|
||||
if (g_irq_worker_cv.wait_until(lock, nextTick, []()
|
||||
{ return g_irq_worker_stop.load(std::memory_order_acquire); }))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto now = clock::now();
|
||||
int ticksToProcess = 0;
|
||||
while (now >= nextTick && ticksToProcess < kMaxCatchupTicks)
|
||||
{
|
||||
++ticksToProcess;
|
||||
nextTick += kVblankPeriod;
|
||||
}
|
||||
if (ticksToProcess == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ticksToProcess; ++i)
|
||||
{
|
||||
bool reschedulePending = false;
|
||||
uint64_t handoffBaseline = 0u;
|
||||
{
|
||||
PS2Runtime::GuestExecutionScope guestExecution(runtime);
|
||||
PS2Runtime::DeferredGuestYieldScope deferYield(reschedulePending);
|
||||
const uint64_t tickValue = signalVSyncFlag(rdram, runtime);
|
||||
ps2_stubs::dispatchGsSyncVCallback(rdram, runtime, tickValue);
|
||||
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankStart);
|
||||
handoffBaseline = runtime->guestExecutionHandoffEpochSnapshot();
|
||||
}
|
||||
if (reschedulePending && !runtime->isStopRequested())
|
||||
{
|
||||
runtime->waitForGuestExecutionHandoff(handoffBaseline);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(500));
|
||||
dispatchIntcHandlersForCause(rdram, runtime, kIntcVblankEnd);
|
||||
}
|
||||
}
|
||||
|
||||
g_irq_worker_running.store(false, std::memory_order_release);
|
||||
g_irq_worker_cv.notify_all();
|
||||
}
|
||||
|
||||
static void ensureInterruptWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
if (!rdram || !runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_irq_worker_mutex);
|
||||
if (g_irq_worker_running.load(std::memory_order_acquire))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_irq_worker_stop.store(false, std::memory_order_release);
|
||||
g_irq_worker_running.store(true, std::memory_order_release);
|
||||
try
|
||||
{
|
||||
std::thread(interruptWorkerMain, rdram, runtime).detach();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
g_irq_worker_running.store(false, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
ensureInterruptWorkerRunning(rdram, runtime);
|
||||
}
|
||||
|
||||
uint64_t GetCurrentVSyncTick()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
|
||||
return g_vsync_tick_counter;
|
||||
}
|
||||
|
||||
void stopInterruptWorker()
|
||||
{
|
||||
g_irq_worker_stop.store(true, std::memory_order_release);
|
||||
g_irq_worker_cv.notify_all();
|
||||
std::unique_lock<std::mutex> lock(g_irq_worker_mutex);
|
||||
g_irq_worker_cv.wait_for(lock, std::chrono::milliseconds(500), []()
|
||||
{ return !g_irq_worker_running.load(std::memory_order_acquire); });
|
||||
g_vsync_cv.notify_all();
|
||||
}
|
||||
|
||||
uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
ensureInterruptWorkerRunning(rdram, runtime);
|
||||
std::unique_lock<std::mutex> lock(g_vsync_flag_mutex);
|
||||
uint64_t current = g_vsync_tick_counter;
|
||||
uint64_t result = current;
|
||||
waitWithGuestExecutionReleasedUntilUnlocked(
|
||||
runtime,
|
||||
lock,
|
||||
[&]()
|
||||
{
|
||||
g_vsync_cv.wait(lock, [current, runtime]()
|
||||
{ return g_vsync_tick_counter > current || (runtime != nullptr && runtime->isStopRequested()); });
|
||||
},
|
||||
[&]()
|
||||
{
|
||||
result = g_vsync_tick_counter;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
(void)WaitForNextVSyncTick(rdram, runtime);
|
||||
EeScheduler &ee = scheduler(rdram, ctx, runtime);
|
||||
ee.waitVSync(ee.currentVSyncTick(), fixedResult);
|
||||
}
|
||||
|
||||
void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t flagAddr = getRegU32(ctx, 4);
|
||||
const uint32_t tickAddr = getRegU32(ctx, 5);
|
||||
|
||||
const uint32_t flagAddress = getRegU32(ctx, 4);
|
||||
const uint32_t tickAddress = getRegU32(ctx, 5);
|
||||
if ((flagAddress != 0u && !getEeGuestStruct<uint32_t>(rdram, flagAddress)) ||
|
||||
(tickAddress != 0u && !getEeGuestStruct<uint64_t>(rdram, tickAddress)))
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
|
||||
g_vsync_registration.flagAddr = flagAddr;
|
||||
g_vsync_registration.tickAddr = tickAddr;
|
||||
setReturnS32(ctx, KE_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
writeGuestU32NoThrow(rdram, flagAddr, 0u);
|
||||
writeGuestU64NoThrow(rdram, tickAddr, 0u);
|
||||
ensureInterruptWorkerRunning(rdram, runtime);
|
||||
scheduler(rdram, ctx, runtime).setVSyncFlag(flagAddress, tickAddress);
|
||||
setReturnS32(ctx, KE_OK);
|
||||
}
|
||||
|
||||
void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
if (cause < 32u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
g_enabled_intc_mask |= (1u << cause);
|
||||
}
|
||||
if (cause == kIntcVblankStart || cause == kIntcVblankEnd)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
static std::atomic<uint32_t> s_enableLogCount{0u};
|
||||
const uint32_t logIndex = s_enableLogCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < 32u)
|
||||
{
|
||||
RUNTIME_LOG("[EnableIntc] cause=" << cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setCauseEnabled(rdram, ctx, runtime, false, true);
|
||||
}
|
||||
|
||||
void iEnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -542,24 +107,7 @@ namespace ps2_syscalls
|
||||
|
||||
void DisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
if (cause < 32u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
g_enabled_intc_mask &= ~(1u << cause);
|
||||
}
|
||||
if (cause == kIntcVblankStart || cause == kIntcVblankEnd)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
static std::atomic<uint32_t> s_disableLogCount{0u};
|
||||
const uint32_t logIndex = s_disableLogCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < 32u)
|
||||
{
|
||||
RUNTIME_LOG("[DisableIntc] cause=" << cause);
|
||||
}
|
||||
});
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setCauseEnabled(rdram, ctx, runtime, false, false);
|
||||
}
|
||||
|
||||
void iDisableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -569,47 +117,7 @@ namespace ps2_syscalls
|
||||
|
||||
void AddIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
IrqHandlerInfo info{};
|
||||
info.cause = getRegU32(ctx, 4);
|
||||
info.handler = getRegU32(ctx, 5);
|
||||
uint32_t next = getRegU32(ctx, 6);
|
||||
info.arg = getRegU32(ctx, 7);
|
||||
info.gp = getRegU32(ctx, 28);
|
||||
info.sp = getRegU32(ctx, 29);
|
||||
info.enabled = true;
|
||||
|
||||
int handlerId = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
info.order = (next == 0) ? --g_intc_head_order : ++g_intc_tail_order;
|
||||
handlerId = g_nextIntcHandlerId++;
|
||||
info.id = handlerId;
|
||||
g_intcHandlers[handlerId] = info;
|
||||
}
|
||||
|
||||
if (info.cause == kIntcVblankStart)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
static std::atomic<uint32_t> s_addHandlerLogCount{0u};
|
||||
const uint32_t logIndex = s_addHandlerLogCount.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < 32u)
|
||||
{
|
||||
auto flags = std::cout.flags();
|
||||
std::cout << "[AddIntcHandler] cause=" << info.cause
|
||||
<< " handler=0x" << std::hex << info.handler
|
||||
<< " arg=0x" << info.arg
|
||||
<< " gp=0x" << info.gp
|
||||
<< " sp=0x" << info.sp
|
||||
<< std::dec
|
||||
<< " id=" << handlerId
|
||||
<< std::endl;
|
||||
std::cout.flags(flags);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ensureInterruptWorkerRunning(rdram, runtime);
|
||||
setReturnS32(ctx, handlerId);
|
||||
addHandler(rdram, ctx, runtime, false);
|
||||
}
|
||||
|
||||
void AddIntcHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -619,40 +127,12 @@ namespace ps2_syscalls
|
||||
|
||||
void RemoveIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
if (handlerId > 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
auto it = g_intcHandlers.find(handlerId);
|
||||
if (it != g_intcHandlers.end() && it->second.cause == cause)
|
||||
{
|
||||
g_intcHandlers.erase(it);
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
removeHandler(rdram, ctx, runtime, false);
|
||||
}
|
||||
|
||||
void AddDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
IrqHandlerInfo info{};
|
||||
info.cause = getRegU32(ctx, 4);
|
||||
info.handler = getRegU32(ctx, 5);
|
||||
uint32_t next = getRegU32(ctx, 6);
|
||||
info.arg = getRegU32(ctx, 7);
|
||||
info.gp = getRegU32(ctx, 28);
|
||||
info.sp = getRegU32(ctx, 29);
|
||||
info.enabled = true;
|
||||
|
||||
int handlerId = 0;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
info.order = (next == 0) ? --g_dmac_head_order : ++g_dmac_tail_order;
|
||||
handlerId = g_nextDmacHandlerId++;
|
||||
info.id = handlerId;
|
||||
g_dmacHandlers[handlerId] = info;
|
||||
}
|
||||
setReturnS32(ctx, handlerId);
|
||||
addHandler(rdram, ctx, runtime, true);
|
||||
}
|
||||
|
||||
void AddDmacHandler2(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -662,81 +142,32 @@ namespace ps2_syscalls
|
||||
|
||||
void RemoveDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
if (handlerId > 0)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
auto it = g_dmacHandlers.find(handlerId);
|
||||
if (it != g_dmacHandlers.end() && it->second.cause == cause)
|
||||
{
|
||||
g_dmacHandlers.erase(it);
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
removeHandler(rdram, ctx, runtime, true);
|
||||
}
|
||||
|
||||
void EnableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
|
||||
{
|
||||
it->second.enabled = true;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setHandlerEnabled(rdram, ctx, runtime, false, true);
|
||||
}
|
||||
|
||||
void DisableIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (auto it = g_intcHandlers.find(handlerId); it != g_intcHandlers.end())
|
||||
{
|
||||
it->second.enabled = false;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setHandlerEnabled(rdram, ctx, runtime, false, false);
|
||||
}
|
||||
|
||||
void EnableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
|
||||
{
|
||||
it->second.enabled = true;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setHandlerEnabled(rdram, ctx, runtime, true, true);
|
||||
}
|
||||
|
||||
void DisableDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const int handlerId = static_cast<int>(getRegU32(ctx, 5));
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
if (auto it = g_dmacHandlers.find(handlerId); it != g_dmacHandlers.end())
|
||||
{
|
||||
it->second.enabled = false;
|
||||
}
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setHandlerEnabled(rdram, ctx, runtime, true, false);
|
||||
}
|
||||
|
||||
void EnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
if (cause < 32u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
g_enabled_dmac_mask |= (1u << cause);
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setCauseEnabled(rdram, ctx, runtime, true, true);
|
||||
}
|
||||
|
||||
void iEnableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -746,13 +177,7 @@ namespace ps2_syscalls
|
||||
|
||||
void DisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
if (cause < 32u)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
g_enabled_dmac_mask &= ~(1u << cause);
|
||||
}
|
||||
setReturnS32(ctx, KE_OK);
|
||||
setCauseEnabled(rdram, ctx, runtime, true, false);
|
||||
}
|
||||
|
||||
void iDisableDmac(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include "ps2_syscalls.h"
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
namespace interrupt_state
|
||||
{
|
||||
struct VSyncFlagRegistration
|
||||
{
|
||||
uint32_t flagAddr;
|
||||
uint32_t tickAddr;
|
||||
};
|
||||
|
||||
extern std::mutex g_irq_handler_mutex;
|
||||
extern std::mutex g_irq_worker_mutex;
|
||||
extern std::condition_variable g_irq_worker_cv;
|
||||
extern std::mutex g_vsync_flag_mutex;
|
||||
extern std::condition_variable g_vsync_cv;
|
||||
extern std::atomic<bool> g_irq_worker_stop;
|
||||
extern std::atomic<bool> g_irq_worker_running;
|
||||
extern uint32_t g_enabled_intc_mask;
|
||||
extern uint32_t g_enabled_dmac_mask;
|
||||
extern uint64_t g_vsync_tick_counter;
|
||||
extern VSyncFlagRegistration g_vsync_registration;
|
||||
}
|
||||
|
||||
void dispatchDmacHandlersForCause(uint8_t *rdram, PS2Runtime *runtime, uint32_t cause);
|
||||
void EnsureVSyncWorkerRunning(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick();
|
||||
void stopInterruptWorker();
|
||||
uint64_t WaitForNextVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, PS2Runtime *runtime);
|
||||
uint64_t GetCurrentVSyncTick(PS2Runtime *runtime);
|
||||
void WaitVSyncTick(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, int fixedResult);
|
||||
void SetVSyncFlag(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void EnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void iEnableIntc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
#include "Common.h"
|
||||
#include "Interrupt.h"
|
||||
#include "Lifecycle.h"
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
using namespace interrupt_state;
|
||||
|
||||
void notifyRuntimeStop()
|
||||
{
|
||||
stopInterruptWorker();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_irq_handler_mutex);
|
||||
g_intcHandlers.clear();
|
||||
g_dmacHandlers.clear();
|
||||
g_nextIntcHandlerId = 1;
|
||||
g_nextDmacHandlerId = 1;
|
||||
g_enabled_intc_mask = 0xFFFFFFFFu;
|
||||
g_enabled_dmac_mask = 0xFFFFFFFFu;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vsync_flag_mutex);
|
||||
g_vsync_registration = {};
|
||||
g_vsync_tick_counter = 0u;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<ThreadInfo>> threads;
|
||||
threads.reserve(32);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
|
||||
for (const auto &entry : g_threads)
|
||||
{
|
||||
if (entry.second)
|
||||
{
|
||||
threads.push_back(entry.second);
|
||||
}
|
||||
}
|
||||
g_threads.clear();
|
||||
g_nextThreadId = 2; // Reserve id 1 for main thread.
|
||||
}
|
||||
g_currentThreadId = 1;
|
||||
|
||||
for (const auto &threadInfo : threads)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(threadInfo->m);
|
||||
threadInfo->forceRelease = true;
|
||||
threadInfo->terminated = true;
|
||||
}
|
||||
threadInfo->cv.notify_all();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<SemaInfo>> semas;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
|
||||
semas.reserve(g_semas.size());
|
||||
for (const auto &entry : g_semas)
|
||||
{
|
||||
if (entry.second)
|
||||
{
|
||||
semas.push_back(entry.second);
|
||||
}
|
||||
}
|
||||
g_semas.clear();
|
||||
g_nextSemaId = 1;
|
||||
}
|
||||
for (const auto &sema : semas)
|
||||
{
|
||||
sema->cv.notify_all();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<EventFlagInfo>> eventFlags;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_event_flag_map_mutex);
|
||||
eventFlags.reserve(g_eventFlags.size());
|
||||
for (const auto &entry : g_eventFlags)
|
||||
{
|
||||
if (entry.second)
|
||||
{
|
||||
eventFlags.push_back(entry.second);
|
||||
}
|
||||
}
|
||||
g_eventFlags.clear();
|
||||
g_nextEventFlagId = 1;
|
||||
}
|
||||
for (const auto &eventFlag : eventFlags)
|
||||
{
|
||||
eventFlag->cv.notify_all();
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_alarm_mutex);
|
||||
g_alarms.clear();
|
||||
}
|
||||
g_alarm_cv.notify_all();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_exit_handler_mutex);
|
||||
g_exit_handlers.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_syscall_override_mutex);
|
||||
g_syscall_overrides.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void joinAllGuestHostThreads()
|
||||
{
|
||||
joinAllHostThreads();
|
||||
}
|
||||
|
||||
void detachAllGuestHostThreads()
|
||||
{
|
||||
detachAllHostThreads();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ps2_syscalls.h"
|
||||
|
||||
namespace ps2_syscalls
|
||||
{
|
||||
void notifyRuntimeStop();
|
||||
void joinAllGuestHostThreads();
|
||||
void detachAllGuestHostThreads();
|
||||
}
|
||||
@@ -6,13 +6,13 @@ namespace ps2_syscalls
|
||||
{
|
||||
namespace
|
||||
{
|
||||
SifRpcDebugEvent makeRpcDebugEvent(const char *op, R5900Context *ctx)
|
||||
SifRpcDebugEvent makeRpcDebugEvent(const char *op, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
SifRpcDebugEvent event{};
|
||||
event.op = op;
|
||||
event.pc = ctx ? ctx->pc : 0u;
|
||||
event.ra = ctx ? getRegU32(ctx, 31) : 0u;
|
||||
event.threadId = static_cast<uint32_t>(g_currentThreadId);
|
||||
event.threadId = runtime ? static_cast<uint32_t>(runtime->eeScheduler().currentThreadId()) : 0u;
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -139,34 +139,13 @@ namespace ps2_syscalls
|
||||
}
|
||||
#endif
|
||||
|
||||
bool signalRpcCompletionSema(uint32_t semaId)
|
||||
bool signalRpcCompletionSema(PS2Runtime *runtime, uint32_t semaId)
|
||||
{
|
||||
if (semaId == 0u || semaId > 0xFFFFu)
|
||||
if (!runtime || semaId == 0u || semaId > 0xFFFFu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto sema = lookupSemaInfo(static_cast<int>(semaId));
|
||||
if (!sema)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool signaled = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(sema->m);
|
||||
if (!sema->deleted && sema->count < sema->maxCount)
|
||||
{
|
||||
sema->count++;
|
||||
signaled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (signaled)
|
||||
{
|
||||
sema->cv.notify_one();
|
||||
}
|
||||
return signaled;
|
||||
return runtime->eeScheduler().signalSemaphore(static_cast<int>(semaId), true) >= 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -261,7 +240,7 @@ namespace ps2_syscalls
|
||||
RUNTIME_LOG("[SifInitRpc] Initialized");
|
||||
}
|
||||
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("InitRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("InitRpc", ctx, runtime);
|
||||
event.result = 0;
|
||||
pushSifRpcDebugEventLocked(event);
|
||||
setReturnS32(ctx, 0);
|
||||
@@ -277,7 +256,7 @@ namespace ps2_syscalls
|
||||
|
||||
if (!client)
|
||||
{
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("BindRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("BindRpc", ctx, runtime);
|
||||
event.clientPtr = clientPtr;
|
||||
event.sid = rpcId;
|
||||
event.mode = mode;
|
||||
@@ -342,7 +321,7 @@ namespace ps2_syscalls
|
||||
client->cbuf = 0;
|
||||
}
|
||||
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("BindRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("BindRpc", ctx, runtime);
|
||||
event.clientPtr = clientPtr;
|
||||
event.serverPtr = serverPtr;
|
||||
event.sid = rpcId;
|
||||
@@ -473,7 +452,7 @@ namespace ps2_syscalls
|
||||
auto *client = reinterpret_cast<t_SifRpcClientData *>(getMemPtr(rdram, clientPtr));
|
||||
if (!client)
|
||||
{
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("CallRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("CallRpc", ctx, runtime);
|
||||
event.clientPtr = clientPtr;
|
||||
event.sid = sidHint;
|
||||
event.rpcNum = rpcNum;
|
||||
@@ -535,24 +514,12 @@ namespace ps2_syscalls
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t resultPointer = 0u;
|
||||
bool handled = false;
|
||||
bool handledByIop = false;
|
||||
bool serverDispatched = false;
|
||||
bool callbackCompleted = false;
|
||||
bool copiedFallback = false;
|
||||
bool zeroedFallback = false;
|
||||
ps2x::iop::RpcResult iopResult{};
|
||||
|
||||
const auto completionSemaphore = [&]()
|
||||
uint32_t completionSemaphore = static_cast<uint32_t>(client->hdr.sema_id);
|
||||
if (completionSemaphore == 0xFFFFFFFFu || completionSemaphore == 0u)
|
||||
{
|
||||
uint32_t semaphore = static_cast<uint32_t>(client->hdr.sema_id);
|
||||
if (semaphore == 0xFFFFFFFFu || semaphore == 0u)
|
||||
{
|
||||
semaphore = endParameter;
|
||||
}
|
||||
return semaphore;
|
||||
};
|
||||
completionSemaphore = endParameter;
|
||||
}
|
||||
|
||||
{
|
||||
ps2x::iop::RpcRequest request{};
|
||||
@@ -569,171 +536,162 @@ namespace ps2_syscalls
|
||||
request.endParameter = endParameter;
|
||||
|
||||
iopResult = PS2IopTransport::handleRpc(runtime, rdram, ctx, request);
|
||||
handled = iopResult.handled;
|
||||
handledByIop = iopResult.handled;
|
||||
resultPointer = iopResult.resultAddress;
|
||||
|
||||
if (iopResult.signalNowaitCompletion &&
|
||||
(mode & kSifRpcModeNowait) != 0u)
|
||||
{
|
||||
(void)signalRpcCompletionSema(completionSemaphore());
|
||||
(void)signalRpcCompletionSema(runtime, completionSemaphore);
|
||||
}
|
||||
if (iopResult.signalCompletion)
|
||||
{
|
||||
(void)signalRpcCompletionSema(completionSemaphore());
|
||||
(void)signalRpcCompletionSema(runtime, completionSemaphore);
|
||||
}
|
||||
}
|
||||
|
||||
if (server && server->func != 0u && iopResult.serverDispatchPolicy != ps2x::iop::ServerDispatchPolicy::Suppress)
|
||||
uint32_t guestFunction = iopResult.guestFunction;
|
||||
uint32_t guestA0 = iopResult.guestArguments[0];
|
||||
uint32_t guestA1 = iopResult.guestArguments[1];
|
||||
uint32_t guestA2 = iopResult.guestArguments[2];
|
||||
uint32_t guestA3 = iopResult.guestArguments[3];
|
||||
uint32_t guestDefaultResult = iopResult.guestDefaultResultAddress;
|
||||
if (guestFunction == 0u && server && server->func != 0u &&
|
||||
iopResult.serverDispatchPolicy != ps2x::iop::ServerDispatchPolicy::Suppress)
|
||||
{
|
||||
uint32_t serverResult = 0u;
|
||||
serverDispatched = rpcInvokeFunction(rdram,
|
||||
ctx,
|
||||
runtime,
|
||||
server->func,
|
||||
rpcNum,
|
||||
server->buf,
|
||||
sendSize,
|
||||
0u,
|
||||
&serverResult);
|
||||
if (serverDispatched)
|
||||
guestFunction = server->func;
|
||||
guestA0 = rpcNum;
|
||||
guestA1 = server->buf;
|
||||
guestA2 = sendSize;
|
||||
guestDefaultResult = server->buf != 0u ? server->buf : receiveBuffer;
|
||||
}
|
||||
const bool serverDispatched = guestFunction != 0u && runtime->hasFunction(guestFunction);
|
||||
|
||||
auto finishCall = [=](const R5900Context *guestResult, R5900Context &parent)
|
||||
{
|
||||
bool handled = iopResult.handled;
|
||||
uint32_t resultPointer = iopResult.resultAddress;
|
||||
bool copiedFallback = false;
|
||||
bool zeroedFallback = false;
|
||||
if (guestResult)
|
||||
{
|
||||
handled = true;
|
||||
resultPointer = serverResult;
|
||||
if (resultPointer == 0u && server->buf != 0u)
|
||||
{
|
||||
resultPointer = server->buf;
|
||||
}
|
||||
resultPointer = getRegU32(guestResult, 2);
|
||||
if (resultPointer == 0u)
|
||||
{
|
||||
resultPointer = receiveBuffer;
|
||||
resultPointer = guestDefaultResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (receiveBuffer != 0u && receiveSize != 0u)
|
||||
{
|
||||
if (handled && resultPointer != 0u && resultPointer != receiveBuffer)
|
||||
if (receiveBuffer != 0u && receiveSize != 0u)
|
||||
{
|
||||
rpcCopyToRdram(rdram,
|
||||
receiveBuffer,
|
||||
resultPointer,
|
||||
receiveSize);
|
||||
}
|
||||
else if (!handled && sendBuf != 0u && sendSize != 0u && sendBuf != receiveBuffer)
|
||||
{
|
||||
const uint32_t copySize = std::min(sendSize, receiveSize);
|
||||
rpcCopyToRdram(rdram, receiveBuffer, sendBuf, copySize);
|
||||
copiedFallback = true;
|
||||
}
|
||||
else if (!handled)
|
||||
{
|
||||
rpcZeroRdram(rdram, receiveBuffer, receiveSize);
|
||||
zeroedFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (endFunction != 0u)
|
||||
{
|
||||
if (iopResult.callbackPolicy == ps2x::iop::CallbackPolicy::Suppress)
|
||||
{
|
||||
callbackCompleted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackCompleted = rpcInvokeFunction(rdram,
|
||||
ctx,
|
||||
runtime,
|
||||
endFunction,
|
||||
endParameter,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
nullptr);
|
||||
if (!callbackCompleted && endFunction >= 0x10000u)
|
||||
if (handled && resultPointer != 0u && resultPointer != receiveBuffer)
|
||||
{
|
||||
const uint32_t normalizedEndFunction = endFunction - 0x10000u;
|
||||
if (runtime->hasFunction(normalizedEndFunction))
|
||||
{
|
||||
callbackCompleted = rpcInvokeFunction(
|
||||
rdram,
|
||||
ctx,
|
||||
runtime,
|
||||
normalizedEndFunction,
|
||||
endParameter,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
nullptr);
|
||||
}
|
||||
rpcCopyToRdram(rdram, receiveBuffer, resultPointer, receiveSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (!callbackCompleted)
|
||||
{
|
||||
const bool signaled = signalRpcCompletionSema(completionSemaphore());
|
||||
static uint32_t unresolvedCallbackWarnings = 0u;
|
||||
if (unresolvedCallbackWarnings < 32u)
|
||||
else if (!handled && sendBuf != 0u && sendSize != 0u && sendBuf != receiveBuffer)
|
||||
{
|
||||
std::cerr
|
||||
<< "[SifCallRpc] unresolved end callback endFunc=0x"
|
||||
<< std::hex << endFunction
|
||||
<< " semaId=0x" << completionSemaphore()
|
||||
<< " fallbackSignal=" << std::dec
|
||||
<< (signaled ? 1 : 0) << std::endl;
|
||||
++unresolvedCallbackWarnings;
|
||||
rpcCopyToRdram(rdram, receiveBuffer, sendBuf, std::min(sendSize, receiveSize));
|
||||
copiedFallback = true;
|
||||
}
|
||||
else if (!handled)
|
||||
{
|
||||
rpcZeroRdram(rdram, receiveBuffer, receiveSize);
|
||||
zeroedFallback = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_rpc_mutex);
|
||||
g_rpc_clients[clientPtr].busy = false;
|
||||
}
|
||||
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("CallRpc", ctx);
|
||||
event.clientPtr = clientPtr;
|
||||
event.serverPtr = serverPtr;
|
||||
event.sid = sid;
|
||||
event.rpcNum = rpcNum;
|
||||
event.mode = mode;
|
||||
event.sendBuf = sendBuf;
|
||||
event.sendSize = sendSize;
|
||||
event.recvBuf = receiveBuffer;
|
||||
event.recvSize = receiveSize;
|
||||
event.resultPtr = resultPointer;
|
||||
event.endFunc = endFunction;
|
||||
event.endParam = endParameter;
|
||||
event.semaId = static_cast<uint32_t>(client->hdr.sema_id);
|
||||
event.flags =
|
||||
((mode & kSifRpcModeNowait) ? kSifRpcDebugFlagNowait : 0u) |
|
||||
(handledByIop ? kSifRpcDebugFlagHandledByHle : 0u) |
|
||||
(callbackCompleted ? kSifRpcDebugFlagCallback : 0u) |
|
||||
(serverDispatched ? kSifRpcDebugFlagServerDispatch : 0u) |
|
||||
(!handled ? kSifRpcDebugFlagUnhandled : 0u) |
|
||||
(copiedFallback ? kSifRpcDebugFlagFallbackCopy : 0u) |
|
||||
(zeroedFallback ? kSifRpcDebugFlagFallbackZero : 0u);
|
||||
fillRpcDebugPreview(rdram,
|
||||
sendBuf,
|
||||
sendSize,
|
||||
event.sendPreview,
|
||||
event.sendPreviewSize);
|
||||
fillRpcDebugPreview(rdram,
|
||||
receiveBuffer,
|
||||
receiveSize,
|
||||
event.recvPreview,
|
||||
event.recvPreviewSize);
|
||||
event.result = 0;
|
||||
|
||||
auto completeClient = [=](R5900Context &base, bool callbackCompleted)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_rpc_mutex);
|
||||
g_rpc_clients[clientPtr].busy = false;
|
||||
}
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("CallRpc", &base, runtime);
|
||||
event.clientPtr = clientPtr;
|
||||
event.serverPtr = serverPtr;
|
||||
event.sid = sid;
|
||||
event.rpcNum = rpcNum;
|
||||
event.mode = mode;
|
||||
event.sendBuf = sendBuf;
|
||||
event.sendSize = sendSize;
|
||||
event.recvBuf = receiveBuffer;
|
||||
event.recvSize = receiveSize;
|
||||
event.resultPtr = resultPointer;
|
||||
event.endFunc = endFunction;
|
||||
event.endParam = endParameter;
|
||||
event.semaId = completionSemaphore;
|
||||
event.flags =
|
||||
((mode & kSifRpcModeNowait) ? kSifRpcDebugFlagNowait : 0u) |
|
||||
(iopResult.handled ? kSifRpcDebugFlagHandledByHle : 0u) |
|
||||
(callbackCompleted ? kSifRpcDebugFlagCallback : 0u) |
|
||||
(serverDispatched ? kSifRpcDebugFlagServerDispatch : 0u) |
|
||||
(!handled ? kSifRpcDebugFlagUnhandled : 0u) |
|
||||
(copiedFallback ? kSifRpcDebugFlagFallbackCopy : 0u) |
|
||||
(zeroedFallback ? kSifRpcDebugFlagFallbackZero : 0u);
|
||||
fillRpcDebugPreview(rdram, sendBuf, sendSize, event.sendPreview, event.sendPreviewSize);
|
||||
fillRpcDebugPreview(rdram, receiveBuffer, receiveSize, event.recvPreview, event.recvPreviewSize);
|
||||
event.result = 0;
|
||||
#if PS2X_ENABLE_IOP_RPC_TRACE
|
||||
if ((event.flags & kSifRpcDebugFlagUnhandled) != 0u)
|
||||
{
|
||||
logUnhandledRpcTrace(event);
|
||||
}
|
||||
if ((event.flags & kSifRpcDebugFlagUnhandled) != 0u)
|
||||
{
|
||||
logUnhandledRpcTrace(event);
|
||||
}
|
||||
#endif
|
||||
pushSifRpcDebugEvent(event);
|
||||
pushSifRpcDebugEvent(event);
|
||||
};
|
||||
|
||||
setReturnS32(ctx, 0);
|
||||
setReturnS32(&parent, 0);
|
||||
if (endFunction == 0u || iopResult.callbackPolicy == ps2x::iop::CallbackPolicy::Suppress)
|
||||
{
|
||||
completeClient(parent, endFunction != 0u);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t callbackFunction = endFunction;
|
||||
if (!runtime->hasFunction(callbackFunction) && callbackFunction >= 0x10000u &&
|
||||
runtime->hasFunction(callbackFunction - 0x10000u))
|
||||
{
|
||||
callbackFunction -= 0x10000u;
|
||||
}
|
||||
if (!runtime->hasFunction(callbackFunction))
|
||||
{
|
||||
(void)signalRpcCompletionSema(runtime, completionSemaphore);
|
||||
completeClient(parent, false);
|
||||
return;
|
||||
}
|
||||
|
||||
GuestInvocation callback{};
|
||||
callback.kind = GuestInvocationKind::RpcCallback;
|
||||
callback.context = parent;
|
||||
callback.context.pc = callbackFunction;
|
||||
SET_GPR_U32(&callback.context, 4, endParameter);
|
||||
SET_GPR_U32(&callback.context, 29, runtime->eeScheduler().invocationStackTop());
|
||||
SET_GPR_U32(&callback.context, 31, 0u);
|
||||
callback.onComplete = [completeClient](const R5900Context &, R5900Context &base)
|
||||
{
|
||||
completeClient(base, true);
|
||||
};
|
||||
runtime->eeScheduler().invokeCurrent(std::move(callback));
|
||||
};
|
||||
|
||||
if (serverDispatched)
|
||||
{
|
||||
GuestInvocation invocation{};
|
||||
invocation.kind = GuestInvocationKind::RpcCallback;
|
||||
invocation.context = *ctx;
|
||||
invocation.context.pc = guestFunction;
|
||||
SET_GPR_U32(&invocation.context, 4, guestA0);
|
||||
SET_GPR_U32(&invocation.context, 5, guestA1);
|
||||
SET_GPR_U32(&invocation.context, 6, guestA2);
|
||||
SET_GPR_U32(&invocation.context, 7, guestA3);
|
||||
SET_GPR_U32(&invocation.context, 29, runtime->eeScheduler().invocationStackTop());
|
||||
SET_GPR_U32(&invocation.context, 31, 0u);
|
||||
invocation.onComplete = [finishCall](const R5900Context &completed, R5900Context &parent)
|
||||
{
|
||||
finishCall(&completed, parent);
|
||||
};
|
||||
runtime->eeScheduler().invokeCurrent(std::move(invocation));
|
||||
}
|
||||
finishCall(nullptr, *ctx);
|
||||
}
|
||||
|
||||
void SifRegisterRpc(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
@@ -754,7 +712,7 @@ namespace ps2_syscalls
|
||||
t_SifRpcServerData *sd = reinterpret_cast<t_SifRpcServerData *>(getMemPtr(rdram, sdPtr));
|
||||
if (!sd)
|
||||
{
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("RegisterRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("RegisterRpc", ctx, runtime);
|
||||
event.serverPtr = sdPtr;
|
||||
event.sid = sid;
|
||||
event.sendBuf = buf;
|
||||
@@ -836,7 +794,7 @@ namespace ps2_syscalls
|
||||
}
|
||||
|
||||
RUNTIME_LOG("[SifRegisterRpc] sid=0x" << std::hex << sid << " sd=0x" << sdPtr << std::dec);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("RegisterRpc", ctx);
|
||||
SifRpcDebugEvent event = makeRpcDebugEvent("RegisterRpc", ctx, runtime);
|
||||
event.serverPtr = sdPtr;
|
||||
event.sid = sid;
|
||||
event.sendBuf = buf;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -405,145 +405,41 @@ namespace ps2_syscalls
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
static uint32_t computeBuiltinFindAddressResult(uint8_t *rdram,
|
||||
uint32_t originalStart,
|
||||
uint32_t originalEnd,
|
||||
uint32_t target);
|
||||
|
||||
bool dispatchSyscallOverride(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
uint32_t handler = 0u;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_syscall_override_mutex);
|
||||
auto it = g_syscall_overrides.find(syscallNumber);
|
||||
if (it == g_syscall_overrides.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
handler = it->second;
|
||||
}
|
||||
|
||||
if (!runtime || !ctx || handler == 0u)
|
||||
if (!runtime || !ctx ||
|
||||
!runtime->findEeSyscallOverride(syscallNumber, handler) ||
|
||||
handler == 0u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t overrideA0 = getRegU32(ctx, 4);
|
||||
const uint32_t overrideA1 = getRegU32(ctx, 5);
|
||||
const uint32_t overrideA2 = getRegU32(ctx, 6);
|
||||
const uint32_t overrideA3 = getRegU32(ctx, 7);
|
||||
const uint32_t overridePc = ctx->pc;
|
||||
const uint32_t overrideRa = getRegU32(ctx, 31);
|
||||
|
||||
thread_local std::vector<uint32_t> s_activeSyscallOverrides;
|
||||
if (std::find(s_activeSyscallOverrides.begin(), s_activeSyscallOverrides.end(), syscallNumber) != s_activeSyscallOverrides.end())
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.bindMainContextForSyscall(*ctx, rdram);
|
||||
if (scheduler.hasInvocation(GuestInvocationKind::SyscallOverride, syscallNumber))
|
||||
{
|
||||
static std::atomic<uint32_t> s_reentrantLogs{0u};
|
||||
constexpr uint32_t kMaxReentrantLogs = 32u;
|
||||
const uint32_t logIndex = s_reentrantLogs.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < kMaxReentrantLogs)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[SyscallOverride:reentrant]"
|
||||
<< " syscall=0x" << std::hex << syscallNumber
|
||||
<< " handler=0x" << handler
|
||||
<< " pc=0x" << ctx->pc
|
||||
<< " ra=0x" << getRegU32(ctx, 31)
|
||||
<< std::dec << std::endl;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
s_activeSyscallOverrides.push_back(syscallNumber);
|
||||
struct ScopedActiveOverride
|
||||
if (!runtime->hasFunction(handler))
|
||||
{
|
||||
std::vector<uint32_t> &active;
|
||||
~ScopedActiveOverride()
|
||||
{
|
||||
if (!active.empty())
|
||||
{
|
||||
active.pop_back();
|
||||
}
|
||||
}
|
||||
} scopedActiveOverride{s_activeSyscallOverrides};
|
||||
|
||||
uint32_t retV0 = 0u;
|
||||
const bool invoked = rpcInvokeFunction(rdram,
|
||||
ctx,
|
||||
runtime,
|
||||
handler,
|
||||
getRegU32(ctx, 4),
|
||||
getRegU32(ctx, 5),
|
||||
getRegU32(ctx, 6),
|
||||
getRegU32(ctx, 7),
|
||||
&retV0);
|
||||
|
||||
if (syscallNumber == 0x83u)
|
||||
{
|
||||
const uint32_t builtinRet = computeBuiltinFindAddressResult(rdram, overrideA0, overrideA1, overrideA2);
|
||||
const bool mismatch = (retV0 != builtinRet);
|
||||
|
||||
static std::atomic<uint32_t> s_findAddressOverrideLogs{0u};
|
||||
static std::atomic<uint32_t> s_findAddressOverrideMismatchLogs{0u};
|
||||
constexpr uint32_t kMaxFindAddressOverrideLogs = 64u;
|
||||
constexpr uint32_t kMaxFindAddressOverrideMismatchLogs = 128u;
|
||||
|
||||
const uint32_t logIndex = s_findAddressOverrideLogs.fetch_add(1u, std::memory_order_relaxed);
|
||||
const uint32_t mismatchIndex = mismatch
|
||||
? s_findAddressOverrideMismatchLogs.fetch_add(1u, std::memory_order_relaxed)
|
||||
: 0u;
|
||||
if (logIndex < kMaxFindAddressOverrideLogs ||
|
||||
(mismatch && mismatchIndex < kMaxFindAddressOverrideMismatchLogs))
|
||||
{
|
||||
const uint32_t guestMinus20c = (retV0 != 0u) ? (retV0 - 0x20Cu) : 0u;
|
||||
const uint32_t guestMinus168 = (retV0 != 0u) ? (retV0 - 0x168u) : 0u;
|
||||
const uint32_t builtinMinus20c = (builtinRet != 0u) ? (builtinRet - 0x20Cu) : 0u;
|
||||
const uint32_t builtinMinus168 = (builtinRet != 0u) ? (builtinRet - 0x168u) : 0u;
|
||||
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[Syscall83:override]"
|
||||
<< " handler=0x" << std::hex << handler
|
||||
<< " invoked=" << (invoked ? "true" : "false")
|
||||
<< " pc=0x" << overridePc
|
||||
<< " ra=0x" << overrideRa
|
||||
<< " a0=0x" << overrideA0
|
||||
<< " a1=0x" << overrideA1
|
||||
<< " a2=0x" << overrideA2
|
||||
<< " a3=0x" << overrideA3
|
||||
<< " guestRet=0x" << retV0
|
||||
<< " builtinRet=0x" << builtinRet
|
||||
<< " guest-20c=0x" << guestMinus20c
|
||||
<< " builtin-20c=0x" << builtinMinus20c
|
||||
<< " guest-168=0x" << guestMinus168
|
||||
<< " builtin-168=0x" << builtinMinus168
|
||||
<< " match=" << (mismatch ? "false" : "true")
|
||||
<< std::dec << std::endl;
|
||||
});
|
||||
}
|
||||
setReturnS32(ctx, KE_ERROR);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!invoked)
|
||||
GuestInvocation invocation{};
|
||||
invocation.kind = GuestInvocationKind::SyscallOverride;
|
||||
invocation.tag = syscallNumber;
|
||||
invocation.context = *ctx;
|
||||
invocation.context.pc = handler;
|
||||
SET_GPR_U32(&invocation.context, 29, scheduler.invocationStackTop());
|
||||
SET_GPR_U32(&invocation.context, 31, 0u);
|
||||
invocation.onComplete = [](const R5900Context &completed, R5900Context &parent)
|
||||
{
|
||||
static std::atomic<uint32_t> s_fallbackLogs{0u};
|
||||
constexpr uint32_t kMaxFallbackLogs = 64u;
|
||||
const uint32_t logIndex = s_fallbackLogs.fetch_add(1u, std::memory_order_relaxed);
|
||||
if (logIndex < kMaxFallbackLogs)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[SyscallOverride:fallback]"
|
||||
<< " syscall=0x" << std::hex << syscallNumber
|
||||
<< " handler=0x" << handler
|
||||
<< " pc=0x" << ctx->pc
|
||||
<< " ra=0x" << getRegU32(ctx, 31)
|
||||
<< std::dec << std::endl;
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setReturnU32(ctx, retV0);
|
||||
return true;
|
||||
parent.r[2] = completed.r[2];
|
||||
};
|
||||
scheduler.invokeCurrent(std::move(invocation));
|
||||
}
|
||||
|
||||
static bool tryResolveGuestSyscallMirrorAddr(uint32_t syscallIndex, uint32_t &guestAddr)
|
||||
@@ -573,73 +469,20 @@ namespace ps2_syscalls
|
||||
}
|
||||
}
|
||||
|
||||
static void seedGuestSyscallTableProbeLocked(uint8_t *rdram)
|
||||
void initializeGuestKernelState(uint8_t *rdram, PS2Runtime *runtime)
|
||||
{
|
||||
writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 0u, kGuestSyscallTableGuestBase >> 16);
|
||||
writeGuestKernelWord(rdram, kGuestSyscallTableProbeBase + 8u, kGuestSyscallTableGuestBase & 0xFFFFu);
|
||||
g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 0u);
|
||||
g_syscall_mirror_addrs.insert(kGuestSyscallTableProbeBase + 8u);
|
||||
}
|
||||
|
||||
static void mirrorGuestSyscallEntryLocked(uint8_t *rdram, uint32_t syscallIndex, uint32_t handler)
|
||||
{
|
||||
uint32_t guestAddr = 0u;
|
||||
if (!tryResolveGuestSyscallMirrorAddr(syscallIndex, guestAddr))
|
||||
if (!runtime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
writeGuestKernelWord(rdram, guestAddr, handler);
|
||||
if (handler == 0u)
|
||||
{
|
||||
g_syscall_mirror_addrs.erase(guestAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
g_syscall_mirror_addrs.insert(guestAddr);
|
||||
}
|
||||
|
||||
void initializeGuestKernelState(uint8_t *rdram)
|
||||
{
|
||||
if (!rdram)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_syscall_override_mutex);
|
||||
for (uint32_t guestAddr : g_syscall_mirror_addrs)
|
||||
{
|
||||
writeGuestKernelWord(rdram, guestAddr, 0u);
|
||||
}
|
||||
g_syscall_mirror_addrs.clear();
|
||||
|
||||
seedGuestSyscallTableProbeLocked(rdram);
|
||||
|
||||
for (const auto &entry : g_syscall_overrides)
|
||||
{
|
||||
mirrorGuestSyscallEntryLocked(rdram, entry.first, entry.second);
|
||||
}
|
||||
runtime->initializeEeKernelState(rdram);
|
||||
}
|
||||
|
||||
void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)runtime;
|
||||
const uint32_t syscallIndex = getRegU32(ctx, 4);
|
||||
const uint32_t handler = getRegU32(ctx, 5);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_syscall_override_mutex);
|
||||
if (handler == 0u)
|
||||
{
|
||||
g_syscall_overrides.erase(syscallIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_syscall_overrides[syscallIndex] = handler;
|
||||
}
|
||||
|
||||
mirrorGuestSyscallEntryLocked(rdram, syscallIndex, handler);
|
||||
}
|
||||
runtime->setEeSyscallOverride(rdram, syscallIndex, handler);
|
||||
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
@@ -1091,7 +934,9 @@ namespace ps2_syscalls
|
||||
// GetThreadTLS (stub): return 0
|
||||
void GetThreadTLS(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
auto info = ensureCurrentThreadInfo(ctx);
|
||||
EeScheduler &ee = runtime->eeScheduler();
|
||||
ee.bindMainContextForSyscall(*ctx, rdram);
|
||||
GuestThread *info = ee.currentThread();
|
||||
if (!info)
|
||||
{
|
||||
setReturnU32(ctx, 0);
|
||||
@@ -1149,11 +994,10 @@ namespace ps2_syscalls
|
||||
return;
|
||||
}
|
||||
|
||||
int tid = g_currentThreadId;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_exit_handler_mutex);
|
||||
g_exit_handlers[tid].push_back({func, arg});
|
||||
}
|
||||
EeScheduler &ee = runtime->eeScheduler();
|
||||
ee.bindMainContextForSyscall(*ctx, rdram);
|
||||
const int tid = ee.currentThreadId();
|
||||
runtime->addEeExitHandler(tid, func, arg);
|
||||
|
||||
setReturnS32(ctx, 0);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace ps2_syscalls
|
||||
void sceSifLoadModule(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void sceSifLoadModuleBuffer(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void TODO(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime, uint32_t encodedSyscallId);
|
||||
void initializeGuestKernelState(uint8_t *rdram);
|
||||
void initializeGuestKernelState(uint8_t *rdram, PS2Runtime *runtime);
|
||||
void SetSyscall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void SetupThread(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
void SetupHeap(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_log.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include "Kernel/Syscalls/Helpers/State.h"
|
||||
@@ -32,39 +33,45 @@
|
||||
namespace
|
||||
{
|
||||
#if defined(PS2X_ENABLE_DEBUG_UI) && !defined(PLATFORM_VITA)
|
||||
const char *threadStatusName(int status)
|
||||
const char *threadStatusName(EeThreadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case THS_RUN:
|
||||
case EeThreadStatus::Running:
|
||||
return "RUN";
|
||||
case THS_READY:
|
||||
case EeThreadStatus::Ready:
|
||||
return "READY";
|
||||
case THS_WAIT:
|
||||
case EeThreadStatus::Waiting:
|
||||
return "WAIT";
|
||||
case THS_SUSPEND:
|
||||
case EeThreadStatus::Suspended:
|
||||
return "SUSPEND";
|
||||
case THS_WAITSUSPEND:
|
||||
case EeThreadStatus::WaitingSuspended:
|
||||
return "WAITSUSP";
|
||||
case THS_DORMANT:
|
||||
case EeThreadStatus::Dormant:
|
||||
return "DORMANT";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
const char *waitTypeName(int waitType)
|
||||
const char *waitTypeName(EeWaitReason waitType)
|
||||
{
|
||||
switch (waitType)
|
||||
{
|
||||
case TSW_NONE:
|
||||
case EeWaitReason::None:
|
||||
return "NONE";
|
||||
case TSW_SLEEP:
|
||||
case EeWaitReason::Sleep:
|
||||
return "SLEEP";
|
||||
case TSW_SEMA:
|
||||
case EeWaitReason::Semaphore:
|
||||
return "SEMA";
|
||||
case TSW_EVENT:
|
||||
case EeWaitReason::EventFlag:
|
||||
return "EVENT";
|
||||
case EeWaitReason::VSync:
|
||||
return "VSYNC";
|
||||
case EeWaitReason::External:
|
||||
return "EXTERNAL";
|
||||
case EeWaitReason::Mpeg:
|
||||
return "MPEG";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
@@ -698,7 +705,7 @@ namespace
|
||||
const uint32_t gp = runtime.m_debugGp.load(std::memory_order_relaxed);
|
||||
|
||||
ImGui::Text("Runtime: %s", runtime.isStopRequested() ? "stop requested" : "running");
|
||||
ImGui::Text("Guest execution waiters: %u", runtime.guestExecutionWaiterCountForTesting());
|
||||
ImGui::Text("EE executor: %s", runtime.eeScheduler().isExecutingGuest() ? "guest" : "scheduler");
|
||||
ImGui::Separator();
|
||||
textHex32("PC", pc);
|
||||
ImGui::SameLine();
|
||||
@@ -756,60 +763,15 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void drawThreadsTab()
|
||||
void drawThreadsTab(PS2Runtime &runtime)
|
||||
{
|
||||
struct ThreadRow
|
||||
{
|
||||
int id = 0;
|
||||
uint32_t entry = 0;
|
||||
uint32_t stack = 0;
|
||||
uint32_t stackSize = 0;
|
||||
uint32_t gp = 0;
|
||||
uint32_t priority = 0;
|
||||
int status = 0;
|
||||
int waitType = 0;
|
||||
int waitId = 0;
|
||||
int currentPriority = 0;
|
||||
int wakeupCount = 0;
|
||||
int suspendCount = 0;
|
||||
uint32_t currentPc = 0u;
|
||||
bool terminated = false;
|
||||
};
|
||||
|
||||
std::vector<ThreadRow> rows;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_thread_map_mutex);
|
||||
rows.reserve(g_threads.size());
|
||||
for (const auto &[id, ptr] : g_threads)
|
||||
{
|
||||
if (!ptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ThreadRow row{};
|
||||
row.id = id;
|
||||
{
|
||||
std::lock_guard<std::mutex> threadLock(ptr->m);
|
||||
row.entry = ptr->entry;
|
||||
row.stack = ptr->stack;
|
||||
row.stackSize = ptr->stackSize;
|
||||
row.gp = ptr->gp;
|
||||
row.priority = ptr->priority;
|
||||
row.currentPriority = ptr->currentPriority;
|
||||
row.status = ptr->status;
|
||||
row.waitType = ptr->waitType;
|
||||
row.waitId = ptr->waitId;
|
||||
row.wakeupCount = ptr->wakeupCount;
|
||||
row.suspendCount = ptr->suspendCount;
|
||||
}
|
||||
row.currentPc = ptr->currentPc.load(std::memory_order_relaxed);
|
||||
row.terminated = ptr->terminated.load(std::memory_order_relaxed);
|
||||
rows.push_back(row);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Text("Threads: %zu activeThreads=%d", rows.size(), g_activeThreads.load(std::memory_order_relaxed));
|
||||
if (ImGui::BeginTable("threads", 12, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
|
||||
const EeKernelSnapshot snapshot = runtime.eeScheduler().snapshot();
|
||||
ImGui::Text("Threads: %zu running=%d", snapshot.threads.size(), snapshot.runningThreadId);
|
||||
ImGui::Text("EE cycle: %llu slice end: %llu next event: %llu",
|
||||
static_cast<unsigned long long>(snapshot.eeCycle),
|
||||
static_cast<unsigned long long>(snapshot.sliceEndCycle),
|
||||
static_cast<unsigned long long>(snapshot.nextEventCycle));
|
||||
if (ImGui::BeginTable("threads", 11, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, 320)))
|
||||
{
|
||||
ImGui::TableSetupColumn("ID");
|
||||
ImGui::TableSetupColumn("Status");
|
||||
@@ -822,9 +784,8 @@ namespace
|
||||
ImGui::TableSetupColumn("Prio");
|
||||
ImGui::TableSetupColumn("Wake");
|
||||
ImGui::TableSetupColumn("Susp");
|
||||
ImGui::TableSetupColumn("Term");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const ThreadRow &row : rows)
|
||||
for (const EeThreadSnapshot &row : snapshot.threads)
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
@@ -832,11 +793,11 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", threadStatusName(row.status));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", waitTypeName(row.waitType));
|
||||
ImGui::Text("%s", waitTypeName(row.waitReason));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", row.waitId);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.currentPc);
|
||||
ImGui::Text("0x%08X", row.pc);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.entry);
|
||||
ImGui::TableNextColumn();
|
||||
@@ -844,23 +805,21 @@ namespace
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", row.gp);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d/%u", row.currentPriority, row.priority);
|
||||
ImGui::Text("%d/%d", row.currentPriority, row.initialPriority);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", row.wakeupCount);
|
||||
ImGui::Text("%u", row.wakeupCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", row.suspendCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%u", row.terminated ? 1u : 0u);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void drawKernelTab()
|
||||
void drawKernelTab(PS2Runtime &runtime)
|
||||
{
|
||||
const EeKernelSnapshot snapshot = runtime.eeScheduler().snapshot();
|
||||
ImGui::SeparatorText("Semaphores");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sema_map_mutex);
|
||||
if (ImGui::BeginTable("semas", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
||||
{
|
||||
ImGui::TableSetupColumn("ID");
|
||||
@@ -871,26 +830,23 @@ namespace
|
||||
ImGui::TableSetupColumn("Attr");
|
||||
ImGui::TableSetupColumn("Deleted");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const auto &[id, sema] : g_semas)
|
||||
for (const EeSemaphoreSnapshot &sema : snapshot.semaphores)
|
||||
{
|
||||
if (!sema)
|
||||
continue;
|
||||
std::lock_guard<std::mutex> semaLock(sema->m);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", id);
|
||||
ImGui::Text("%d", sema.id);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", sema->count);
|
||||
ImGui::Text("%d", sema.count);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", sema->maxCount);
|
||||
ImGui::Text("%d", sema.maxCount);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", sema->initCount);
|
||||
ImGui::Text("-");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", sema->waiters);
|
||||
ImGui::Text("%u", sema.waiters);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", sema->attr);
|
||||
ImGui::Text("-");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%u", sema->deleted ? 1u : 0u);
|
||||
ImGui::Text("0");
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -898,7 +854,6 @@ namespace
|
||||
|
||||
ImGui::SeparatorText("Event flags");
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_event_flag_map_mutex);
|
||||
if (ImGui::BeginTable("evf", 6, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable))
|
||||
{
|
||||
ImGui::TableSetupColumn("ID");
|
||||
@@ -908,24 +863,21 @@ namespace
|
||||
ImGui::TableSetupColumn("Attr");
|
||||
ImGui::TableSetupColumn("Deleted");
|
||||
ImGui::TableHeadersRow();
|
||||
for (const auto &[id, evf] : g_eventFlags)
|
||||
for (const EeEventFlagSnapshot &evf : snapshot.eventFlags)
|
||||
{
|
||||
if (!evf)
|
||||
continue;
|
||||
std::lock_guard<std::mutex> evfLock(evf->m);
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", id);
|
||||
ImGui::Text("%d", evf.id);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", evf->bits);
|
||||
ImGui::Text("0x%08X", evf.bits);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", evf->initBits);
|
||||
ImGui::Text("0x%08X", evf.initBits);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", evf->waiters);
|
||||
ImGui::Text("%u", evf.waiters);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("0x%08X", evf->attr);
|
||||
ImGui::Text("0x%08X", evf.attr);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%u", evf->deleted ? 1u : 0u);
|
||||
ImGui::Text("0");
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -2249,12 +2201,12 @@ void PS2DebugPanel::draw(PS2Runtime &runtime)
|
||||
}
|
||||
if (ImGui::BeginTabItem("Threads"))
|
||||
{
|
||||
drawThreadsTab();
|
||||
drawThreadsTab(runtime);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Kernel"))
|
||||
{
|
||||
drawKernelTab();
|
||||
drawKernelTab(runtime);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("IOP/SIF"))
|
||||
|
||||
@@ -490,13 +490,16 @@ void GS::reset()
|
||||
m_preferredDisplaySourceFrame = {};
|
||||
m_preferredDisplayDestFbp = 0;
|
||||
m_hasPreferredDisplaySource = false;
|
||||
m_hostPresentationFrame.clear();
|
||||
m_hostPresentationWidth = 0u;
|
||||
m_hostPresentationHeight = 0u;
|
||||
m_hostPresentationDisplayFbp = 0u;
|
||||
m_hostPresentationSourceFbp = 0u;
|
||||
m_hostPresentationUsedPreferred = false;
|
||||
m_hasHostPresentationFrame = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> presentationLock(m_presentationMutex);
|
||||
m_hostPresentationFrame.clear();
|
||||
m_hostPresentationWidth = 0u;
|
||||
m_hostPresentationHeight = 0u;
|
||||
m_hostPresentationDisplayFbp = 0u;
|
||||
m_hostPresentationSourceFbp = 0u;
|
||||
m_hostPresentationUsedPreferred = false;
|
||||
m_hasHostPresentationFrame = false;
|
||||
}
|
||||
|
||||
m_debugHistoryWrite = 0;
|
||||
m_debugHistoryCount = 0;
|
||||
@@ -562,12 +565,15 @@ GSDebugSnapshot GS::getDebugSnapshot() const
|
||||
snapshot.preferredDisplaySourceFrame = m_preferredDisplaySourceFrame;
|
||||
snapshot.preferredDisplayDestFbp = m_preferredDisplayDestFbp;
|
||||
snapshot.hasPreferredDisplaySource = m_hasPreferredDisplaySource;
|
||||
snapshot.hostPresentationWidth = m_hostPresentationWidth;
|
||||
snapshot.hostPresentationHeight = m_hostPresentationHeight;
|
||||
snapshot.hostPresentationDisplayFbp = m_hostPresentationDisplayFbp;
|
||||
snapshot.hostPresentationSourceFbp = m_hostPresentationSourceFbp;
|
||||
snapshot.hostPresentationUsedPreferred = m_hostPresentationUsedPreferred;
|
||||
snapshot.hasHostPresentationFrame = m_hasHostPresentationFrame;
|
||||
{
|
||||
std::lock_guard<std::mutex> presentationLock(m_presentationMutex);
|
||||
snapshot.hostPresentationWidth = m_hostPresentationWidth;
|
||||
snapshot.hostPresentationHeight = m_hostPresentationHeight;
|
||||
snapshot.hostPresentationDisplayFbp = m_hostPresentationDisplayFbp;
|
||||
snapshot.hostPresentationSourceFbp = m_hostPresentationSourceFbp;
|
||||
snapshot.hostPresentationUsedPreferred = m_hostPresentationUsedPreferred;
|
||||
snapshot.hasHostPresentationFrame = m_hasHostPresentationFrame;
|
||||
}
|
||||
snapshot.localToHostPendingBytes = (m_localToHostReadPos < m_localToHostBuffer.size())
|
||||
? (m_localToHostBuffer.size() - m_localToHostReadPos)
|
||||
: 0u;
|
||||
@@ -637,7 +643,7 @@ void GS::recordDebugEventUnlocked(GSDebugHistoryEntry entry)
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t tick = ps2_syscalls::GetCurrentVSyncTick();
|
||||
const uint64_t tick = m_privRegs ? m_privRegs->vsyncTick.load(std::memory_order_acquire) : 0u;
|
||||
if (m_debugLastVsyncTick == UINT64_MAX)
|
||||
{
|
||||
m_debugLastVsyncTick = tick;
|
||||
@@ -959,8 +965,100 @@ bool GS::copyFrameToHostRgbaUnlocked(const GSFrameReg &frame,
|
||||
|
||||
void GS::latchHostPresentationFrame()
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
latchHostPresentationFrameUnlocked();
|
||||
thread_local std::vector<uint8_t> vramSnapshot;
|
||||
thread_local GS presentationGs;
|
||||
|
||||
thread_local GSRegisters privateRegisters{};
|
||||
GSFrameReg contextFrames[2]{};
|
||||
GSFrameReg preferredSource{};
|
||||
uint32_t preferredDestFbp = 0u;
|
||||
bool hasPreferredSource = false;
|
||||
uint32_t vramSize = 0u;
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
if (!m_privRegs || !m_vram || m_vramSize == 0u)
|
||||
{
|
||||
std::lock_guard<std::mutex> presentationLock(m_presentationMutex);
|
||||
m_hostPresentationFrame.clear();
|
||||
m_hostPresentationWidth = 0u;
|
||||
m_hostPresentationHeight = 0u;
|
||||
m_hostPresentationDisplayFbp = 0u;
|
||||
m_hostPresentationSourceFbp = 0u;
|
||||
m_hostPresentationUsedPreferred = false;
|
||||
m_hasHostPresentationFrame = false;
|
||||
return;
|
||||
}
|
||||
|
||||
vramSize = m_vramSize;
|
||||
vramSnapshot.resize(vramSize);
|
||||
std::memcpy(vramSnapshot.data(), m_vram, vramSize);
|
||||
|
||||
privateRegisters.pmode = m_privRegs->pmode;
|
||||
privateRegisters.smode1 = m_privRegs->smode1;
|
||||
privateRegisters.smode2 = m_privRegs->smode2;
|
||||
privateRegisters.srfsh = m_privRegs->srfsh;
|
||||
privateRegisters.synch1 = m_privRegs->synch1;
|
||||
privateRegisters.synch2 = m_privRegs->synch2;
|
||||
privateRegisters.syncv = m_privRegs->syncv;
|
||||
privateRegisters.dispfb1 = m_privRegs->dispfb1;
|
||||
privateRegisters.display1 = m_privRegs->display1;
|
||||
privateRegisters.dispfb2 = m_privRegs->dispfb2;
|
||||
privateRegisters.display2 = m_privRegs->display2;
|
||||
privateRegisters.extbuf = m_privRegs->extbuf;
|
||||
privateRegisters.extdata = m_privRegs->extdata;
|
||||
privateRegisters.extwrite = m_privRegs->extwrite;
|
||||
privateRegisters.bgcolor = m_privRegs->bgcolor;
|
||||
privateRegisters.csr.store(m_privRegs->csr.load(std::memory_order_acquire), std::memory_order_relaxed);
|
||||
privateRegisters.vsyncTick.store(m_privRegs->vsyncTick.load(std::memory_order_acquire), std::memory_order_relaxed);
|
||||
privateRegisters.imr = m_privRegs->imr;
|
||||
privateRegisters.busdir = m_privRegs->busdir;
|
||||
privateRegisters.siglblid = m_privRegs->siglblid;
|
||||
|
||||
contextFrames[0] = m_ctx[0].frame;
|
||||
contextFrames[1] = m_ctx[1].frame;
|
||||
preferredSource = m_preferredDisplaySourceFrame;
|
||||
preferredDestFbp = m_preferredDisplayDestFbp;
|
||||
hasPreferredSource = m_hasPreferredDisplaySource;
|
||||
}
|
||||
|
||||
presentationGs.init(vramSnapshot.data(), vramSize, &privateRegisters);
|
||||
presentationGs.m_ctx[0].frame = contextFrames[0];
|
||||
presentationGs.m_ctx[1].frame = contextFrames[1];
|
||||
presentationGs.m_preferredDisplaySourceFrame = preferredSource;
|
||||
presentationGs.m_preferredDisplayDestFbp = preferredDestFbp;
|
||||
presentationGs.m_hasPreferredDisplaySource = hasPreferredSource;
|
||||
presentationGs.latchHostPresentationFrameUnlocked();
|
||||
|
||||
uint32_t displayFbp = 0u;
|
||||
uint32_t sourceFbp = 0u;
|
||||
uint32_t width = 0u;
|
||||
uint32_t height = 0u;
|
||||
bool usedPreferred = false;
|
||||
bool hasFrame = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> presentationLock(m_presentationMutex);
|
||||
m_hostPresentationFrame.swap(presentationGs.m_hostPresentationFrame);
|
||||
m_hostPresentationWidth = presentationGs.m_hostPresentationWidth;
|
||||
m_hostPresentationHeight = presentationGs.m_hostPresentationHeight;
|
||||
m_hostPresentationDisplayFbp = presentationGs.m_hostPresentationDisplayFbp;
|
||||
m_hostPresentationSourceFbp = presentationGs.m_hostPresentationSourceFbp;
|
||||
m_hostPresentationUsedPreferred = presentationGs.m_hostPresentationUsedPreferred;
|
||||
m_hasHostPresentationFrame = presentationGs.m_hasHostPresentationFrame;
|
||||
|
||||
displayFbp = m_hostPresentationDisplayFbp;
|
||||
sourceFbp = m_hostPresentationSourceFbp;
|
||||
width = m_hostPresentationWidth;
|
||||
height = m_hostPresentationHeight;
|
||||
usedPreferred = m_hostPresentationUsedPreferred;
|
||||
hasFrame = m_hasHostPresentationFrame;
|
||||
}
|
||||
|
||||
if (hasFrame)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
recordPresentDebugEventUnlocked(displayFbp, sourceFbp, width, height, usedPreferred);
|
||||
}
|
||||
}
|
||||
|
||||
void GS::latchHostPresentationFrameUnlocked()
|
||||
@@ -980,7 +1078,7 @@ void GS::latchHostPresentationFrameUnlocked()
|
||||
const GSPmodeState pmode = decodePmode(m_privRegs->pmode);
|
||||
const GSSmode2State smode2 = decodeSMode2(m_privRegs->smode2);
|
||||
const bool applyFieldMode = smode2.interlaced && !smode2.frameMode;
|
||||
const bool oddField = (ps2_syscalls::GetCurrentVSyncTick() & 1ull) != 0ull;
|
||||
const bool oddField = (m_privRegs->vsyncTick.load(std::memory_order_acquire) & 1ull) != 0ull;
|
||||
const GSFrameReg displayFrame1 = decodeDisplayFrame(m_privRegs->dispfb1);
|
||||
const GSFrameReg displayFrame2 = decodeDisplayFrame(m_privRegs->dispfb2);
|
||||
const GSDisplayReadOrigin displayOrigin1 = decodeDisplayReadOrigin(m_privRegs->dispfb1);
|
||||
@@ -1249,7 +1347,7 @@ bool GS::copyLatchedHostPresentationFrame(std::vector<uint8_t> &outPixels,
|
||||
uint32_t *outSourceFbp,
|
||||
bool *outUsedPreferred) const
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
std::lock_guard<std::mutex> lock(m_presentationMutex);
|
||||
if (!m_hasHostPresentationFrame || m_hostPresentationFrame.empty())
|
||||
{
|
||||
outPixels.clear();
|
||||
@@ -1355,7 +1453,7 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
bool pre = ((tagLo >> 46) & 1) != 0;
|
||||
if (pre)
|
||||
{
|
||||
writeRegister(GS_REG_PRIM, (tagLo >> 47) & 0x7FF);
|
||||
writeRegisterUnlocked(GS_REG_PRIM, (tagLo >> 47) & 0x7FF);
|
||||
}
|
||||
|
||||
uint8_t regs[16];
|
||||
@@ -1385,7 +1483,7 @@ void GS::processGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
{
|
||||
if (offset + 8 > sizeBytes)
|
||||
return;
|
||||
writeRegister(regs[r], loadLE64(data + offset));
|
||||
writeRegisterUnlocked(regs[r], loadLE64(data + offset));
|
||||
offset += 8;
|
||||
}
|
||||
}
|
||||
@@ -1420,7 +1518,7 @@ bool GS::processNativePackedGIFPacket(const uint8_t *data, uint32_t sizeBytes)
|
||||
|
||||
const bool pre = ((tag.lo >> 46u) & 1u) != 0u;
|
||||
if (pre)
|
||||
writeRegister(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu);
|
||||
writeRegisterUnlocked(GS_REG_PRIM, (tag.lo >> 47u) & 0x7FFu);
|
||||
|
||||
uint32_t offset = tag.payloadOffset;
|
||||
for (uint32_t loop = 0u; loop < tag.nloop; ++loop)
|
||||
@@ -1451,13 +1549,23 @@ void GS::uploadImageNative(uint64_t bitbltbuf,
|
||||
uint32_t sizeBytes)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
uploadImageNativeUnlocked(bitbltbuf, trxpos, trxreg, trxdir, data, sizeBytes);
|
||||
}
|
||||
|
||||
void GS::uploadImageNativeUnlocked(uint64_t bitbltbuf,
|
||||
uint64_t trxpos,
|
||||
uint64_t trxreg,
|
||||
uint64_t trxdir,
|
||||
const uint8_t *data,
|
||||
uint32_t sizeBytes)
|
||||
{
|
||||
if (!data || sizeBytes == 0 || !m_vram)
|
||||
return;
|
||||
|
||||
writeRegister(GS_REG_BITBLTBUF, bitbltbuf);
|
||||
writeRegister(GS_REG_TRXPOS, trxpos);
|
||||
writeRegister(GS_REG_TRXREG, trxreg);
|
||||
writeRegister(GS_REG_TRXDIR, trxdir);
|
||||
writeRegisterUnlocked(GS_REG_BITBLTBUF, bitbltbuf);
|
||||
writeRegisterUnlocked(GS_REG_TRXPOS, trxpos);
|
||||
writeRegisterUnlocked(GS_REG_TRXREG, trxreg);
|
||||
writeRegisterUnlocked(GS_REG_TRXDIR, trxdir);
|
||||
processImageData(data, sizeBytes);
|
||||
++m_nativeImageUploadCount;
|
||||
}
|
||||
@@ -1528,7 +1636,7 @@ bool GS::tryProcessNativeImageUploadPacket(const uint8_t *data, uint32_t sizeByt
|
||||
if (offset + imageBytes != sizeBytes)
|
||||
return false;
|
||||
|
||||
uploadImageNative(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes);
|
||||
uploadImageNativeUnlocked(regs[0], regs[1], regs[2], regs[3], data + offset, imageBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1537,7 +1645,7 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
|
||||
switch (regDesc)
|
||||
{
|
||||
case 0x00:
|
||||
writeRegister(GS_REG_PRIM, lo & 0x7FF);
|
||||
writeRegisterUnlocked(GS_REG_PRIM, lo & 0x7FF);
|
||||
break;
|
||||
case 0x01:
|
||||
m_curR = static_cast<uint8_t>(lo & 0xFF);
|
||||
@@ -1705,13 +1813,13 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
|
||||
case 0x0E:
|
||||
{
|
||||
uint8_t addr = static_cast<uint8_t>(hi & 0xFF);
|
||||
writeRegister(addr, lo);
|
||||
writeRegisterUnlocked(addr, lo);
|
||||
break;
|
||||
}
|
||||
case 0x0F:
|
||||
break;
|
||||
default:
|
||||
writeRegister(regDesc, lo);
|
||||
writeRegisterUnlocked(regDesc, lo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1719,6 +1827,11 @@ void GS::writeRegisterPacked(uint8_t regDesc, uint64_t lo, uint64_t hi)
|
||||
void GS::writeRegister(uint8_t regAddr, uint64_t value)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_stateMutex);
|
||||
writeRegisterUnlocked(regAddr, value);
|
||||
}
|
||||
|
||||
void GS::writeRegisterUnlocked(uint8_t regAddr, uint64_t value)
|
||||
{
|
||||
const bool interestingReg =
|
||||
regAddr == GS_REG_PRIM ||
|
||||
regAddr == GS_REG_RGBAQ ||
|
||||
|
||||
@@ -458,21 +458,17 @@ bool PS2IopHostAdapter::invokeGuestFunction(uint64_t callToken,
|
||||
uint32_t a3,
|
||||
uint32_t *resultAddress)
|
||||
{
|
||||
if (!m_activeContext || callToken == 0 || callToken != m_activeToken)
|
||||
(void)callToken;
|
||||
(void)address;
|
||||
(void)a0;
|
||||
(void)a1;
|
||||
(void)a2;
|
||||
(void)a3;
|
||||
if (resultAddress)
|
||||
{
|
||||
return false;
|
||||
*resultAddress = 0u;
|
||||
}
|
||||
return rpcInvokeFunction(m_activeRdram
|
||||
? m_activeRdram
|
||||
: m_runtime.memory().getRDRAM(),
|
||||
m_activeContext,
|
||||
&m_runtime,
|
||||
address,
|
||||
a0,
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
resultAddress);
|
||||
return false;
|
||||
}
|
||||
|
||||
void PS2IopHostAdapter::log(ps2x::iop::LogLevel level, std::string_view message)
|
||||
|
||||
+181
-357
@@ -5,6 +5,7 @@
|
||||
#include "game_overrides.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
#include "ThreadNaming.h"
|
||||
#include "Kernel/Stubs/Audio.h"
|
||||
#include "Kernel/Stubs/GS.h"
|
||||
@@ -107,9 +108,6 @@ namespace
|
||||
};
|
||||
|
||||
thread_local DispatchHistory g_dispatchHistory;
|
||||
thread_local std::unordered_map<PS2Runtime *, uint32_t> g_guestExecutionDepths;
|
||||
thread_local uint32_t g_deferredGuestYieldDepth = 0u;
|
||||
thread_local bool g_deferredGuestYieldPending = false;
|
||||
|
||||
bool computeFileCrc32(const std::string &path, uint32_t &crcOut)
|
||||
{
|
||||
@@ -375,40 +373,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::GuestExecutionScope::GuestExecutionScope(PS2Runtime *runtime) noexcept
|
||||
: m_runtime(runtime)
|
||||
{
|
||||
if (m_runtime)
|
||||
{
|
||||
m_runtime->enterGuestExecution();
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::GuestExecutionScope::~GuestExecutionScope()
|
||||
{
|
||||
if (m_runtime)
|
||||
{
|
||||
m_runtime->leaveGuestExecution();
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::GuestExecutionReleaseScope::GuestExecutionReleaseScope(PS2Runtime *runtime) noexcept
|
||||
: m_runtime(runtime)
|
||||
{
|
||||
if (m_runtime)
|
||||
{
|
||||
m_depth = m_runtime->releaseGuestExecution();
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::GuestExecutionReleaseScope::~GuestExecutionReleaseScope()
|
||||
{
|
||||
if (m_runtime && m_depth != 0u)
|
||||
{
|
||||
m_runtime->reacquireGuestExecution(m_depth);
|
||||
}
|
||||
}
|
||||
|
||||
static void UploadFrame(Texture2D &tex, PS2Runtime *rt, uint32_t &outWidth, uint32_t &outHeight)
|
||||
{
|
||||
static uint64_t s_lastPresentationTick = std::numeric_limits<uint64_t>::max();
|
||||
@@ -422,7 +386,7 @@ static void UploadFrame(Texture2D &tex, PS2Runtime *rt, uint32_t &outWidth, uint
|
||||
static std::vector<uint8_t> s_scratch;
|
||||
static std::vector<uint8_t> s_uploadBuffer(DEFAULT_FB_SIZE, 0u);
|
||||
|
||||
const uint64_t currentTick = ps2_syscalls::GetCurrentVSyncTick();
|
||||
const uint64_t currentTick = rt->eeScheduler().currentVSyncTick();
|
||||
const bool needsLatch = !s_hasLatchedInitialFrame || currentTick != s_lastPresentationTick;
|
||||
if (needsLatch)
|
||||
{
|
||||
@@ -517,6 +481,7 @@ PS2Runtime::PS2Runtime()
|
||||
{
|
||||
m_iopHost = std::make_unique<PS2IopHostAdapter>(*this);
|
||||
m_iopSubsystem = std::make_unique<ps2x::iop::IopSubsystem>(*m_iopHost);
|
||||
m_eeScheduler = std::make_unique<EeScheduler>(*this);
|
||||
#if defined(PS2X_IOP_ENABLE_PLUGINS) && PS2X_IOP_ENABLE_PLUGINS && \
|
||||
!defined(PLATFORM_VITA) && (defined(_WIN32) || defined(__linux__))
|
||||
if (const char *applicationDirectory = GetApplicationDirectory();
|
||||
@@ -569,7 +534,6 @@ PS2Runtime::~PS2Runtime()
|
||||
try
|
||||
{
|
||||
requestStop();
|
||||
ps2_syscalls::detachAllGuestHostThreads();
|
||||
m_iopSubsystem.reset();
|
||||
m_iopHost.reset();
|
||||
#if defined(PLATFORM_VITA)
|
||||
@@ -658,28 +622,38 @@ bool PS2Runtime::syncCoreSubsystems()
|
||||
m_memory.setGifArbiter(&m_gifArbiter);
|
||||
m_memory.setVu1MscalCallback([this](uint32_t startPC, uint32_t top, uint32_t itop)
|
||||
{
|
||||
R5900Context *cpuContext = m_eeScheduler ? m_eeScheduler->currentContext() : nullptr;
|
||||
if (!cpuContext)
|
||||
{
|
||||
cpuContext = &m_cpuContext;
|
||||
}
|
||||
m_vu1.state().dBitEnabled =
|
||||
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
|
||||
(cpuContext->vu0_fbrst & (1u << 10)) != 0u;
|
||||
m_vu1.state().tBitEnabled =
|
||||
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
|
||||
(cpuContext->vu0_fbrst & (1u << 11)) != 0u;
|
||||
m_vu1.execute(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
||||
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
||||
m_gs, &m_memory, startPC, top, itop, 65536);
|
||||
m_cpuContext.vu0_vpu_stat =
|
||||
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
|
||||
cpuContext->vu0_vpu_stat =
|
||||
(cpuContext->vu0_vpu_stat & ~0x0600u) |
|
||||
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
|
||||
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
|
||||
m_memory.setVu1MscntCallback([this](uint32_t top, uint32_t itop)
|
||||
{
|
||||
R5900Context *cpuContext = m_eeScheduler ? m_eeScheduler->currentContext() : nullptr;
|
||||
if (!cpuContext)
|
||||
{
|
||||
cpuContext = &m_cpuContext;
|
||||
}
|
||||
m_vu1.state().dBitEnabled =
|
||||
(m_cpuContext.vu0_fbrst & (1u << 10)) != 0u;
|
||||
(cpuContext->vu0_fbrst & (1u << 10)) != 0u;
|
||||
m_vu1.state().tBitEnabled =
|
||||
(m_cpuContext.vu0_fbrst & (1u << 11)) != 0u;
|
||||
(cpuContext->vu0_fbrst & (1u << 11)) != 0u;
|
||||
m_vu1.resume(m_memory.getVU1Code(), PS2_VU1_CODE_SIZE,
|
||||
m_memory.getVU1Data(), PS2_VU1_DATA_SIZE,
|
||||
m_gs, &m_memory, top, itop, 65536);
|
||||
m_cpuContext.vu0_vpu_stat =
|
||||
(m_cpuContext.vu0_vpu_stat & ~0x0600u) |
|
||||
cpuContext->vu0_vpu_stat =
|
||||
(cpuContext->vu0_vpu_stat & ~0x0600u) |
|
||||
(m_vu1.state().stoppedByD ? 0x0200u : 0u) |
|
||||
(m_vu1.state().stoppedByT ? 0x0400u : 0u); });
|
||||
resetIop();
|
||||
@@ -1338,6 +1312,14 @@ bool PS2Runtime::dispatchGuestBranch(uint8_t *rdram,
|
||||
ctx->pc = targetPc;
|
||||
const bool isCall = (kind == GuestBranchKind::DirectCall || kind == GuestBranchKind::IndirectCall);
|
||||
|
||||
// Every inter-function transfer is also a deterministic EE safe point.
|
||||
// Backward edges inside generated functions use eeCheckpointDue(), while
|
||||
// this charge bounds straight-line call chains that have no local loop.
|
||||
if (m_eeScheduler && m_eeScheduler->checkpointDue(EeScheduler::kGuestDispatchCycles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kind == GuestBranchKind::Return)
|
||||
{
|
||||
if (!hasFunction(targetPc))
|
||||
@@ -1995,252 +1977,6 @@ uint32_t PS2Runtime::reserveAsyncCallbackStack(uint32_t size, uint32_t alignment
|
||||
return top - 0x10u;
|
||||
}
|
||||
|
||||
void PS2Runtime::dispatchLoop(uint8_t *rdram, R5900Context *ctx)
|
||||
{
|
||||
uint32_t lastPc = std::numeric_limits<uint32_t>::max();
|
||||
uint32_t samePcCount = 0;
|
||||
constexpr uint32_t kSamePcYieldInterval = 0x4000u;
|
||||
|
||||
while (!isStopRequested())
|
||||
{
|
||||
const uint32_t pc = ctx->pc;
|
||||
|
||||
if (pc == lastPc)
|
||||
{
|
||||
++samePcCount;
|
||||
if ((samePcCount % kSamePcYieldInterval) == 0u)
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
RUNTIME_LOG("CPU is doing some work at PC 0x" << std::hex << pc << ". PC not updating.");
|
||||
});
|
||||
std::this_thread::yield();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
samePcCount = 0;
|
||||
lastPc = pc;
|
||||
}
|
||||
|
||||
m_debugPc.store(pc, std::memory_order_relaxed);
|
||||
m_debugRa.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0)), std::memory_order_relaxed);
|
||||
m_debugSp.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0)), std::memory_order_relaxed);
|
||||
m_debugGp.store(static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0)), std::memory_order_relaxed);
|
||||
|
||||
RecompiledFunction fn = lookupFunction(pc);
|
||||
const uint32_t dispatchedPc = pc;
|
||||
const uint32_t dispatchedRa = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
||||
|
||||
uint64_t handoffBaseline = 0u;
|
||||
{
|
||||
GuestExecutionScope guestExecution(this);
|
||||
fn(rdram, ctx, this);
|
||||
handoffBaseline = guestExecutionHandoffEpochSnapshot();
|
||||
}
|
||||
|
||||
waitForGuestExecutionHandoff(handoffBaseline);
|
||||
|
||||
if (ctx->pc == 0u)
|
||||
{
|
||||
const uint32_t ra = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[31], 0));
|
||||
const uint32_t sp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[29], 0));
|
||||
const uint32_t gp = static_cast<uint32_t>(_mm_extract_epi32(ctx->r[28], 0));
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
std::cerr << "[dispatch:pc-zero] from=0x" << std::hex << dispatchedPc
|
||||
<< " fromRa=0x" << dispatchedRa
|
||||
<< " ra=0x" << ra
|
||||
<< " sp=0x" << sp
|
||||
<< " gp=0x" << gp
|
||||
<< " trace=" << formatDispatchHistory()
|
||||
<< std::dec << std::endl;
|
||||
});
|
||||
|
||||
// PC=0 means this guest thread returned (usually via jr $ra with RA=0).
|
||||
// Do not request a global runtime stop here: other guest threads may still run.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::enterGuestExecution()
|
||||
{
|
||||
uint32_t &depth = g_guestExecutionDepths[this];
|
||||
|
||||
if (depth != 0u)
|
||||
{
|
||||
m_guestExecutionMutex.lock();
|
||||
++depth;
|
||||
return;
|
||||
}
|
||||
|
||||
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
|
||||
m_guestExecutionMutex.lock();
|
||||
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
|
||||
depth = 1u;
|
||||
markGuestExecutionAcquired();
|
||||
}
|
||||
|
||||
void PS2Runtime::leaveGuestExecution()
|
||||
{
|
||||
auto it = g_guestExecutionDepths.find(this);
|
||||
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
--it->second;
|
||||
m_guestExecutionMutex.unlock();
|
||||
if (it->second == 0u)
|
||||
{
|
||||
g_guestExecutionDepths.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t PS2Runtime::releaseGuestExecution()
|
||||
{
|
||||
auto it = g_guestExecutionDepths.find(this);
|
||||
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
const uint32_t depth = it->second;
|
||||
for (uint32_t i = 0; i < depth; ++i)
|
||||
{
|
||||
m_guestExecutionMutex.unlock();
|
||||
}
|
||||
g_guestExecutionDepths.erase(it);
|
||||
return depth;
|
||||
}
|
||||
|
||||
void PS2Runtime::reacquireGuestExecution(uint32_t depth)
|
||||
{
|
||||
if (depth == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t &heldDepth = g_guestExecutionDepths[this];
|
||||
uint32_t remaining = depth;
|
||||
|
||||
if (heldDepth == 0u)
|
||||
{
|
||||
m_guestExecutionWaiters.fetch_add(1u, std::memory_order_acq_rel);
|
||||
m_guestExecutionMutex.lock();
|
||||
m_guestExecutionWaiters.fetch_sub(1u, std::memory_order_acq_rel);
|
||||
heldDepth = 1u;
|
||||
markGuestExecutionAcquired();
|
||||
--remaining;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < remaining; ++i)
|
||||
{
|
||||
m_guestExecutionMutex.lock();
|
||||
++heldDepth;
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::markGuestExecutionAcquired()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_guestExecutionHandoffMutex);
|
||||
m_guestExecutionHandoffEpoch.fetch_add(1u, std::memory_order_acq_rel);
|
||||
}
|
||||
m_guestExecutionHandoffCv.notify_all();
|
||||
}
|
||||
|
||||
void PS2Runtime::waitForGuestExecutionHandoff()
|
||||
{
|
||||
waitForGuestExecutionHandoff(guestExecutionHandoffEpochSnapshot());
|
||||
}
|
||||
|
||||
void PS2Runtime::waitForGuestExecutionHandoff(uint64_t baselineEpoch)
|
||||
{
|
||||
// Lock-free fast path
|
||||
if (m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
|
||||
|
||||
if (m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool handedOff = m_guestExecutionHandoffCv.wait_for(
|
||||
lock,
|
||||
std::chrono::milliseconds(2),
|
||||
[&]()
|
||||
{
|
||||
return m_guestExecutionWaiters.load(std::memory_order_acquire) == 0u ||
|
||||
m_guestExecutionHandoffEpoch.load(std::memory_order_relaxed) != baselineEpoch ||
|
||||
isStopRequested();
|
||||
});
|
||||
|
||||
if (!handedOff)
|
||||
{
|
||||
m_guestExecutionHandoffTimeouts.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
PS2Runtime::DeferredGuestYieldScope::DeferredGuestYieldScope(bool &pendingOut) noexcept
|
||||
: m_pendingOut(pendingOut)
|
||||
{
|
||||
++g_deferredGuestYieldDepth;
|
||||
}
|
||||
|
||||
PS2Runtime::DeferredGuestYieldScope::~DeferredGuestYieldScope()
|
||||
{
|
||||
if (--g_deferredGuestYieldDepth == 0u && g_deferredGuestYieldPending)
|
||||
{
|
||||
g_deferredGuestYieldPending = false;
|
||||
m_pendingOut = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::yieldGuestExecutionAfterWake()
|
||||
{
|
||||
if (g_deferredGuestYieldDepth != 0u)
|
||||
{
|
||||
g_deferredGuestYieldPending = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = g_guestExecutionDepths.find(this);
|
||||
if (it == g_guestExecutionDepths.end() || it->second == 0u)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t handoffEpoch = m_guestExecutionHandoffEpoch.load(std::memory_order_acquire);
|
||||
{
|
||||
GuestExecutionReleaseScope releaseGuestExecution(this);
|
||||
std::unique_lock<std::mutex> lock(m_guestExecutionHandoffMutex);
|
||||
m_guestExecutionHandoffCv.wait_for(lock, std::chrono::milliseconds(1), [&]()
|
||||
{ return m_guestExecutionHandoffEpoch.load(std::memory_order_acquire) != handoffEpoch; });
|
||||
}
|
||||
}
|
||||
|
||||
bool PS2Runtime::shouldPreemptGuestExecution()
|
||||
{
|
||||
constexpr uint32_t kContendedYieldInterval = 1024u;
|
||||
constexpr uint32_t kUncontendedYieldInterval = 16384u;
|
||||
|
||||
thread_local uint32_t s_backEdgeYieldCounter = 0u;
|
||||
const uint32_t waiterCount = m_guestExecutionWaiters.load(std::memory_order_acquire);
|
||||
const uint32_t yieldInterval = (waiterCount != 0u) ? kContendedYieldInterval : kUncontendedYieldInterval;
|
||||
if (++s_backEdgeYieldCounter < yieldInterval)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
s_backEdgeYieldCounter = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t PS2Runtime::Load8(uint8_t *rdram, R5900Context *ctx, uint32_t vaddr)
|
||||
{
|
||||
try
|
||||
@@ -2411,7 +2147,10 @@ void PS2Runtime::kickGifDmaChainFromMMIO(uint8_t *rdram,
|
||||
void PS2Runtime::requestStop()
|
||||
{
|
||||
m_stopRequested.store(true, std::memory_order_relaxed);
|
||||
ps2_syscalls::notifyRuntimeStop();
|
||||
if (m_eeScheduler)
|
||||
{
|
||||
m_eeScheduler->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
bool PS2Runtime::isStopRequested() const
|
||||
@@ -2419,6 +2158,146 @@ bool PS2Runtime::isStopRequested() const
|
||||
return m_stopRequested.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
EeScheduler &PS2Runtime::eeScheduler()
|
||||
{
|
||||
return *m_eeScheduler;
|
||||
}
|
||||
|
||||
const EeScheduler &PS2Runtime::eeScheduler() const
|
||||
{
|
||||
return *m_eeScheduler;
|
||||
}
|
||||
|
||||
void PS2Runtime::postEeEvent(EeEvent event)
|
||||
{
|
||||
m_eeScheduler->postEvent(event);
|
||||
}
|
||||
|
||||
bool PS2Runtime::eeCheckpointDue(uint32_t cycles) noexcept
|
||||
{
|
||||
return m_eeScheduler->checkpointDue(cycles);
|
||||
}
|
||||
|
||||
[[noreturn]] void PS2Runtime::eeWaitVSyncTicks(uint32_t ticks, uint32_t resumePc)
|
||||
{
|
||||
const uint64_t currentTick = m_eeScheduler->currentVSyncTick();
|
||||
const uint64_t waitTicks = std::max<uint64_t>(1u, ticks);
|
||||
m_eeScheduler->waitVSync(currentTick + waitTicks - 1u,
|
||||
0,
|
||||
[resumePc](R5900Context &context)
|
||||
{
|
||||
context.pc = resumePc;
|
||||
});
|
||||
}
|
||||
|
||||
void PS2Runtime::addEeExitHandler(int threadId, uint32_t function, uint32_t argument)
|
||||
{
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
m_eeExitHandlers[threadId].push_back({function, argument});
|
||||
}
|
||||
|
||||
std::vector<PS2Runtime::EeExitHandlerRegistration> PS2Runtime::takeEeExitHandlers(int threadId)
|
||||
{
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
auto it = m_eeExitHandlers.find(threadId);
|
||||
if (it == m_eeExitHandlers.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
auto handlers = std::move(it->second);
|
||||
m_eeExitHandlers.erase(it);
|
||||
return handlers;
|
||||
}
|
||||
|
||||
void PS2Runtime::removeEeExitHandlers(int threadId)
|
||||
{
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
m_eeExitHandlers.erase(threadId);
|
||||
}
|
||||
|
||||
bool PS2Runtime::findEeSyscallOverride(uint32_t syscallNumber, uint32_t &handler) const
|
||||
{
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
const auto it = m_eeSyscallOverrides.find(syscallNumber);
|
||||
if (it == m_eeSyscallOverrides.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
handler = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PS2Runtime::setEeSyscallOverride(uint8_t *rdram, uint32_t syscallNumber, uint32_t handler)
|
||||
{
|
||||
constexpr uint32_t kTableBase = 0x80011F80u & 0x1FFFFFFFu;
|
||||
constexpr uint32_t kMirrorLimit = 0x00080000u;
|
||||
const int64_t offset = static_cast<int64_t>(static_cast<int32_t>(syscallNumber)) * 4;
|
||||
const int64_t address = static_cast<int64_t>(kTableBase) + offset;
|
||||
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
if (handler == 0u)
|
||||
{
|
||||
m_eeSyscallOverrides.erase(syscallNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_eeSyscallOverrides[syscallNumber] = handler;
|
||||
}
|
||||
if (!rdram || address < 0 || address + 4 > kMirrorLimit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const uint32_t guestAddress = static_cast<uint32_t>(address);
|
||||
std::memcpy(rdram + guestAddress, &handler, sizeof(handler));
|
||||
if (handler == 0u)
|
||||
{
|
||||
m_eeSyscallMirrorAddresses.erase(guestAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_eeSyscallMirrorAddresses.insert(guestAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::initializeEeKernelState(uint8_t *rdram)
|
||||
{
|
||||
if (!rdram)
|
||||
{
|
||||
return;
|
||||
}
|
||||
constexpr uint32_t kTableGuestBase = 0x80011F80u;
|
||||
constexpr uint32_t kTableBase = kTableGuestBase & 0x1FFFFFFFu;
|
||||
constexpr uint32_t kMirrorLimit = 0x00080000u;
|
||||
constexpr uint32_t kProbeBase = 0x000002F0u;
|
||||
|
||||
std::lock_guard lock(m_eeKernelStateMutex);
|
||||
for (const uint32_t address : m_eeSyscallMirrorAddresses)
|
||||
{
|
||||
const uint32_t zero = 0u;
|
||||
std::memcpy(rdram + address, &zero, sizeof(zero));
|
||||
}
|
||||
m_eeSyscallMirrorAddresses.clear();
|
||||
const uint32_t high = kTableGuestBase >> 16;
|
||||
const uint32_t low = kTableGuestBase & 0xFFFFu;
|
||||
std::memcpy(rdram + kProbeBase, &high, sizeof(high));
|
||||
std::memcpy(rdram + kProbeBase + 8u, &low, sizeof(low));
|
||||
m_eeSyscallMirrorAddresses.insert(kProbeBase);
|
||||
m_eeSyscallMirrorAddresses.insert(kProbeBase + 8u);
|
||||
|
||||
for (const auto &[syscallNumber, handler] : m_eeSyscallOverrides)
|
||||
{
|
||||
const int64_t offset = static_cast<int64_t>(static_cast<int32_t>(syscallNumber)) * 4;
|
||||
const int64_t address = static_cast<int64_t>(kTableBase) + offset;
|
||||
if (address < 0 || address + 4 > kMirrorLimit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const uint32_t guestAddress = static_cast<uint32_t>(address);
|
||||
std::memcpy(rdram + guestAddress, &handler, sizeof(handler));
|
||||
m_eeSyscallMirrorAddresses.insert(guestAddress);
|
||||
}
|
||||
}
|
||||
|
||||
void PS2Runtime::HandleIntegerOverflow(R5900Context *ctx)
|
||||
{
|
||||
raiseCop0Exception(ctx, EXCEPTION_INTEGER_OVERFLOW);
|
||||
@@ -2430,9 +2309,8 @@ void PS2Runtime::run()
|
||||
ps2_stubs::resetSifState();
|
||||
resetIop();
|
||||
ps2_stubs::resetAudioStubState();
|
||||
ps2_stubs::resetGsSyncVCallbackState();
|
||||
ps2_stubs::resetMpegStubState();
|
||||
ps2_syscalls::initializeGuestKernelState(m_memory.getRDRAM());
|
||||
initializeEeKernelState(m_memory.getRDRAM());
|
||||
m_cpuContext.r[4] = _mm_setzero_si128();
|
||||
m_cpuContext.r[5] = _mm_setzero_si128();
|
||||
m_cpuContext.r[29] = _mm_set_epi64x(0, static_cast<int64_t>(PS2_RAM_SIZE - 0x10u));
|
||||
@@ -2448,7 +2326,6 @@ void PS2Runtime::run()
|
||||
Texture2D frameTex = LoadTextureFromImage(blank);
|
||||
UnloadImage(blank);
|
||||
|
||||
g_activeThreads.store(1, std::memory_order_relaxed);
|
||||
std::atomic<bool> gameThreadFinished{false};
|
||||
|
||||
std::thread gameThread([&]()
|
||||
@@ -2456,7 +2333,8 @@ void PS2Runtime::run()
|
||||
ThreadNaming::SetCurrentThreadName("GameThread");
|
||||
try
|
||||
{
|
||||
dispatchLoop(m_memory.getRDRAM(), &m_cpuContext);
|
||||
m_eeScheduler->reset(m_memory.getRDRAM(), m_cpuContext);
|
||||
m_eeScheduler->run();
|
||||
uint32_t pc = m_debugPc.load(std::memory_order_relaxed);
|
||||
RUNTIME_LOG("Game thread returned. PC=0x" << std::hex << pc
|
||||
<< " RA=0x" << static_cast<uint32_t>(_mm_extract_epi32(m_cpuContext.r[31], 0)) << std::dec << std::endl);
|
||||
@@ -2469,13 +2347,10 @@ void PS2Runtime::run()
|
||||
{
|
||||
std::cerr << "Error during program execution: unknown exception" << std::endl;
|
||||
}
|
||||
g_activeThreads.fetch_sub(1, std::memory_order_relaxed);
|
||||
gameThreadFinished.store(true, std::memory_order_release); });
|
||||
|
||||
ps2_syscalls::EnsureVSyncWorkerRunning(m_memory.getRDRAM(), this);
|
||||
|
||||
uint64_t tick = 0;
|
||||
while (!isStopRequested() && g_activeThreads.load(std::memory_order_relaxed) > 0)
|
||||
while (!isStopRequested() && !gameThreadFinished.load(std::memory_order_acquire))
|
||||
{
|
||||
PS2_IF_AGRESSIVE_LOGS({
|
||||
tick++;
|
||||
@@ -2490,7 +2365,7 @@ void PS2Runtime::run()
|
||||
const uint32_t dbgRa = m_debugRa.load(std::memory_order_relaxed);
|
||||
const uint32_t dbgSp = m_debugSp.load(std::memory_order_relaxed);
|
||||
const uint32_t dbgGp = m_debugGp.load(std::memory_order_relaxed);
|
||||
const int activeThreads = g_activeThreads.load(std::memory_order_relaxed);
|
||||
const auto eeSnapshot = m_eeScheduler->snapshot();
|
||||
|
||||
RUNTIME_LOG("[run:tick] tick=" << tick
|
||||
<< " pc=0x" << std::hex << dbgPc
|
||||
@@ -2500,7 +2375,7 @@ void PS2Runtime::run()
|
||||
<< " dispfb1=0x" << gs.dispfb1
|
||||
<< " display1=0x" << gs.display1
|
||||
<< std::dec
|
||||
<< " activeThreads=" << activeThreads
|
||||
<< " activeThreads=" << eeSnapshot.threads.size()
|
||||
<< " dma=" << curDma
|
||||
<< " gif=" << curGif
|
||||
<< " gsw=" << curGs
|
||||
@@ -2543,54 +2418,9 @@ void PS2Runtime::run()
|
||||
}
|
||||
|
||||
requestStop();
|
||||
|
||||
const auto joinDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
||||
while (!gameThreadFinished.load(std::memory_order_acquire) &&
|
||||
std::chrono::steady_clock::now() < joinDeadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
if (gameThread.joinable())
|
||||
{
|
||||
if (gameThreadFinished.load(std::memory_order_acquire))
|
||||
{
|
||||
gameThread.join();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "[run] game thread did not stop within timeout; detaching" << std::endl;
|
||||
gameThread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
const auto workerDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
|
||||
while (g_activeThreads.load(std::memory_order_relaxed) > 0 &&
|
||||
std::chrono::steady_clock::now() < workerDeadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
if (g_activeThreads.load(std::memory_order_relaxed) > 0)
|
||||
{
|
||||
requestStop();
|
||||
const auto finalWorkerDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000);
|
||||
while (g_activeThreads.load(std::memory_order_relaxed) > 0 &&
|
||||
std::chrono::steady_clock::now() < finalWorkerDeadline)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}
|
||||
|
||||
if (g_activeThreads.load(std::memory_order_relaxed) == 0)
|
||||
{
|
||||
ps2_syscalls::joinAllGuestHostThreads();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "[run] guest host threads did not stop within timeout; detaching remaining worker threads"
|
||||
<< std::endl;
|
||||
ps2_syscalls::detachAllGuestHostThreads();
|
||||
gameThread.join();
|
||||
}
|
||||
|
||||
if (m_debugUiInitialized && m_debugUiShutdownCallback)
|
||||
@@ -2601,11 +2431,5 @@ void PS2Runtime::run()
|
||||
UnloadTexture(frameTex);
|
||||
CloseWindow();
|
||||
|
||||
const int remainingThreads = g_activeThreads.load(std::memory_order_relaxed);
|
||||
RUNTIME_LOG("[run] exiting loop, activeThreads=" << remainingThreads);
|
||||
if (remainingThreads > 0)
|
||||
{
|
||||
std::cerr << "[run] warning: " << remainingThreads
|
||||
<< " guest worker thread(s) still active during shutdown." << std::endl;
|
||||
}
|
||||
RUNTIME_LOG("[run] exiting loop");
|
||||
}
|
||||
|
||||
+54
-23
@@ -26,38 +26,69 @@
|
||||
namespace
|
||||
{
|
||||
#if defined(__ANDROID__)
|
||||
int g_logcatPipeFds[2]{-1, -1};
|
||||
std::thread g_logcatThread;
|
||||
|
||||
void stopLogcatRedirect()
|
||||
{
|
||||
std::fflush(stdout);
|
||||
std::fflush(stderr);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
if (g_logcatPipeFds[1] >= 0)
|
||||
{
|
||||
close(g_logcatPipeFds[1]);
|
||||
g_logcatPipeFds[1] = -1;
|
||||
}
|
||||
if (g_logcatThread.joinable())
|
||||
{
|
||||
g_logcatThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void redirectStdioToLogcat()
|
||||
{
|
||||
static int pipeFds[2];
|
||||
if (pipe(pipeFds) != 0)
|
||||
if (pipe(g_logcatPipeFds) != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setvbuf(stdout, nullptr, _IOLBF, 0);
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
dup2(pipeFds[1], STDOUT_FILENO);
|
||||
dup2(pipeFds[1], STDERR_FILENO);
|
||||
dup2(g_logcatPipeFds[1], STDOUT_FILENO);
|
||||
dup2(g_logcatPipeFds[1], STDERR_FILENO);
|
||||
|
||||
std::thread([]()
|
||||
{
|
||||
FILE *reader = fdopen(pipeFds[0], "r");
|
||||
if (!reader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), reader))
|
||||
{
|
||||
size_t len = std::strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
{
|
||||
line[len - 1] = '\0';
|
||||
}
|
||||
__android_log_write(ANDROID_LOG_INFO, "ps2x", line);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
g_logcatThread = std::thread([]()
|
||||
{
|
||||
FILE *reader = fdopen(g_logcatPipeFds[0], "r");
|
||||
if (!reader)
|
||||
{
|
||||
return;
|
||||
}
|
||||
char line[1024];
|
||||
while (fgets(line, sizeof(line), reader))
|
||||
{
|
||||
size_t len = std::strlen(line);
|
||||
if (len > 0 && line[len - 1] == '\n')
|
||||
{
|
||||
line[len - 1] = '\0';
|
||||
}
|
||||
__android_log_write(ANDROID_LOG_INFO, "ps2x", line);
|
||||
}
|
||||
fclose(reader);
|
||||
g_logcatPipeFds[0] = -1;
|
||||
});
|
||||
if (std::atexit(stopLogcatRedirect) != 0)
|
||||
{
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
close(g_logcatPipeFds[1]);
|
||||
g_logcatPipeFds[1] = -1;
|
||||
if (g_logcatThread.joinable())
|
||||
{
|
||||
g_logcatThread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -157,6 +157,34 @@ void register_code_generator_tests()
|
||||
{
|
||||
MiniTest::Case("CodeGenerator", [](TestCase &tc)
|
||||
{
|
||||
tc.Run("SYSCALL publishes its continuation before entering the runtime", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "syscall_resume";
|
||||
func.start = 0x9000;
|
||||
func.end = 0x9008;
|
||||
func.isRecompiled = true;
|
||||
|
||||
Instruction syscall{};
|
||||
syscall.address = 0x9000;
|
||||
syscall.opcode = OPCODE_SPECIAL;
|
||||
syscall.function = SPECIAL_SYSCALL;
|
||||
syscall.raw = (0x44u << 6) | SPECIAL_SYSCALL;
|
||||
|
||||
Instruction after = makeNop(0x9004);
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
const std::string generated = gen.generateFunction(func, {syscall, after}, false);
|
||||
const size_t continuation = generated.find("ctx->pc = 0x9004u;");
|
||||
const size_t dispatch = generated.find("runtime->handleSyscall(rdram, ctx, 0x44u);");
|
||||
|
||||
t.IsTrue(continuation != std::string::npos,
|
||||
"generated syscall must publish the next guest PC");
|
||||
t.IsTrue(dispatch != std::string::npos,
|
||||
"generated syscall must still dispatch the encoded syscall");
|
||||
t.IsTrue(continuation < dispatch,
|
||||
"the continuation PC must be visible before a syscall can transfer to the scheduler");
|
||||
});
|
||||
|
||||
tc.Run("R5900 MULT writes rd when rd is non-zero", [](TestCase &t) {
|
||||
CodeGenerator gen({}, {});
|
||||
|
||||
@@ -1278,6 +1306,35 @@ void register_code_generator_tests()
|
||||
"JAL should pass call-site and fallthrough PCs to the runtime helper");
|
||||
});
|
||||
|
||||
tc.Run("JAL to a resolved syscall publishes fallthrough before the handler", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "jal_syscall_resume";
|
||||
func.start = 0xA040;
|
||||
func.end = 0xA048;
|
||||
func.isRecompiled = true;
|
||||
|
||||
Symbol target;
|
||||
target.name = "GetThreadId";
|
||||
target.address = 0xB040;
|
||||
target.isFunction = true;
|
||||
|
||||
CodeGenerator gen({target}, {});
|
||||
gen.setRelocationCallNames({{0xA040u, "GetThreadId"}});
|
||||
const std::string generated = gen.generateFunction(
|
||||
func, {makeJal(0xA040, 0xB040), makeNop(0xA044)}, false);
|
||||
const size_t continuation = generated.find("ctx->pc = 0xA048u;");
|
||||
const size_t handler = generated.find("ps2_syscalls::GetThreadId(rdram, ctx, runtime);");
|
||||
|
||||
t.IsTrue(continuation != std::string::npos,
|
||||
"a resolved HLE JAL should publish its fallthrough PC");
|
||||
t.IsTrue(handler != std::string::npos,
|
||||
"the resolved syscall handler should still be called directly");
|
||||
t.IsTrue(continuation < handler,
|
||||
"the fallthrough must be restart-safe before a blocking HLE handler runs");
|
||||
t.IsTrue(generated.find("__entryPc") == std::string::npos,
|
||||
"resolved HLE calls must not retain the unchanged-PC compatibility guard");
|
||||
});
|
||||
|
||||
tc.Run("trailing JAL without decoded delay slot still emits call flow", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "jal_truncated";
|
||||
@@ -1421,8 +1478,8 @@ void register_code_generator_tests()
|
||||
"mid-function backward loop head should be re-enterable after returning to the dispatcher");
|
||||
t.IsTrue(generated.find("ctx->pc = 0x1104u;") != std::string::npos,
|
||||
"backward internal branch should preserve the loop target in ctx->pc");
|
||||
t.IsTrue(generated.find("if (runtime->shouldPreemptGuestExecution()) {") != std::string::npos,
|
||||
"backward internal branch should consult the runtime preemption policy before re-entering the loop");
|
||||
t.IsTrue(generated.find("if (runtime->eeCheckpointDue()) {") != std::string::npos,
|
||||
"backward internal branch should consult the EE event checkpoint before re-entering the loop");
|
||||
t.IsTrue(generated.find("return;") != std::string::npos,
|
||||
"backward internal branch should return to the dispatcher when the runtime preemption policy requests it");
|
||||
t.IsTrue(generated.find("runtime->cooperativeGuestYield();") == std::string::npos,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "ps2_stubs.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
#include "runtime/ps2_gs_memory.h"
|
||||
#include "runtime/ps2_gs_rasterizer.h"
|
||||
#include "runtime/ps2_gs_psmct32.h"
|
||||
@@ -25,6 +26,21 @@ namespace
|
||||
{
|
||||
std::atomic<uint32_t> g_gsSyncCallbackHits{0u};
|
||||
std::atomic<uint32_t> g_gsSyncCallbackLastTick{0u};
|
||||
std::atomic<int32_t> g_gsSyncFirstField{-1};
|
||||
std::atomic<int32_t> g_gsSyncSecondField{-1};
|
||||
std::atomic<uint32_t> g_gsSyncCallbackSp{0u};
|
||||
std::atomic<uint32_t> g_gsSyncCallbackGp{0u};
|
||||
std::atomic<uint32_t> g_gsSyncCallbackPrevious{0u};
|
||||
|
||||
constexpr uint32_t kGsSyncWait0Pc = 0x0011F000u;
|
||||
constexpr uint32_t kGsSyncResume0Pc = 0x0011F010u;
|
||||
constexpr uint32_t kGsSyncWait1Pc = 0x0011F020u;
|
||||
constexpr uint32_t kGsSyncResume1Pc = 0x0011F030u;
|
||||
constexpr uint32_t kGsCallbackMainPc = 0x0011F040u;
|
||||
constexpr uint32_t kGsCallbackResumePc = 0x0011F050u;
|
||||
constexpr uint32_t kGsCallbackPc = 0x00120000u;
|
||||
constexpr uint32_t kGsCallbackGp = 0x0036A7F0u;
|
||||
constexpr uint32_t kGsCallbackCallerSp = 0x00123450u;
|
||||
|
||||
static_assert(sizeof(GsImageMem) == 12, "GsImageMem size mismatch");
|
||||
|
||||
@@ -95,10 +111,54 @@ namespace
|
||||
(void)runtime;
|
||||
|
||||
g_gsSyncCallbackLastTick.store(getRegU32(ctx, 4), std::memory_order_relaxed);
|
||||
g_gsSyncCallbackSp.store(getRegU32(ctx, 29), std::memory_order_relaxed);
|
||||
g_gsSyncCallbackGp.store(getRegU32(ctx, 28), std::memory_order_relaxed);
|
||||
g_gsSyncCallbackHits.fetch_add(1u, std::memory_order_relaxed);
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void testGsSyncWait0(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
ctx->pc = kGsSyncResume0Pc;
|
||||
ps2_stubs::sceGsSyncV(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void testGsSyncResume0(uint8_t *, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
g_gsSyncFirstField.store(static_cast<int32_t>(getRegU32(ctx, 2)), std::memory_order_release);
|
||||
ctx->pc = kGsSyncWait1Pc;
|
||||
}
|
||||
|
||||
void testGsSyncWait1(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
ctx->pc = kGsSyncResume1Pc;
|
||||
ps2_stubs::sceGsSyncV(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void testGsSyncResume1(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_gsSyncSecondField.store(static_cast<int32_t>(getRegU32(ctx, 2)), std::memory_order_release);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void testGsCallbackMain(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
setRegU32(*ctx, 4, kGsCallbackPc);
|
||||
setRegU32(*ctx, 28, kGsCallbackGp);
|
||||
setRegU32(*ctx, 29, kGsCallbackCallerSp);
|
||||
ctx->pc = kGsCallbackResumePc;
|
||||
ps2_stubs::sceGsSyncVCallback(rdram, ctx, runtime);
|
||||
g_gsSyncCallbackPrevious.store(getRegU32(ctx, 2), std::memory_order_release);
|
||||
ps2_syscalls::WaitVSyncTick(rdram, ctx, runtime, -1);
|
||||
}
|
||||
|
||||
void testGsCallbackResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void writeGsImageTest(uint8_t *rdram, uint32_t addr, const GsImageMem &image)
|
||||
{
|
||||
std::memcpy(rdram + addr, &image, sizeof(image));
|
||||
@@ -3899,11 +3959,8 @@ void register_ps2_gs_tests()
|
||||
"sceGsResetGraph should free its temporary GIF packet");
|
||||
});
|
||||
|
||||
tc.Run("sceGsSyncV waits on VBlank and reports interlaced field parity", [](TestCase &t)
|
||||
tc.Run("sceGsSyncV resumes through the scheduler with deterministic field parity", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
ps2_stubs::resetGsSyncVCallbackState();
|
||||
|
||||
PS2Runtime runtime;
|
||||
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
|
||||
@@ -3914,66 +3971,57 @@ void register_ps2_gs_tests()
|
||||
setRegU32(resetCtx, 6, 2u);
|
||||
setRegU32(resetCtx, 7, 1u);
|
||||
ps2_stubs::sceGsResetGraph(rdram.data(), &resetCtx, &runtime);
|
||||
runtime.registerFunction(kGsSyncWait0Pc, testGsSyncWait0);
|
||||
runtime.registerFunction(kGsSyncResume0Pc, testGsSyncResume0);
|
||||
runtime.registerFunction(kGsSyncWait1Pc, testGsSyncWait1);
|
||||
runtime.registerFunction(kGsSyncResume1Pc, testGsSyncResume1);
|
||||
g_gsSyncFirstField.store(-1, std::memory_order_release);
|
||||
g_gsSyncSecondField.store(-1, std::memory_order_release);
|
||||
|
||||
R5900Context sync0{};
|
||||
ps2_stubs::sceGsSyncV(rdram.data(), &sync0, &runtime);
|
||||
t.Equals(static_cast<int32_t>(getRegU32Test(sync0, 2)), 0, "first interlaced sceGsSyncV should report even field");
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kGsSyncWait0Pc;
|
||||
runtime.eeScheduler().reset(rdram.data(), mainContext);
|
||||
runtime.eeScheduler().run();
|
||||
|
||||
R5900Context sync1{};
|
||||
ps2_stubs::sceGsSyncV(rdram.data(), &sync1, &runtime);
|
||||
t.Equals(static_cast<int32_t>(getRegU32Test(sync1, 2)), 1, "second interlaced sceGsSyncV should report odd field");
|
||||
|
||||
R5900Context resetProgCtx{};
|
||||
setRegU32(resetProgCtx, 4, 0u);
|
||||
setRegU32(resetProgCtx, 5, 0u);
|
||||
setRegU32(resetProgCtx, 6, 2u);
|
||||
setRegU32(resetProgCtx, 7, 1u);
|
||||
ps2_stubs::sceGsResetGraph(rdram.data(), &resetProgCtx, &runtime);
|
||||
|
||||
R5900Context syncProg{};
|
||||
ps2_stubs::sceGsSyncV(rdram.data(), &syncProg, &runtime);
|
||||
t.Equals(static_cast<int32_t>(getRegU32Test(syncProg, 2)), 1, "progressive sceGsSyncV should always return one");
|
||||
|
||||
runtime.requestStop();
|
||||
notifyRuntimeStop();
|
||||
ps2_stubs::resetGsSyncVCallbackState();
|
||||
t.Equals(g_gsSyncFirstField.load(std::memory_order_acquire), 0,
|
||||
"first interlaced VBlank should report even field");
|
||||
t.Equals(g_gsSyncSecondField.load(std::memory_order_acquire), 1,
|
||||
"second interlaced VBlank should report odd field");
|
||||
});
|
||||
|
||||
tc.Run("sceGsSyncVCallback uses the shared VBlank worker", [](TestCase &t)
|
||||
tc.Run("sceGsSyncVCallback runs as a scheduler invocation on its callback stack", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
ps2_stubs::resetGsSyncVCallbackState();
|
||||
g_gsSyncCallbackHits.store(0u, std::memory_order_relaxed);
|
||||
g_gsSyncCallbackLastTick.store(0u, std::memory_order_relaxed);
|
||||
g_gsSyncCallbackSp.store(0u, std::memory_order_relaxed);
|
||||
g_gsSyncCallbackGp.store(0u, std::memory_order_relaxed);
|
||||
g_gsSyncCallbackPrevious.store(0xFFFFFFFFu, std::memory_order_relaxed);
|
||||
|
||||
PS2Runtime runtime;
|
||||
t.IsTrue(runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
std::vector<uint8_t> rdram(PS2_RAM_SIZE, 0u);
|
||||
runtime.configureGuestHeap(0x01F00000u, 0x01F00000u);
|
||||
runtime.registerFunction(kGsCallbackMainPc, testGsCallbackMain);
|
||||
runtime.registerFunction(kGsCallbackResumePc, testGsCallbackResume);
|
||||
runtime.registerFunction(kGsCallbackPc, testGsSyncVCallback);
|
||||
|
||||
constexpr uint32_t kCallbackAddr = 0x120000u;
|
||||
runtime.registerFunction(kCallbackAddr, testGsSyncVCallback);
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kGsCallbackMainPc;
|
||||
runtime.eeScheduler().reset(rdram.data(), mainContext);
|
||||
runtime.eeScheduler().run();
|
||||
|
||||
R5900Context callbackCtx{};
|
||||
setRegU32(callbackCtx, 4, kCallbackAddr);
|
||||
ps2_stubs::sceGsSyncVCallback(rdram.data(), &callbackCtx, &runtime);
|
||||
t.Equals(getRegU32Test(callbackCtx, 2), 0u, "first sceGsSyncVCallback registration should return no previous callback");
|
||||
|
||||
const bool callbackFired = waitUntil([]() {
|
||||
return g_gsSyncCallbackHits.load(std::memory_order_acquire) > 0u;
|
||||
}, std::chrono::milliseconds(80));
|
||||
|
||||
t.IsTrue(callbackFired, "registered GS VSync callback should fire from the VBlank worker");
|
||||
t.Equals(g_gsSyncCallbackPrevious.load(std::memory_order_acquire), 0u,
|
||||
"first callback registration should return no previous callback");
|
||||
t.Equals(g_gsSyncCallbackHits.load(std::memory_order_acquire), 1u,
|
||||
"the callback should execute once at the next VBlank boundary");
|
||||
t.IsTrue(g_gsSyncCallbackLastTick.load(std::memory_order_acquire) > 0u,
|
||||
"VSync callback should receive a positive tick value");
|
||||
|
||||
R5900Context clearCtx{};
|
||||
setRegU32(clearCtx, 4, 0u);
|
||||
ps2_stubs::sceGsSyncVCallback(rdram.data(), &clearCtx, &runtime);
|
||||
t.Equals(getRegU32Test(clearCtx, 2), kCallbackAddr, "clearing sceGsSyncVCallback should return the previous callback");
|
||||
|
||||
runtime.requestStop();
|
||||
notifyRuntimeStop();
|
||||
ps2_stubs::resetGsSyncVCallbackState();
|
||||
t.Equals(g_gsSyncCallbackGp.load(std::memory_order_acquire), kGsCallbackGp,
|
||||
"callback invocation should preserve the registered GP");
|
||||
t.IsTrue(g_gsSyncCallbackSp.load(std::memory_order_acquire) >= 0x01F00000u,
|
||||
"callback invocation should use the reserved async stack pool");
|
||||
t.IsTrue(g_gsSyncCallbackSp.load(std::memory_order_acquire) != kGsCallbackCallerSp,
|
||||
"callback invocation must not reuse the caller stack");
|
||||
});
|
||||
|
||||
tc.Run("GS T4HL/T4HH shared-plane upload preserves both index planes via RMW", [](TestCase &t)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
#include "MiniTest.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "Stubs/DMA.h"
|
||||
#include "runtime/ps2_gs_gpu.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
@@ -46,12 +45,32 @@ namespace
|
||||
}
|
||||
};
|
||||
|
||||
std::atomic<uint32_t> g_vblankStartHits{0u};
|
||||
std::atomic<uint32_t> g_vblankEndHits{0u};
|
||||
std::atomic<uint32_t> g_lastIntcArg{0u};
|
||||
std::atomic<uint32_t> g_dmacSendHits{0u};
|
||||
std::atomic<uint32_t> g_dmacSendLastCause{0u};
|
||||
std::atomic<uint32_t> g_dmacSendLastChcr{0u};
|
||||
constexpr uint32_t kIdleVSyncWaitPc = 0x00160000u;
|
||||
constexpr uint32_t kVSyncWaitPc = 0x00160100u;
|
||||
constexpr uint32_t kVSyncResumePc = 0x00160110u;
|
||||
constexpr uint32_t kIrqWaitPc = 0x00160200u;
|
||||
constexpr uint32_t kIrqResumePc = 0x00160210u;
|
||||
constexpr uint32_t kIntcHandlerPc = 0x00160220u;
|
||||
constexpr uint32_t kISemaWaitPc = 0x00160300u;
|
||||
constexpr uint32_t kISemaResumePc = 0x00160310u;
|
||||
constexpr uint32_t kISemaDriverPc = 0x00160320u;
|
||||
constexpr uint32_t kISemaHandlerPc = 0x00160330u;
|
||||
constexpr uint32_t kEventWaitPc = 0x00160400u;
|
||||
constexpr uint32_t kEventResumePc = 0x00160410u;
|
||||
constexpr uint32_t kEventProducerPc = 0x00160420u;
|
||||
|
||||
constexpr uint32_t kVSyncFlagAddr = 0x1800u;
|
||||
constexpr uint32_t kVSyncTickAddr = 0x1810u;
|
||||
constexpr uint32_t kEventResultAddr = 0x1820u;
|
||||
|
||||
std::vector<int> g_dispatchTrace;
|
||||
int g_testSemaphoreId = 0;
|
||||
int g_testEventFlagId = 0;
|
||||
int32_t g_resumedResult = 0;
|
||||
uint32_t g_vsyncFlag = 0;
|
||||
uint64_t g_vsyncTick = 0;
|
||||
uint64_t g_vsyncCsr = 0;
|
||||
|
||||
void setRegU32(R5900Context &ctx, int reg, uint32_t value)
|
||||
{
|
||||
@@ -73,25 +92,6 @@ namespace
|
||||
std::memcpy(rdram + addr, &value, sizeof(value));
|
||||
}
|
||||
|
||||
void writeGuestU64(uint8_t *rdram, uint32_t addr, uint64_t value)
|
||||
{
|
||||
std::memcpy(rdram + addr, &value, sizeof(value));
|
||||
}
|
||||
|
||||
uint64_t makeDmaTag(uint16_t qwc, uint8_t id, uint32_t addr, bool irq = false)
|
||||
{
|
||||
return static_cast<uint64_t>(qwc) |
|
||||
(static_cast<uint64_t>(id & 0x7u) << 28) |
|
||||
(irq ? (1ull << 31) : 0ull) |
|
||||
(static_cast<uint64_t>(addr & 0x7FFFFFFFu) << 32);
|
||||
}
|
||||
|
||||
void writeDmaTag(uint8_t *rdram, uint32_t tagAddr, uint64_t tagLo)
|
||||
{
|
||||
std::memset(rdram + tagAddr, 0, 16);
|
||||
std::memcpy(rdram + tagAddr, &tagLo, sizeof(tagLo));
|
||||
}
|
||||
|
||||
uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
@@ -124,58 +124,128 @@ namespace
|
||||
void cleanupRuntime(TestEnv &env)
|
||||
{
|
||||
env.runtime.requestStop();
|
||||
notifyRuntimeStop();
|
||||
}
|
||||
|
||||
void testIntcHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
void idleVSyncWait(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
(void)runtime;
|
||||
WaitVSyncTick(rdram, ctx, runtime, -1);
|
||||
}
|
||||
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
const uint32_t arg = getRegU32(ctx, 5);
|
||||
g_lastIntcArg.store(arg, std::memory_order_relaxed);
|
||||
void schedulerVSyncWait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.setVSyncFlag(kVSyncFlagAddr, kVSyncTickAddr);
|
||||
ctx->pc = kVSyncResumePc;
|
||||
scheduler.waitVSync(scheduler.currentVSyncTick());
|
||||
}
|
||||
|
||||
if (cause == 2u)
|
||||
{
|
||||
g_vblankStartHits.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
else if (cause == 3u)
|
||||
{
|
||||
g_vblankEndHits.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
void schedulerVSyncResume(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_vsyncFlag = readGuestU32(rdram, kVSyncFlagAddr);
|
||||
g_vsyncTick = readGuestU64(rdram, kVSyncTickAddr);
|
||||
g_vsyncCsr = runtime->memory().gs().csr.load(std::memory_order_acquire);
|
||||
g_resumedResult = getRegS32(*ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerIntcHandler(uint8_t *, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
g_dispatchTrace.push_back(2);
|
||||
g_lastIntcArg.store(getRegU32(ctx, 5), std::memory_order_relaxed);
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void testDmacSendHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
void schedulerIrqWait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)rdram;
|
||||
|
||||
const uint32_t cause = getRegU32(ctx, 4);
|
||||
g_dmacSendHits.fetch_add(1u, std::memory_order_relaxed);
|
||||
g_dmacSendLastCause.store(cause, std::memory_order_relaxed);
|
||||
|
||||
uint32_t channelBase = 0u;
|
||||
if (cause == 0u)
|
||||
{
|
||||
channelBase = 0x10008000u;
|
||||
}
|
||||
else if (cause == 1u)
|
||||
{
|
||||
channelBase = 0x10009000u;
|
||||
}
|
||||
else if (cause == 2u)
|
||||
{
|
||||
channelBase = 0x1000A000u;
|
||||
}
|
||||
|
||||
if (runtime && channelBase != 0u)
|
||||
{
|
||||
g_dmacSendLastChcr.store(runtime->memory().readIORegister(channelBase + 0x00u), std::memory_order_relaxed);
|
||||
}
|
||||
g_dispatchTrace.push_back(1);
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.addIrqHandler(false, 2u, kIntcHandlerPc, true, 0xCAFEu, 0u, 0u);
|
||||
ctx->pc = kIrqResumePc;
|
||||
scheduler.waitVSync(scheduler.currentVSyncTick());
|
||||
}
|
||||
|
||||
void schedulerIrqResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(3);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerISemaHandler(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(3);
|
||||
runtime->eeScheduler().signalSemaphore(g_testSemaphoreId, true);
|
||||
g_dispatchTrace.push_back(4);
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void schedulerISemaDriver(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(2);
|
||||
ctx->pc = 0u;
|
||||
runtime->eeScheduler().dispatchIrq(true, 5u);
|
||||
}
|
||||
|
||||
void schedulerISemaWait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(1);
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
g_testSemaphoreId = scheduler.createSemaphore(0, 1, 0u, 0u);
|
||||
scheduler.addIrqHandler(true, 5u, kISemaHandlerPc, true, 0u, 0u, 0u);
|
||||
|
||||
EeThreadCreateParams driver{};
|
||||
driver.entry = kISemaDriverPc;
|
||||
driver.stack = 0x1C000u;
|
||||
driver.stackSize = 0x1000u;
|
||||
driver.priority = 10;
|
||||
const int driverId = scheduler.createThread(driver);
|
||||
scheduler.startThread(driverId, 0u, *ctx, false);
|
||||
|
||||
ctx->pc = kISemaResumePc;
|
||||
scheduler.waitSemaphore(g_testSemaphoreId);
|
||||
}
|
||||
|
||||
void schedulerISemaResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(5);
|
||||
g_resumedResult = getRegS32(*ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerEventProducer(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(2);
|
||||
ctx->pc = 0u;
|
||||
runtime->eeScheduler().setEventFlag(g_testEventFlagId, 0x6u, false);
|
||||
runtime->eeScheduler().transferIfRequested(false);
|
||||
}
|
||||
|
||||
void schedulerEventWait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(1);
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
g_testEventFlagId = scheduler.createEventFlag(0u, 0u, 0u);
|
||||
|
||||
EeThreadCreateParams producer{};
|
||||
producer.entry = kEventProducerPc;
|
||||
producer.stack = 0x1D000u;
|
||||
producer.stackSize = 0x1000u;
|
||||
producer.priority = 10;
|
||||
const int producerId = scheduler.createThread(producer);
|
||||
scheduler.startThread(producerId, 0u, *ctx, false);
|
||||
|
||||
ctx->pc = kEventResumePc;
|
||||
scheduler.waitEventFlag(g_testEventFlagId, 0x2u, WEF_OR | WEF_CLEAR, kEventResultAddr);
|
||||
}
|
||||
|
||||
void schedulerEventResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(3);
|
||||
g_resumedResult = getRegS32(*ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,443 +253,8 @@ void register_ps2_runtime_interrupt_tests()
|
||||
{
|
||||
MiniTest::Case("PS2RuntimeInterrupt", [](TestCase &tc)
|
||||
{
|
||||
tc.Run("SetVSyncFlag arms a one-shot vblank notification", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kFlagAddr = 0x1000u;
|
||||
constexpr uint32_t kTickAddr = 0x1010u;
|
||||
|
||||
writeGuestU32(env.rdram.data(), kFlagAddr, 0xDEADBEEFu);
|
||||
writeGuestU32(env.rdram.data(), kTickAddr + 0u, 0xAAAAAAAAu);
|
||||
writeGuestU32(env.rdram.data(), kTickAddr + 4u, 0xBBBBBBBBu);
|
||||
|
||||
R5900Context ctx{};
|
||||
setRegU32(ctx, 4, kFlagAddr);
|
||||
setRegU32(ctx, 5, kTickAddr);
|
||||
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &ctx, &env.runtime), "SetVSyncFlag syscall should dispatch");
|
||||
t.Equals(getRegS32(ctx, 2), KE_OK, "SetVSyncFlag should return KE_OK");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kFlagAddr), 0u, "SetVSyncFlag should reset flag to zero");
|
||||
t.Equals(readGuestU64(env.rdram.data(), kTickAddr), 0ull, "SetVSyncFlag should reset tick counter to zero");
|
||||
|
||||
const bool firstTickSeen = waitUntil([&]() {
|
||||
return readGuestU64(env.rdram.data(), kTickAddr) > 0u;
|
||||
}, std::chrono::milliseconds(300));
|
||||
t.IsTrue(firstTickSeen, "VSync worker should update tick value");
|
||||
|
||||
const uint64_t firstTick = readGuestU64(env.rdram.data(), kTickAddr);
|
||||
t.IsTrue(firstTick > 0u, "First observed VSync tick should be positive");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kFlagAddr), 1u, "VSync worker should set flag to one");
|
||||
|
||||
const bool tickRewritten = waitUntil([&]() {
|
||||
return readGuestU64(env.rdram.data(), kTickAddr) != firstTick;
|
||||
}, std::chrono::milliseconds(100));
|
||||
t.IsTrue(!tickRewritten, "consumed registration should not be written again");
|
||||
|
||||
// Re-arming registers a fresh one-shot notification.
|
||||
writeGuestU32(env.rdram.data(), kFlagAddr, 0u);
|
||||
R5900Context rearmCtx{};
|
||||
setRegU32(rearmCtx, 4, kFlagAddr);
|
||||
setRegU32(rearmCtx, 5, kTickAddr);
|
||||
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &rearmCtx, &env.runtime), "SetVSyncFlag re-arm should dispatch");
|
||||
const bool rearmedTickSeen = waitUntil([&]() {
|
||||
return readGuestU64(env.rdram.data(), kTickAddr) > firstTick;
|
||||
}, std::chrono::milliseconds(300));
|
||||
t.IsTrue(rearmedTickSeen, "re-armed registration should observe a later tick");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kFlagAddr), 1u, "re-armed registration should set flag to one");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("VSync worker updates GS CSR FIELD bit for MMIO polling loops", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kFlagAddr = 0x1080u;
|
||||
constexpr uint32_t kTickAddr = 0x1090u;
|
||||
constexpr uint64_t kGsCsrFieldMask = 0x2000ull;
|
||||
|
||||
env.runtime.memory().gs().csr = 0x3ull;
|
||||
|
||||
R5900Context ctx{};
|
||||
setRegU32(ctx, 4, kFlagAddr);
|
||||
setRegU32(ctx, 5, kTickAddr);
|
||||
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &ctx, &env.runtime), "SetVSyncFlag syscall should dispatch");
|
||||
|
||||
const uint64_t initialField = env.runtime.memory().gs().csr & kGsCsrFieldMask;
|
||||
const bool firstFieldFlip = waitUntil([&]() {
|
||||
return (env.runtime.memory().gs().csr & kGsCsrFieldMask) != initialField;
|
||||
}, std::chrono::milliseconds(300));
|
||||
t.IsTrue(firstFieldFlip, "VSync worker should toggle GS CSR FIELD for direct CSR polling");
|
||||
t.Equals(env.runtime.memory().gs().csr & 0x3ull, 0x3ull, "VSync FIELD update should preserve CSR status bits");
|
||||
|
||||
const uint64_t fieldAfterFirstFlip = env.runtime.memory().gs().csr & kGsCsrFieldMask;
|
||||
const bool secondFieldFlip = waitUntil([&]() {
|
||||
return (env.runtime.memory().gs().csr & kGsCsrFieldMask) != fieldAfterFirstFlip;
|
||||
}, std::chrono::milliseconds(300));
|
||||
t.IsTrue(secondFieldFlip, "VSync worker should keep alternating GS CSR FIELD");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
// Regression test for the GS CSR data race: a two-writer word-level
|
||||
// lost-update guard. Pre-fix, every CSR update was a plain (non-atomic)
|
||||
// 64-bit load-modify-store of the WHOLE word, so two threads that own
|
||||
// logically disjoint bits could still clobber each other: thread A's
|
||||
// read-modify-write of the word can overwrite thread B's bit with the
|
||||
// stale value A loaded before B's update landed.
|
||||
//
|
||||
// Two racer threads with disjoint bit ownership run concurrently:
|
||||
// - racer A owns SIGNAL (bit 0): sets it via the GIF register path
|
||||
// (GS_REG_SIGNAL) then W1C-clears ONLY bit 0 via the MMIO write path;
|
||||
// - racer B owns FINISH (bit 1): same protocol with GS_REG_FINISH and
|
||||
// a W1C write of only bit 1.
|
||||
// Each racer checks only its own bit after each half-op. With the fix
|
||||
// (std::atomic CSR, every update a single atomic RMW) each racer is the
|
||||
// sole writer of its bit, so its bit deterministically reflects its own
|
||||
// last operation: zero anomalies are possible. Pre-fix, the racers'
|
||||
// whole-word W1C RMWs constantly interleave and lose each other's
|
||||
// set/clear, lighting up the anomaly counters.
|
||||
//
|
||||
// Why racer-vs-racer instead of racer-vs-vsync: the vsync worker (which
|
||||
// motivated the fix) writes CSR only once per ~16.7ms tick, a window far
|
||||
// too narrow to hit deterministically in a bounded test. The corrupting
|
||||
// mechanism -- a non-atomic whole-word RMW clobbering a concurrently
|
||||
// written disjoint bit -- is identical, so guarding it with two
|
||||
// high-frequency writers also guards the vsync FIELD interleaving. The
|
||||
// real vsync worker still runs throughout (started via the same
|
||||
// SetVSyncFlag syscall production uses) and its FIELD (bit 13) toggling
|
||||
// is asserted when at least two ticks were observed.
|
||||
tc.Run("Disjoint-bit GS CSR writers (SIGNAL vs FINISH vs vsync FIELD) never lose word-level updates", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kFlagAddr = 0x1180u;
|
||||
constexpr uint32_t kTickAddr = 0x1190u;
|
||||
constexpr uint64_t kGsCsrFieldMask = 0x2000ull;
|
||||
constexpr uint32_t kCsrAddr = PS2_GS_PRIV_REG_BASE + 0x1000u;
|
||||
constexpr uint32_t kIterations = 80000u;
|
||||
|
||||
GS gs;
|
||||
gs.init(env.runtime.memory().getGSVRAM(), static_cast<uint32_t>(PS2_GS_VRAM_SIZE),
|
||||
&env.runtime.memory().gs());
|
||||
|
||||
// Drive the real vsync worker via the same syscall path production
|
||||
// code uses; it runs on its own thread and toggles CSR.FIELD once
|
||||
// per tick via updateGsCsrFieldForVSync.
|
||||
R5900Context ctx{};
|
||||
setRegU32(ctx, 4, kFlagAddr);
|
||||
setRegU32(ctx, 5, kTickAddr);
|
||||
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &ctx, &env.runtime), "SetVSyncFlag syscall should dispatch");
|
||||
const uint64_t tickBefore = GetCurrentVSyncTick();
|
||||
|
||||
std::atomic<uint32_t> setAnomaliesA{0u}, clearAnomaliesA{0u};
|
||||
std::atomic<uint32_t> setAnomaliesB{0u}, clearAnomaliesB{0u};
|
||||
std::atomic<uint32_t> racersDone{0u};
|
||||
|
||||
// ownBit: the single CSR status bit this racer exclusively owns.
|
||||
// Each iteration: raise the bit via the GIF register-write path,
|
||||
// verify it reads back set, W1C-clear only that bit via the guest
|
||||
// MMIO path, verify it reads back clear. The other racer and the
|
||||
// vsync worker never touch this bit, so under atomic RMWs both
|
||||
// checks are exact -- any anomaly is a lost word-level update.
|
||||
auto racerBody = [&](uint8_t gifReg, uint64_t gifValue, uint64_t ownBit,
|
||||
std::atomic<uint32_t> &setAnomalies, std::atomic<uint32_t> &clearAnomalies) {
|
||||
for (uint32_t i = 0; i < kIterations; ++i)
|
||||
{
|
||||
gs.writeRegister(gifReg, gifValue);
|
||||
if ((env.runtime.memory().gs().csr.load() & ownBit) == 0ull)
|
||||
{
|
||||
setAnomalies.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
env.runtime.memory().write64(kCsrAddr, ownBit);
|
||||
if ((env.runtime.memory().gs().csr.load() & ownBit) != 0ull)
|
||||
{
|
||||
clearAnomalies.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
racersDone.fetch_add(1u, std::memory_order_relaxed);
|
||||
};
|
||||
|
||||
const uint64_t signalValue = (0xFFFFFFFFull << 32) | 0x11223344ull;
|
||||
std::thread racerA(racerBody, GS_REG_SIGNAL, signalValue, 0x1ull,
|
||||
std::ref(setAnomaliesA), std::ref(clearAnomaliesA));
|
||||
std::thread racerB(racerBody, GS_REG_FINISH, 0ull, 0x2ull,
|
||||
std::ref(setAnomaliesB), std::ref(clearAnomaliesB));
|
||||
|
||||
// While the racers hammer bits 0..1, watch for CSR.FIELD (bit 13)
|
||||
// flips from the vsync worker. Polling ends when both racers finish,
|
||||
// so this adds no fixed wall-clock cost.
|
||||
const uint64_t initialField = env.runtime.memory().gs().csr.load() & kGsCsrFieldMask;
|
||||
bool fieldFlipped = false;
|
||||
while (racersDone.load(std::memory_order_relaxed) < 2u)
|
||||
{
|
||||
if ((env.runtime.memory().gs().csr.load() & kGsCsrFieldMask) != initialField)
|
||||
{
|
||||
fieldFlipped = true;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
racerA.join();
|
||||
racerB.join();
|
||||
const uint64_t ticksElapsed = GetCurrentVSyncTick() - tickBefore;
|
||||
|
||||
t.Equals(setAnomaliesA.load(), 0u, "racer A: SIGNAL set must never be lost to a concurrent whole-word CSR RMW");
|
||||
t.Equals(clearAnomaliesA.load(), 0u, "racer A: SIGNAL W1C-clear must never be lost to a concurrent whole-word CSR RMW");
|
||||
t.Equals(setAnomaliesB.load(), 0u, "racer B: FINISH set must never be lost to a concurrent whole-word CSR RMW");
|
||||
t.Equals(clearAnomaliesB.load(), 0u, "racer B: FINISH W1C-clear must never be lost to a concurrent whole-word CSR RMW");
|
||||
t.Equals(env.runtime.memory().gs().csr.load() & 0x3ull, 0x0ull,
|
||||
"final CSR status bits must match both racers' ledgers (last op on each bit was a clear)");
|
||||
if (ticksElapsed >= 2u)
|
||||
{
|
||||
t.IsTrue(fieldFlipped, "VSync worker should toggle GS CSR FIELD while the racers run");
|
||||
}
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("INTC VBLANK handlers respect EnableIntc and DisableIntc masks", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
|
||||
g_vblankStartHits.store(0u, std::memory_order_relaxed);
|
||||
g_vblankEndHits.store(0u, std::memory_order_relaxed);
|
||||
g_lastIntcArg.store(0u, std::memory_order_relaxed);
|
||||
|
||||
constexpr uint32_t kFlagAddr = 0x1100u;
|
||||
constexpr uint32_t kTickAddr = 0x1110u;
|
||||
constexpr uint32_t kHandlerAddr = 0x00ABC100u;
|
||||
|
||||
env.runtime.registerFunction(kHandlerAddr, &testIntcHandler);
|
||||
|
||||
R5900Context addStart{};
|
||||
setRegU32(addStart, 4, 2u); // VBLANK start
|
||||
setRegU32(addStart, 5, kHandlerAddr);
|
||||
setRegU32(addStart, 6, 0u);
|
||||
setRegU32(addStart, 7, 0xCAFE0002u);
|
||||
setRegU32(addStart, 28, 0x12340000u);
|
||||
setRegU32(addStart, 29, 0x001FFFE0u);
|
||||
t.IsTrue(callSyscall(0x10u, env.rdram.data(), &addStart, &env.runtime), "AddIntcHandler syscall should dispatch");
|
||||
t.IsTrue(getRegS32(addStart, 2) > 0, "AddIntcHandler for cause 2 should return handler id");
|
||||
|
||||
R5900Context addEnd{};
|
||||
setRegU32(addEnd, 4, 3u); // VBLANK end
|
||||
setRegU32(addEnd, 5, kHandlerAddr);
|
||||
setRegU32(addEnd, 6, 0u);
|
||||
setRegU32(addEnd, 7, 0xCAFE0003u);
|
||||
setRegU32(addEnd, 28, 0x12340000u);
|
||||
setRegU32(addEnd, 29, 0x001FFFE0u);
|
||||
t.IsTrue(callSyscall(0x10u, env.rdram.data(), &addEnd, &env.runtime), "AddIntcHandler syscall should dispatch");
|
||||
t.IsTrue(getRegS32(addEnd, 2) > 0, "AddIntcHandler for cause 3 should return handler id");
|
||||
|
||||
R5900Context vsyncCtx{};
|
||||
setRegU32(vsyncCtx, 4, kFlagAddr);
|
||||
setRegU32(vsyncCtx, 5, kTickAddr);
|
||||
t.IsTrue(callSyscall(0x73u, env.rdram.data(), &vsyncCtx, &env.runtime), "SetVSyncFlag syscall should dispatch");
|
||||
t.Equals(getRegS32(vsyncCtx, 2), KE_OK, "SetVSyncFlag should succeed");
|
||||
|
||||
const bool startSeen = waitUntil([&]() {
|
||||
return g_vblankStartHits.load(std::memory_order_relaxed) > 0u;
|
||||
}, std::chrono::milliseconds(400));
|
||||
const bool endSeen = waitUntil([&]() {
|
||||
return g_vblankEndHits.load(std::memory_order_relaxed) > 0u;
|
||||
}, std::chrono::milliseconds(400));
|
||||
|
||||
t.IsTrue(startSeen, "VBLANK start handler should fire while cause 2 is enabled");
|
||||
t.IsTrue(endSeen, "VBLANK end handler should fire while cause 3 is enabled");
|
||||
|
||||
R5900Context disableStart{};
|
||||
setRegU32(disableStart, 4, 2u);
|
||||
t.IsTrue(callSyscall(0x15u, env.rdram.data(), &disableStart, &env.runtime), "DisableIntc syscall should dispatch");
|
||||
t.Equals(getRegS32(disableStart, 2), KE_OK, "DisableIntc should return KE_OK");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(40));
|
||||
const uint32_t startAfterDisable = g_vblankStartHits.load(std::memory_order_relaxed);
|
||||
const uint32_t endAfterDisable = g_vblankEndHits.load(std::memory_order_relaxed);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(80));
|
||||
const uint32_t startLater = g_vblankStartHits.load(std::memory_order_relaxed);
|
||||
const uint32_t endLater = g_vblankEndHits.load(std::memory_order_relaxed);
|
||||
|
||||
t.Equals(startLater, startAfterDisable, "cause 2 handler count should stop increasing while cause 2 is disabled");
|
||||
t.IsTrue(endLater > endAfterDisable, "cause 3 handler should keep firing while still enabled");
|
||||
|
||||
R5900Context enableStart{};
|
||||
setRegU32(enableStart, 4, 2u);
|
||||
t.IsTrue(callSyscall(0x14u, env.rdram.data(), &enableStart, &env.runtime), "EnableIntc syscall should dispatch");
|
||||
t.Equals(getRegS32(enableStart, 2), KE_OK, "EnableIntc should return KE_OK");
|
||||
|
||||
const bool startResumed = waitUntil([&]() {
|
||||
return g_vblankStartHits.load(std::memory_order_relaxed) > startLater;
|
||||
}, std::chrono::milliseconds(300));
|
||||
t.IsTrue(startResumed, "cause 2 handler should resume after re-enable");
|
||||
|
||||
const uint32_t lastArg = g_lastIntcArg.load(std::memory_order_relaxed);
|
||||
t.IsTrue(lastArg == 0xCAFE0002u || lastArg == 0xCAFE0003u,
|
||||
"handler should receive configured argument value");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("sceDmaSend dispatches completed VIF1 DMAC handler with latched END tag", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kHandlerAddr = 0x00ABD100u;
|
||||
constexpr uint32_t kVif1Ch = 0x10009000u;
|
||||
constexpr uint32_t kTag0 = 0x00028000u;
|
||||
constexpr uint32_t kTag1 = kTag0 + 0x20u;
|
||||
|
||||
uint8_t *rdram = env.runtime.memory().getRDRAM();
|
||||
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 1u, 0u, false)); // CNT
|
||||
writeGuestU64(rdram, kTag0 + 0x10u, 0u);
|
||||
writeGuestU64(rdram, kTag0 + 0x18u, 0u);
|
||||
writeDmaTag(rdram, kTag1, makeDmaTag(0u, 7u, 0u, false)); // END
|
||||
|
||||
g_dmacSendHits.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
|
||||
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
|
||||
|
||||
R5900Context addCtx{};
|
||||
setRegU32(addCtx, 4, 1u);
|
||||
setRegU32(addCtx, 5, kHandlerAddr);
|
||||
setRegU32(addCtx, 6, 0u);
|
||||
setRegU32(addCtx, 7, 0u);
|
||||
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
|
||||
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register VIF1 handler");
|
||||
|
||||
R5900Context enableCtx{};
|
||||
setRegU32(enableCtx, 4, 1u);
|
||||
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
|
||||
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable VIF1 cause");
|
||||
|
||||
R5900Context sendCtx{};
|
||||
setRegU32(sendCtx, 4, kVif1Ch);
|
||||
setRegU32(sendCtx, 5, kTag0);
|
||||
ps2_stubs::sceDmaSend(rdram, &sendCtx, &env.runtime);
|
||||
|
||||
t.Equals(getRegS32(sendCtx, 2), 0, "sceDmaSend should succeed");
|
||||
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u, "sceDmaSend should dispatch the VIF1 DMAC handler");
|
||||
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 1u, "DMAC handler should observe VIF1 cause");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u, "handler should see VIF1 STR cleared");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u, "handler should see the latched END tag id");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("MMIO VIF1 chain completion dispatches DMAC handler after CHCR store", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kHandlerAddr = 0x00ABD180u;
|
||||
constexpr uint32_t kVif1Ch = 0x10009000u;
|
||||
constexpr uint32_t kTag0 = 0x00028200u;
|
||||
constexpr uint32_t kTag1 = kTag0 + 0x20u;
|
||||
|
||||
uint8_t *rdram = env.runtime.memory().getRDRAM();
|
||||
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 1u, 0u, false)); // CNT
|
||||
writeGuestU64(rdram, kTag0 + 0x10u, 0u);
|
||||
writeGuestU64(rdram, kTag0 + 0x18u, 0u);
|
||||
writeDmaTag(rdram, kTag1, makeDmaTag(0u, 7u, 0u, false)); // END
|
||||
|
||||
g_dmacSendHits.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
|
||||
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
|
||||
|
||||
R5900Context addCtx{};
|
||||
setRegU32(addCtx, 4, 1u);
|
||||
setRegU32(addCtx, 5, kHandlerAddr);
|
||||
setRegU32(addCtx, 6, 0u);
|
||||
setRegU32(addCtx, 7, 0u);
|
||||
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
|
||||
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register VIF1 handler");
|
||||
|
||||
R5900Context enableCtx{};
|
||||
setRegU32(enableCtx, 4, 1u);
|
||||
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
|
||||
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable VIF1 cause");
|
||||
|
||||
R5900Context storeCtx{};
|
||||
env.runtime.Store32(rdram, &storeCtx, kVif1Ch + 0x30u, kTag0);
|
||||
env.runtime.Store32(rdram, &storeCtx, kVif1Ch + 0x00u, 0x185u);
|
||||
|
||||
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u, "CHCR store should dispatch the VIF1 DMAC handler");
|
||||
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 1u, "DMAC handler should observe VIF1 cause");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u, "handler should see VIF1 STR cleared");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u, "handler should see the latched END tag id");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("native GIF DMA MMIO kick dispatches completed DMAC handler", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
|
||||
constexpr uint32_t kHandlerAddr = 0x00ABD1C0u;
|
||||
constexpr uint32_t kDStat = 0x1000E010u;
|
||||
constexpr uint32_t kDPcr = 0x1000E020u;
|
||||
constexpr uint32_t kTag0 = 0x00028400u;
|
||||
|
||||
uint8_t *rdram = env.runtime.memory().getRDRAM();
|
||||
writeDmaTag(rdram, kTag0, makeDmaTag(1u, 7u, 0u, false)); // END
|
||||
writeGuestU64(rdram, kTag0 + 0x10u, 0x1122334455667788ull);
|
||||
writeGuestU64(rdram, kTag0 + 0x18u, 0x99AABBCCDDEEFF00ull);
|
||||
|
||||
g_dmacSendHits.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastCause.store(0u, std::memory_order_relaxed);
|
||||
g_dmacSendLastChcr.store(0u, std::memory_order_relaxed);
|
||||
env.runtime.registerFunction(kHandlerAddr, &testDmacSendHandler);
|
||||
|
||||
R5900Context addCtx{};
|
||||
setRegU32(addCtx, 4, 2u);
|
||||
setRegU32(addCtx, 5, kHandlerAddr);
|
||||
setRegU32(addCtx, 6, 0u);
|
||||
setRegU32(addCtx, 7, 0u);
|
||||
ps2_syscalls::AddDmacHandler(rdram, &addCtx, &env.runtime);
|
||||
t.IsTrue(getRegS32(addCtx, 2) > 0, "AddDmacHandler should register GIF handler");
|
||||
|
||||
R5900Context enableCtx{};
|
||||
setRegU32(enableCtx, 4, 2u);
|
||||
ps2_syscalls::EnableDmac(rdram, &enableCtx, &env.runtime);
|
||||
t.Equals(getRegS32(enableCtx, 2), KE_OK, "EnableDmac should enable GIF cause");
|
||||
|
||||
R5900Context kickCtx{};
|
||||
env.runtime.kickGifDmaChainFromMMIO(rdram, &kickCtx, 4u, 4u, kTag0, 0x105u);
|
||||
|
||||
t.Equals(env.runtime.memory().readIORegister(kDPcr), 4u, "native GIF kick should preserve D_PCR write");
|
||||
t.IsTrue((env.runtime.memory().readIORegister(kDStat) & (1u << 2)) != 0u,
|
||||
"native GIF kick should raise D_STAT GIF completion status");
|
||||
t.Equals(g_dmacSendHits.load(std::memory_order_relaxed), 1u,
|
||||
"native GIF kick should dispatch the GIF DMAC handler");
|
||||
t.Equals(g_dmacSendLastCause.load(std::memory_order_relaxed), 2u,
|
||||
"DMAC handler should observe GIF cause");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x100u, 0u,
|
||||
"handler should see GIF STR cleared");
|
||||
t.Equals(g_dmacSendLastChcr.load(std::memory_order_relaxed) & 0x70000000u, 0x70000000u,
|
||||
"handler should see the latched END tag id");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("negative interrupt-safe EE syscall ids dispatch", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kEventParamAddr = 0x1200u;
|
||||
@@ -684,97 +319,8 @@ void register_ps2_runtime_interrupt_tests()
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("WaitEventFlag blocks and wakes when SetEventFlag publishes bits", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kParamAddr = 0x1200u;
|
||||
constexpr uint32_t kResBitsAddr = 0x1300u;
|
||||
|
||||
const uint32_t eventParam[3] = {
|
||||
0u, // attr
|
||||
0u, // option
|
||||
0u // init bits
|
||||
};
|
||||
std::memcpy(env.rdram.data() + kParamAddr, eventParam, sizeof(eventParam));
|
||||
|
||||
R5900Context createCtx{};
|
||||
setRegU32(createCtx, 4, kParamAddr);
|
||||
CreateEventFlag(env.rdram.data(), &createCtx, &env.runtime);
|
||||
const int32_t eid = getRegS32(createCtx, 2);
|
||||
t.IsTrue(eid > 0, "CreateEventFlag should return a valid id");
|
||||
|
||||
writeGuestU32(env.rdram.data(), kResBitsAddr, 0u);
|
||||
|
||||
std::atomic<bool> waiterDone{false};
|
||||
std::atomic<bool> waiterThrew{false};
|
||||
std::atomic<int32_t> waiterRet{0x7FFFFFFF};
|
||||
std::atomic<uint32_t> waiterResBits{0u};
|
||||
|
||||
std::thread waiter([&]()
|
||||
{
|
||||
try
|
||||
{
|
||||
R5900Context waitCtx{};
|
||||
setRegU32(waitCtx, 4, static_cast<uint32_t>(eid));
|
||||
setRegU32(waitCtx, 5, 0x4u); // wait bits
|
||||
setRegU32(waitCtx, 6, WEF_OR); // OR mode
|
||||
setRegU32(waitCtx, 7, kResBitsAddr);
|
||||
WaitEventFlag(env.rdram.data(), &waitCtx, &env.runtime);
|
||||
waiterRet.store(getRegS32(waitCtx, 2), std::memory_order_relaxed);
|
||||
waiterResBits.store(readGuestU32(env.rdram.data(), kResBitsAddr), std::memory_order_relaxed);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
waiterThrew.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
waiterDone.store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
t.IsFalse(waiterDone.load(std::memory_order_acquire), "WaitEventFlag should block before matching bits are set");
|
||||
|
||||
R5900Context signalCtx{};
|
||||
setRegU32(signalCtx, 4, static_cast<uint32_t>(eid));
|
||||
setRegU32(signalCtx, 5, 0x4u);
|
||||
SetEventFlag(env.rdram.data(), &signalCtx, &env.runtime);
|
||||
t.Equals(getRegS32(signalCtx, 2), KE_OK, "SetEventFlag should succeed");
|
||||
|
||||
const bool woke = waitUntil([&]() {
|
||||
return waiterDone.load(std::memory_order_acquire);
|
||||
}, std::chrono::milliseconds(300));
|
||||
if (!woke)
|
||||
{
|
||||
// Force unblock for deterministic test cleanup.
|
||||
R5900Context deleteCtx{};
|
||||
setRegU32(deleteCtx, 4, static_cast<uint32_t>(eid));
|
||||
DeleteEventFlag(env.rdram.data(), &deleteCtx, &env.runtime);
|
||||
}
|
||||
|
||||
if (waiter.joinable())
|
||||
{
|
||||
waiter.join();
|
||||
}
|
||||
|
||||
t.IsFalse(waiterThrew.load(std::memory_order_acquire),
|
||||
"WaitEventFlag waiter thread should not throw");
|
||||
t.IsTrue(woke, "WaitEventFlag should wake after SetEventFlag publishes matching bits");
|
||||
t.Equals(waiterRet.load(std::memory_order_relaxed), KE_OK, "waiter should return KE_OK");
|
||||
t.IsTrue((waiterResBits.load(std::memory_order_relaxed) & 0x4u) != 0u,
|
||||
"waiter result bits should include published bit");
|
||||
|
||||
R5900Context deleteCtx{};
|
||||
setRegU32(deleteCtx, 4, static_cast<uint32_t>(eid));
|
||||
DeleteEventFlag(env.rdram.data(), &deleteCtx, &env.runtime);
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("PollEventFlag WEF_CLEAR clears only matched bits", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kParamAddr = 0x1400u;
|
||||
@@ -830,60 +376,146 @@ void register_ps2_runtime_interrupt_tests()
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
tc.Run("WaitVSyncTick returns when runtime stop is requested", [](TestCase &t)
|
||||
tc.Run("VBlank deadline resumes the waiter and publishes flag tick and FIELD atomically", [](TestCase &t)
|
||||
{
|
||||
notifyRuntimeStop();
|
||||
TestEnv env;
|
||||
t.IsTrue(env.runtime.memory().initialize(), "runtime memory initialize should succeed");
|
||||
env.runtime.registerFunction(kVSyncWaitPc, schedulerVSyncWait);
|
||||
env.runtime.registerFunction(kVSyncResumePc, schedulerVSyncResume);
|
||||
|
||||
std::atomic<bool> waiterDone{false};
|
||||
std::atomic<bool> waiterThrew{false};
|
||||
std::thread waiter([&]()
|
||||
g_resumedResult = -1;
|
||||
g_vsyncFlag = 0u;
|
||||
g_vsyncTick = 0u;
|
||||
g_vsyncCsr = 0u;
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kVSyncWaitPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
t.Equals(g_vsyncFlag, 1u, "VBlank start should set the registered guest flag");
|
||||
t.Equals(g_vsyncTick, 1ull, "the first centralized VBlank deadline should publish tick one");
|
||||
t.Equals(g_resumedResult, 0, "the first VBlank field should return even-field parity");
|
||||
t.Equals(g_vsyncCsr & 0x2000ull, 0x2000ull,
|
||||
"the first VBlank should publish GS CSR.FIELD before resuming guest code");
|
||||
});
|
||||
|
||||
tc.Run("VBlank IRQ invocation completes before the resumed base context", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
env.runtime.registerFunction(kIrqWaitPc, schedulerIrqWait);
|
||||
env.runtime.registerFunction(kIrqResumePc, schedulerIrqResume);
|
||||
env.runtime.registerFunction(kIntcHandlerPc, schedulerIntcHandler);
|
||||
|
||||
g_dispatchTrace.clear();
|
||||
g_lastIntcArg.store(0u, std::memory_order_relaxed);
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kIrqWaitPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
const std::vector<int> expected{1, 2, 3};
|
||||
t.IsTrue(g_dispatchTrace == expected,
|
||||
"the dispatcher should run wait, IRQ frame, then the resumed base context in exact order");
|
||||
t.Equals(g_lastIntcArg.load(std::memory_order_relaxed), 0xCAFEu,
|
||||
"the IRQ frame should receive its registered argument");
|
||||
});
|
||||
|
||||
tc.Run("iSignalSema defers selection until IRQ return", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
env.runtime.registerFunction(kISemaWaitPc, schedulerISemaWait);
|
||||
env.runtime.registerFunction(kISemaResumePc, schedulerISemaResume);
|
||||
env.runtime.registerFunction(kISemaDriverPc, schedulerISemaDriver);
|
||||
env.runtime.registerFunction(kISemaHandlerPc, schedulerISemaHandler);
|
||||
|
||||
g_dispatchTrace.clear();
|
||||
g_resumedResult = -1;
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kISemaWaitPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
const std::vector<int> expected{1, 2, 3, 4, 5};
|
||||
t.IsTrue(g_dispatchTrace == expected,
|
||||
"iSignalSema should make the waiter ready but finish the IRQ frame before selecting it");
|
||||
t.Equals(g_resumedResult, g_testSemaphoreId,
|
||||
"the resumed waiter should receive the semaphore id from the direct FIFO handoff");
|
||||
const EeSemaphore *semaphore = env.runtime.eeScheduler().semaphore(g_testSemaphoreId);
|
||||
t.IsTrue(semaphore != nullptr, "the signaled semaphore should still exist");
|
||||
if (semaphore)
|
||||
{
|
||||
t.Equals(semaphore->count, 0, "direct handoff must not increment the semaphore count");
|
||||
t.Equals(static_cast<uint32_t>(semaphore->waiters.size()), 0u,
|
||||
"the awakened waiter must be removed from the semaphore queue");
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("event-flag completion writes observed bits before strict-priority resume", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
env.runtime.registerFunction(kEventWaitPc, schedulerEventWait);
|
||||
env.runtime.registerFunction(kEventResumePc, schedulerEventResume);
|
||||
env.runtime.registerFunction(kEventProducerPc, schedulerEventProducer);
|
||||
|
||||
g_dispatchTrace.clear();
|
||||
g_resumedResult = -1;
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kEventWaitPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
const std::vector<int> expected{1, 2, 3};
|
||||
t.IsTrue(g_dispatchTrace == expected,
|
||||
"the higher-priority event waiter should resume at the producer scheduling point");
|
||||
t.Equals(g_resumedResult, KE_OK, "the resumed event waiter should receive KE_OK");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kEventResultAddr), 0x6u,
|
||||
"the event output should contain the bits observed before clear mode is applied");
|
||||
const EeEventFlag *flag = env.runtime.eeScheduler().eventFlag(g_testEventFlagId);
|
||||
t.IsTrue(flag != nullptr, "the event flag should still exist");
|
||||
if (flag)
|
||||
{
|
||||
t.Equals(flag->bits, 0x4u, "WEF_CLEAR should remove only the requested matched bit");
|
||||
}
|
||||
});
|
||||
|
||||
tc.Run("scheduler stop wakes an idle VSync wait without a timeout", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
env.runtime.registerFunction(kIdleVSyncWaitPc, idleVSyncWait);
|
||||
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kIdleVSyncWaitPc;
|
||||
std::atomic<bool> schedulerDone{false};
|
||||
std::atomic<bool> schedulerThrew{false};
|
||||
std::thread gameThread([&]()
|
||||
{
|
||||
try
|
||||
{
|
||||
WaitVSyncTick(env.rdram.data(), &env.runtime);
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
waiterThrew.store(true, std::memory_order_release);
|
||||
schedulerThrew.store(true, std::memory_order_release);
|
||||
}
|
||||
waiterDone.store(true, std::memory_order_release);
|
||||
schedulerDone.store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
env.runtime.requestStop();
|
||||
|
||||
bool wokeOnStop = waitUntil([&]() {
|
||||
return waiterDone.load(std::memory_order_acquire);
|
||||
const bool becameIdle = waitUntil([&]() {
|
||||
const EeKernelSnapshot snapshot = env.runtime.eeScheduler().snapshot();
|
||||
return snapshot.runningThreadId == 0 &&
|
||||
!snapshot.threads.empty() &&
|
||||
snapshot.threads.front().waitReason == EeWaitReason::VSync;
|
||||
}, std::chrono::milliseconds(80));
|
||||
|
||||
if (!wokeOnStop)
|
||||
{
|
||||
// Fallback wake-up for deterministic cleanup: one extra tick on fresh runtime.
|
||||
TestEnv wakeEnv;
|
||||
R5900Context setCtx{};
|
||||
constexpr uint32_t kWakeFlagAddr = 0x1500u;
|
||||
constexpr uint32_t kWakeTickAddr = 0x1510u;
|
||||
setRegU32(setCtx, 4, kWakeFlagAddr);
|
||||
setRegU32(setCtx, 5, kWakeTickAddr);
|
||||
(void)callSyscall(0x73u, wakeEnv.rdram.data(), &setCtx, &wakeEnv.runtime);
|
||||
(void)waitUntil([&]() {
|
||||
return readGuestU64(wakeEnv.rdram.data(), kWakeTickAddr) > 0u;
|
||||
}, std::chrono::milliseconds(300));
|
||||
wakeEnv.runtime.requestStop();
|
||||
wokeOnStop = waitUntil([&]() {
|
||||
return waiterDone.load(std::memory_order_acquire);
|
||||
}, std::chrono::milliseconds(80));
|
||||
}
|
||||
env.runtime.requestStop();
|
||||
gameThread.join();
|
||||
|
||||
if (waiter.joinable())
|
||||
{
|
||||
waiter.join();
|
||||
}
|
||||
|
||||
t.IsFalse(waiterThrew.load(std::memory_order_acquire),
|
||||
"WaitVSyncTick waiter thread should not throw");
|
||||
t.IsTrue(wokeOnStop, "WaitVSyncTick waiter should unblock when runtime is stopping");
|
||||
t.IsTrue(becameIdle, "VSync wait should leave the sole guest thread waiting");
|
||||
t.IsTrue(schedulerDone.load(std::memory_order_acquire),
|
||||
"requestStop should wake the scheduler's event wait");
|
||||
t.IsFalse(schedulerThrew.load(std::memory_order_acquire),
|
||||
"the scheduler stop path should not throw");
|
||||
|
||||
cleanupRuntime(env);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
#include "ps2_iop_transport.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
@@ -112,6 +113,13 @@ namespace
|
||||
uint32_t g_dmacHandlerValue = 0u;
|
||||
uint32_t g_dmacHandlerLastCause = 0u;
|
||||
uint32_t g_dmacHandlerLastArg = 0u;
|
||||
int32_t g_sifDmaResult = 0;
|
||||
|
||||
constexpr uint32_t kSchedulerSifDmaEntryPc = 0x00101000u;
|
||||
constexpr uint32_t kSchedulerSifDmaResumePc = 0x00101010u;
|
||||
constexpr uint32_t kSchedulerSifDmaHandlerPc = 0x00101020u;
|
||||
constexpr uint32_t kSchedulerSifDmaDescAddr = 0x00020300u;
|
||||
constexpr uint32_t kSchedulerSifDmaHandlerArg = 0x12345678u;
|
||||
|
||||
void testDmacHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
@@ -124,6 +132,28 @@ namespace
|
||||
}
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void schedulerSifDmaEntry(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
runtime->eeScheduler().addIrqHandler(true,
|
||||
5u,
|
||||
kSchedulerSifDmaHandlerPc,
|
||||
true,
|
||||
kSchedulerSifDmaHandlerArg,
|
||||
0u,
|
||||
0u);
|
||||
setRegU32(*ctx, 4, kSchedulerSifDmaDescAddr);
|
||||
setRegU32(*ctx, 5, 1u);
|
||||
ctx->pc = kSchedulerSifDmaResumePc;
|
||||
ps2_stubs::sceSifSetDma(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void schedulerSifDmaResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_sifDmaResult = getRegS32(*ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
void register_ps2_sif_dma_tests()
|
||||
@@ -205,30 +235,18 @@ void register_ps2_sif_dma_tests()
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kDescAddr = 0x00020300u;
|
||||
constexpr uint32_t kSrcAddr = 0x00020400u;
|
||||
constexpr uint32_t kDstAddr = 0x00020500u;
|
||||
constexpr uint32_t kHandlerAddr = 0x00100000u;
|
||||
constexpr uint32_t kHandlerWriteAddr = 0x00020600u;
|
||||
constexpr uint32_t kHandlerArg = 0x12345678u;
|
||||
|
||||
g_dmacHandlerWriteAddr = kHandlerWriteAddr;
|
||||
g_dmacHandlerValue = 0xCAFEBABEu;
|
||||
g_dmacHandlerLastCause = 0u;
|
||||
g_dmacHandlerLastArg = 0u;
|
||||
env.runtime.registerFunction(kHandlerAddr, &testDmacHandler);
|
||||
|
||||
setRegU32(env.ctx, 4, 5u);
|
||||
setRegU32(env.ctx, 5, kHandlerAddr);
|
||||
setRegU32(env.ctx, 6, 0u);
|
||||
setRegU32(env.ctx, 7, kHandlerArg);
|
||||
ps2_syscalls::AddDmacHandler(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
const int32_t handlerId = getRegS32(env.ctx, 2);
|
||||
t.IsTrue(handlerId > 0, "AddDmacHandler should register a handler");
|
||||
|
||||
setRegU32(env.ctx, 4, 5u);
|
||||
ps2_syscalls::EnableDmac(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), 0, "EnableDmac should succeed");
|
||||
g_sifDmaResult = 0;
|
||||
env.runtime.registerFunction(kSchedulerSifDmaEntryPc, schedulerSifDmaEntry);
|
||||
env.runtime.registerFunction(kSchedulerSifDmaResumePc, schedulerSifDmaResume);
|
||||
env.runtime.registerFunction(kSchedulerSifDmaHandlerPc, testDmacHandler);
|
||||
|
||||
std::array<uint8_t, 16> payload{};
|
||||
for (size_t i = 0; i < payload.size(); ++i)
|
||||
@@ -242,17 +260,19 @@ void register_ps2_sif_dma_tests()
|
||||
kDstAddr,
|
||||
static_cast<int32_t>(payload.size()),
|
||||
0};
|
||||
std::memcpy(env.rdram.data() + kDescAddr, &desc, sizeof(desc));
|
||||
std::memcpy(env.rdram.data() + kSchedulerSifDmaDescAddr, &desc, sizeof(desc));
|
||||
|
||||
setRegU32(env.ctx, 4, kDescAddr);
|
||||
setRegU32(env.ctx, 5, 1u);
|
||||
ps2_stubs::sceSifSetDma(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kSchedulerSifDmaEntryPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
t.IsTrue(getRegS32(env.ctx, 2) > 0, "sceSifSetDma should still report success");
|
||||
t.IsTrue(g_sifDmaResult > 0, "sceSifSetDma should still report success");
|
||||
t.Equals(readGuestU32(env.rdram.data(), kHandlerWriteAddr), g_dmacHandlerValue,
|
||||
"sceSifSetDma should invoke registered DMAC handlers");
|
||||
"the scheduler should execute the queued DMAC invocation");
|
||||
t.Equals(g_dmacHandlerLastCause, 5u, "DMAC handler should observe cause 5");
|
||||
t.Equals(g_dmacHandlerLastArg, kHandlerArg, "DMAC handler should receive registered argument");
|
||||
t.Equals(g_dmacHandlerLastArg, kSchedulerSifDmaHandlerArg,
|
||||
"DMAC handler should receive registered argument");
|
||||
});
|
||||
|
||||
tc.Run("sceSifSetDma acknowledges DTX work-buffer transfers by advancing the EE footer ticket", [](TestCase &t)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "MiniTest.h"
|
||||
#include "ps2_runtime.h"
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_iop_transport.h"
|
||||
#include "ps2_syscalls.h"
|
||||
#include "ps2_stubs.h"
|
||||
#include "runtime/ee_scheduler.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -161,6 +163,23 @@ namespace
|
||||
|
||||
constexpr uint32_t K_DTX_DISPATCH_RESULT_ADDR = 0x0002D800u;
|
||||
constexpr uint32_t K_DTX_DISPATCH_RESULT_MARKER = 0xD15CA7C1u;
|
||||
constexpr uint32_t K_DTX_SCHEDULER_CALL = 0x00102000u;
|
||||
constexpr uint32_t K_DTX_SCHEDULER_RESUME = 0x00102010u;
|
||||
constexpr uint32_t K_LOTR_SOUND_SCHEDULER_CALL = 0x00102020u;
|
||||
constexpr uint32_t K_LOTR_SOUND_SCHEDULER_RESUME = 0x00102030u;
|
||||
uint32_t g_schedulerRpcClient = 0u;
|
||||
uint32_t g_schedulerRpcNumber = 0u;
|
||||
uint32_t g_schedulerRpcSend = 0u;
|
||||
uint32_t g_schedulerRpcReceive = 0u;
|
||||
uint32_t g_schedulerRpcResult = 0u;
|
||||
uint32_t g_schedulerLotrSoundClient = 0u;
|
||||
uint32_t g_schedulerLotrSoundSend = 0u;
|
||||
uint32_t g_schedulerLotrSoundReceive = 0u;
|
||||
uint32_t g_schedulerLotrSoundEndFunction = 0u;
|
||||
uint32_t g_schedulerLotrSoundSemaphore = 0u;
|
||||
uint32_t g_schedulerLotrSoundResult = 0u;
|
||||
|
||||
void writeGuestU32(uint8_t *rdram, uint32_t addr, uint32_t value);
|
||||
|
||||
void lotrSoundEndCallback(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
@@ -178,6 +197,49 @@ namespace
|
||||
ctx->pc = ::getRegU32(ctx, 31);
|
||||
}
|
||||
|
||||
void schedulerLotrSoundRpcCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
SET_GPR_U32(ctx, 4, g_schedulerLotrSoundClient);
|
||||
SET_GPR_U32(ctx, 5, 0u);
|
||||
SET_GPR_U32(ctx, 6, K_SIF_RPC_MODE_NOWAIT);
|
||||
SET_GPR_U32(ctx, 7, g_schedulerLotrSoundSend);
|
||||
SET_GPR_U32(ctx, 8, 0x2000u);
|
||||
SET_GPR_U32(ctx, 9, g_schedulerLotrSoundReceive);
|
||||
SET_GPR_U32(ctx, 10, 0x2000u);
|
||||
SET_GPR_U32(ctx, 11, g_schedulerLotrSoundEndFunction);
|
||||
writeGuestU32(rdram, ::getRegU32(ctx, 29), g_schedulerLotrSoundSemaphore);
|
||||
ctx->pc = K_LOTR_SOUND_SCHEDULER_RESUME;
|
||||
SifCallRpc(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void schedulerLotrSoundRpcResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_schedulerLotrSoundResult = ::getRegU32(ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerDtxRpcCall(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
SET_GPR_U32(ctx, 4, g_schedulerRpcClient);
|
||||
SET_GPR_U32(ctx, 5, g_schedulerRpcNumber);
|
||||
SET_GPR_U32(ctx, 6, 0u);
|
||||
SET_GPR_U32(ctx, 7, g_schedulerRpcSend);
|
||||
SET_GPR_U32(ctx, 8, 8u);
|
||||
SET_GPR_U32(ctx, 9, g_schedulerRpcReceive);
|
||||
SET_GPR_U32(ctx, 10, sizeof(uint32_t));
|
||||
SET_GPR_U32(ctx, 11, 0u);
|
||||
ctx->pc = K_DTX_SCHEDULER_RESUME;
|
||||
SifCallRpc(rdram, ctx, runtime);
|
||||
}
|
||||
|
||||
void schedulerDtxRpcResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_schedulerRpcResult = ::getRegU32(ctx, 2);
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void recvxDtxDispatcher(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
(void)runtime;
|
||||
@@ -643,7 +705,6 @@ void register_ps2_sif_rpc_tests()
|
||||
configureProfile(env, "SLUS_205.78");
|
||||
|
||||
constexpr uint32_t kClientAddr = 0x00039000u;
|
||||
constexpr uint32_t kSemaParamAddr = 0x00039100u;
|
||||
constexpr uint32_t kSendAddr = 0x0003A000u;
|
||||
constexpr uint32_t kRecvAddr = 0x0003C000u;
|
||||
constexpr uint32_t kEndFunc = 0x001FFD70u;
|
||||
@@ -653,13 +714,6 @@ void register_ps2_sif_rpc_tests()
|
||||
|
||||
SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
|
||||
const uint32_t semaParam[6] = {0u, 1u, 0u, 0u, 0u, 0u};
|
||||
std::memcpy(env.rdram.data() + kSemaParamAddr, semaParam, sizeof(semaParam));
|
||||
setRegU32(env.ctx, 4, kSemaParamAddr);
|
||||
CreateSema(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
const int32_t semaId = getRegS32(env.ctx, 2);
|
||||
t.IsTrue(semaId > 0, "CreateSema should return a positive semaphore id");
|
||||
|
||||
setRegU32(env.ctx, 4, kClientAddr);
|
||||
setRegU32(env.ctx, 5, IOP_SID_LOTR_SOUND);
|
||||
setRegU32(env.ctx, 6, 0u);
|
||||
@@ -668,19 +722,25 @@ void register_ps2_sif_rpc_tests()
|
||||
|
||||
writeGuestU32(env.rdram.data(), kSendAddr, 3u);
|
||||
std::memset(env.rdram.data() + kRecvAddr, 0xAA, 0x2000u);
|
||||
setRegU32(env.ctx, 4, kClientAddr);
|
||||
setRegU32(env.ctx, 5, 0u);
|
||||
setRegU32(env.ctx, 6, K_SIF_RPC_MODE_NOWAIT);
|
||||
setRegU32(env.ctx, 7, kSendAddr);
|
||||
setRegU32(env.ctx, 8, 0x2000u);
|
||||
setRegU32(env.ctx, 9, kRecvAddr);
|
||||
setRegU32(env.ctx, 10, 0x2000u);
|
||||
setRegU32(env.ctx, 11, kEndFunc);
|
||||
setRegU32(env.ctx, 29, K_STACK_ADDR);
|
||||
writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, static_cast<uint32_t>(semaId));
|
||||
SifCallRpc(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
env.runtime.registerFunction(K_LOTR_SOUND_SCHEDULER_CALL, schedulerLotrSoundRpcCall);
|
||||
env.runtime.registerFunction(K_LOTR_SOUND_SCHEDULER_RESUME, schedulerLotrSoundRpcResume);
|
||||
g_schedulerLotrSoundClient = kClientAddr;
|
||||
g_schedulerLotrSoundSend = kSendAddr;
|
||||
g_schedulerLotrSoundReceive = kRecvAddr;
|
||||
g_schedulerLotrSoundEndFunction = kEndFunc;
|
||||
g_schedulerLotrSoundResult = static_cast<uint32_t>(-1);
|
||||
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifCallRpc should succeed for LotR sound RPC");
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = K_LOTR_SOUND_SCHEDULER_CALL;
|
||||
setRegU32(mainContext, 29, K_STACK_ADDR);
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
const int32_t semaId = env.runtime.eeScheduler().createSemaphore(0, 1, 0u, 0u);
|
||||
t.IsTrue(semaId > 0, "scheduler should create a positive semaphore id");
|
||||
g_schedulerLotrSoundSemaphore = static_cast<uint32_t>(semaId);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
t.Equals(g_schedulerLotrSoundResult, static_cast<uint32_t>(KE_OK),
|
||||
"SifCallRpc should resume with KE_OK for LotR sound RPC");
|
||||
t.Equals(g_lotrSoundCallbackHits.load(), 1u,
|
||||
"LotR SOUND_JP callback should consume the HLE response");
|
||||
t.Equals(readGuestStruct<uint32_t>(env.rdram.data(), kRecvAddr + 0u), 0u,
|
||||
@@ -688,9 +748,8 @@ void register_ps2_sif_rpc_tests()
|
||||
t.IsTrue(readGuestStruct<uint32_t>(env.rdram.data(), kRecvAddr + 4u) != 0u,
|
||||
"LotR sound response should advance the IOP update counter");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(semaId));
|
||||
PollSema(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), semaId, "LotR sound callback completion should signal the sema");
|
||||
t.Equals(env.runtime.eeScheduler().pollSemaphore(semaId), semaId,
|
||||
"LotR sound callback completion should signal the sema");
|
||||
});
|
||||
|
||||
tc.Run("RECVX sound callbacks complete in HLE and only clear busy on designated callbacks", [](TestCase &t)
|
||||
@@ -1156,7 +1215,22 @@ void register_ps2_sif_rpc_tests()
|
||||
|
||||
writeGuestU32(env.rdram.data(), kFnTableSlot, kRegisteredHandlerAddr);
|
||||
writeGuestU32(env.rdram.data(), kRecvAddr, 0u);
|
||||
callUrpc();
|
||||
env.runtime.registerFunction(K_DTX_SCHEDULER_CALL, schedulerDtxRpcCall);
|
||||
env.runtime.registerFunction(K_DTX_SCHEDULER_RESUME, schedulerDtxRpcResume);
|
||||
g_schedulerRpcClient = kClientAddr;
|
||||
g_schedulerRpcNumber = kRpcNum;
|
||||
g_schedulerRpcSend = kSendAddr;
|
||||
g_schedulerRpcReceive = kRecvAddr;
|
||||
g_schedulerRpcResult = static_cast<uint32_t>(-1);
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = K_DTX_SCHEDULER_CALL;
|
||||
setRegU32(mainContext, 29, K_STACK_ADDR);
|
||||
writeGuestU32(env.rdram.data(), K_STACK_ADDR + 0x00u, 0u);
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
t.Equals(g_schedulerRpcResult, static_cast<uint32_t>(KE_OK),
|
||||
"DTX URPC should resume its base context with KE_OK");
|
||||
|
||||
t.Equals(g_dtxDispatcherHits.load(), 1u,
|
||||
"registered DTX function-table slot should enter the guest dispatcher");
|
||||
|
||||
Reference in New Issue
Block a user