From 0d1a8e3afd39f82fca04250c02e91b1fca18ef61 Mon Sep 17 00:00:00 2001 From: Silent Date: Sun, 11 Oct 2020 17:59:25 +0200 Subject: [PATCH] Make RDTSC and ACPI timers stateful to fix overflows Stateless RDTSC and ACPI timers were ticking relative to the host QPC and multiplied to nanoseconds. This resulted in values so huge they would overflow since 20-30 minutes. Introducing state allows to multiply to nanoseconds only over a delta value, which should be reasonably small in almost call cases. --- src/core/kernel/exports/EmuKrnlKe.cpp | 73 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index 6feacfd28..dcf3808ea 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -324,43 +324,64 @@ void InitDpcThread() g_DpcData.DpcEvent = CreateEvent(/*lpEventAttributes=*/nullptr, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, /*lpName=*/nullptr); } -#define XBOX_TSC_FREQUENCY 733333333 // Xbox Time Stamp Counter Frequency = 733333333 (CPU Clock) -#define XBOX_ACPI_FREQUENCY 3375000 // Xbox ACPI frequency (3.375 mhz) -ULONGLONG NativeToXbox_FactorForRdtsc; -ULONGLONG NativeToXbox_FactorForAcpi; +static constexpr uint32_t XBOX_TSC_FREQUENCY = 733333333; // Xbox Time Stamp Counter Frequency = 733333333 (CPU Clock) +static constexpr uint32_t XBOX_ACPI_FREQUENCY = 3375000; // Xbox ACPI frequency (3.375 mhz) +static constexpr uint32_t SEC_TO_NSEC = 1000000000; // For seconds -> nanoseconds conversions +static uint64_t NativeToXbox_FactorForRdtsc = 0, NativeToXbox_FactorForAcpi = 0; -ULONGLONG CxbxGetPerformanceCounter(bool acpi) { - LARGE_INTEGER tsc; - ULARGE_INTEGER scaledTsc; +// State for CxbxGetPerformanceCounter - concurrent access should be next to non-existent, but secure against it anyway +static std::mutex RdtscLock, AcpiLock; +static LARGE_INTEGER LastRdtscQPC, LastAcpiQPC; +static ULONGLONG CurrentRdtsc = 0, CurrentAcpi = 0; +static ULONGLONG CurrentRdtscRemainder = 0, CurrentAcpiRemainder = 0; - QueryPerformanceCounter(&tsc); +ULONGLONG CxbxGetPerformanceCounter(bool acpi) +{ + if (acpi == false && NativeToXbox_FactorForRdtsc != 0) { + std::lock_guard lock(RdtscLock); - scaledTsc.QuadPart = 1000000000; - scaledTsc.QuadPart *= (ULONGLONG)tsc.QuadPart; + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); - if (acpi == false && NativeToXbox_FactorForRdtsc) { - scaledTsc.QuadPart /= NativeToXbox_FactorForRdtsc; - return scaledTsc.QuadPart; - } else if (acpi == true && NativeToXbox_FactorForRdtsc) { - scaledTsc.QuadPart /= NativeToXbox_FactorForAcpi; - return scaledTsc.QuadPart; + LARGE_INTEGER lastTsc = std::exchange(LastRdtscQPC, tsc); + tsc.QuadPart -= lastTsc.QuadPart; + tsc.QuadPart *= SEC_TO_NSEC; + tsc.QuadPart += CurrentRdtscRemainder; + ULONGLONG quotient = tsc.QuadPart / NativeToXbox_FactorForRdtsc; + ULONGLONG remainder = tsc.QuadPart % NativeToXbox_FactorForRdtsc; + + CurrentRdtscRemainder = remainder; + return CurrentRdtsc += quotient; + } else if (acpi == true && NativeToXbox_FactorForAcpi != 0) { + std::lock_guard lock(AcpiLock); + + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); + + LARGE_INTEGER lastTsc = std::exchange(LastAcpiQPC, tsc); + tsc.QuadPart -= lastTsc.QuadPart; + tsc.QuadPart *= SEC_TO_NSEC; + tsc.QuadPart += CurrentAcpiRemainder; + ULONGLONG quotient = tsc.QuadPart / NativeToXbox_FactorForAcpi; + ULONGLONG remainder = tsc.QuadPart % NativeToXbox_FactorForAcpi; + + CurrentAcpiRemainder = remainder; + return CurrentAcpi += quotient; } - return (uint64_t)tsc.QuadPart; + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); + return static_cast(tsc.QuadPart); } void CxbxInitPerformanceCounters() { - uint64_t t; - t = 1000000000; - t *= HostClockFrequency; - t /= XBOX_TSC_FREQUENCY; - NativeToXbox_FactorForRdtsc = t; + NativeToXbox_FactorForRdtsc = Muldiv64(HostClockFrequency, SEC_TO_NSEC, XBOX_TSC_FREQUENCY); + NativeToXbox_FactorForAcpi = Muldiv64(HostClockFrequency, SEC_TO_NSEC, XBOX_ACPI_FREQUENCY); - t = 1000000000; - t *= HostClockFrequency; - t /= XBOX_ACPI_FREQUENCY; - NativeToXbox_FactorForAcpi = t; + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); + LastRdtscQPC = LastAcpiQPC = tsc; // Let's initialize the Dpc handling thread too, // here for now (should be called by our caller)