// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include #include #include #include #include #include "ProbeScript.h" namespace StarletBootstrapProbe { namespace { // The script's error, or "" when it parses. std::string Refusal(std::string_view script) { std::string error; return ParseScript(script, &error) ? "" : error; } std::vector Steps(std::string_view script) { std::string error; const auto steps = ParseScript(script, &error); EXPECT_TRUE(steps.has_value()) << error; return steps.value_or(std::vector{}); } class FakeBus final : public ScriptBus { public: void StoreWord(u32 address, u32 word) override { log.push_back("store"); words[address] = word; } u32 LoadWord(u32 address) override { return words[address]; } void WriteRegister(u32 offset, u32 value) override { log.push_back(offset == IPC_PPCMSG ? "ppcmsg" : "ppcctrl"); registers[offset] = value; } u32 ReadRegister(u32 offset) override { return registers[offset]; } std::map words; std::map registers; std::vector log; }; TEST(StarletProbeScript, TheRangeIsTheContracts) { EXPECT_EQ(IPC_RANGE_BASE, u32{0x135E0000}); EXPECT_EQ(IPC_RANGE_END, u32{0x13600000}); EXPECT_TRUE(InIPCRange(0x135E0000)); EXPECT_TRUE(InIPCRange(0x135FFFFC)); EXPECT_FALSE(InIPCRange(0x135DFFFC)); EXPECT_FALSE(InIPCRange(0x13600000)); EXPECT_FALSE(InIPCRange(0x135E0002)); EXPECT_FALSE(InIPCRange(0x135FFFFE)); // The package window and the kernel's memory lie above the boundary. EXPECT_FALSE(InIPCRange(0x13720000)); } TEST(StarletProbeScript, ParsesEveryStepCommentsAndBlankLines) { const auto steps = Steps("# a comment\n" "\n" "store 0x135E0000 1 # trailing\n" " ppcmsg\t0x135E0000\r\n" "ppcctrl 0x31\n" "wait 0x02 0x02 4096\n" "expect-armmsg 0x135E0000\n" "expect 0x135E0004 0xFFFFFFFA\n" "run 8192"); ASSERT_EQ(steps.size(), size_t{7}); EXPECT_EQ(steps[0].kind, StepKind::Store); EXPECT_EQ(steps[0].line, 3); EXPECT_EQ(steps[0].first, u32{0x135E0000}); EXPECT_EQ(steps[3].kind, StepKind::Wait); EXPECT_EQ(steps[3].cycles, u64{4096}); EXPECT_EQ(steps[5].second, u32{0xFFFFFFFA}); EXPECT_EQ(steps[6].kind, StepKind::Run); } TEST(StarletProbeScript, APpcMsgMayNameAnAddressOutOfTheRange) { // A register's value: the negative scenario's, an envelope the kernel must refuse. EXPECT_EQ(Refusal("ppcmsg 0x00001000\n"), ""); } TEST(StarletProbeScript, RefusesWhatIsNotTheFormat) { EXPECT_EQ(Refusal("nop\n"), "line 1: unknown step: nop"); EXPECT_EQ(Refusal("\n\nstore 1\n"), "line 3: takes an address and a word"); EXPECT_EQ(Refusal("store 0x135E0000 1 2\n"), "line 1: takes an address and a word"); EXPECT_EQ(Refusal("store 0x135E0000 0x100000000\n"), "line 1: takes an address and a word"); EXPECT_EQ(Refusal("store 0x135E0000 x\n"), "line 1: not a number: x"); EXPECT_EQ(Refusal("store 0x 1\n"), "line 1: not a number: 0x"); EXPECT_EQ(Refusal("store -4 1\n"), "line 1: not a number: -4"); EXPECT_EQ(Refusal("ppcmsg\n"), "line 1: takes one word"); EXPECT_EQ(Refusal("ppcctrl 0x40\n"), "line 1: takes PPCCTRL's six bits"); EXPECT_EQ(Refusal("wait 1 1\n"), "line 1: takes a mask, a value and a bound in cycles"); EXPECT_EQ(Refusal("wait 1 1 0\n"), "line 1: takes a mask, a value and a bound in cycles"); EXPECT_EQ(Refusal("run 0\n"), "line 1: takes a number of cycles"); EXPECT_EQ(Refusal("run 1073741825\n"), "line 1: takes a number of cycles"); } TEST(StarletProbeScript, RefusesAStoreOrAnExpectOutOfTheRange) { const std::string_view why = "line 1: the address is not a whole word of the range"; EXPECT_EQ(Refusal("store 0x135DFFFC 1\n"), why); EXPECT_EQ(Refusal("store 0x13600000 1\n"), why); EXPECT_EQ(Refusal("store 0x135E0002 1\n"), why); EXPECT_EQ(Refusal("store 0x13720000 1\n"), why); EXPECT_EQ(Refusal("expect 0x00200000 1\n"), why); } TEST(StarletProbeScript, RefusesTooManySteps) { std::string script; for (size_t index = 0; index < SCRIPT_MAX_STEPS; ++index) script += "run 4096\n"; EXPECT_EQ(Refusal(script), ""); script += "run 4096\n"; EXPECT_EQ(Refusal(script), "line 257: more than 256 steps"); } TEST(StarletProbeScript, StepsRunInOrderUpToOneThatWaits) { FakeBus bus; ScriptRunner runner(Steps("store 0x135E0000 7\n" "ppcmsg 0x135E0000\n" "ppcctrl 0x31\n" "wait 0x02 0x02 8192\n" "ppcctrl 0x32\n")); runner.Advance(bus, 0); EXPECT_EQ(bus.log, (std::vector{"store", "ppcmsg", "ppcctrl"})); EXPECT_EQ(bus.words[0x135E0000], u32{7}); EXPECT_EQ(bus.registers[IPC_PPCMSG], u32{0x135E0000}); EXPECT_FALSE(runner.Done()); EXPECT_EQ(runner.Line(), 4); // Y2 raised by the code under test: the wait ends, the next step runs. runner.Advance(bus, 4096); EXPECT_FALSE(runner.Done()); bus.registers[IPC_PPCCTRL] = 0x33; runner.Advance(bus, 4096); EXPECT_TRUE(runner.Done()); EXPECT_EQ(bus.registers[IPC_PPCCTRL], u32{0x32}); EXPECT_FALSE(runner.Failed()); } TEST(StarletProbeScript, AWaitThatRunsOutOfItsBoundFailsItsLine) { FakeBus bus; ScriptRunner runner(Steps("\nwait 0x04 0x04 8192\n")); runner.Advance(bus, 0); runner.Advance(bus, 4096); EXPECT_FALSE(runner.Failed()); bus.registers[IPC_PPCCTRL] = 0x30; runner.Advance(bus, 4096); ASSERT_TRUE(runner.Failed()); EXPECT_EQ(runner.Failure(), "line 2: PPCCTRL reads 0x30 after 8192 cycles"); EXPECT_FALSE(runner.Done()); // A failed script runs nothing more. bus.registers[IPC_PPCCTRL] = 0x34; runner.Advance(bus, 4096); EXPECT_FALSE(runner.Done()); } TEST(StarletProbeScript, AnExpectationNotMetFailsItsLine) { FakeBus bus; ScriptRunner armmsg(Steps("expect-armmsg 0x135E0000\n")); armmsg.Advance(bus, 0); EXPECT_EQ(armmsg.Failure(), "line 1: ARMMSG reads 0x0, not 0x135e0000"); ScriptRunner word(Steps("expect 0x135E0004 1\n")); word.Advance(bus, 0); EXPECT_EQ(word.Failure(), "line 1: the word at 0x135e0004 reads 0x0, not 0x1"); bus.words[0x135E0004] = 1; ScriptRunner met(Steps("expect 0x135E0004 1\n")); met.Advance(bus, 0); EXPECT_TRUE(met.Done()); EXPECT_FALSE(met.Failed()); } TEST(StarletProbeScript, RunWaitsItsCycles) { FakeBus bus; ScriptRunner runner(Steps("run 8192\nppcctrl 1\n")); runner.Advance(bus, 0); runner.Advance(bus, 4096); EXPECT_TRUE(bus.log.empty()); runner.Advance(bus, 4096); EXPECT_EQ(bus.log, (std::vector{"ppcctrl"})); EXPECT_TRUE(runner.Done()); } TEST(StarletProbeScript, AnEmptyScriptIsDone) { FakeBus bus; ScriptRunner runner(Steps("# nothing\n")); runner.Advance(bus, 0); EXPECT_TRUE(runner.Done()); } } // namespace } // namespace StarletBootstrapProbe