Preserve formatted NAND boot and Wii Remote pairing, add DMA/SRAM conformance tests and a Wii comparison probe. Keep P1 profiling tools and native update A/B evidence; reject the unhelpful targeted SRAM shortcut.
191 lines
7.3 KiB
C++
191 lines
7.3 KiB
C++
// 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 <array>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <filesystem>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <winsock2.h>
|
|
|
|
#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<u8>(op >> 8);
|
|
bytes[offset + 1] = static_cast<u8>(op);
|
|
}
|
|
std::array<u8, 8192> 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<const u8> frame) { network.Input(frame); });
|
|
std::string error;
|
|
if (!network.Start(
|
|
directory, [&](std::span<const u8> frame) { device.ReceiveFrame(frame); }, &error))
|
|
throw std::runtime_error(error);
|
|
const auto control = [&](u8 type, u8 request, u16 value) {
|
|
std::array<u8, 8> setup{
|
|
type, request, static_cast<u8>(value), static_cast<u8>(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<u16>(frame.size());
|
|
std::vector<u8> framed{static_cast<u8>(length), static_cast<u8>(length >> 8),
|
|
static_cast<u8>(~length), static_cast<u8>(~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<u8, 2048> 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<u32>(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<double, std::milli>(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<u8>(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<u8>(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;
|
|
}
|
|
}
|