Files
Yaya48 c230971f07 IOS LLE: checkpoint NAND conformance and diagnostic tooling
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.
2026-09-23 13:30:17 +02:00

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;
}
}