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.
This commit is contained in:
1 parent
b931671fde
commit
c230971f07
64 files changed
+6488
-238
No files matched your search
@@ -0,0 +1,190 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user