mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-09-26 08:51:05 -04:00
Feature/runtime ecosystem refactor (#107)
* feat: remove memory and pad from stub section * feat: add support for resume entry targets in CodeGenerator (this allow jumps in address outside function) feat: refactor entry point discovery one more try to reduce big generated file * feat: optmizations for release build * feat: remove unused file * feat: added some test cases for code gen * feat: added log macro and remove win specific code * feat: refactor runtime folder structure feat: added reset sound driver RPC state and compatibility layout feat: rename and added new test feat: update RPC calls to use defined constants feat: added more PSMC(16, 32) feat: change cd read to try find the asset ignoring case sensitive fix: fix some render problems feat: add logs on pad feat: added more RPC handles * feat: added game override for code veronica * feat: apply vita patch * feat: flags to disable build * feat: fix merges feat: break a lot of tests * feat: better throw error on empty cd path feat: remove recompiler unusde function feat: apply missing patch * feat: gamedp is now part of lib feat: missing file * feat: small cleanup * feat: missing vita changes * feat: fix more merge * feat: fix tests * feat: last missing feature * feat: added missing import * feat: rename test local functions * feat: init syscall on ps2 list * feat: added DMA helpers * feat: faster builds feat: more implement for darkcloud * feat: back missing file * feat: added missing includes * feat: remove test * feat: missing include * feat: read register funtion * feat: build fix * feat: force exit on detach thread
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
#include "ps2_runtime_macros.h"
|
||||
#include "ps2_syscalls.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
using namespace ps2_syscalls;
|
||||
@@ -24,7 +26,13 @@ namespace
|
||||
constexpr int KE_SEMA_ZERO = -419;
|
||||
constexpr int KE_SEMA_OVF = -420;
|
||||
|
||||
constexpr int THS_SUSPEND = 0x08;
|
||||
constexpr int THS_WAITSUSPEND = 0x0C;
|
||||
constexpr int THS_DORMANT = 0x10;
|
||||
constexpr uint32_t TSW_EVENT = 3u;
|
||||
|
||||
constexpr uint32_t K_EVENT_WAIT_READY_ADDR = 0x1800u;
|
||||
constexpr uint32_t K_EVENT_WAIT_GATE_ADDR = 0x1804u;
|
||||
|
||||
struct EeThreadStatus
|
||||
{
|
||||
@@ -78,6 +86,28 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t readGuestU32(const uint8_t *rdram, uint32_t addr)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
std::memcpy(&value, rdram + addr, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
bool waitUntil(Predicate pred, std::chrono::milliseconds timeout)
|
||||
{
|
||||
const auto deadline = std::chrono::steady_clock::now() + timeout;
|
||||
while (std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
if (pred())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
return pred();
|
||||
}
|
||||
|
||||
bool callSyscall(uint32_t syscallNumber, uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
return dispatchNumericSyscall(syscallNumber, rdram, ctx, runtime);
|
||||
@@ -138,6 +168,38 @@ namespace
|
||||
ctx->pc = ::getRegU32(ctx, 31);
|
||||
}
|
||||
|
||||
void waitEventAfterSuspendHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
if (!rdram || !ctx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
writeGuestU32(rdram, K_EVENT_WAIT_READY_ADDR, 1u);
|
||||
while (readGuestU32(rdram, K_EVENT_WAIT_GATE_ADDR) == 0u)
|
||||
{
|
||||
if (runtime && runtime->isStopRequested())
|
||||
{
|
||||
ctx->pc = 0u;
|
||||
return;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
const uint32_t eid = ::getRegU32(ctx, 4);
|
||||
setRegU32(*ctx, 4, eid);
|
||||
setRegU32(*ctx, 5, 0x4u);
|
||||
setRegU32(*ctx, 6, 1u);
|
||||
setRegU32(*ctx, 7, 0u);
|
||||
WaitEventFlag(rdram, ctx, runtime);
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void alarmNoopHandler(uint8_t *, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
struct TestEnv
|
||||
{
|
||||
std::vector<uint8_t> rdram;
|
||||
@@ -364,6 +426,138 @@ void register_ps2_runtime_kernel_tests()
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteSema should clean up legacy-decoded semaphore");
|
||||
});
|
||||
|
||||
tc.Run("WaitEventFlag preserves waitsuspend state when a suspended thread blocks", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kEventParamAddr = 0x1600u;
|
||||
constexpr uint32_t kWaitThreadEntry = 0x00260000u;
|
||||
|
||||
const uint32_t eventParam[3] = {
|
||||
0u,
|
||||
0u,
|
||||
0u
|
||||
};
|
||||
std::memcpy(env.rdram.data() + kEventParamAddr, eventParam, sizeof(eventParam));
|
||||
writeGuestU32(env.rdram.data(), K_EVENT_WAIT_READY_ADDR, 0u);
|
||||
writeGuestU32(env.rdram.data(), K_EVENT_WAIT_GATE_ADDR, 0u);
|
||||
|
||||
R5900Context createEventCtx{};
|
||||
setRegU32(createEventCtx, 4, kEventParamAddr);
|
||||
CreateEventFlag(env.rdram.data(), &createEventCtx, &env.runtime);
|
||||
const int32_t eid = getRegS32(createEventCtx, 2);
|
||||
t.IsTrue(eid > 0, "CreateEventFlag should return a valid event id");
|
||||
|
||||
env.runtime.registerFunction(kWaitThreadEntry, &waitEventAfterSuspendHandler);
|
||||
|
||||
const uint32_t threadParam[7] = {
|
||||
0u,
|
||||
kWaitThreadEntry,
|
||||
0x00310000u,
|
||||
0x00000800u,
|
||||
0x00120000u,
|
||||
6u,
|
||||
0u
|
||||
};
|
||||
|
||||
writeGuestWords(env.rdram.data(), K_PARAM_ADDR, threadParam, std::size(threadParam));
|
||||
setRegU32(env.ctx, 4, K_PARAM_ADDR);
|
||||
CreateThread(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
const int32_t tid = getRegS32(env.ctx, 2);
|
||||
t.IsTrue(tid >= 2, "CreateThread should return a valid worker thread id");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(tid));
|
||||
setRegU32(env.ctx, 5, static_cast<uint32_t>(eid));
|
||||
StartThread(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "StartThread should launch the event waiter");
|
||||
|
||||
const bool ready = waitUntil([&]()
|
||||
{
|
||||
return readGuestU32(env.rdram.data(), K_EVENT_WAIT_READY_ADDR) == 1u;
|
||||
}, std::chrono::milliseconds(200));
|
||||
t.IsTrue(ready, "waiter thread should reach the suspend gate before blocking");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(tid));
|
||||
SuspendThread(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SuspendThread should succeed for the running waiter");
|
||||
|
||||
writeGuestU32(env.rdram.data(), K_EVENT_WAIT_GATE_ADDR, 1u);
|
||||
|
||||
const bool waiting = waitUntil([&]()
|
||||
{
|
||||
R5900Context statusCtx{};
|
||||
setRegU32(statusCtx, 4, static_cast<uint32_t>(tid));
|
||||
setRegU32(statusCtx, 5, K_STATUS_ADDR);
|
||||
ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime);
|
||||
if (getRegS32(statusCtx, 2) != KE_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EeThreadStatus status{};
|
||||
std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status));
|
||||
return status.waitType == TSW_EVENT;
|
||||
}, std::chrono::milliseconds(200));
|
||||
t.IsTrue(waiting, "waiter thread should block on the event flag");
|
||||
|
||||
EeThreadStatus waitingStatus{};
|
||||
std::memcpy(&waitingStatus, env.rdram.data() + K_STATUS_ADDR, sizeof(waitingStatus));
|
||||
t.Equals(waitingStatus.status, THS_WAITSUSPEND,
|
||||
"event-flag wait should report THS_WAITSUSPEND when the thread is already suspended");
|
||||
|
||||
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 wake the waiting thread");
|
||||
|
||||
const bool suspended = waitUntil([&]()
|
||||
{
|
||||
R5900Context statusCtx{};
|
||||
setRegU32(statusCtx, 4, static_cast<uint32_t>(tid));
|
||||
setRegU32(statusCtx, 5, K_STATUS_ADDR);
|
||||
ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime);
|
||||
if (getRegS32(statusCtx, 2) != KE_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EeThreadStatus status{};
|
||||
std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status));
|
||||
return status.status == THS_SUSPEND && status.waitType == 0u;
|
||||
}, std::chrono::milliseconds(200));
|
||||
t.IsTrue(suspended, "after wake, a still-suspended waiter should move to THS_SUSPEND");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(tid));
|
||||
ResumeThread(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "ResumeThread should release the waiter after the event is set");
|
||||
|
||||
const bool dormant = waitUntil([&]()
|
||||
{
|
||||
R5900Context statusCtx{};
|
||||
setRegU32(statusCtx, 4, static_cast<uint32_t>(tid));
|
||||
setRegU32(statusCtx, 5, K_STATUS_ADDR);
|
||||
ReferThreadStatus(env.rdram.data(), &statusCtx, &env.runtime);
|
||||
if (getRegS32(statusCtx, 2) != KE_OK)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EeThreadStatus status{};
|
||||
std::memcpy(&status, env.rdram.data() + K_STATUS_ADDR, sizeof(status));
|
||||
return status.status == THS_DORMANT;
|
||||
}, std::chrono::milliseconds(200));
|
||||
t.IsTrue(dormant, "waiter thread should return to dormant after the event is signaled and resumed");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(eid));
|
||||
DeleteEventFlag(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteEventFlag should clean up the test event flag");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(tid));
|
||||
DeleteThread(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "DeleteThread should clean up the waiter thread");
|
||||
});
|
||||
|
||||
tc.Run("setup heap and allocator primitives track end-of-heap", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
@@ -401,6 +595,36 @@ void register_ps2_runtime_kernel_tests()
|
||||
t.Equals(reused, heapBase, "guestFree should make the head block reusable");
|
||||
});
|
||||
|
||||
tc.Run("ReleaseAlarm aliases CancelAlarm and cache toggles succeed", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kAlarmHandlerAddr = 0x00270000u;
|
||||
env.runtime.registerFunction(kAlarmHandlerAddr, &alarmNoopHandler);
|
||||
|
||||
setRegU32(env.ctx, 4, 0xFFFFu);
|
||||
setRegU32(env.ctx, 5, kAlarmHandlerAddr);
|
||||
setRegU32(env.ctx, 6, 0u);
|
||||
SetAlarm(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
const int32_t alarmId = getRegS32(env.ctx, 2);
|
||||
t.IsTrue(alarmId > 0, "SetAlarm should create a cancellable alarm");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(alarmId));
|
||||
ReleaseAlarm(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "ReleaseAlarm should cancel active alarms");
|
||||
|
||||
setRegU32(env.ctx, 4, static_cast<uint32_t>(alarmId));
|
||||
CancelAlarm(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_ERROR,
|
||||
"CancelAlarm should report missing alarms after ReleaseAlarm consumes them");
|
||||
|
||||
EnableCache(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "EnableCache should succeed as a no-op");
|
||||
|
||||
DisableCache(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "DisableCache should succeed as a no-op");
|
||||
});
|
||||
|
||||
tc.Run("setup heap and thread invalid ids use documented kernel errors", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
Reference in New Issue
Block a user