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
@@ -122,6 +122,8 @@ add_library(common
|
||||
OneShotEvent.h
|
||||
PcapFile.cpp
|
||||
PcapFile.h
|
||||
PerformanceTrace.cpp
|
||||
PerformanceTrace.h
|
||||
Profiler.cpp
|
||||
Profiler.h
|
||||
Projection.h
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MemoryUtil.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
@@ -72,6 +73,8 @@ public:
|
||||
void FreeCodeSpace()
|
||||
{
|
||||
ASSERT(!m_is_child);
|
||||
if (region)
|
||||
PerformanceTrace::CodeReset("Generated", this, region, region + total_region_size);
|
||||
Common::FreeMemoryPages(region, total_region_size);
|
||||
region = nullptr;
|
||||
region_size = 0;
|
||||
@@ -97,7 +100,12 @@ public:
|
||||
{
|
||||
Common::UnWriteProtectMemory(region, region_size, allow_execute);
|
||||
}
|
||||
void ResetCodePtr() { T::SetCodePtr(region, region + region_size); }
|
||||
void ResetCodePtr()
|
||||
{
|
||||
if (region)
|
||||
PerformanceTrace::CodeReset("Generated", this, region, region + region_size);
|
||||
T::SetCodePtr(region, region + region_size);
|
||||
}
|
||||
size_t GetSpaceLeft() const
|
||||
{
|
||||
ASSERT(static_cast<size_t>(T::GetCodePtr() - region) < region_size);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/IOFile.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <process.h>
|
||||
@@ -66,8 +67,24 @@ bool IsEnabled()
|
||||
return s_is_enabled;
|
||||
}
|
||||
|
||||
bool IsTraceEnabled()
|
||||
{
|
||||
return PerformanceTrace::Enabled();
|
||||
}
|
||||
|
||||
void Register(const void* base_address, u32 code_size, const std::string& symbol_name)
|
||||
{
|
||||
// Block mappings (including invalidation) are emitted by JitCache itself.
|
||||
if (!symbol_name.starts_with("JIT_PPC_"))
|
||||
{
|
||||
// Vertex decoding also uses JitRegister. Do not attribute it to the guest CPU.
|
||||
const char* domain = symbol_name.starts_with("JIT_") ? "PPC" :
|
||||
symbol_name.starts_with("VertexLoader") ? "VertexDecode" :
|
||||
"OtherGenerated";
|
||||
PerformanceTrace::CodeMap(domain, nullptr, base_address,
|
||||
static_cast<const u8*>(base_address) + code_size, 0, 0,
|
||||
symbol_name.c_str());
|
||||
}
|
||||
#ifdef USE_VTUNE
|
||||
iJIT_Method_Load jmethod = {0};
|
||||
jmethod.method_id = iJIT_GetNewMethodID();
|
||||
|
||||
@@ -15,12 +15,14 @@ void Init(const std::string& perf_dir);
|
||||
void Shutdown();
|
||||
void Register(const void* base_address, u32 code_size, const std::string& symbol_name);
|
||||
bool IsEnabled();
|
||||
// ETW mappings can be requested without enabling perf-map/VTune registration.
|
||||
bool IsTraceEnabled();
|
||||
|
||||
template <typename... Args>
|
||||
inline void Register(const void* base_address, u32 code_size, fmt::format_string<Args...> format,
|
||||
Args&&... args)
|
||||
{
|
||||
if (!IsEnabled())
|
||||
if (!IsEnabled() && !IsTraceEnabled())
|
||||
return;
|
||||
|
||||
Register(base_address, code_size, fmt::format(format, std::forward<Args>(args)...));
|
||||
@@ -30,7 +32,7 @@ template <typename... Args>
|
||||
inline void Register(const void* start, const void* end, fmt::format_string<Args...> format,
|
||||
Args&&... args)
|
||||
{
|
||||
if (!IsEnabled())
|
||||
if (!IsEnabled() && !IsTraceEnabled())
|
||||
return;
|
||||
|
||||
u32 code_size = (u32)((const char*)end - (const char*)start);
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/PerformanceTrace.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <TraceLoggingProvider.h>
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
TRACELOGGING_DEFINE_PROVIDER(s_dolphin_perf, "Dolphin.Performance",
|
||||
(0x54ee9231, 0x28be, 0x4ab0, 0x98, 0x1e, 0xb2, 0x6f, 0x97, 0x9c, 0x56,
|
||||
0xa4));
|
||||
#endif
|
||||
|
||||
namespace Common::PerformanceTrace
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr u32 SCHEMA = 1;
|
||||
constexpr u32 SAMPLE_MASK = 1023;
|
||||
struct ThreadState
|
||||
{
|
||||
bool sampling = false;
|
||||
u32 rng = 0x9e3779b9;
|
||||
u64 roots = 0;
|
||||
u64 selected = 0;
|
||||
u64 last_snapshot = 0;
|
||||
ScopeStack stack;
|
||||
std::array<u64, static_cast<size_t>(Counter::Count)> counters{};
|
||||
};
|
||||
thread_local ThreadState s_thread;
|
||||
|
||||
#ifdef _WIN32
|
||||
struct Registration
|
||||
{
|
||||
Registration() : status(TraceLoggingRegister(s_dolphin_perf)) {}
|
||||
~Registration()
|
||||
{
|
||||
if (status == ERROR_SUCCESS)
|
||||
TraceLoggingUnregister(s_dolphin_perf);
|
||||
}
|
||||
ULONG status;
|
||||
};
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
bool ScopeStack::Begin(Region region, u64 now)
|
||||
{
|
||||
if (m_depth == m_stack.size() || static_cast<size_t>(region) >= m_timings.size())
|
||||
{
|
||||
++m_errors;
|
||||
return false;
|
||||
}
|
||||
m_stack[m_depth++] = {region, now, 0};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScopeStack::End(u64 now)
|
||||
{
|
||||
if (m_depth == 0)
|
||||
{
|
||||
++m_errors;
|
||||
return false;
|
||||
}
|
||||
const Entry entry = m_stack[--m_depth];
|
||||
if (now < entry.start || now - entry.start < entry.children)
|
||||
{
|
||||
++m_errors;
|
||||
return false;
|
||||
}
|
||||
const u64 elapsed = now - entry.start;
|
||||
auto& timing = m_timings[static_cast<size_t>(entry.region)];
|
||||
++timing.calls;
|
||||
timing.inclusive += elapsed;
|
||||
timing.exclusive += elapsed - entry.children;
|
||||
if (m_depth != 0)
|
||||
m_stack[m_depth - 1].children += elapsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScopeStack::Reset()
|
||||
{
|
||||
m_depth = 0;
|
||||
m_errors = 0;
|
||||
m_timings = {};
|
||||
}
|
||||
|
||||
bool Requested()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static const bool requested = [] {
|
||||
const char* value = std::getenv("DOLPHIN_PERF_TRACE");
|
||||
return value && std::strcmp(value, "1") == 0;
|
||||
}();
|
||||
return requested;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Enabled()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!Requested())
|
||||
return false;
|
||||
static Registration registration;
|
||||
return registration.status == ERROR_SUCCESS && TraceLoggingProviderEnabled(s_dolphin_perf, 0, 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 Now()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER value;
|
||||
QueryPerformanceCounter(&value);
|
||||
return static_cast<u64>(value.QuadPart);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
u64 Frequency()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static const u64 frequency = [] {
|
||||
LARGE_INTEGER value;
|
||||
QueryPerformanceFrequency(&value);
|
||||
return static_cast<u64>(value.QuadPart);
|
||||
}();
|
||||
return frequency;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Add(Counter counter, u64 value)
|
||||
{
|
||||
if (Requested() && static_cast<size_t>(counter) < s_thread.counters.size())
|
||||
s_thread.counters[static_cast<size_t>(counter)] += value;
|
||||
}
|
||||
|
||||
u64 NewGeneration()
|
||||
{
|
||||
static std::atomic<u64> generation{0};
|
||||
return Requested() ? generation.fetch_add(1, std::memory_order_relaxed) + 1 : 0;
|
||||
}
|
||||
|
||||
SampleRoot::SampleRoot(bool force_for_test)
|
||||
{
|
||||
m_denominator = force_for_test ? 1 : SAMPLE_MASK + 1;
|
||||
if (!Enabled() || s_thread.sampling)
|
||||
return;
|
||||
++s_thread.roots;
|
||||
auto& x = s_thread.rng;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
if (!force_for_test && (x & SAMPLE_MASK) != 0)
|
||||
return;
|
||||
++s_thread.selected;
|
||||
s_thread.stack.Reset();
|
||||
m_start = Now();
|
||||
m_active = s_thread.stack.Begin(Region::Slice, m_start);
|
||||
s_thread.sampling = m_active;
|
||||
}
|
||||
|
||||
SampleRoot::~SampleRoot()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
const u64 end = Now();
|
||||
s_thread.stack.End(end);
|
||||
s_thread.sampling = false;
|
||||
#ifdef _WIN32
|
||||
// Sampled wall times, NOT exact total CPU times. Nested regions are exclusive here.
|
||||
for (size_t i = 0; i < s_thread.stack.Timings().size(); ++i)
|
||||
{
|
||||
const auto& timing = s_thread.stack.Timings()[i];
|
||||
if (timing.calls == 0)
|
||||
continue;
|
||||
TraceLoggingWrite(s_dolphin_perf, "ScopeSample", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(m_start, "BeginQPC"), TraceLoggingUInt64(end, "EndQPC"),
|
||||
TraceLoggingUInt64(s_thread.selected, "Sample"),
|
||||
TraceLoggingUInt32(m_denominator, "SamplingDenominator"),
|
||||
TraceLoggingUInt32(static_cast<u32>(i), "Region"),
|
||||
TraceLoggingUInt64(timing.calls, "Calls"),
|
||||
TraceLoggingUInt64(timing.inclusive, "InclusiveQPC"),
|
||||
TraceLoggingUInt64(timing.exclusive, "ExclusiveQPC"),
|
||||
TraceLoggingUInt64(s_thread.stack.Errors(), "AccountingErrors"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Scope::Scope(Region region)
|
||||
{
|
||||
if (s_thread.sampling)
|
||||
m_active = s_thread.stack.Begin(region, Now());
|
||||
}
|
||||
Scope::~Scope()
|
||||
{
|
||||
if (m_active)
|
||||
s_thread.stack.End(Now());
|
||||
}
|
||||
|
||||
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
|
||||
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting)
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
const u64 now = Now();
|
||||
if (now - s_thread.last_snapshot < Frequency())
|
||||
return;
|
||||
s_thread.last_snapshot = now;
|
||||
#ifdef _WIN32
|
||||
TraceLoggingWrite(
|
||||
s_dolphin_perf, "Snapshot", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(now, "QPC"), TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
||||
TraceLoggingUInt64(core_id, "CoreId"), TraceLoggingUInt64(instructions, "Instructions"),
|
||||
TraceLoggingUInt64(native_instructions, "NativeInstructions"),
|
||||
TraceLoggingUInt64(slow_reads, "SlowReads"), TraceLoggingUInt64(slow_writes, "SlowWrites"),
|
||||
TraceLoggingUInt64(compiles, "Compiles"), TraceLoggingUInt64(fallbacks, "Fallbacks"),
|
||||
TraceLoggingUInt32(pc, "GuestPC"), TraceLoggingBool(waiting, "Waiting"),
|
||||
TraceLoggingUInt64(s_thread.roots, "Roots"),
|
||||
TraceLoggingUInt64(s_thread.selected, "SelectedRoots"));
|
||||
for (size_t i = 0; i < s_thread.counters.size(); ++i)
|
||||
{
|
||||
TraceLoggingWrite(s_dolphin_perf, "Counter", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(now, "QPC"),
|
||||
TraceLoggingUInt32(static_cast<u32>(i), "Counter"),
|
||||
TraceLoggingUInt64(s_thread.counters[i], "Value"));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
|
||||
u32 guest_pc, u64 identity, const char* kind)
|
||||
{
|
||||
if (!begin || reinterpret_cast<uintptr_t>(end) <= reinterpret_cast<uintptr_t>(begin) ||
|
||||
!Enabled())
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
TraceLoggingWrite(s_dolphin_perf, "CodeMap", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingString(domain, "Domain"),
|
||||
TraceLoggingPointer(owner, "Owner"), TraceLoggingPointer(begin, "Begin"),
|
||||
TraceLoggingPointer(end, "End"), TraceLoggingUInt32(guest_pc, "GuestPC"),
|
||||
TraceLoggingUInt64(identity, "Identity"), TraceLoggingString(kind, "Kind"));
|
||||
#endif
|
||||
}
|
||||
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end)
|
||||
{
|
||||
CodeMap(domain, owner, begin, end, 0, 0, "invalidate");
|
||||
}
|
||||
void Frame(const char* kind, s64 guest_ticks, u32 guest_frequency)
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
TraceLoggingWrite(s_dolphin_perf, "Frame", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(Now(), "QPC"),
|
||||
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
||||
TraceLoggingString(kind, "Kind"), TraceLoggingInt64(guest_ticks, "GuestTicks"),
|
||||
TraceLoggingUInt32(guest_frequency, "GuestFrequency"));
|
||||
#endif
|
||||
}
|
||||
void Marker(const char* name)
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
TraceLoggingWrite(s_dolphin_perf, "Marker", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(Now(), "QPC"),
|
||||
TraceLoggingUInt64(Frequency(), "QPCFrequency"),
|
||||
TraceLoggingString(name, "Name"));
|
||||
#endif
|
||||
}
|
||||
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak)
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
TraceLoggingWrite(s_dolphin_perf, "USBQueue", TraceLoggingUInt32(SCHEMA, "Schema"),
|
||||
TraceLoggingUInt64(Now(), "QPC"), TraceLoggingPointer(owner, "Owner"),
|
||||
TraceLoggingUInt64(frames, "Frames"), TraceLoggingUInt64(bytes, "Bytes"),
|
||||
TraceLoggingUInt64(oldest_qpc, "OldestQPC"),
|
||||
TraceLoggingUInt64(peak, "PeakFrames"));
|
||||
#endif
|
||||
}
|
||||
} // namespace Common::PerformanceTrace
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2026 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Common::PerformanceTrace
|
||||
{
|
||||
// Opt-in diagnostic events, never guest state. Windows provider:
|
||||
// Dolphin.Performance / {54ee9231-28be-4ab0-981e-b26f979c56a4}, schema 1.
|
||||
// Set DOLPHIN_PERF_TRACE=1 before process creation and start ETW before launching.
|
||||
enum class Region : u32
|
||||
{
|
||||
Slice,
|
||||
ARM,
|
||||
Jit,
|
||||
Compile,
|
||||
Devices,
|
||||
NAND,
|
||||
Journal,
|
||||
JournalBuild,
|
||||
JournalWrite,
|
||||
JournalFlush,
|
||||
NAT,
|
||||
DMARead,
|
||||
DMAWrite,
|
||||
AES,
|
||||
SHA,
|
||||
Count
|
||||
};
|
||||
enum class Counter : u32
|
||||
{
|
||||
JournalTransactions,
|
||||
JournalBytes,
|
||||
JournalErrors,
|
||||
NatRXFrames,
|
||||
NatRXBytes,
|
||||
NatTXFrames,
|
||||
NatTXBytes,
|
||||
NatPollErrors,
|
||||
UsbRXFrames,
|
||||
UsbRXBytes,
|
||||
UsbDrops,
|
||||
UsbDeliveredBytes,
|
||||
NANDReads,
|
||||
NANDPrograms,
|
||||
NANDErases,
|
||||
Count
|
||||
};
|
||||
|
||||
struct Timing
|
||||
{
|
||||
u64 calls = 0;
|
||||
u64 inclusive = 0;
|
||||
u64 exclusive = 0;
|
||||
};
|
||||
|
||||
// Clock-independent accounting, also exercised with deterministic timestamps in tests.
|
||||
class ScopeStack
|
||||
{
|
||||
public:
|
||||
bool Begin(Region region, u64 now);
|
||||
bool End(u64 now);
|
||||
void Reset();
|
||||
size_t Depth() const { return m_depth; }
|
||||
u64 Errors() const { return m_errors; }
|
||||
const auto& Timings() const { return m_timings; }
|
||||
|
||||
private:
|
||||
struct Entry
|
||||
{
|
||||
Region region;
|
||||
u64 start;
|
||||
u64 children;
|
||||
};
|
||||
std::array<Entry, 32> m_stack{};
|
||||
std::array<Timing, static_cast<size_t>(Region::Count)> m_timings{};
|
||||
size_t m_depth = 0;
|
||||
u64 m_errors = 0;
|
||||
};
|
||||
|
||||
bool Requested();
|
||||
bool Enabled();
|
||||
u64 Now();
|
||||
u64 Frequency();
|
||||
u64 NewGeneration();
|
||||
void Add(Counter counter, u64 value = 1);
|
||||
void Snapshot(u64 core_id, u64 instructions, u64 native_instructions, u64 slow_reads,
|
||||
u64 slow_writes, u64 compiles, u64 fallbacks, u32 pc, bool waiting);
|
||||
void CodeMap(const char* domain, const void* owner, const void* begin, const void* end,
|
||||
u32 guest_pc, u64 identity, const char* kind);
|
||||
void CodeReset(const char* domain, const void* owner, const void* begin, const void* end);
|
||||
void Frame(const char* kind, s64 guest_ticks = 0, u32 guest_frequency = 0);
|
||||
void Marker(const char* name);
|
||||
void Queue(const void* owner, u64 frames, u64 bytes, u64 oldest_qpc, u64 peak);
|
||||
|
||||
// Only scopes inside a randomly selected root slice read the clock. No per-access timers.
|
||||
class SampleRoot
|
||||
{
|
||||
public:
|
||||
explicit SampleRoot(bool force_for_test = false);
|
||||
~SampleRoot();
|
||||
SampleRoot(const SampleRoot&) = delete;
|
||||
SampleRoot& operator=(const SampleRoot&) = delete;
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
u64 m_start = 0;
|
||||
u32 m_denominator = 1024;
|
||||
};
|
||||
class Scope
|
||||
{
|
||||
public:
|
||||
explicit Scope(Region region);
|
||||
~Scope();
|
||||
Scope(const Scope&) = delete;
|
||||
Scope& operator=(const Scope&) = delete;
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
};
|
||||
} // namespace Common::PerformanceTrace
|
||||
@@ -258,6 +258,8 @@ const Info<std::string> MAIN_GPU_DETERMINISM_MODE{{System::Main, "Core", "GPUDet
|
||||
const Info<s32> MAIN_OVERRIDE_BOOT_IOS{{System::Main, "Core", "OverrideBootIOS"}, -1};
|
||||
const Info<bool> MAIN_WII_IOS_LLE{{System::Main, "Core", "WiiIOSLLE"}, false};
|
||||
const Info<bool> MAIN_WII_STARLET_JIT{{System::Main, "Core", "WiiStarletJIT"}, true};
|
||||
const Info<bool> MAIN_WII_STARLET_JIT_MEMORY_PROFILING{
|
||||
{System::Main, "Core", "WiiStarletJITMemoryProfiling"}, false};
|
||||
const Info<bool> MAIN_WII_LLE_ETHERNET{{System::Main, "Core", "WiiLLEEthernet"}, false};
|
||||
|
||||
GPUDeterminismMode GetGPUDeterminismMode()
|
||||
|
||||
@@ -168,6 +168,7 @@ extern const Info<bool> MAIN_REAL_WII_REMOTE_REPEAT_REPORTS;
|
||||
extern const Info<s32> MAIN_OVERRIDE_BOOT_IOS;
|
||||
extern const Info<bool> MAIN_WII_IOS_LLE;
|
||||
extern const Info<bool> MAIN_WII_STARLET_JIT;
|
||||
extern const Info<bool> MAIN_WII_STARLET_JIT_MEMORY_PROFILING;
|
||||
extern const Info<bool> MAIN_WII_LLE_ETHERNET;
|
||||
extern const Info<std::string> MAIN_WII_NUS_SHOP_URL;
|
||||
extern const Info<bool> MAIN_WII_WIILINK_ENABLE;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#if defined(_M_X86_64)
|
||||
// ARMCore owns ARMJitX64 by value through unique_ptr construction in this translation unit, so a
|
||||
// private JIT layout change must rebuild this file as well as the emitter.
|
||||
@@ -32,7 +33,8 @@ constexpr u32 ModeValue(ARMCore::Mode mode)
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ARMCore::ARMCore(ARMBus& bus) : m_bus(bus)
|
||||
ARMCore::ARMCore(ARMBus& bus, bool profile_jit_memory)
|
||||
: m_bus(bus), m_profile_jit_memory(profile_jit_memory)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
@@ -52,6 +54,7 @@ void ARMCore::SetJitEnabled(bool enabled)
|
||||
|
||||
void ARMCore::Reset(u32 reset_vector)
|
||||
{
|
||||
m_trace_generation = Common::PerformanceTrace::NewGeneration();
|
||||
m_registers.fill(0);
|
||||
m_user_r8_r14.fill(0);
|
||||
m_fiq_r8_r14.fill(0);
|
||||
@@ -237,9 +240,12 @@ u32 ARMCore::WalkPageTables(u32 modified_address, const u8** first_descriptor,
|
||||
u32* first_descriptor_value, const u8** second_descriptor,
|
||||
u32* second_descriptor_value) const
|
||||
{
|
||||
const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer,
|
||||
u32* host_value) {
|
||||
const u8* const pointer = m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32));
|
||||
const auto read_descriptor = [&](u32 physical_address, const u8** host_pointer, u32* host_value) {
|
||||
// Only native block validation needs a persistent descriptor pointer. Ordinary TLB
|
||||
// misses need the bus value alone; do not resolve the same physical memory twice.
|
||||
const u8* const pointer = (host_pointer || host_value) ?
|
||||
m_bus.GetDirectMemoryPointer(physical_address, sizeof(u32)) :
|
||||
nullptr;
|
||||
if (host_pointer)
|
||||
*host_pointer = pointer;
|
||||
if (host_value)
|
||||
@@ -791,6 +797,7 @@ u64 ARMCore::Run(u64 instruction_count)
|
||||
|
||||
u64 ARMCore::RunCycles(u64 cycle_budget)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::ARM);
|
||||
u64 cycles = 0;
|
||||
while (cycles < cycle_budget && !m_yield_requested)
|
||||
{
|
||||
@@ -1341,6 +1348,8 @@ std::vector<ARMCore::HotPCSample> ARMCore::TakeHotJitFallbackIntervalSamples(siz
|
||||
|
||||
void ARMCore::DoState(PointerWrap& p)
|
||||
{
|
||||
if (p.IsReadMode())
|
||||
m_trace_generation = Common::PerformanceTrace::NewGeneration();
|
||||
p.DoArray(m_registers);
|
||||
p.Do(m_cpsr);
|
||||
p.DoArray(m_user_r8_r14);
|
||||
|
||||
@@ -112,7 +112,8 @@ public:
|
||||
static constexpr u32 CPSR_T = 1U << 5;
|
||||
static constexpr u32 CPSR_MODE_MASK = 0x1f;
|
||||
|
||||
explicit ARMCore(ARMBus& bus);
|
||||
// Host-only diagnostic choice, fixed for this core's lifetime and not part of guest state.
|
||||
explicit ARMCore(ARMBus& bus, bool profile_jit_memory = false);
|
||||
~ARMCore();
|
||||
|
||||
void Reset(u32 reset_vector = 0);
|
||||
@@ -165,6 +166,8 @@ public:
|
||||
u64 GetExecutedInstructions() const { return m_executed_instructions; }
|
||||
void SetJitEnabled(bool enabled);
|
||||
bool IsJitEnabled() const { return m_jit_enabled; }
|
||||
bool IsJitMemoryProfilingEnabled() const { return m_profile_jit_memory; }
|
||||
u64 GetTraceGeneration() const { return m_trace_generation; }
|
||||
u64 GetJitExecutedInstructions() const;
|
||||
u64 GetJitNativeExecutedInstructions() const;
|
||||
size_t GetJitCompiledBlockCount() const;
|
||||
@@ -322,6 +325,8 @@ private:
|
||||
void WriteCP15(u32 opcode1, u32 crn, u32 crm, u32 opcode2, u32 value);
|
||||
|
||||
ARMBus& m_bus;
|
||||
const bool m_profile_jit_memory;
|
||||
u64 m_trace_generation = 0; // Host diagnostics only; never serialized.
|
||||
std::array<u32, 16> m_registers{};
|
||||
u32 m_cpsr = 0;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Common/BitSet.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Common/x64ABI.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/IOS/Starlet/ARMCore.h"
|
||||
@@ -94,6 +95,7 @@ ARMJitX64::ARMJitX64(ARMCore& core) : m_core(core)
|
||||
|
||||
ARMJitX64::~ARMJitX64()
|
||||
{
|
||||
Common::PerformanceTrace::CodeReset("ARM", this, region, region + region_size);
|
||||
FreeCodeSpace();
|
||||
}
|
||||
|
||||
@@ -128,6 +130,7 @@ void ARMJitX64::RequestClear()
|
||||
|
||||
void ARMJitX64::ClearCodeCache()
|
||||
{
|
||||
Common::PerformanceTrace::CodeReset("ARM", this, m_block_code_begin, region + region_size);
|
||||
m_discarded_block_count += m_blocks.size();
|
||||
m_blocks.clear();
|
||||
std::ranges::fill(m_fast_entries, FastEntry{});
|
||||
@@ -150,6 +153,7 @@ void ARMJitX64::InvalidateTranslationContext()
|
||||
|
||||
u32 ARMJitX64::Run(u64 cycle_budget)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Jit);
|
||||
if (cycle_budget == 0 || !m_run_entry)
|
||||
return 0;
|
||||
|
||||
@@ -405,6 +409,7 @@ void ARMJitX64::GenerateDispatcher()
|
||||
RET();
|
||||
|
||||
m_block_code_begin = AlignCode16();
|
||||
Common::PerformanceTrace::CodeMap("ARM", this, region, m_block_code_begin, 0, 0, "dispatcher");
|
||||
}
|
||||
|
||||
u32 ARMJitX64::MakeFastEntryKey(u32 address, u32 control, u32 process_id)
|
||||
@@ -562,13 +567,17 @@ ARMJitX64::Block* ARMJitX64::GetOrCompileBlock(u32 address, u32* physical_addres
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const u8* const trace_begin = GetCodePtr();
|
||||
Block block = CompileBlock(virtual_address, thumb);
|
||||
Common::PerformanceTrace::CodeMap("ARM", this, trace_begin, GetCodePtr(), virtual_address,
|
||||
block_key, thumb ? "thumb" : "arm");
|
||||
++m_lifetime_compiled_block_count;
|
||||
return &m_blocks.emplace(block_key, block).first->second;
|
||||
}
|
||||
|
||||
ARMJitX64::Block ARMJitX64::CompileBlock(u32 address, bool thumb)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Compile);
|
||||
const u8* const body = AlignCode16();
|
||||
LoadRegisterCache();
|
||||
MOV(8, MPCWritten(), Imm8(0));
|
||||
@@ -2256,8 +2265,10 @@ void ARMJitX64::EmitFastmemAddress(std::vector<FixupBranch>* slow_paths, u32 acc
|
||||
|
||||
// Addresses above MEM2 are only directly accessible when they hit the high Starlet SRAM
|
||||
// mirror. Everything else remains on the exact bus/MMIO path.
|
||||
if (sram_access == SRAMFastmemAccess::None || !m_sram_base || !m_boot0_mapped ||
|
||||
!m_sram_split_mode)
|
||||
// SRAM writes are still bus-only. Reject them before testing the direct-read aperture;
|
||||
// retain R9's physical address for the exact slow write without redundant SRAM checks.
|
||||
if (sram_access == SRAMFastmemAccess::None || sram_access == SRAMFastmemAccess::Write ||
|
||||
!m_sram_base || !m_boot0_mapped || !m_sram_split_mode)
|
||||
{
|
||||
slow_paths->push_back(J(Jump::Near));
|
||||
SetJumpTarget(below_mem2);
|
||||
@@ -2311,37 +2322,20 @@ void ARMJitX64::EmitFastmemAddress(std::vector<FixupBranch>* slow_paths, u32 acc
|
||||
MOV(32, R(EAX), R(R9));
|
||||
AND(32, R(EAX), Imm32(0x1ffff));
|
||||
|
||||
// Full-chain profiling gives a narrow safe aperture for reads. Direct writes to the measured
|
||||
// IOS pages 0x00/0x19 passed isolated bounds tests, but reproducibly terminated BootMii during
|
||||
// its hardware setup. Keep every SRAM write on the exact bus path until write coherency with
|
||||
// the live boot chain is understood. Boot0, split-window holes and register-list transfers also
|
||||
// remain on the bus.
|
||||
// Limit scalar direct reads to pages measured across the boot chain and native IOS update.
|
||||
// EDX is an aperture page in [0, 31]; the mask avoids a growing chain of comparisons. Keep
|
||||
// ECX intact for the caller's unaligned ARM word rotation. Boot0 and split-window holes still
|
||||
// use the bus. Writes and register-list transfers were rejected above: direct SRAM writes
|
||||
// previously regressed BootMii despite passing isolated bounds tests.
|
||||
MOV(32, R(EDX), R(EAX));
|
||||
SHR(32, R(EDX), Imm8(12));
|
||||
if (sram_access == SRAMFastmemAccess::Write)
|
||||
{
|
||||
slow_paths->push_back(J(Jump::Near));
|
||||
}
|
||||
else
|
||||
{
|
||||
CMP(32, R(EDX), Imm32(0x00));
|
||||
const FixupBranch profiled_read_page_00 = J_CC(CC_E, Jump::Near);
|
||||
CMP(32, R(EDX), Imm32(0x10));
|
||||
const FixupBranch irq_vector_read_page_10 = J_CC(CC_E, Jump::Near);
|
||||
CMP(32, R(EDX), Imm32(0x12));
|
||||
const FixupBranch profiled_read_page_12 = J_CC(CC_E, Jump::Near);
|
||||
CMP(32, R(EDX), Imm32(0x14));
|
||||
const FixupBranch profiled_read_page_14 = J_CC(CC_E, Jump::Near);
|
||||
CMP(32, R(EDX), Imm32(0x19));
|
||||
const FixupBranch profiled_read_page_19 = J_CC(CC_E, Jump::Near);
|
||||
CMP(32, R(EDX), Imm32(0x1e));
|
||||
slow_paths->push_back(J_CC(CC_NE, Jump::Near));
|
||||
SetJumpTarget(profiled_read_page_00);
|
||||
SetJumpTarget(irq_vector_read_page_10);
|
||||
SetJumpTarget(profiled_read_page_12);
|
||||
SetJumpTarget(profiled_read_page_14);
|
||||
SetJumpTarget(profiled_read_page_19);
|
||||
}
|
||||
constexpr u32 profiled_read_pages = (1U << 0x00) | (1U << 0x01) | (1U << 0x02) | (1U << 0x04) |
|
||||
(1U << 0x10) | (1U << 0x12) | (1U << 0x13) | (1U << 0x14) |
|
||||
(1U << 0x15) | (1U << 0x16) | (1U << 0x19) | (1U << 0x1a) |
|
||||
(1U << 0x1c) | (1U << 0x1d) | (1U << 0x1e);
|
||||
MOV(32, R(R8), Imm32(profiled_read_pages));
|
||||
BT(32, R(R8), R(EDX));
|
||||
slow_paths->push_back(J_CC(CC_NC, Jump::Near));
|
||||
|
||||
MOV(64, R(R8), ImmPtr(m_sram_split_mode));
|
||||
CMP(8, MatR(R8), Imm8(0));
|
||||
@@ -2408,17 +2402,25 @@ void ARMJitX64::EmitThumbMemorySlowPath(const std::vector<FixupBranch>& slow_pat
|
||||
MOV(32, R(ABI_PARAM4), Imm32(0));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(access_size));
|
||||
MOV(64, R(ABI_PARAM1), ImmPtr(this));
|
||||
ABI_CallFunction(ReadMemorySlow);
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? ReadMemorySlow<true> :
|
||||
ReadMemorySlow<false>);
|
||||
if (sign_extend)
|
||||
MOVSX(32, access_size * 8, EAX, R(EAX));
|
||||
MOV(32, MStoredRegister(rd), R(EAX));
|
||||
}
|
||||
else
|
||||
{
|
||||
MOV(32, R(ABI_PARAM4), MStoredRegister(rd));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(access_size));
|
||||
MOV(32, R(ABI_PARAM3), MStoredRegister(rd));
|
||||
MOV(64, R(ABI_PARAM1), ImmPtr(this));
|
||||
ABI_CallFunction(WriteMemorySlow);
|
||||
if (access_size == 1)
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<1, true> :
|
||||
WriteMemorySlow<1, false>);
|
||||
else if (access_size == 2)
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<2, true> :
|
||||
WriteMemorySlow<2, false>);
|
||||
else
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<4, true> :
|
||||
WriteMemorySlow<4, false>);
|
||||
}
|
||||
if (access_size > 1)
|
||||
{
|
||||
@@ -2514,16 +2516,21 @@ void ARMJitX64::EmitARMMemorySlowPath(const std::vector<FixupBranch>& slow_paths
|
||||
MOV(32, R(ABI_PARAM4), R(RBP));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(access_size));
|
||||
MOV(64, R(ABI_PARAM1), ImmPtr(this));
|
||||
ABI_CallFunction(ReadMemorySlow);
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? ReadMemorySlow<true> :
|
||||
ReadMemorySlow<false>);
|
||||
MOV(32, MStoredRegister(rd), R(EAX));
|
||||
}
|
||||
else
|
||||
{
|
||||
MOV(32, R(ABI_PARAM2), R(R9));
|
||||
MOV(32, R(ABI_PARAM4), MStoredRegister(rd));
|
||||
MOV(32, R(ABI_PARAM3), Imm32(access_size));
|
||||
MOV(32, R(ABI_PARAM3), MStoredRegister(rd));
|
||||
MOV(64, R(ABI_PARAM1), ImmPtr(this));
|
||||
ABI_CallFunction(WriteMemorySlow);
|
||||
if (byte)
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<1, true> :
|
||||
WriteMemorySlow<1, false>);
|
||||
else
|
||||
ABI_CallFunction(m_core.IsJitMemoryProfilingEnabled() ? WriteMemorySlow<4, true> :
|
||||
WriteMemorySlow<4, false>);
|
||||
}
|
||||
if (writeback)
|
||||
MOV(32, MStoredRegister(rn), R(RBX));
|
||||
@@ -2959,29 +2966,35 @@ void ARMJitX64::ExceptionReturn(ARMJitX64* jit, u32 target)
|
||||
core.WritePC(target);
|
||||
}
|
||||
|
||||
template <bool profile>
|
||||
u32 ARMJitX64::ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size,
|
||||
u32 byte_offset)
|
||||
{
|
||||
++jit->m_slow_read_count;
|
||||
if ((jit->m_slow_read_count & 0xff) == 0)
|
||||
++jit->m_slow_memory_address_samples[physical_address & ~3ULL];
|
||||
switch (ClassifySlowMemoryAddress(physical_address))
|
||||
// The helper is selected at code generation, so disabling detailed diagnostics adds no
|
||||
// per-access mode branch. Keep the aggregate counter and the exact bus transaction in both.
|
||||
if constexpr (profile)
|
||||
{
|
||||
case SlowMemoryRegion::RAM:
|
||||
++jit->m_slow_ram_read_count;
|
||||
break;
|
||||
case SlowMemoryRegion::SRAM:
|
||||
++jit->m_slow_sram_read_count;
|
||||
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
|
||||
jit->m_slow_sram_high_access_count);
|
||||
++jit->m_slow_sram_page_read_access_count[(physical_address & 0x1ffff) >> 12];
|
||||
break;
|
||||
case SlowMemoryRegion::MMIO:
|
||||
++jit->m_slow_mmio_read_count;
|
||||
break;
|
||||
case SlowMemoryRegion::Other:
|
||||
++jit->m_slow_other_read_count;
|
||||
break;
|
||||
if ((jit->m_slow_read_count & 0xff) == 0)
|
||||
++jit->m_slow_memory_address_samples[physical_address & ~3ULL];
|
||||
switch (ClassifySlowMemoryAddress(physical_address))
|
||||
{
|
||||
case SlowMemoryRegion::RAM:
|
||||
++jit->m_slow_ram_read_count;
|
||||
break;
|
||||
case SlowMemoryRegion::SRAM:
|
||||
++jit->m_slow_sram_read_count;
|
||||
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
|
||||
jit->m_slow_sram_high_access_count);
|
||||
++jit->m_slow_sram_page_read_access_count[(physical_address & 0x1ffff) >> 12];
|
||||
break;
|
||||
case SlowMemoryRegion::MMIO:
|
||||
++jit->m_slow_mmio_read_count;
|
||||
break;
|
||||
case SlowMemoryRegion::Other:
|
||||
++jit->m_slow_other_read_count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ARMCore& core = jit->m_core;
|
||||
if (access_size == 1)
|
||||
@@ -2998,45 +3011,52 @@ u32 ARMJitX64::ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_s
|
||||
return std::rotr(value, static_cast<int>((byte_offset & 3) * 8));
|
||||
}
|
||||
|
||||
void ARMJitX64::WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 value)
|
||||
template <u32 access_size, bool profile>
|
||||
void ARMJitX64::WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 value)
|
||||
{
|
||||
// Select width while compiling the guest instruction, not on every dynamic bus write.
|
||||
// Mapping, protection, MMIO side effects, endian conversion and aggregate counts stay live.
|
||||
static_assert(access_size == 1 || access_size == 2 || access_size == 4);
|
||||
++jit->m_slow_write_count;
|
||||
if ((jit->m_slow_write_count & 0xff) == 0)
|
||||
++jit->m_slow_memory_address_samples[(1ULL << 32) | (physical_address & ~3ULL)];
|
||||
switch (ClassifySlowMemoryAddress(physical_address))
|
||||
if constexpr (profile)
|
||||
{
|
||||
case SlowMemoryRegion::RAM:
|
||||
++jit->m_slow_ram_write_count;
|
||||
break;
|
||||
case SlowMemoryRegion::SRAM:
|
||||
++jit->m_slow_sram_write_count;
|
||||
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
|
||||
jit->m_slow_sram_high_access_count);
|
||||
++jit->m_slow_sram_page_write_access_count[(physical_address & 0x1ffff) >> 12];
|
||||
break;
|
||||
case SlowMemoryRegion::MMIO:
|
||||
++jit->m_slow_mmio_write_count;
|
||||
break;
|
||||
case SlowMemoryRegion::Other:
|
||||
++jit->m_slow_other_write_count;
|
||||
break;
|
||||
if ((jit->m_slow_write_count & 0xff) == 0)
|
||||
++jit->m_slow_memory_address_samples[(1ULL << 32) | (physical_address & ~3ULL)];
|
||||
switch (ClassifySlowMemoryAddress(physical_address))
|
||||
{
|
||||
case SlowMemoryRegion::RAM:
|
||||
++jit->m_slow_ram_write_count;
|
||||
break;
|
||||
case SlowMemoryRegion::SRAM:
|
||||
++jit->m_slow_sram_write_count;
|
||||
++(physical_address < 0xfff00000 ? jit->m_slow_sram_low_access_count :
|
||||
jit->m_slow_sram_high_access_count);
|
||||
++jit->m_slow_sram_page_write_access_count[(physical_address & 0x1ffff) >> 12];
|
||||
break;
|
||||
case SlowMemoryRegion::MMIO:
|
||||
++jit->m_slow_mmio_write_count;
|
||||
break;
|
||||
case SlowMemoryRegion::Other:
|
||||
++jit->m_slow_other_write_count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ARMCore& core = jit->m_core;
|
||||
if (access_size == 1)
|
||||
if constexpr (access_size == 1)
|
||||
{
|
||||
core.m_bus.Write8(physical_address, static_cast<u8>(value));
|
||||
return;
|
||||
}
|
||||
if (access_size == 2)
|
||||
else if constexpr (access_size == 2)
|
||||
{
|
||||
const u16 halfword = static_cast<u16>(value);
|
||||
core.m_bus.Write16(physical_address, core.m_big_endian ? halfword : std::byteswap(halfword));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!core.m_big_endian)
|
||||
value = std::byteswap(value);
|
||||
core.m_bus.Write32(physical_address & ~3U, value);
|
||||
else
|
||||
{
|
||||
if (!core.m_big_endian)
|
||||
value = std::byteswap(value);
|
||||
core.m_bus.Write32(physical_address & ~3U, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<u64, u64>> ARMJitX64::TakeHotSlowMemorySamples(size_t maximum_count)
|
||||
|
||||
@@ -227,8 +227,10 @@ private:
|
||||
static void ExecuteUserBankBlockTransfer(ARMJitX64* jit, u32 instruction, u32 address);
|
||||
static void ExecuteThumbPushPop(ARMJitX64* jit, u16 instruction, u32 address);
|
||||
static void ExceptionReturn(ARMJitX64* jit, u32 target);
|
||||
template <bool profile>
|
||||
static u32 ReadMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 byte_offset);
|
||||
static void WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 access_size, u32 value);
|
||||
template <u32 access_size, bool profile>
|
||||
static void WriteMemorySlow(ARMJitX64* jit, u32 physical_address, u32 value);
|
||||
static u32 TranslateAddress(ARMJitX64* jit, u32 address);
|
||||
static void EnterUndefinedARM(ARMJitX64* jit, u32 instruction, u32 address);
|
||||
static void EnterSVCARM(ARMJitX64* jit, u32 address);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
|
||||
namespace IOS::LLE
|
||||
{
|
||||
@@ -43,6 +44,8 @@ AX88772::AX88772(Transmit transmit) : m_transmit(std::move(transmit))
|
||||
|
||||
void AX88772::Reset()
|
||||
{
|
||||
m_trace_rx_times = {};
|
||||
m_trace_rx_head = m_trace_rx_peak = 0;
|
||||
m_address = m_configuration = 0;
|
||||
m_control_valid = m_control_stalled = false;
|
||||
m_control.clear();
|
||||
@@ -77,6 +80,11 @@ bool AX88772::LinkUp() const
|
||||
|
||||
void AX88772::DoState(PointerWrap& p)
|
||||
{
|
||||
if (p.IsReadMode())
|
||||
{
|
||||
m_trace_rx_times = {};
|
||||
m_trace_rx_head = m_trace_rx_peak = 0;
|
||||
}
|
||||
p.Do(m_mac);
|
||||
p.Do(m_eeprom);
|
||||
p.Do(m_phy);
|
||||
@@ -403,6 +411,8 @@ bool AX88772::ApplyControl(std::span<const u8> data)
|
||||
if (value & 1)
|
||||
{
|
||||
m_rx.clear();
|
||||
m_trace_rx_times = {};
|
||||
m_trace_rx_head = 0;
|
||||
m_rx_offset = 0;
|
||||
}
|
||||
if (value & 2)
|
||||
@@ -451,7 +461,10 @@ void AX88772::ReceiveFrame(std::span<const u8> frame)
|
||||
{
|
||||
if (!LinkUp() || (m_rx_control & 0x80) == 0 || (m_medium & 0x100) == 0 || frame.size() < 14 ||
|
||||
frame.size() > MAX_FRAME || m_rx.size() >= MAX_QUEUE)
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbDrops);
|
||||
return;
|
||||
}
|
||||
const bool broadcast =
|
||||
std::all_of(frame.begin(), frame.begin() + 6, [](u8 b) { return b == 0xff; });
|
||||
const bool multicast = (frame[0] & 1) != 0;
|
||||
@@ -465,7 +478,28 @@ void AX88772::ReceiveFrame(std::span<const u8> frame)
|
||||
packet.insert(packet.end(), frame.begin(), frame.end());
|
||||
if (length & 1)
|
||||
packet.push_back(0);
|
||||
if (Common::PerformanceTrace::Requested())
|
||||
{
|
||||
m_trace_rx_times[(m_trace_rx_head + m_rx.size()) % m_trace_rx_times.size()] =
|
||||
Common::PerformanceTrace::Now();
|
||||
m_trace_rx_peak = std::max(m_trace_rx_peak, m_rx.size() + 1);
|
||||
}
|
||||
m_rx.push_back(std::move(packet));
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbRXFrames);
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbRXBytes, frame.size());
|
||||
}
|
||||
|
||||
void AX88772::TraceQueue() const
|
||||
{
|
||||
if (!Common::PerformanceTrace::Enabled())
|
||||
return;
|
||||
u64 bytes = 0;
|
||||
for (const auto& packet : m_rx)
|
||||
bytes += packet.size();
|
||||
if (!m_rx.empty())
|
||||
bytes -= m_rx_offset;
|
||||
Common::PerformanceTrace::Queue(this, m_rx.size(), bytes,
|
||||
m_rx.empty() ? 0 : m_trace_rx_times[m_trace_rx_head], m_trace_rx_peak);
|
||||
}
|
||||
|
||||
AX88772::Result AX88772::Transfer(u8 endpoint, u8 direction, std::span<u8> buffer, size_t* actual)
|
||||
@@ -560,9 +594,11 @@ AX88772::Result AX88772::Transfer(u8 endpoint, u8 direction, std::span<u8> buffe
|
||||
*actual = std::min(buffer.size(), packet.size() - m_rx_offset);
|
||||
std::copy_n(packet.begin() + m_rx_offset, *actual, buffer.begin());
|
||||
m_rx_offset += *actual;
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::UsbDeliveredBytes, *actual);
|
||||
if (m_rx_offset == packet.size())
|
||||
{
|
||||
m_rx.pop_front();
|
||||
m_trace_rx_head = (m_trace_rx_head + 1) % m_trace_rx_times.size();
|
||||
m_rx_offset = 0;
|
||||
}
|
||||
return Result::Completed;
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
void ReceiveFrame(std::span<const u8> frame);
|
||||
Result Transfer(u8 endpoint, u8 direction, std::span<u8> buffer, size_t* actual);
|
||||
u8 GetAddress() const { return m_address; }
|
||||
void TraceQueue() const;
|
||||
|
||||
private:
|
||||
static constexpr u8 INTERRUPT_INTERVAL_MS = 10;
|
||||
@@ -77,5 +78,8 @@ private:
|
||||
// Diagnostics only; not part of guest-visible/savestate state.
|
||||
u32 m_control_log_count = 0;
|
||||
u64 m_interrupt_reports = 0;
|
||||
std::array<u64, 128> m_trace_rx_times{};
|
||||
size_t m_trace_rx_head = 0;
|
||||
size_t m_trace_rx_peak = 0;
|
||||
};
|
||||
} // namespace IOS::LLE
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
|
||||
namespace IOS::LLE
|
||||
{
|
||||
@@ -137,31 +138,49 @@ bool NANDJournal::Open(const std::string& path, const Common::SHA1::Digest& sour
|
||||
|
||||
bool NANDJournal::Append(const Pages& pages, bool replace)
|
||||
{
|
||||
namespace Trace = Common::PerformanceTrace;
|
||||
const Trace::Scope trace(Trace::Region::Journal);
|
||||
if (!m_file || pages.size() > PAGE_COUNT)
|
||||
return false;
|
||||
if (pages.empty() && !replace)
|
||||
return true;
|
||||
std::vector<u8> record;
|
||||
record.reserve(TRANSACTION_HEADER_SIZE + pages.size() * ENTRY_SIZE);
|
||||
Put32(record, TRANSACTION_MAGIC);
|
||||
Put32(record, replace ? 1 : 0);
|
||||
Put32(record, static_cast<u32>(pages.size()));
|
||||
for (const auto& [page, data] : pages)
|
||||
Common::SHA1::Digest checksum;
|
||||
{
|
||||
if (page >= PAGE_COUNT)
|
||||
return false;
|
||||
Put32(record, page);
|
||||
record.insert(record.end(), data.begin(), data.end());
|
||||
const Trace::Scope build_trace(Trace::Region::JournalBuild);
|
||||
record.reserve(TRANSACTION_HEADER_SIZE + pages.size() * ENTRY_SIZE);
|
||||
Put32(record, TRANSACTION_MAGIC);
|
||||
Put32(record, replace ? 1 : 0);
|
||||
Put32(record, static_cast<u32>(pages.size()));
|
||||
for (const auto& [page, data] : pages)
|
||||
{
|
||||
if (page >= PAGE_COUNT)
|
||||
return false;
|
||||
Put32(record, page);
|
||||
record.insert(record.end(), data.begin(), data.end());
|
||||
}
|
||||
checksum = Common::SHA1::CalculateDigest(record);
|
||||
}
|
||||
const auto checksum = Common::SHA1::CalculateDigest(record);
|
||||
if (!m_file.WriteArray(record.data(), record.size()) || !m_file.WriteArray(checksum) ||
|
||||
!m_file.Flush())
|
||||
bool success;
|
||||
{
|
||||
const Trace::Scope write_trace(Trace::Region::JournalWrite);
|
||||
success = m_file.WriteArray(record.data(), record.size()) && m_file.WriteArray(checksum);
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
const Trace::Scope flush_trace(Trace::Region::JournalFlush);
|
||||
success = m_file.Flush();
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
Trace::Add(Trace::Counter::JournalErrors);
|
||||
// Never acknowledge volatile writes as successful after a storage error.
|
||||
ERROR_LOG_FMT(IOS, "Persistent NAND write failed; subsequent NAND writes are disabled");
|
||||
m_file.Close();
|
||||
return false;
|
||||
}
|
||||
Trace::Add(Trace::Counter::JournalTransactions);
|
||||
Trace::Add(Trace::Counter::JournalBytes, record.size() + checksum.size());
|
||||
return true;
|
||||
}
|
||||
} // namespace IOS::LLE
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "Common/DynamicLibrary.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/Network.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Common/StringUtil.h"
|
||||
|
||||
namespace IOS::LLE
|
||||
@@ -192,6 +193,8 @@ bool SlirpNetwork::Start(const std::string& directory, Receive receive, std::str
|
||||
auto& self = *static_cast<Impl*>(opaque);
|
||||
if (length >= 14 && length <= 1518 && self.receive)
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatRXFrames);
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatRXBytes, length);
|
||||
if (++self.rx_frames <= 8)
|
||||
INFO_LOG_FMT(IOS_NET, "Starlet NAT -> USB Ethernet: frame {} ({} bytes)", self.rx_frames,
|
||||
length);
|
||||
@@ -244,8 +247,11 @@ bool SlirpNetwork::Start(const std::string& directory, Receive receive, std::str
|
||||
|
||||
void SlirpNetwork::Input(std::span<const u8> frame)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAT);
|
||||
if (m_impl && frame.size() >= 14 && frame.size() <= 1518)
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatTXFrames);
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatTXBytes, frame.size());
|
||||
if (++m_impl->tx_frames <= 8)
|
||||
INFO_LOG_FMT(IOS_NET, "Starlet USB Ethernet -> NAT: frame {} ({} bytes, type {:#06x})",
|
||||
m_impl->tx_frames, frame.size(), (u16(frame[12]) << 8) | frame[13]);
|
||||
@@ -256,6 +262,7 @@ void SlirpNetwork::Input(std::span<const u8> frame)
|
||||
|
||||
void SlirpNetwork::Poll(u64 arm_cycles)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAT);
|
||||
if (!m_impl)
|
||||
return;
|
||||
auto& self = *m_impl;
|
||||
@@ -285,6 +292,7 @@ void SlirpNetwork::Poll(u64 arm_cycles)
|
||||
#endif
|
||||
if (result < 0)
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NatPollErrors);
|
||||
#ifdef _WIN32
|
||||
const int error = WSAGetLastError();
|
||||
#else
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/GekkoDisassembler.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
@@ -43,7 +44,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
|
||||
}
|
||||
// ARMCore owns generation-tagged software TLB and instruction caches, so
|
||||
// construct it only after the complete physical bus exists.
|
||||
m_core = std::make_unique<ARMCore>(*m_memory);
|
||||
m_core = std::make_unique<ARMCore>(*m_memory,
|
||||
Config::Get(Config::MAIN_WII_STARLET_JIT_MEMORY_PROFILING));
|
||||
#if defined(_M_X86_64)
|
||||
m_core->SetJitEnabled(Config::Get(Config::MAIN_WII_STARLET_JIT));
|
||||
#else
|
||||
@@ -62,6 +64,8 @@ bool Starlet::Init(const std::string& dump_directory, std::string* error)
|
||||
m_system.GetCoreTiming().ScheduleEvent(0, m_run_event);
|
||||
INFO_LOG_FMT(IOS, "Starlet LLE started at PC {:#010x} ({})", m_core->GetRegister(15),
|
||||
m_core->IsJitEnabled() ? "JIT" : "interpreter");
|
||||
INFO_LOG_FMT(IOS, "Starlet JIT detailed memory profiling={} (aggregate counts remain active)",
|
||||
m_core->IsJitMemoryProfilingEnabled());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -176,6 +180,7 @@ void Starlet::RunCallback(Core::System& system, u64, s64 cycles_late)
|
||||
|
||||
void Starlet::RunSlice(s64 cycles_late)
|
||||
{
|
||||
const Common::PerformanceTrace::SampleRoot trace_root;
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
@@ -205,6 +210,13 @@ void Starlet::RunSlice(s64 cycles_late)
|
||||
const u64 now_ms = check_realtime_diagnostic ? Common::Timer::NowMs() : 0;
|
||||
if (check_realtime_diagnostic && now_ms >= m_next_realtime_diagnostic_ms)
|
||||
{
|
||||
Common::PerformanceTrace::Snapshot(
|
||||
m_core->GetTraceGeneration(), m_core->GetExecutedInstructions(),
|
||||
m_core->GetJitNativeExecutedInstructions(), m_core->GetJitSlowReadCount(),
|
||||
m_core->GetJitSlowWriteCount(), m_core->GetJitLifetimeCompiledBlockCount(),
|
||||
m_core->GetJitFallbackInstructionCount(), m_core->GetRegister(15),
|
||||
m_core->IsWaitingForExternalEvent());
|
||||
m_memory->TraceNetwork();
|
||||
m_next_realtime_diagnostic_ms = now_ms + 1000;
|
||||
const PowerPC::PowerPCState& ppc = m_system.GetPPCState();
|
||||
const PowerPC::TryReadInstResult instruction = m_system.GetMMU().TryReadInstruction(ppc.pc);
|
||||
@@ -282,18 +294,19 @@ void Starlet::RunSlice(s64 cycles_late)
|
||||
INFO_LOG_FMT(IOS,
|
||||
"Starlet live JIT: instructions={} native={:.1f}% fallbacks={} blocks={} "
|
||||
"translations={} slow-reads={} "
|
||||
"slow-writes={} slow-regions=ram:{}/sram:{}/mmio:{}/other:{} "
|
||||
"slow-writes={} memory-profile={} slow-regions=ram:{}/sram:{}/mmio:{}/other:{} "
|
||||
"interval-fallbacks={} idle-polls={} ipc-armctrl={:#04x} "
|
||||
"PC={:#010x} "
|
||||
"CPSR={:#010x} irqflag={:#010x} irqmask={:#010x} fiqmask={:#010x}",
|
||||
jit_executed, jit_executed == 0 ? 0.0 : 100.0 * jit_native / jit_executed,
|
||||
m_core->GetJitFallbackInstructionCount(), m_core->GetJitCompiledBlockCount(),
|
||||
m_core->GetJitAddressTranslationCount(), m_core->GetJitSlowReadCount(),
|
||||
m_core->GetJitSlowWriteCount(), m_core->GetJitSlowRAMAccessCount(),
|
||||
m_core->GetJitSlowSRAMAccessCount(), m_core->GetJitSlowMMIOAccessCount(),
|
||||
m_core->GetJitSlowOtherAccessCount(), interval_fallbacks,
|
||||
m_core->GetMemoryPollEntryCount(), ipc_arm_ctrl, m_core->GetRegister(15),
|
||||
m_core->GetCPSR(), m_system.GetWiiIPC().ReadStarletRegister(0x38),
|
||||
m_core->GetJitSlowWriteCount(), m_core->IsJitMemoryProfilingEnabled(),
|
||||
m_core->GetJitSlowRAMAccessCount(), m_core->GetJitSlowSRAMAccessCount(),
|
||||
m_core->GetJitSlowMMIOAccessCount(), m_core->GetJitSlowOtherAccessCount(),
|
||||
interval_fallbacks, m_core->GetMemoryPollEntryCount(), ipc_arm_ctrl,
|
||||
m_core->GetRegister(15), m_core->GetCPSR(),
|
||||
m_system.GetWiiIPC().ReadStarletRegister(0x38),
|
||||
m_system.GetWiiIPC().ReadStarletRegister(0x3c),
|
||||
m_system.GetWiiIPC().ReadStarletRegister(0x40));
|
||||
INFO_LOG_FMT(IOS,
|
||||
@@ -322,50 +335,53 @@ void Starlet::RunSlice(s64 cycles_late)
|
||||
m_core->GetJitFastEntryReplacementCount(),
|
||||
m_core->GetJitInstructionCacheClearCount(), m_core->GetJitCodeSpaceClearCount(),
|
||||
m_core->GetJitDiscardedBlockCount());
|
||||
std::array<u32, 4> hot_sram_read_pages{};
|
||||
std::array<u64, 4> hot_sram_read_page_counts{};
|
||||
std::array<u32, 4> hot_sram_write_pages{};
|
||||
std::array<u64, 4> hot_sram_write_page_counts{};
|
||||
const auto rank_sram_page = [](u32 page, u64 count, auto& pages, auto& counts) {
|
||||
for (size_t rank = 0; rank < counts.size(); ++rank)
|
||||
{
|
||||
if (count <= counts[rank])
|
||||
continue;
|
||||
for (size_t move = counts.size() - 1; move > rank; --move)
|
||||
if (m_core->IsJitMemoryProfilingEnabled())
|
||||
{
|
||||
std::array<u32, 4> hot_sram_read_pages{};
|
||||
std::array<u64, 4> hot_sram_read_page_counts{};
|
||||
std::array<u32, 4> hot_sram_write_pages{};
|
||||
std::array<u64, 4> hot_sram_write_page_counts{};
|
||||
const auto rank_sram_page = [](u32 page, u64 count, auto& pages, auto& counts) {
|
||||
for (size_t rank = 0; rank < counts.size(); ++rank)
|
||||
{
|
||||
counts[move] = counts[move - 1];
|
||||
pages[move] = pages[move - 1];
|
||||
if (count <= counts[rank])
|
||||
continue;
|
||||
for (size_t move = counts.size() - 1; move > rank; --move)
|
||||
{
|
||||
counts[move] = counts[move - 1];
|
||||
pages[move] = pages[move - 1];
|
||||
}
|
||||
counts[rank] = count;
|
||||
pages[rank] = page;
|
||||
break;
|
||||
}
|
||||
counts[rank] = count;
|
||||
pages[rank] = page;
|
||||
break;
|
||||
};
|
||||
for (u32 page = 0; page < 32; ++page)
|
||||
{
|
||||
rank_sram_page(page, m_core->GetJitSlowSRAMPageReadAccessCount(page), hot_sram_read_pages,
|
||||
hot_sram_read_page_counts);
|
||||
rank_sram_page(page, m_core->GetJitSlowSRAMPageWriteAccessCount(page), hot_sram_write_pages,
|
||||
hot_sram_write_page_counts);
|
||||
}
|
||||
INFO_LOG_FMT(
|
||||
IOS,
|
||||
"Starlet slow SRAM aliases=low:{}/high:{} reads:{}/writes:{} "
|
||||
"hot-reads={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{} "
|
||||
"hot-writes={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{}",
|
||||
m_core->GetJitSlowSRAMLowAccessCount(), m_core->GetJitSlowSRAMHighAccessCount(),
|
||||
m_core->GetJitSlowSRAMReadAccessCount(), m_core->GetJitSlowSRAMWriteAccessCount(),
|
||||
hot_sram_read_pages[0], hot_sram_read_page_counts[0], hot_sram_read_pages[1],
|
||||
hot_sram_read_page_counts[1], hot_sram_read_pages[2], hot_sram_read_page_counts[2],
|
||||
hot_sram_read_pages[3], hot_sram_read_page_counts[3], hot_sram_write_pages[0],
|
||||
hot_sram_write_page_counts[0], hot_sram_write_pages[1], hot_sram_write_page_counts[1],
|
||||
hot_sram_write_pages[2], hot_sram_write_page_counts[2], hot_sram_write_pages[3],
|
||||
hot_sram_write_page_counts[3]);
|
||||
for (const auto& [key, samples] : m_core->TakeHotJitSlowMemorySamples(8))
|
||||
{
|
||||
const bool write = (key >> 32) != 0;
|
||||
INFO_LOG_FMT(IOS, "Starlet slow memory {} address={:#010x} samples={}",
|
||||
write ? "write" : "read", static_cast<u32>(key), samples);
|
||||
}
|
||||
};
|
||||
for (u32 page = 0; page < 32; ++page)
|
||||
{
|
||||
rank_sram_page(page, m_core->GetJitSlowSRAMPageReadAccessCount(page), hot_sram_read_pages,
|
||||
hot_sram_read_page_counts);
|
||||
rank_sram_page(page, m_core->GetJitSlowSRAMPageWriteAccessCount(page), hot_sram_write_pages,
|
||||
hot_sram_write_page_counts);
|
||||
}
|
||||
INFO_LOG_FMT(IOS,
|
||||
"Starlet slow SRAM aliases=low:{}/high:{} reads:{}/writes:{} "
|
||||
"hot-reads={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{} "
|
||||
"hot-writes={:#04x}:{}, {:#04x}:{}, {:#04x}:{}, {:#04x}:{}",
|
||||
m_core->GetJitSlowSRAMLowAccessCount(), m_core->GetJitSlowSRAMHighAccessCount(),
|
||||
m_core->GetJitSlowSRAMReadAccessCount(), m_core->GetJitSlowSRAMWriteAccessCount(),
|
||||
hot_sram_read_pages[0], hot_sram_read_page_counts[0], hot_sram_read_pages[1],
|
||||
hot_sram_read_page_counts[1], hot_sram_read_pages[2], hot_sram_read_page_counts[2],
|
||||
hot_sram_read_pages[3], hot_sram_read_page_counts[3], hot_sram_write_pages[0],
|
||||
hot_sram_write_page_counts[0], hot_sram_write_pages[1],
|
||||
hot_sram_write_page_counts[1], hot_sram_write_pages[2],
|
||||
hot_sram_write_page_counts[2], hot_sram_write_pages[3],
|
||||
hot_sram_write_page_counts[3]);
|
||||
for (const auto& [key, samples] : m_core->TakeHotJitSlowMemorySamples(8))
|
||||
{
|
||||
const bool write = (key >> 32) != 0;
|
||||
INFO_LOG_FMT(IOS, "Starlet slow memory {} address={:#010x} samples={}",
|
||||
write ? "write" : "read", static_cast<u32>(key), samples);
|
||||
}
|
||||
const ARMCore::HotPCSample current = m_core->GetCurrentPCSample();
|
||||
INFO_LOG_FMT(IOS, "Starlet current PC {:#010x} {} instruction={:#010x}", current.address,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Core/IOS/Starlet/StarletMemory.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
@@ -15,8 +16,10 @@
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Common/SDCardUtil.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/HW/MMIO.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
@@ -873,6 +876,7 @@ std::optional<u32> StarletMemory::TryReadBroadwayResetInstruction(u32 address) c
|
||||
|
||||
void StarletMemory::AdvanceCycles(u64 cycles)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::Devices);
|
||||
const u32 old_timer = GetTimer();
|
||||
m_arm_cycles += cycles;
|
||||
const u32 new_timer = GetTimer();
|
||||
@@ -909,6 +913,12 @@ void StarletMemory::AdvanceCycles(u64 cycles)
|
||||
}
|
||||
}
|
||||
|
||||
void StarletMemory::TraceNetwork() const
|
||||
{
|
||||
if (m_ethernet)
|
||||
m_ethernet->TraceQueue();
|
||||
}
|
||||
|
||||
bool StarletMemory::IsIdlePollAddress(u32 address, u32 size) const
|
||||
{
|
||||
if (size == 0 || address > std::numeric_limits<u32>::max() - (size - 1))
|
||||
@@ -1007,21 +1017,11 @@ u32 StarletMemory::GetBootROMOffset(u32 address) const
|
||||
|
||||
u32 StarletMemory::GetSRAMOffset(u32 address) const
|
||||
{
|
||||
const u32 window_offset = address & (SRAM_WINDOW_SIZE - 1);
|
||||
if (!m_sram_split_mode)
|
||||
{
|
||||
if (window_offset < SRAM_A_SIZE)
|
||||
return window_offset;
|
||||
if (window_offset < SRAM_A_SIZE + SRAM_B_SIZE)
|
||||
return SRAM_A_SIZE + window_offset - SRAM_A_SIZE;
|
||||
return INVALID_SRAM_OFFSET;
|
||||
}
|
||||
|
||||
if (window_offset < SRAM_B_SIZE)
|
||||
return SRAM_A_SIZE + window_offset;
|
||||
if (window_offset >= SRAM_A_SIZE)
|
||||
return window_offset - SRAM_A_SIZE;
|
||||
return INVALID_SRAM_OFFSET;
|
||||
// SM swaps the two 64 KiB halves of the CPU aperture. Bank B occupies only 32 KiB;
|
||||
// its remaining half is a hole in either mapping, never a wrap into bank A.
|
||||
static_assert(SRAM_SIZE == SRAM_A_SIZE + SRAM_B_SIZE);
|
||||
const u32 offset = (address & (SRAM_WINDOW_SIZE - 1)) ^ (m_sram_split_mode ? SRAM_A_SIZE : 0);
|
||||
return offset < SRAM_SIZE ? offset : INVALID_SRAM_OFFSET;
|
||||
}
|
||||
|
||||
u8 StarletMemory::ReadMapped8(u32 address) const
|
||||
@@ -1092,6 +1092,56 @@ void StarletMemory::WriteDMA8(u32 address, u8 value)
|
||||
WriteMapped8(address, value);
|
||||
}
|
||||
|
||||
u8* StarletMemory::GetDMAMemoryPointer(u32 address, size_t size) const
|
||||
{
|
||||
// Only coalesce a transfer wholly inside ordinary backing memory. In particular, DMA's
|
||||
// low SRAM aperture uses physical banks, independent of SM and the CPU boot0 overlay.
|
||||
// Subtraction after checking the starting address also prevents 32-bit range wraparound.
|
||||
if (address >= SRAM_BASE && address < SRAM_BASE + SRAM_SIZE &&
|
||||
size <= SRAM_BASE + SRAM_SIZE - address)
|
||||
{
|
||||
return const_cast<u8*>(m_sram.data()) + (address - SRAM_BASE);
|
||||
}
|
||||
if ((address < Memory::MEM1_SIZE_RETAIL && size <= Memory::MEM1_SIZE_RETAIL - address) ||
|
||||
(address >= 0x10000000 && address < 0x10000000 + Memory::MEM2_SIZE_RETAIL &&
|
||||
size <= 0x10000000 + Memory::MEM2_SIZE_RETAIL - address))
|
||||
{
|
||||
const auto memory = m_system.GetMemory().GetSpanForAddress(address);
|
||||
if (size <= memory.size())
|
||||
return memory.data();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void StarletMemory::ReadDMABuffer(u32 address, std::span<u8> buffer) const
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::DMARead);
|
||||
if (buffer.empty())
|
||||
return;
|
||||
if (const u8* memory = GetDMAMemoryPointer(address, buffer.size()))
|
||||
{
|
||||
// Callers use separate device staging buffers, never an overlapping guest-memory span.
|
||||
std::memcpy(buffer.data(), memory, buffer.size());
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
buffer[i] = ReadDMA8(address + static_cast<u32>(i));
|
||||
}
|
||||
|
||||
void StarletMemory::WriteDMABuffer(u32 address, std::span<const u8> buffer)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::DMAWrite);
|
||||
if (buffer.empty())
|
||||
return;
|
||||
if (u8* memory = GetDMAMemoryPointer(address, buffer.size()))
|
||||
{
|
||||
std::memcpy(memory, buffer.data(), buffer.size());
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
WriteDMA8(address + static_cast<u32>(i), buffer[i]);
|
||||
}
|
||||
|
||||
void StarletMemory::ClearRegisterCache()
|
||||
{
|
||||
for (auto& entry : m_register_cache)
|
||||
@@ -1633,17 +1683,17 @@ void StarletMemory::AdvanceOHCI(size_t controller, u64 cycles)
|
||||
|
||||
u32 StarletMemory::ReadOHCIMemory32(u32 address) const
|
||||
{
|
||||
return static_cast<u32>(ReadDMA8(address)) | static_cast<u32>(ReadDMA8(address + 1)) << 8 |
|
||||
static_cast<u32>(ReadDMA8(address + 2)) << 16 |
|
||||
static_cast<u32>(ReadDMA8(address + 3)) << 24;
|
||||
std::array<u8, 4> bytes;
|
||||
ReadDMABuffer(address, bytes);
|
||||
return static_cast<u32>(bytes[0]) | static_cast<u32>(bytes[1]) << 8 |
|
||||
static_cast<u32>(bytes[2]) << 16 | static_cast<u32>(bytes[3]) << 24;
|
||||
}
|
||||
|
||||
void StarletMemory::WriteOHCIMemory32(u32 address, u32 value)
|
||||
{
|
||||
WriteDMA8(address, static_cast<u8>(value));
|
||||
WriteDMA8(address + 1, static_cast<u8>(value >> 8));
|
||||
WriteDMA8(address + 2, static_cast<u8>(value >> 16));
|
||||
WriteDMA8(address + 3, static_cast<u8>(value >> 24));
|
||||
const std::array<u8, 4> bytes = {static_cast<u8>(value), static_cast<u8>(value >> 8),
|
||||
static_cast<u8>(value >> 16), static_cast<u8>(value >> 24)};
|
||||
WriteDMABuffer(address, bytes);
|
||||
}
|
||||
|
||||
std::vector<u8> StarletMemory::ReadOHCIBuffer(u32 current_buffer, u32 buffer_end) const
|
||||
@@ -1661,11 +1711,9 @@ std::vector<u8> StarletMemory::ReadOHCIBuffer(u32 current_buffer, u32 buffer_end
|
||||
return {};
|
||||
|
||||
std::vector<u8> buffer(first_size + second_size);
|
||||
for (size_t i = 0; i < first_size; ++i)
|
||||
buffer[i] = ReadDMA8(current_buffer + static_cast<u32>(i));
|
||||
ReadDMABuffer(current_buffer, std::span(buffer).first(first_size));
|
||||
const u32 second_page = buffer_end & ~0xfffU;
|
||||
for (size_t i = 0; i < second_size; ++i)
|
||||
buffer[first_size + i] = ReadDMA8(second_page + static_cast<u32>(i));
|
||||
ReadDMABuffer(second_page, std::span(buffer).subspan(first_size));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@@ -1678,15 +1726,13 @@ void StarletMemory::WriteOHCIBuffer(u32 current_buffer, u32 buffer_end, const u8
|
||||
const size_t first_capacity =
|
||||
same_page ? buffer_end - current_buffer + 1 : 0x1000 - (current_buffer & 0xfff);
|
||||
const size_t first_size = std::min(size, first_capacity);
|
||||
for (size_t i = 0; i < first_size; ++i)
|
||||
WriteDMA8(current_buffer + static_cast<u32>(i), data[i]);
|
||||
WriteDMABuffer(current_buffer, std::span(data, first_size));
|
||||
if (size > first_size)
|
||||
{
|
||||
const u32 second_page = buffer_end & ~0xfffU;
|
||||
const size_t second_size =
|
||||
std::min(size - first_size, static_cast<size_t>((buffer_end & 0xfff) + 1));
|
||||
for (size_t i = 0; i < second_size; ++i)
|
||||
WriteDMA8(second_page + static_cast<u32>(i), data[first_size + i]);
|
||||
WriteDMABuffer(second_page, std::span(data + first_size, second_size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3081,15 +3127,13 @@ bool StarletMemory::TransferSDCardBlocks(bool read, u32 argument, u32 block_size
|
||||
{
|
||||
if (!m_sd_card.ReadBytes(data.data(), data.size()))
|
||||
return false;
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
WriteDMA8(dma_address + static_cast<u32>(i), data[i]);
|
||||
WriteDMABuffer(dma_address, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Config::Get(Config::MAIN_ALLOW_SD_WRITES))
|
||||
return false;
|
||||
for (size_t i = 0; i < data.size(); ++i)
|
||||
data[i] = ReadDMA8(dma_address + static_cast<u32>(i));
|
||||
ReadDMABuffer(dma_address, data);
|
||||
if (!m_sd_card.WriteBytes(data.data(), data.size()))
|
||||
return false;
|
||||
}
|
||||
@@ -3479,16 +3523,28 @@ u8 StarletMemory::Read8(u32 address)
|
||||
|
||||
u16 StarletMemory::Read16(u32 address)
|
||||
{
|
||||
// Aligned reads stay inside one SRAM/boot0 mapping segment. Resolve the live mapping once,
|
||||
// including its holes, before considering unrelated memory and peripheral ranges.
|
||||
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
||||
{
|
||||
if (IsBootROMAddress(address))
|
||||
return Common::swap16(m_boot_rom.data() + GetBootROMOffset(address));
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
return offset == INVALID_SRAM_OFFSET ? 0 : Common::swap16(m_sram.data() + offset);
|
||||
}
|
||||
|
||||
if (IsMemoryAddress(address) && IsMemoryAddress(address + 1))
|
||||
return m_system.GetMemory().Read_U16(address);
|
||||
|
||||
if (IsBootROMAddress(address) && IsBootROMAddress(address + 1))
|
||||
{
|
||||
const u32 offset = GetBootROMOffset(address);
|
||||
return static_cast<u16>((static_cast<u16>(m_boot_rom[offset]) << 8) | m_boot_rom[offset + 1]);
|
||||
if (GetBootROMOffset(address + 1) == offset + 1)
|
||||
return static_cast<u16>((static_cast<u16>(m_boot_rom[offset]) << 8) | m_boot_rom[offset + 1]);
|
||||
}
|
||||
|
||||
if (IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 1))
|
||||
if (!IsBootROMAddress(address) && !IsBootROMAddress(address + 1) &&
|
||||
IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 1))
|
||||
{
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
const u32 end_offset = GetSRAMOffset(address + 1);
|
||||
@@ -3515,6 +3571,18 @@ u32 StarletMemory::Read32(u32 address)
|
||||
{
|
||||
return m_system.GetWiiIPC().ReadStarletRegister(address - HW_BASE);
|
||||
}
|
||||
// Keep timer/IPC reads first: SRAM optimization must not add range tests to their hot path.
|
||||
if (IsSRAMWindowAddress(address))
|
||||
{
|
||||
if (IsBootROMAddress(address))
|
||||
return Common::swap32(m_boot_rom.data() + GetBootROMOffset(address));
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
return offset == INVALID_SRAM_OFFSET ? 0 : Common::swap32(m_sram.data() + offset);
|
||||
}
|
||||
// IOS probes HW_VERSION frequently. It is ordinary register backing, with no read side
|
||||
// effects: retain its live byte values without decoding the device range four times.
|
||||
if (address == HW_VERSION)
|
||||
return ReadRegister(address);
|
||||
}
|
||||
|
||||
if ((address & 3) == 0 && IsDIAddress(address))
|
||||
@@ -3534,12 +3602,18 @@ u32 StarletMemory::Read32(u32 address)
|
||||
if (IsBootROMAddress(address) && IsBootROMAddress(address + 3))
|
||||
{
|
||||
const u32 offset = GetBootROMOffset(address);
|
||||
return (static_cast<u32>(m_boot_rom[offset]) << 24) |
|
||||
(static_cast<u32>(m_boot_rom[offset + 1]) << 16) |
|
||||
(static_cast<u32>(m_boot_rom[offset + 2]) << 8) | m_boot_rom[offset + 3];
|
||||
// Unaligned reads can cross the 8 KiB ROM mirror or the boot0/SRAM overlay boundary.
|
||||
// Such reads must resolve each byte; indexing beyond m_boot_rom would read unrelated data.
|
||||
if (GetBootROMOffset(address + 3) == offset + 3)
|
||||
{
|
||||
return (static_cast<u32>(m_boot_rom[offset]) << 24) |
|
||||
(static_cast<u32>(m_boot_rom[offset + 1]) << 16) |
|
||||
(static_cast<u32>(m_boot_rom[offset + 2]) << 8) | m_boot_rom[offset + 3];
|
||||
}
|
||||
}
|
||||
|
||||
if (IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 3))
|
||||
if (!IsBootROMAddress(address) && !IsBootROMAddress(address + 3) &&
|
||||
IsSRAMWindowAddress(address) && IsSRAMWindowAddress(address + 3))
|
||||
{
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
const u32 end_offset = GetSRAMOffset(address + 3);
|
||||
@@ -3769,6 +3843,19 @@ void StarletMemory::Write8(u32 address, u8 value)
|
||||
|
||||
void StarletMemory::Write16(u32 address, u16 value)
|
||||
{
|
||||
// An aligned scalar cannot cross an SRAM bank, aperture or boot0-overlay boundary.
|
||||
// Keep this on the bus (including live mapping/protection), but resolve it only once.
|
||||
if ((address & 1) == 0 && IsSRAMWindowAddress(address))
|
||||
{
|
||||
if (!IsBootROMAddress(address))
|
||||
{
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
if (offset != INVALID_SRAM_OFFSET)
|
||||
Common::WriteSwap16(m_sram.data() + offset, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsMemoryAddress(address) && IsMemoryAddress(address + 1))
|
||||
{
|
||||
m_system.GetMemory().Write_U16(value, address);
|
||||
@@ -3793,6 +3880,20 @@ void StarletMemory::Write16(u32 address, u16 value)
|
||||
|
||||
void StarletMemory::Write32(u32 address, u32 value)
|
||||
{
|
||||
// SRAM is ordinary byte-addressed storage, not MMIO. Aligned words stay inside one
|
||||
// mapping segment, so there is no need to decode every device or map all four bytes.
|
||||
// Unaligned/boundary accesses below still use the original byte-exact bus path.
|
||||
if ((address & 3) == 0 && IsSRAMWindowAddress(address))
|
||||
{
|
||||
if (!IsBootROMAddress(address))
|
||||
{
|
||||
const u32 offset = GetSRAMOffset(address);
|
||||
if (offset != INVALID_SRAM_OFFSET)
|
||||
Common::WriteSwap32(m_sram.data() + offset, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Match the aligned read fast path above. WriteRegister keeps the byte-addressable backing image
|
||||
// coherent, while the exact device handlers retain W1C interrupt flags, reset side effects and
|
||||
// the Starlet/Broadway scheduling boundary of the four-byte Write8 path.
|
||||
@@ -4005,6 +4106,7 @@ bool StarletMemory::ReadRawNANDPage(u32 page, NANDPage* raw)
|
||||
|
||||
bool StarletMemory::CommitNANDPages(const NANDJournal::Pages& pages)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::NAND);
|
||||
if (m_nand_journal && !m_nand_journal->Append(pages))
|
||||
return false;
|
||||
for (const auto& [page, data] : pages)
|
||||
@@ -4068,14 +4170,26 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
const char* const end = std::find(entry.name, std::end(entry.name), '\0');
|
||||
return std::string_view(entry.name, static_cast<size_t>(end - entry.name));
|
||||
};
|
||||
const auto find_child = [&superblock, &entry_name](u16 parent, std::string_view name) {
|
||||
if (parent >= superblock->fst.size())
|
||||
bool invalid_directory = false;
|
||||
const auto find_child = [&superblock, &entry_name, &invalid_directory](u16 parent,
|
||||
std::string_view name) {
|
||||
if (parent >= superblock->fst.size() ||
|
||||
(superblock->fst[parent].mode & 3) !=
|
||||
static_cast<u8>(DiscIO::NANDImporter::Type::Directory))
|
||||
{
|
||||
invalid_directory = true;
|
||||
return std::optional<u16>{};
|
||||
}
|
||||
std::bitset<DiscIO::NANDImporter::FSTEntryCount> visited;
|
||||
u16 child = superblock->fst[parent].sub;
|
||||
while (child != NAND_INVALID_ENTRY)
|
||||
{
|
||||
if (child >= superblock->fst.size())
|
||||
if (child >= superblock->fst.size() || visited.test(child))
|
||||
{
|
||||
invalid_directory = true;
|
||||
return std::optional<u16>{};
|
||||
}
|
||||
visited.set(child);
|
||||
if (entry_name(superblock->fst[child]) == name)
|
||||
return std::optional<u16>{child};
|
||||
child = superblock->fst[child].sib;
|
||||
@@ -4089,8 +4203,17 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
entry = entry ? find_child(*entry, component) : std::nullopt;
|
||||
if (!entry)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SFFS is missing /shared2/sys/SYSCONF");
|
||||
return false;
|
||||
if (invalid_directory)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Invalid raw NAND directory while looking for SYSCONF");
|
||||
return false;
|
||||
}
|
||||
// Pairing injection is a convenience, not a prerequisite for boot0/BootMii.
|
||||
// A freshly formatted NAND may not have SYSCONF yet. Leave it untouched:
|
||||
// native software can create it, and controllers keep their default identities.
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Skipping Wii Remote auto-pairing: /shared2/sys/SYSCONF is absent; "
|
||||
"continuing boot without modifying NAND");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4159,8 +4282,13 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
return false;
|
||||
const std::string_view name(reinterpret_cast<const char*>(sysconf.data() + *raw_offset + 1),
|
||||
name_size);
|
||||
if (name == "BT.DINF" && type == 1)
|
||||
if (name == "BT.DINF")
|
||||
{
|
||||
if (type != 1)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SYSCONF has an invalid BT.DINF type");
|
||||
return false;
|
||||
}
|
||||
const std::optional<u16> size_minus_one = read_be16(value_header);
|
||||
if (!size_minus_one)
|
||||
return false;
|
||||
@@ -4170,8 +4298,15 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
}
|
||||
}
|
||||
|
||||
if (!pairing_offset)
|
||||
{
|
||||
INFO_LOG_FMT(IOS_WIIMOTE, "Skipping Wii Remote auto-pairing: SYSCONF has no BT.DINF entry; "
|
||||
"continuing boot without modifying NAND");
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<u8> pairing = IOS::HLE::GetEmulatedWiimotePairingData();
|
||||
if (!pairing_offset || !pairing_size || *pairing_size != pairing.size() ||
|
||||
if (!pairing_size || *pairing_size != pairing.size() ||
|
||||
*pairing_offset + *pairing_size > sysconf.size())
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_WIIMOTE, "Raw NAND SYSCONF has no compatible BT.DINF section");
|
||||
@@ -4223,8 +4358,8 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
std::ranges::copy(pairing, sysconf.begin() + static_cast<ptrdiff_t>(*pairing_offset));
|
||||
|
||||
// Re-encrypt only the file's SFFS clusters and place their raw pages in the
|
||||
// persistent overlay. Preserve all spare metadata from the dump and replace only
|
||||
// the ECC for the modified ciphertext.
|
||||
// persistent overlay. Preserve unrelated spare metadata and regenerate the
|
||||
// plaintext HMAC copies and ciphertext ECC for each modified cluster.
|
||||
size_t file_offset = 0;
|
||||
NANDJournal::Pages pairing_pages;
|
||||
for (size_t cluster_number = 0; cluster_number < cluster_indices.size(); ++cluster_number)
|
||||
@@ -4274,10 +4409,20 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
return false;
|
||||
std::copy_n(encrypted_cluster.begin() + page_in_cluster * NAND_PAGE_DATA_SIZE,
|
||||
NAND_PAGE_DATA_SIZE, raw.begin());
|
||||
// SFFS keeps redundant HMACs at spare offset 0x0c in the seventh and
|
||||
// eighth pages.
|
||||
if (page_in_cluster >= 6)
|
||||
std::ranges::copy(hmac, raw.begin() + NAND_PAGE_DATA_SIZE + 0x0c);
|
||||
// Native SFFS stores a full HMAC at spare byte 1 in the seventh page.
|
||||
// Its redundant copy spans that page's bytes 21-32 (first 12 bytes)
|
||||
// and the eighth page's bytes 1-8 (last 8 bytes). Byte 0 is the bad-block
|
||||
// marker. Writing two full copies at 0x0c leaves both native copies
|
||||
// invalid, so IOS discards the updated SYSCONF and loses the pairings.
|
||||
if (page_in_cluster == 6)
|
||||
{
|
||||
std::ranges::copy(hmac, raw.begin() + NAND_PAGE_DATA_SIZE + 1);
|
||||
std::copy_n(hmac.begin(), 12, raw.begin() + NAND_PAGE_DATA_SIZE + 21);
|
||||
}
|
||||
else if (page_in_cluster == 7)
|
||||
{
|
||||
std::copy_n(hmac.begin() + 12, 8, raw.begin() + NAND_PAGE_DATA_SIZE + 1);
|
||||
}
|
||||
const auto ecc = CalculateNANDECC(raw.data());
|
||||
std::ranges::copy(ecc, raw.begin() + NAND_PAGE_DATA_SIZE + 0x30);
|
||||
pairing_pages.insert_or_assign(page, raw);
|
||||
@@ -4293,6 +4438,7 @@ bool StarletMemory::InstallEmulatedWiimotePairings()
|
||||
|
||||
bool StarletMemory::ReadNANDPage(u32 command)
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDReads);
|
||||
if (!(command & NAND_CTRL_READ) || !m_nand_read_pending)
|
||||
return false;
|
||||
|
||||
@@ -4306,21 +4452,18 @@ bool StarletMemory::ReadNANDPage(u32 command)
|
||||
if (!ReadRawNANDPage(m_nand_read_page, &raw))
|
||||
return false;
|
||||
|
||||
for (u32 i = 0; i < length; ++i)
|
||||
{
|
||||
const u32 raw_offset = m_nand_read_column + i;
|
||||
u32 destination = data_address + i;
|
||||
if (m_nand_read_column < NAND_PAGE_DATA_SIZE && raw_offset >= NAND_PAGE_DATA_SIZE)
|
||||
destination = spare_address + raw_offset - NAND_PAGE_DATA_SIZE;
|
||||
WriteDMA8(destination, raw[raw_offset]);
|
||||
}
|
||||
const u32 data_size = m_nand_read_column < NAND_PAGE_DATA_SIZE ?
|
||||
std::min(length, NAND_PAGE_DATA_SIZE - m_nand_read_column) :
|
||||
length;
|
||||
const auto transfer = std::span(raw).subspan(m_nand_read_column, length);
|
||||
WriteDMABuffer(data_address, transfer.first(data_size));
|
||||
WriteDMABuffer(spare_address, transfer.subspan(data_size));
|
||||
|
||||
if (command & NAND_CTRL_ECC)
|
||||
{
|
||||
const auto ecc = CalculateNANDECC(raw.data());
|
||||
const u32 calculated_ecc_address = spare_address ^ 0x40U;
|
||||
for (u32 i = 0; i < ecc.size(); ++i)
|
||||
WriteDMA8(calculated_ecc_address + i, ecc[i]);
|
||||
WriteDMABuffer(calculated_ecc_address, ecc);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4383,21 +4526,17 @@ bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input)
|
||||
|
||||
const u32 data_address = ReadRegister(NAND_DATA) & ~0xfU;
|
||||
const u32 spare_address = ReadRegister(NAND_ECC) & ~0xfU;
|
||||
for (u32 i = 0; i < length; ++i)
|
||||
{
|
||||
const u32 raw_offset = column + i;
|
||||
u32 source = data_address + i;
|
||||
if (column < NAND_PAGE_DATA_SIZE && raw_offset >= NAND_PAGE_DATA_SIZE)
|
||||
source = spare_address + raw_offset - NAND_PAGE_DATA_SIZE;
|
||||
m_nand_program_data[raw_offset] = ReadDMA8(source);
|
||||
}
|
||||
const u32 data_size =
|
||||
column < NAND_PAGE_DATA_SIZE ? std::min(length, NAND_PAGE_DATA_SIZE - column) : length;
|
||||
const auto transfer = std::span(m_nand_program_data).subspan(column, length);
|
||||
ReadDMABuffer(data_address, transfer.first(data_size));
|
||||
ReadDMABuffer(spare_address, transfer.subspan(data_size));
|
||||
|
||||
if ((command & NAND_CTRL_ECC) != 0 && column == 0 && length >= NAND_PAGE_DATA_SIZE)
|
||||
{
|
||||
const auto ecc = CalculateNANDECC(m_nand_program_data.data());
|
||||
const u32 calculated_ecc_address = spare_address ^ 0x40U;
|
||||
for (u32 i = 0; i < ecc.size(); ++i)
|
||||
WriteDMA8(calculated_ecc_address + i, ecc[i]);
|
||||
WriteDMABuffer(calculated_ecc_address, ecc);
|
||||
m_nand_program_ecc_enabled = true;
|
||||
}
|
||||
return true;
|
||||
@@ -4405,6 +4544,7 @@ bool StarletMemory::StageNANDProgram(u32 command, bool random_data_input)
|
||||
|
||||
bool StarletMemory::CommitNANDProgram()
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDPrograms);
|
||||
if (!m_nand_program_pending)
|
||||
return false;
|
||||
|
||||
@@ -4452,6 +4592,7 @@ bool StarletMemory::StageNANDErase()
|
||||
|
||||
bool StarletMemory::CommitNANDErase()
|
||||
{
|
||||
Common::PerformanceTrace::Add(Common::PerformanceTrace::Counter::NANDErases);
|
||||
if (!m_nand_erase_pending)
|
||||
return false;
|
||||
|
||||
@@ -4557,6 +4698,7 @@ void StarletMemory::HandleAESWrite(u32 address, u32 value)
|
||||
|
||||
void StarletMemory::ExecuteAESCommand(u32 command)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::AES);
|
||||
if (command == 0)
|
||||
return;
|
||||
if ((command & AES_CTRL_EXEC) == 0)
|
||||
@@ -4567,8 +4709,7 @@ void StarletMemory::ExecuteAESCommand(u32 command)
|
||||
const u32 destination = ReadRegister(AES_DEST) & ~0xfU;
|
||||
std::vector<u8> input(size);
|
||||
std::vector<u8> output(size);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
input[i] = ReadDMA8(source + static_cast<u32>(i));
|
||||
ReadDMABuffer(source, input);
|
||||
|
||||
std::array<u8, 16> next_iv{};
|
||||
bool succeeded = true;
|
||||
@@ -4588,8 +4729,7 @@ void StarletMemory::ExecuteAESCommand(u32 command)
|
||||
{
|
||||
if ((command & AES_CTRL_ENABLE) != 0)
|
||||
m_aes_iv = next_iv;
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
WriteDMA8(destination + static_cast<u32>(i), output[i]);
|
||||
WriteDMABuffer(destination, output);
|
||||
WriteRegister(AES_SRC, source + static_cast<u32>(size));
|
||||
WriteRegister(AES_DEST, destination + static_cast<u32>(size));
|
||||
}
|
||||
@@ -4667,6 +4807,7 @@ void StarletMemory::CompressSHA1(const u8* block)
|
||||
|
||||
void StarletMemory::ExecuteSHACommand(u32 command)
|
||||
{
|
||||
const Common::PerformanceTrace::Scope trace(Common::PerformanceTrace::Region::SHA);
|
||||
if (!(command & (1U << 31)))
|
||||
return;
|
||||
|
||||
@@ -4675,8 +4816,7 @@ void StarletMemory::ExecuteSHACommand(u32 command)
|
||||
std::array<u8, 64> block{};
|
||||
for (u32 i = 0; i < blocks; ++i)
|
||||
{
|
||||
for (u32 j = 0; j < block.size(); ++j)
|
||||
block[j] = ReadDMA8(source + i * 64 + j);
|
||||
ReadDMABuffer(source + i * 64, block);
|
||||
CompressSHA1(block.data());
|
||||
}
|
||||
for (u32 i = 0; i < m_sha_state.size(); ++i)
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -74,11 +75,16 @@ public:
|
||||
const u8* GetDirectMemoryPointer(u32 address, u32 size) const override;
|
||||
|
||||
u64 GetCycles() const { return m_arm_cycles; }
|
||||
void TraceNetwork() const;
|
||||
std::optional<u32> TryReadBroadwayResetInstruction(u32 address) const;
|
||||
void SetWiimoteSource(size_t index, WiimoteCommon::HIDWiimote* source);
|
||||
bool EnableEthernetNAT(const std::string& runtime_directory, std::string* error);
|
||||
|
||||
private:
|
||||
// Synthetic NAND fixtures exercise startup pairing without any console dumps or keys.
|
||||
friend class StarletPairingTest;
|
||||
friend class StarletDMATest;
|
||||
|
||||
static constexpr u32 NAND_PAGE_DATA_SIZE = 0x800;
|
||||
static constexpr u32 NAND_PAGE_SPARE_SIZE = 0x40;
|
||||
static constexpr u32 NAND_RAW_PAGE_SIZE = NAND_PAGE_DATA_SIZE + NAND_PAGE_SPARE_SIZE;
|
||||
@@ -96,6 +102,9 @@ private:
|
||||
void WriteMapped8(u32 address, u8 value);
|
||||
u8 ReadDMA8(u32 address) const;
|
||||
void WriteDMA8(u32 address, u8 value);
|
||||
u8* GetDMAMemoryPointer(u32 address, size_t size) const;
|
||||
void ReadDMABuffer(u32 address, std::span<u8> buffer) const;
|
||||
void WriteDMABuffer(u32 address, std::span<const u8> buffer);
|
||||
bool IsBootROMAddress(u32 address) const;
|
||||
u32 GetBootROMOffset(u32 address) const;
|
||||
u32 GetSRAMOffset(u32 address) const;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/JitRegister.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/Host.h"
|
||||
@@ -189,6 +190,10 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link,
|
||||
LinkBlock(block);
|
||||
}
|
||||
|
||||
Common::PerformanceTrace::CodeMap("PPC", this, block.near_begin, block.near_end,
|
||||
block.effectiveAddress, block.physicalAddress, "block-near");
|
||||
Common::PerformanceTrace::CodeMap("PPC", this, block.far_begin, block.far_end,
|
||||
block.effectiveAddress, block.physicalAddress, "block-far");
|
||||
const Common::Symbol* symbol = nullptr;
|
||||
if (Common::JitRegister::IsEnabled() &&
|
||||
(symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr)
|
||||
@@ -497,6 +502,8 @@ void JitBaseBlockCache::UnlinkBlock(const JitBlock& block)
|
||||
|
||||
void JitBaseBlockCache::DestroyBlock(JitBlock& block)
|
||||
{
|
||||
Common::PerformanceTrace::CodeReset("PPC", this, block.near_begin, block.near_end);
|
||||
Common::PerformanceTrace::CodeReset("PPC", this, block.far_begin, block.far_end);
|
||||
if (m_entry_points_ptr)
|
||||
{
|
||||
if (m_entry_points_ptr[block.fast_block_map_index] == block.normalEntry)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <implot.h>
|
||||
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "Common/PerformanceTrace.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
@@ -24,6 +25,7 @@ PerformanceMetrics::PerformanceMetrics()
|
||||
|
||||
void PerformanceMetrics::Reset()
|
||||
{
|
||||
Common::PerformanceTrace::Marker("PerformanceMetricsReset");
|
||||
m_fps_counter.Reset();
|
||||
m_vps_counter.Reset();
|
||||
|
||||
@@ -38,11 +40,13 @@ void PerformanceMetrics::Reset()
|
||||
|
||||
void PerformanceMetrics::CountFrame()
|
||||
{
|
||||
Common::PerformanceTrace::Frame("produced");
|
||||
m_fps_counter.Count();
|
||||
}
|
||||
|
||||
void PerformanceMetrics::CountVBlank()
|
||||
{
|
||||
Common::PerformanceTrace::Frame("vblank");
|
||||
m_vps_counter.Count();
|
||||
}
|
||||
|
||||
@@ -62,6 +66,7 @@ void PerformanceMetrics::AdjustClockSpeed(s64 ticks, u32 new_ppc_clock, u32 old_
|
||||
|
||||
void PerformanceMetrics::CountPerformanceMarker(s64 core_ticks, u32 ticks_per_second)
|
||||
{
|
||||
Common::PerformanceTrace::Frame("guest-clock", core_ticks, ticks_per_second);
|
||||
const auto clock_time = Clock::now();
|
||||
const auto work_time = clock_time - m_time_sleeping;
|
||||
|
||||
|
||||
Reference in new issue
Block a user