// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later // Local qualification only: synthetic ARM code/data, synthetic NAND journal, no // firmware/profile. #include #include #include #include #include #include #include #include #include #include "Common/Network.h" #include "Common/PerformanceTrace.h" #include "Core/IOS/Starlet/ARMCore.h" #include "Core/IOS/Starlet/AX88772.h" #include "Core/IOS/Starlet/NANDJournal.h" #include "Core/IOS/Starlet/SlirpNetwork.h" #include "PrivateETW.h" namespace Trace = Common::PerformanceTrace; using namespace IOS::LLE; class ProbeBus final : public ARMBus { public: u8 Read8(u32 address) override { return bytes.at(address); } void Write8(u32 address, u8 value) override { bytes.at(address) = value; } const u8* GetDirectMemoryPointer(u32 address, u32 size) const override { return address < bytes.size() && size <= bytes.size() - address ? bytes.data() + address : nullptr; } void Op(u32 offset, u16 op) { bytes[offset] = static_cast(op >> 8); bytes[offset + 1] = static_cast(op); } std::array bytes{}; }; void Check(bool value) { if (!value) throw std::runtime_error("P1 local probe validation failed"); } void LocalNAT(const std::string& directory) { SlirpNetwork network; AX88772 device([&](std::span frame) { network.Input(frame); }); std::string error; if (!network.Start( directory, [&](std::span frame) { device.ReceiveFrame(frame); }, &error)) throw std::runtime_error(error); const auto control = [&](u8 type, u8 request, u16 value) { std::array setup{ type, request, static_cast(value), static_cast(value >> 8), 0, 0, 0, 0}; size_t actual = 0; Check(device.Transfer(0, 0, setup, &actual) == AX88772::Result::Completed); Check(device.Transfer(0, 2, {}, &actual) == AX88772::Result::Completed); }; control(0, 9, 1); control(0x40, 0x10, 0x88); control(0x40, 0x1b, 0x306); device.SetLink(true); // ARP reaches only libslirp's virtual gateway: no DNS, Internet or remote server. Common::MACAddress mac{2, 0x44, 0x4f, 0x4c, 0, 1}; Common::ARPPacket request(Common::MACAddress{255, 255, 255, 255, 255, 255}, mac); request.arp_header.opcode = htons(1); request.arp_header.sender_address = mac; request.arp_header.sender_ip = htonl(0x0a00020f); request.arp_header.target_ip = htonl(0x0a000202); const auto frame = request.Build(); const u16 length = static_cast(frame.size()); std::vector framed{static_cast(length), static_cast(length >> 8), static_cast(~length), static_cast(~length >> 8)}; framed.insert(framed.end(), frame.begin(), frame.end()); Trace::Marker("LocalNATBegin"); for (u32 i = 0; i < 32; ++i) { Trace::SampleRoot root(true); size_t actual = 0; Check(device.Transfer(3, 1, framed, &actual) == AX88772::Result::Completed); network.Poll(243000); device.TraceQueue(); std::array response{}; Check(device.Transfer(2, 2, response, &actual) == AX88772::Result::Completed); Check(actual >= 46); const auto arp = Common::PacketView(response.data() + 4, response[0] | (response[1] << 8)).GetARPPacket(); Check(arp && ntohs(arp->arp_header.opcode) == 2 && arp->arp_header.sender_ip == htonl(0x0a000202)); device.TraceQueue(); } Trace::Marker("LocalNATEnd"); } int main(int argc, char** argv) { try { if (argc != 3 && argc != 4) throw std::runtime_error("Usage: TraceProbeP1.exe NEW-synthetic-journal-path " "libslirp-runtime-directory [NEW-private-ETL]"); const std::string journal_path = argv[1]; Check(!std::filesystem::exists(journal_path)); PrivateETW private_trace(argc == 4 ? argv[3] : nullptr); Trace::Marker("ProbeBegin"); ProbeBus bus; bus.Op(0, 0x6810); // ldr r0,[r2] bus.Op(2, 0x6851); // ldr r1,[r2,#4] bus.Op(4, 0x46c0); // nop bus.Op(6, 0xe7fb); // b 0 bus.Write32(4096, 0x12345678); bus.Write32(4100, 0x87654321); ARMCore cpu(bus); cpu.SetCPSR(static_cast(ARMCore::Mode::Supervisor) | ARMCore::CPSR_T); cpu.SetRegister(2, 4096); cpu.SetJitEnabled(true); Trace::Marker("ARMWorkBegin"); const auto start = std::chrono::steady_clock::now(); // Fixed work, not a duration-based loop: suitable for ON/OFF cost // comparisons. constexpr u64 slices = 160000; for (u64 i = 0; i < slices; ++i) { Trace::SampleRoot root; Check(cpu.RunCycles(4096) == 4096); if ((i & 1023) == 0) Trace::Snapshot(cpu.GetTraceGeneration(), cpu.GetExecutedInstructions(), cpu.GetJitNativeExecutedInstructions(), cpu.GetJitSlowReadCount(), cpu.GetJitSlowWriteCount(), cpu.GetJitLifetimeCompiledBlockCount(), cpu.GetJitFallbackInstructionCount(), cpu.GetRegister(15), false); } const double ms = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); Check(cpu.GetRegister(0) == 0x12345678 && cpu.GetRegister(1) == 0x87654321); Check(cpu.GetExecutedInstructions() == slices * 4096); Trace::Marker("ARMWorkEnd"); Trace::Marker("KnownSleepBegin"); std::this_thread::sleep_for(std::chrono::milliseconds(750)); Trace::Marker("KnownSleepEnd"); { Trace::Marker("SyntheticJournalBegin"); NANDJournal journal; NANDJournal::Pages pages; Common::SHA1::Digest synthetic_identity{}; std::string error; Check(journal.Open(journal_path, synthetic_identity, &pages, &error)); for (u32 i = 0; i < 128; ++i) { Trace::SampleRoot root(true); NANDJournal::Pages update; update[i].fill(static_cast(i)); Check(journal.Append(update)); } Trace::Marker("SyntheticJournalEnd"); } { NANDJournal verifier; NANDJournal::Pages pages; Common::SHA1::Digest synthetic_identity{}; std::string error; Check(verifier.Open(journal_path, synthetic_identity, &pages, &error)); Check(pages.size() == 128); for (u32 i = 0; i < 128; ++i) for (u8 value : pages.at(i)) Check(value == static_cast(i)); } LocalNAT(std::filesystem::absolute(argv[2]).string()); cpu.Reset(); std::this_thread::sleep_for(std::chrono::milliseconds(1100)); Trace::Snapshot(cpu.GetTraceGeneration(), cpu.GetExecutedInstructions(), cpu.GetJitNativeExecutedInstructions(), cpu.GetJitSlowReadCount(), cpu.GetJitSlowWriteCount(), cpu.GetJitLifetimeCompiledBlockCount(), cpu.GetJitFallbackInstructionCount(), cpu.GetRegister(15), false); Trace::Marker("ProbeEnd"); const bool trace_enabled = Trace::Enabled(); Check(private_trace.Close()); std::printf("P1Probe exact=OK trace-requested=%d trace-enabled=%d " "arm-ms=%.3f instructions=%llu journal-pages=128 nat-arp-replies=32\n", Trace::Requested(), trace_enabled, ms, slices * 4096); return 0; } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; } }