From b937805eb573cbad52fc698fd149752eae0ddc81 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Fri, 30 Oct 2026 00:53:47 -0400 Subject: core_timing: request the timer thread's stop before waking it CoreTiming::Reset set pause_event and event, and only then requested the stop. The woken timer thread could take the wake-up, still see no stop request, and go back to waiting on pause_event for good; the join that follows then hung the frontend while booting a game ("Not Responding", main thread in CoreTiming::Reset -> std::thread::join). Request the stop first, so the thread sees it whenever it wakes. --- src/core/core_timing.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/core/core_timing.cpp') diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index d2ee6769cc..b85bd30a58 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -304,10 +304,15 @@ std::optional CoreTiming::Advance() { void CoreTiming::Reset() { paused = true; + // Request the stop before waking the thread: woken first, it could take the wake-up, see no + // stop request yet, and go back to waiting on pause_event for good (the join below then hung + // the frontend at boot). + if (timer_thread.joinable()) { + timer_thread.request_stop(); + } pause_event.Set(); event.Set(); if (timer_thread.joinable()) { - timer_thread.request_stop(); timer_thread.join(); } has_started = false; -- cgit v1.2.3 From 0218eacfd560d0ce48b3ffe64d279ee85bbf1347 Mon Sep 17 00:00:00 2001 From: Takoroka <137927095+Takoroka@users.noreply.github.com> Date: Fri, 30 Oct 2026 01:55:20 -0400 Subject: nx2: run the guest counter at 31.25 MHz for Switch 2 titles The Switch 2 counter runs at 31.25 MHz and its SDK converts ticks with that constant: Mario Kart World's sdk builds 31,250,000 in nn::os::GetSystemTickFrequency, TickManager::ConvertToTimeSpan and TickManager::ConvertToTick (19.2 MHz appears nowhere). Eglantine counted at the Switch 1 rate for every title, so every tick-based time in a Switch 2 game was off by 1.63x: elapsed times read short, and a deadline of one 60 Hz frame converted to ticks waited ~27 ms. The wall clock now has a guest counter frequency, set at boot: 31.25 MHz for Switch 2 programs, 19.2 MHz otherwise, EDEN_NX2_CNTFRQ= to force one. CNTPCT reads, CNTFRQ_EL0, svcGetSystemTick, thread tick counts and the time services' tick conversions follow it, and the CNTPCT spin detection keeps its ~1.7 us threshold. Mario Kart World, title screen and attract demo, 180 s, same build: frame times unchanged (17-19 ms once warm), process CPU time 848 s instead of 930 s. --- src/common/cpu_features.cpp | 31 ++++++++++++++++++++++++++----- src/common/cpu_features.h | 22 +++++++++++++++++++--- src/core/arm/dynarmic/arm_dynarmic_64.cpp | 6 ++++-- src/core/arm/dynarmic/arm_dynarmic_64.h | 1 + src/core/arm/nce/patcher.cpp | 3 ++- src/core/core.cpp | 25 ++++++++++++++++++++++--- src/core/core_timing.cpp | 2 +- src/core/hle/service/psc/time/common.h | 11 ++++++----- 8 files changed, 81 insertions(+), 20 deletions(-) (limited to 'src/core/core_timing.cpp') diff --git a/src/common/cpu_features.cpp b/src/common/cpu_features.cpp index 52fd003c44..93eecb1dc5 100644 --- a/src/common/cpu_features.cpp +++ b/src/common/cpu_features.cpp @@ -278,8 +278,16 @@ std::chrono::milliseconds WallClock::GetTimeMS() const { s64 WallClock::GetCNTPCT() const { if (!invariant) - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; - return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor); + return MultiplyAndDivide64(GetUptime(), GetGuestCNTFRQ(), NsRatio::den); + return MultiplyHigh(GetUptime(), cntpct_rdtsc_factor.load(std::memory_order_relaxed)); +} + +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); + if (invariant) { + cntpct_rdtsc_factor.store(GetFixedPoint64Factor(frequency, rdtsc_frequency), + std::memory_order_relaxed); + } } s64 WallClock::GetGPUTick() const { @@ -336,7 +344,7 @@ namespace { } // namespace WallClock::WallClock(bool invariant_, u64 rdtsc_frequency_) noexcept { - const u64 host_cntfrq = std::max(GetHostCNTFRQ(), 1); + host_cntfrq = std::max(GetHostCNTFRQ(), 1); ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq); us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq); ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq); @@ -361,6 +369,11 @@ s64 WallClock::GetCNTPCT() const { return MultiplyHigh(GetUptime(), guest_cntfrq_factor); } +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); + guest_cntfrq_factor = GetFixedPointFactor(frequency, host_cntfrq); +} + s64 WallClock::GetGPUTick() const { return MultiplyHigh(GetUptime(), gputick_cntfrq_factor); } @@ -399,7 +412,11 @@ std::chrono::milliseconds WallClock::GetTimeMS() const { } s64 WallClock::GetCNTPCT() const { - return GetUptime() * NsToCNTPCTRatio::num / NsToCNTPCTRatio::den; + return MultiplyAndDivide64(GetUptime(), GetGuestCNTFRQ(), NsRatio::den); +} + +void WallClock::SetGuestCNTFRQ(u64 frequency) { + guest_cntfrq.store(frequency, std::memory_order_relaxed); } s64 WallClock::GetGPUTick() const { @@ -419,9 +436,13 @@ u64 WallClock::NsToTicks(std::chrono::nanoseconds ns) const { } #endif +u64 WallClock::CPUTickToGuestCNTPCT(u64 cpu_tick) const { + return MultiplyAndDivide64(cpu_tick, GetGuestCNTFRQ(), CPUTickFreq); +} + // Wall clock MUST be initialized AFTER g_cpu_caps // C++ only guarantees ctor init in the order they appear in TU -const WallClock g_wall_clock = [] { +WallClock g_wall_clock = [] { #if defined(ARCHITECTURE_x86_64) auto const& caps = Common::g_cpu_caps; return WallClock(caps.invariant_tsc && caps.tsc_frequency > std::nano::den, caps.tsc_frequency); diff --git a/src/common/cpu_features.h b/src/common/cpu_features.h index 61aa2767ab..31be2dc956 100644 --- a/src/common/cpu_features.h +++ b/src/common/cpu_features.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -20,11 +21,24 @@ namespace Common { class WallClock { public: static constexpr u64 CNTFRQ = 19'200'000; // CNTPCT_EL0 Frequency = 19.2 MHz + static constexpr u64 Switch2CNTFRQ = 31'250'000; // Switch 2 CNTPCT_EL0 Frequency = 31.25 MHz static constexpr u64 GPUTickFreq = 614'400'000; // GM20B GPU Tick Frequency = 614.4 MHz static constexpr u64 CPUTickFreq = 1'020'000'000; // T210/4 A57 CPU Tick Frequency = 1020.0 MHz explicit WallClock(bool invariant, u64 rdtsc_frequency_) noexcept; + /// Sets the guest counter's frequency: CNTFRQ, or Switch2CNTFRQ for Switch 2 titles (their SDK + /// converts ticks with a constant 31.25 MHz). Called at boot, before guest code runs. + void SetGuestCNTFRQ(u64 frequency); + + /// @returns The guest counter's frequency (CNTFRQ_EL0). + [[nodiscard]] u64 GetGuestCNTFRQ() const { + return guest_cntfrq.load(std::memory_order_relaxed); + } + + /// @returns Emulated CPU ticks (single core timing) as guest CNTPCT ticks. + [[nodiscard]] u64 CPUTickToGuestCNTPCT(u64 cpu_tick) const; + /// @returns The time in nanoseconds since the construction of this clock. std::chrono::nanoseconds GetTimeNS() const; @@ -34,7 +48,7 @@ public: /// @returns The time in milliseconds since the construction of this clock. std::chrono::milliseconds GetTimeMS() const; - /// @returns The guest CNTPCT ticks since the construction of this clock. + /// @returns The guest CNTPCT ticks (at GetGuestCNTFRQ()) since the construction of this clock. s64 GetCNTPCT() const; /// @returns The guest GPU ticks since the construction of this clock. @@ -91,6 +105,7 @@ public: using CPUTickToCNTPCTRatio = std::ratio; using CPUTickToGPUTickRatio = std::ratio; + std::atomic guest_cntfrq{CNTFRQ}; #if defined(ARCHITECTURE_x86_64) u64 rdtsc_frequency; u64 ns_rdtsc_factor; @@ -98,7 +113,7 @@ public: u64 ms_rdtsc_factor; u64 rdtsc_ns_integer; u64 rdtsc_ns_factor; - u64 cntpct_rdtsc_factor; + std::atomic cntpct_rdtsc_factor; u64 gputick_rdtsc_factor; bool invariant; #elif defined(HAS_NCE) @@ -106,6 +121,7 @@ public: [[nodiscard]] inline FactorType GetGuestCNTFRQFactor() const { return guest_cntfrq_factor; } + u64 host_cntfrq; FactorType ns_cntfrq_factor; FactorType us_cntfrq_factor; FactorType ms_cntfrq_factor; @@ -183,6 +199,6 @@ std::optional GetProcessorCount(); /// @brief Global cpu caps extern const CPUCaps g_cpu_caps; /// @brief Global wall clock -extern const WallClock g_wall_clock; +extern WallClock g_wall_clock; } // namespace Common diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index a2c0124f33..e65e38dd9e 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -180,7 +180,7 @@ u64 DynarmicCallbacks64::GetCNTPCT() { const char* value = std::getenv("EDEN_NX2_CNTPCT_PAUSE"); return value == nullptr || value[0] != '0'; }(); - if (pause_spins && ticks - m_last_cntpct < 32) { + if (pause_spins && ticks - m_last_cntpct < m_spin_ticks) { #if defined(_M_X64) || defined(__x86_64__) _mm_pause(); _mm_pause(); @@ -269,7 +269,9 @@ void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_s config.tpidr_el0 = &m_cb->m_tpidr_el0; config.dczid_el0 = 4; config.ctr_el0 = 0x8444c004; - config.cntfrq_el0 = Hardware::CNTFREQ; + // 31.25 MHz for Switch 2 titles (Common::WallClock::SetGuestCNTFRQ, set at boot). + config.cntfrq_el0 = static_cast(Common::g_wall_clock.GetGuestCNTFRQ()); + m_cb->m_spin_ticks = config.cntfrq_el0 / 600'000; // Unpredictable instructions config.define_unpredictable_behaviour = true; diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.h b/src/core/arm/dynarmic/arm_dynarmic_64.h index acd1fe773f..66b07474c1 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.h +++ b/src/core/arm/dynarmic/arm_dynarmic_64.h @@ -66,6 +66,7 @@ public: u64 m_tpidrro_el0{}; u64 m_tpidr_el0{}; u64 m_last_cntpct{}; ///< The previous counter read (GetCNTPCT spin detection) + u64 m_spin_ticks{32}; ///< Reads closer than this (~1.7 us) count as a polling loop Kernel::KProcess* m_process{}; const bool m_debugger_enabled{}; const bool m_check_memory_access{}; diff --git a/src/core/arm/nce/patcher.cpp b/src/core/arm/nce/patcher.cpp index f21d7cacdc..72c3497f03 100644 --- a/src/core/arm/nce/patcher.cpp +++ b/src/core/arm/nce/patcher.cpp @@ -627,7 +627,7 @@ void Patcher::WriteMsrHandler(ModuleDestLabel module_dest, oaknut::XReg src_reg, } void Patcher::WriteCntfrqHandler(ModuleDestLabel module_dest, oaknut::XReg dest_reg, oaknut::VectorCodeGenerator& cg) { - cg.MOV(dest_reg, Common::WallClock::CNTFRQ); + cg.MOV(dest_reg, Common::g_wall_clock.GetGuestCNTFRQ()); // Jump back to the instruction after the emulated MRS. if (&cg == &c_pre) @@ -642,6 +642,7 @@ void Patcher::WriteCntpctHandler(ModuleDestLabel module_dest, oaknut::XReg dest_ #else static Common::WallClock clock(true, 1); #endif + clock.SetGuestCNTFRQ(Common::g_wall_clock.GetGuestCNTFRQ()); const auto factor = clock.GetGuestCNTFRQFactor(); const auto raw_factor = std::bit_cast>(factor); diff --git a/src/core/core.cpp b/src/core/core.cpp index f3d224b1ce..3728ccf8f7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -2,11 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include #include #include #include "game_settings.h" +#include "common/cpu_features.h" #include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging.h" @@ -313,16 +315,33 @@ struct System::Impl { fs_controller.InitTempStorage(); } + const bool switch2_program = + virtual_filesystem != nullptr && + IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath)); + // Switch 2 titles size their heap from the total memory and need the 12 GiB layout. Set it // as a per-game value, so RestoreGlobalState brings back the configured layout afterwards. - if (virtual_filesystem != nullptr && - Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_12Gb && - IsSwitch2Program(GetGameFileFromPath(virtual_filesystem, filepath))) { + if (switch2_program && + Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_12Gb) { LOG_INFO(Core, "Switch 2 program: using the 12 GiB memory layout"); Settings::values.memory_layout_mode.SetGlobal(false); Settings::values.memory_layout_mode.SetValue(Settings::MemoryLayout::Memory_12Gb); } + // The Switch 2 counter runs at 31.25 MHz and its SDK converts ticks with that constant + // (nn::os::GetSystemTickFrequency, TickManager::ConvertToTimeSpan/ConvertToTick). At 19.2 MHz + // every tick-based time in the game was off by 1.63x: elapsed times read short and a frame + // deadline in ticks waited ~27 ms. EDEN_NX2_CNTFRQ= overrides it (A/B tests). + static const u64 forced_cntfrq = [] { + const char* value = std::getenv("EDEN_NX2_CNTFRQ"); + return value != nullptr ? std::strtoull(value, nullptr, 10) : 0ULL; + }(); + const u64 cntfrq = forced_cntfrq != 0 ? forced_cntfrq + : switch2_program ? Common::WallClock::Switch2CNTFRQ + : Common::WallClock::CNTFRQ; + Common::g_wall_clock.SetGuestCNTFRQ(cntfrq); + LOG_INFO(Core, "Guest counter frequency: {} Hz", cntfrq); + InitializeKernel(system); if (params.applet_type == Service::AM::AppletType::Application) { diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index b85bd30a58..dfc3f6f2a6 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -224,7 +224,7 @@ void CoreTiming::ResetTicks() { } u64 CoreTiming::GetClockTicks() const { - u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks); + u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::g_wall_clock.CPUTickToGuestCNTPCT(cpu_ticks); if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue()); target != CPU_CLOCK_BASE_MHZ) { fres = fres * target / CPU_CLOCK_BASE_MHZ; diff --git a/src/core/hle/service/psc/time/common.h b/src/core/hle/service/psc/time/common.h index 2183fa72b8..6f7d2cb88b 100644 --- a/src/core/hle/service/psc/time/common.h +++ b/src/core/hle/service/psc/time/common.h @@ -134,12 +134,13 @@ struct OperationEvent : public Common::IntrusiveListBaseNode { Kernel::KEvent* m_event{}; }; -constexpr inline std::chrono::nanoseconds ConvertToTimeSpan(s64 ticks) { +inline std::chrono::nanoseconds ConvertToTimeSpan(s64 ticks) { constexpr auto one_second_ns{ std::chrono::duration_cast(std::chrono::seconds(1)).count()}; - constexpr s64 max{Common::WallClock::CNTFRQ * - ((std::numeric_limits::max)() / one_second_ns)}; + // The guest counter's frequency: 31.25 MHz for Switch 2 titles. + const s64 frequency{static_cast(Common::g_wall_clock.GetGuestCNTFRQ())}; + const s64 max{frequency * ((std::numeric_limits::max)() / one_second_ns)}; if (ticks > max) { return std::chrono::nanoseconds((std::numeric_limits::max)()); @@ -147,8 +148,8 @@ constexpr inline std::chrono::nanoseconds ConvertToTimeSpan(s64 ticks) { return std::chrono::nanoseconds((std::numeric_limits::min)()); } - auto a{ticks / Common::WallClock::CNTFRQ * one_second_ns}; - auto b{((ticks % Common::WallClock::CNTFRQ) * one_second_ns) / Common::WallClock::CNTFRQ}; + auto a{ticks / frequency * one_second_ns}; + auto b{((ticks % frequency) * one_second_ns) / frequency}; return std::chrono::nanoseconds(a + b); } -- cgit v1.2.3