mirror of
https://github.com/ran-j/PS2Recomp.git
synced 2026-10-01 02:17:14 -04:00
feat: IOP emulator
refactor: codegen to catch callbacks on mips code feat: added a lot of entries or IOP emulator
This commit is contained in:
@@ -157,6 +157,29 @@ void register_code_generator_tests()
|
||||
{
|
||||
MiniTest::Case("CodeGenerator", [](TestCase &tc)
|
||||
{
|
||||
tc.Run("Generated sources cannot be shadowed by stale local declaration headers", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "header_lookup";
|
||||
func.start = 0x8F00;
|
||||
func.end = 0x8F04;
|
||||
func.isRecompiled = true;
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
gen.setRenamedFunctions({{func.start, "header_lookup_0x8f00"}});
|
||||
|
||||
const std::string generated = gen.generateFunction(func, {makeNop(func.start)}, true);
|
||||
const std::string registration = gen.generateFunctionRegistration({func}, {});
|
||||
|
||||
t.IsTrue(generated.find("#include <ps2_recompiled_functions.h>") != std::string::npos,
|
||||
"function sources must resolve declarations through the configured include path");
|
||||
t.IsTrue(generated.find("#include <ps2_recompiled_stubs.h>") != std::string::npos,
|
||||
"function sources must resolve stub declarations through the configured include path");
|
||||
t.IsTrue(registration.find("#include <ps2_recompiled_functions.h>") != std::string::npos,
|
||||
"the registration source must use the same unambiguous declaration header");
|
||||
t.IsTrue(registration.find("#include <ps2_recompiled_stubs.h>") != std::string::npos,
|
||||
"the registration source must use the same unambiguous stub header");
|
||||
});
|
||||
|
||||
tc.Run("SYSCALL publishes its continuation before entering the runtime", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "syscall_resume";
|
||||
@@ -185,6 +208,42 @@ void register_code_generator_tests()
|
||||
"the continuation PC must be visible before a syscall can transfer to the scheduler");
|
||||
});
|
||||
|
||||
tc.Run("SYSCALL fallthrough is a resumable entry", [](TestCase &t) {
|
||||
Function func;
|
||||
func.name = "syscall_resume_entry";
|
||||
func.start = 0x9100;
|
||||
func.end = 0x9108;
|
||||
func.isRecompiled = true;
|
||||
|
||||
Instruction syscall{};
|
||||
syscall.address = 0x9100;
|
||||
syscall.opcode = OPCODE_SPECIAL;
|
||||
syscall.function = SPECIAL_SYSCALL;
|
||||
syscall.raw = (0x83u << 6) | SPECIAL_SYSCALL;
|
||||
|
||||
Instruction after = makeNop(0x9104);
|
||||
CodeGenerator gen({}, {});
|
||||
CodeGenerator::AnalysisResult analysis =
|
||||
gen.collectInternalBranchTargets(func, {syscall, after});
|
||||
|
||||
t.IsTrue(analysis.resumeEntryPoints.contains(0x9104u),
|
||||
"a syscall can yield through a guest override, so its fallthrough must be resumable");
|
||||
|
||||
const std::string generated = gen.generateFunction(func, {syscall, after}, false);
|
||||
t.IsTrue(generated.find("case 0x9104u: goto label_9104;") != std::string::npos,
|
||||
"the owner wrapper must resume directly after the syscall");
|
||||
|
||||
gen.setRenamedFunctions({{0x9100u, "syscall_resume_entry_0x9100"}});
|
||||
gen.setResumeEntryTargets({{0x9100u,
|
||||
std::vector<uint32_t>(analysis.resumeEntryPoints.begin(),
|
||||
analysis.resumeEntryPoints.end())}});
|
||||
const std::string registration = gen.generateFunctionRegistration({func}, {});
|
||||
t.IsTrue(registration.find(
|
||||
"g_ps2RecompiledFunctionTable[1] = syscall_resume_entry_0x9100; // 0x9104") !=
|
||||
std::string::npos,
|
||||
"the syscall continuation must register to the owner wrapper");
|
||||
});
|
||||
|
||||
tc.Run("R5900 MULT writes rd when rd is non-zero", [](TestCase &t) {
|
||||
CodeGenerator gen({}, {});
|
||||
|
||||
@@ -606,6 +665,46 @@ void register_code_generator_tests()
|
||||
"multiple resume pcs should register to the same owner wrapper");
|
||||
});
|
||||
|
||||
tc.Run("configured internal guest handlers register to their owner wrapper", [](TestCase &t) {
|
||||
Function owner;
|
||||
owner.name = "sdk_bootstrap_owner";
|
||||
owner.start = 0x7000;
|
||||
owner.end = 0x7020;
|
||||
owner.isRecompiled = true;
|
||||
owner.isStub = false;
|
||||
|
||||
std::vector<Instruction> instructions{
|
||||
makeNop(0x7000), makeNop(0x7004), makeNop(0x7008), makeNop(0x700C),
|
||||
makeNop(0x7010), makeNop(0x7014), makeNop(0x7018), makeNop(0x701C)};
|
||||
std::vector<Function> functions{owner};
|
||||
std::unordered_map<uint32_t, std::vector<Instruction>> decoded{{owner.start, instructions}};
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> targetsByOwner;
|
||||
|
||||
const size_t added = PS2Recompiler::CollectInternalEntryTargets(
|
||||
functions, decoded, {0x7008u, 0x7018u}, targetsByOwner);
|
||||
|
||||
t.IsTrue(added == 2u,
|
||||
"both address-qualified internal handlers should be promoted");
|
||||
t.IsTrue(targetsByOwner.at(owner.start).size() == 2u,
|
||||
"both handlers should belong to the containing generated wrapper");
|
||||
|
||||
CodeGenerator gen({}, {});
|
||||
gen.setRenamedFunctions({{owner.start, "sdk_bootstrap_owner_0x7000"}});
|
||||
gen.setResumeEntryTargets(targetsByOwner);
|
||||
|
||||
const std::string generated = gen.generateFunction(owner, instructions, false);
|
||||
const std::string registration = gen.generateFunctionRegistration(functions, {});
|
||||
|
||||
t.IsTrue(generated.find("case 0x7008u: goto label_7008;") != std::string::npos,
|
||||
"the owner must enter directly at the first installed handler");
|
||||
t.IsTrue(generated.find("case 0x7018u: goto label_7018;") != std::string::npos,
|
||||
"the owner must enter directly at the second installed handler");
|
||||
t.IsTrue(registration.find("sdk_bootstrap_owner_0x7000; // 0x7008") != std::string::npos,
|
||||
"the first handler address must dispatch to its owner wrapper");
|
||||
t.IsTrue(registration.find("sdk_bootstrap_owner_0x7000; // 0x7018") != std::string::npos,
|
||||
"the second handler address must dispatch to its owner wrapper");
|
||||
});
|
||||
|
||||
tc.Run("external mid-function entry can register to the owner wrapper", [](TestCase &t) {
|
||||
Function caller;
|
||||
caller.name = "caller";
|
||||
|
||||
@@ -155,6 +155,78 @@ static bool writeMinimalMipsElfWithJalFallbackTarget(const std::filesystem::path
|
||||
return writer.save(elfPath.string());
|
||||
}
|
||||
|
||||
static bool writeMinimalMipsElfWithAddressTakenCallbacks(const std::filesystem::path &elfPath)
|
||||
{
|
||||
ELFIO::elfio writer;
|
||||
writer.create(ELFIO::ELFCLASS32, ELFIO::ELFDATA2LSB);
|
||||
writer.set_os_abi(ELFIO::ELFOSABI_NONE);
|
||||
writer.set_type(ELFIO::ET_EXEC);
|
||||
writer.set_machine(ELFIO::EM_MIPS);
|
||||
writer.set_entry(0x00100000u);
|
||||
|
||||
ELFIO::section *text = writer.sections.add(".text");
|
||||
text->set_type(ELFIO::SHT_PROGBITS);
|
||||
text->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR);
|
||||
text->set_addr_align(4);
|
||||
text->set_address(0x00100000u);
|
||||
|
||||
std::array<uint32_t, 30> textWords{};
|
||||
textWords[0] = 0x3C040010u; // lui a0,0x10
|
||||
textWords[1] = 0xAC800000u; // sw zero,0(a0)
|
||||
textWords[2] = 0x0C040008u; // jal 0x00100020 (callback registrar)
|
||||
textWords[3] = 0x24840040u; // addiu a0,a0,0x40 (delay slot)
|
||||
textWords[4] = 0x03E00008u; // jr ra
|
||||
textWords[5] = 0x00000000u; // nop
|
||||
textWords[6] = 0x3C080010u; // lui t0,0x10
|
||||
textWords[7] = 0x25080070u; // addiu t0,t0,0x70 (code label, not a callback argument)
|
||||
|
||||
textWords[8] = 0x03E00008u; // registrar at 0x00100020
|
||||
textWords[9] = 0x00000000u;
|
||||
|
||||
textWords[16] = 0x27BDFFF0u; // callback at 0x00100040: addiu sp,sp,-0x10
|
||||
textWords[17] = 0xFFBF0000u; // sd ra,0(sp)
|
||||
textWords[18] = 0xDFBF0000u; // ld ra,0(sp)
|
||||
textWords[19] = 0x03E00008u; // jr ra
|
||||
textWords[20] = 0x27BD0010u; // addiu sp,sp,0x10
|
||||
|
||||
textWords[24] = 0x03E00008u; // table leaf at 0x00100060
|
||||
textWords[25] = 0x00000000u;
|
||||
textWords[26] = 0x03E00008u; // table leaf at 0x00100068
|
||||
textWords[27] = 0x00000000u;
|
||||
textWords[28] = 0x03E00008u; // isolated pointer target at 0x00100070
|
||||
textWords[29] = 0x00000000u;
|
||||
|
||||
text->set_data(reinterpret_cast<const char *>(textWords.data()),
|
||||
static_cast<ELFIO::Elf_Word>(textWords.size() * sizeof(uint32_t)));
|
||||
|
||||
ELFIO::section *rodata = writer.sections.add(".rodata");
|
||||
rodata->set_type(ELFIO::SHT_PROGBITS);
|
||||
rodata->set_flags(ELFIO::SHF_ALLOC);
|
||||
rodata->set_addr_align(4);
|
||||
rodata->set_address(0x00200000u);
|
||||
|
||||
std::array<uint32_t, 20> tableWords{};
|
||||
tableWords[1] = 0x00100060u;
|
||||
tableWords[3] = 0x00100068u;
|
||||
tableWords[16] = 0x00100070u; // plausible entry, but not part of a pointer cluster
|
||||
rodata->set_data(reinterpret_cast<const char *>(tableWords.data()),
|
||||
static_cast<ELFIO::Elf_Word>(tableWords.size() * sizeof(uint32_t)));
|
||||
|
||||
ELFIO::segment *textSegment = writer.segments.add();
|
||||
textSegment->set_type(ELFIO::PT_LOAD);
|
||||
textSegment->set_flags(ELFIO::PF_R | ELFIO::PF_X);
|
||||
textSegment->set_align(0x1000);
|
||||
textSegment->add_section_index(text->get_index(), text->get_addr_align());
|
||||
|
||||
ELFIO::segment *dataSegment = writer.segments.add();
|
||||
dataSegment->set_type(ELFIO::PT_LOAD);
|
||||
dataSegment->set_flags(ELFIO::PF_R);
|
||||
dataSegment->set_align(0x1000);
|
||||
dataSegment->add_section_index(rodata->get_index(), rodata->get_addr_align());
|
||||
|
||||
return writer.save(elfPath.string());
|
||||
}
|
||||
|
||||
static bool writeMinimalMipsElfWithInitializer(const std::filesystem::path &elfPath,
|
||||
const std::string &functionName,
|
||||
uint32_t initializerTarget)
|
||||
@@ -838,6 +910,42 @@ void register_ps2_recompiler_tests()
|
||||
std::filesystem::remove(configPath, removeError);
|
||||
});
|
||||
|
||||
tc.Run("config manager loads modern and legacy guest entry hints", [](TestCase &t) {
|
||||
const auto uniqueSuffix = std::to_string(
|
||||
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
const std::filesystem::path configPath =
|
||||
std::filesystem::temp_directory_path() / ("ps2recomp-entry-hints-" + uniqueSuffix + ".toml");
|
||||
|
||||
std::ofstream configFile(configPath);
|
||||
t.IsTrue(static_cast<bool>(configFile), "temp config file should be writable");
|
||||
if (!configFile)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
configFile << "[general]\n";
|
||||
configFile << "input = \"dummy.elf\"\n";
|
||||
configFile << "output = \"out\"\n";
|
||||
configFile << "entry_points = [\"callback@0x7008\"]\n";
|
||||
configFile << "untracked_stubs = [\"legacy_callback@0x7018\"]\n";
|
||||
configFile.close();
|
||||
|
||||
ConfigManager manager(configPath.string());
|
||||
const RecompilerConfig config = manager.loadConfig();
|
||||
|
||||
t.Equals(config.entryPointHints.size(), static_cast<size_t>(2),
|
||||
"modern and legacy entry metadata should be merged");
|
||||
t.IsTrue(std::find(config.entryPointHints.begin(), config.entryPointHints.end(),
|
||||
"callback@0x7008") != config.entryPointHints.end(),
|
||||
"modern entry_points metadata should load");
|
||||
t.IsTrue(std::find(config.entryPointHints.begin(), config.entryPointHints.end(),
|
||||
"legacy_callback@0x7018") != config.entryPointHints.end(),
|
||||
"legacy untracked_stubs metadata should remain compatible");
|
||||
|
||||
std::error_code removeError;
|
||||
std::filesystem::remove(configPath, removeError);
|
||||
});
|
||||
|
||||
tc.Run("elf parser ignores STT_FUNC symbols in non-executable sections", [](TestCase &t) {
|
||||
const auto uniqueSuffix = std::to_string(
|
||||
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
@@ -947,6 +1055,50 @@ void register_ps2_recompiler_tests()
|
||||
std::filesystem::remove(mapPath, removeError);
|
||||
});
|
||||
|
||||
tc.Run("elf parser discovers address-taken callbacks in stripped ELFs", [](TestCase &t) {
|
||||
const auto uniqueSuffix = std::to_string(
|
||||
static_cast<unsigned long long>(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
const std::filesystem::path elfPath =
|
||||
std::filesystem::temp_directory_path() / ("ps2recomp-address-taken-" + uniqueSuffix + ".elf");
|
||||
|
||||
const bool writeOk = writeMinimalMipsElfWithAddressTakenCallbacks(elfPath);
|
||||
t.IsTrue(writeOk, "temporary stripped ELF should be generated");
|
||||
if (!writeOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ElfParser parser(elfPath.string());
|
||||
const bool parseOk = parser.parse();
|
||||
t.IsTrue(parseOk, "generated ELF should parse");
|
||||
if (!parseOk)
|
||||
{
|
||||
std::error_code removeError;
|
||||
std::filesystem::remove(elfPath, removeError);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto functions = parser.extractFunctions();
|
||||
auto hasStart = [&functions](uint32_t start)
|
||||
{
|
||||
return std::any_of(functions.begin(), functions.end(),
|
||||
[start](const Function &function)
|
||||
{ return function.start == start; });
|
||||
};
|
||||
|
||||
t.IsTrue(hasStart(0x00100040u),
|
||||
"LUI plus delay-slot ADDIU should discover the callback entry");
|
||||
t.IsTrue(hasStart(0x00100060u),
|
||||
"clustered rodata pointers should discover the first leaf callback");
|
||||
t.IsTrue(hasStart(0x00100068u),
|
||||
"clustered rodata pointers should discover the second leaf callback");
|
||||
t.IsFalse(hasStart(0x00100070u),
|
||||
"an isolated data pointer or non-callback code materialization must not become a function");
|
||||
|
||||
std::error_code removeError;
|
||||
std::filesystem::remove(elfPath, removeError);
|
||||
});
|
||||
|
||||
tc.Run("runtime call resolution includes Veronica compatibility aliases", [](TestCase &t) {
|
||||
t.Equals(ps2_runtime_calls::resolveSyscallName("ReleaseAlarm"), std::string_view{"ReleaseAlarm"},
|
||||
"ReleaseAlarm should resolve as a syscall name");
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace
|
||||
constexpr uint32_t kIrqWaitPc = 0x00160200u;
|
||||
constexpr uint32_t kIrqResumePc = 0x00160210u;
|
||||
constexpr uint32_t kIntcHandlerPc = 0x00160220u;
|
||||
constexpr uint32_t kIrqStackWaitPc = 0x00160230u;
|
||||
constexpr uint32_t kIrqStackResumePc = 0x00160240u;
|
||||
constexpr uint32_t kIrqStackHandlerPc = 0x00160250u;
|
||||
constexpr uint32_t kIrqRegistrationSp = 0x001E0000u;
|
||||
constexpr uint32_t kIrqRegistrationGuardAddr = kIrqRegistrationSp - 16u;
|
||||
constexpr uint32_t kISemaWaitPc = 0x00160300u;
|
||||
constexpr uint32_t kISemaResumePc = 0x00160310u;
|
||||
constexpr uint32_t kISemaDriverPc = 0x00160320u;
|
||||
@@ -83,6 +88,7 @@ namespace
|
||||
uint64_t g_vsyncTick = 0;
|
||||
uint64_t g_vsyncCsr = 0;
|
||||
std::atomic<bool> g_timer2Resumed{false};
|
||||
uint32_t g_irqObservedSp = 0u;
|
||||
|
||||
void setRegU32(R5900Context &ctx, int reg, uint32_t value)
|
||||
{
|
||||
@@ -184,6 +190,34 @@ namespace
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerIrqStackHandler(uint8_t *rdram, R5900Context *ctx, PS2Runtime *)
|
||||
{
|
||||
g_irqObservedSp = getRegU32(ctx, 29);
|
||||
const uint64_t clobber = 0u;
|
||||
std::memcpy(rdram + g_irqObservedSp - sizeof(clobber), &clobber, sizeof(clobber));
|
||||
ctx->pc = 0u;
|
||||
}
|
||||
|
||||
void schedulerIrqStackWait(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
EeScheduler &scheduler = runtime->eeScheduler();
|
||||
scheduler.addIrqHandler(false,
|
||||
2u,
|
||||
kIrqStackHandlerPc,
|
||||
true,
|
||||
0u,
|
||||
0u,
|
||||
kIrqRegistrationSp);
|
||||
ctx->pc = kIrqStackResumePc;
|
||||
scheduler.waitVSync(scheduler.currentVSyncTick());
|
||||
}
|
||||
|
||||
void schedulerIrqStackResume(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
ctx->pc = 0u;
|
||||
runtime->requestStop();
|
||||
}
|
||||
|
||||
void schedulerISemaHandler(uint8_t *, R5900Context *ctx, PS2Runtime *runtime)
|
||||
{
|
||||
g_dispatchTrace.push_back(3);
|
||||
@@ -467,6 +501,32 @@ void register_ps2_runtime_interrupt_tests()
|
||||
"the IRQ frame should receive its registered argument");
|
||||
});
|
||||
|
||||
tc.Run("IRQ callbacks use an isolated invocation stack", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
env.runtime.registerFunction(kIrqStackWaitPc, schedulerIrqStackWait);
|
||||
env.runtime.registerFunction(kIrqStackResumePc, schedulerIrqStackResume);
|
||||
env.runtime.registerFunction(kIrqStackHandlerPc, schedulerIrqStackHandler);
|
||||
|
||||
constexpr uint64_t guard = 0x1122334455667788ull;
|
||||
std::memcpy(env.rdram.data() + kIrqRegistrationGuardAddr, &guard, sizeof(guard));
|
||||
g_irqObservedSp = 0u;
|
||||
|
||||
R5900Context mainContext{};
|
||||
mainContext.pc = kIrqStackWaitPc;
|
||||
env.runtime.eeScheduler().reset(env.rdram.data(), mainContext);
|
||||
env.runtime.eeScheduler().run();
|
||||
|
||||
uint64_t guardAfter = 0u;
|
||||
std::memcpy(&guardAfter,
|
||||
env.rdram.data() + kIrqRegistrationGuardAddr,
|
||||
sizeof(guardAfter));
|
||||
t.IsTrue(g_irqObservedSp != 0u && g_irqObservedSp != kIrqRegistrationSp,
|
||||
"IRQ handler must not reuse the transient stack captured at registration");
|
||||
t.Equals(guardAfter, guard,
|
||||
"IRQ handler stack writes must not clobber the registering thread's live frame");
|
||||
});
|
||||
|
||||
tc.Run("iSignalSema defers selection until IRQ return", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
@@ -319,6 +319,39 @@ void register_ps2_sif_rpc_tests()
|
||||
{
|
||||
MiniTest::Case("PS2SifRpc", [](TestCase &tc)
|
||||
{
|
||||
tc.Run("SifInitRpc does not reset the running IOP", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
env.runtime.eeScheduler().accountCycles(80u);
|
||||
const uint64_t cyclesBeforeInit = env.runtime.iopDebugSnapshot().emulatorCycles;
|
||||
t.IsTrue(cyclesBeforeInit != 0u, "IOP cycle counter should advance before RPC initialization");
|
||||
|
||||
SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
|
||||
t.Equals(env.runtime.iopDebugSnapshot().emulatorCycles, cyclesBeforeInit,
|
||||
"SifInitRpc must not reboot or reset the IOP");
|
||||
});
|
||||
|
||||
tc.Run("emulated RPC bind waits for a registered IOP server", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
constexpr uint32_t kClientAddr = 0x00021F00u;
|
||||
constexpr uint32_t kUnregisteredSid = 0x13572468u;
|
||||
|
||||
SifInitRpc(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
setRegU32(env.ctx, 4, kClientAddr);
|
||||
setRegU32(env.ctx, 5, kUnregisteredSid);
|
||||
setRegU32(env.ctx, 6, 0u);
|
||||
SifBindRpc(env.rdram.data(), &env.ctx, &env.runtime);
|
||||
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "SifBindRpc transport should complete");
|
||||
const SifRpcClientData client = readGuestStruct<SifRpcClientData>(env.rdram.data(), kClientAddr);
|
||||
t.Equals(client.server, 0u,
|
||||
"client server pointer must stay null until the emulated IRX registers its SID");
|
||||
});
|
||||
|
||||
tc.Run("register bind call updates descriptors and payload", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
@@ -826,7 +859,7 @@ void register_ps2_sif_rpc_tests()
|
||||
t.Equals(getRegS32(env.ctx, 2), 0, "RECVX snddrv client should no longer be RPC-busy");
|
||||
});
|
||||
|
||||
tc.Run("bind before register creates placeholder then remaps", [](TestCase &t)
|
||||
tc.Run("hybrid bind before register waits then remaps", [](TestCase &t)
|
||||
{
|
||||
TestEnv env;
|
||||
|
||||
@@ -846,11 +879,9 @@ void register_ps2_sif_rpc_tests()
|
||||
t.Equals(getRegS32(env.ctx, 2), KE_OK, "initial bind without registered server should still succeed");
|
||||
|
||||
const SifRpcClientData clientBeforeRegister = readGuestStruct<SifRpcClientData>(env.rdram.data(), kClientAddr);
|
||||
t.IsTrue(clientBeforeRegister.server != 0u, "bind should allocate placeholder server when sid is missing");
|
||||
t.IsTrue(clientBeforeRegister.server >= 0x01F10000u && clientBeforeRegister.server < 0x01F20000u,
|
||||
"placeholder server should come from rpc server pool");
|
||||
t.Equals(clientBeforeRegister.buf, 0u, "placeholder server starts with empty buf");
|
||||
t.Equals(clientBeforeRegister.cbuf, 0u, "placeholder server starts with empty cbuf");
|
||||
t.Equals(clientBeforeRegister.server, 0u, "bind must wait until a hybrid backend owns the SID");
|
||||
t.Equals(clientBeforeRegister.buf, 0u, "unbound client starts with empty buf");
|
||||
t.Equals(clientBeforeRegister.cbuf, 0u, "unbound client starts with empty cbuf");
|
||||
|
||||
setRegU32(env.ctx, 4, kQdAddr);
|
||||
setRegU32(env.ctx, 5, 0x44u);
|
||||
@@ -873,7 +904,7 @@ void register_ps2_sif_rpc_tests()
|
||||
t.Equals(clientAfterRegister.server, kSdAddr, "register should remap pre-bound clients to concrete server descriptor");
|
||||
t.Equals(clientAfterRegister.buf, kServerBufAddr, "register should update client buf from server descriptor");
|
||||
t.Equals(clientAfterRegister.cbuf, kServerCbufAddr, "register should update client cbuf from server descriptor");
|
||||
t.IsTrue(clientAfterRegister.server != clientBeforeRegister.server, "client server pointer should switch from placeholder to real server");
|
||||
t.IsTrue(clientAfterRegister.server != clientBeforeRegister.server, "client server pointer should switch from unbound to real server");
|
||||
|
||||
setRegU32(env.ctx, 4, kSdAddr);
|
||||
setRegU32(env.ctx, 5, kQdAddr);
|
||||
|
||||
Reference in New Issue
Block a user