From 15d422d9883129c8ba69610fd9acdeede9ba7a5c Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 20 Nov 2019 22:08:41 +0100 Subject: [PATCH] [Base] Optional raw Clock source. New cvar clock_source_raw allows to use the cpu cycle counter itself as an alternative time source, if system timing resolution is to low and causes problems. --- src/xenia/base/clock.cc | 23 +++++++- src/xenia/base/clock.h | 14 ++++- src/xenia/base/clock_posix.cc | 8 +-- src/xenia/base/clock_win.cc | 6 +- src/xenia/base/clock_x64.cc | 105 ++++++++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 src/xenia/base/clock_x64.cc diff --git a/src/xenia/base/clock.cc b/src/xenia/base/clock.cc index c0f5eff9a..af18967f2 100644 --- a/src/xenia/base/clock.cc +++ b/src/xenia/base/clock.cc @@ -20,13 +20,17 @@ DEFINE_bool(clock_no_scaling, false, "Disable scaling code. Time management and locking is bypassed. " "Guest system time is directly pulled from host.", "CPU"); +DEFINE_bool(clock_source_raw, false, + "Use the RDTSC instruction as the time source. " + "Host CPU must support invariant TSC. ", + "CPU"); namespace xe { // Time scalar applied to all time operations. double guest_time_scalar_ = 1.0; // Tick frequency of guest. -uint64_t guest_tick_frequency_ = Clock::host_tick_frequency(); +uint64_t guest_tick_frequency_ = Clock::host_tick_frequency_platform(); // Base FILETIME of the guest system from app start. uint64_t guest_system_time_base_ = Clock::QueryHostSystemTime(); // Combined time and frequency ratio between host and guest. @@ -44,7 +48,7 @@ std::mutex tick_mutex_; void RecomputeGuestTickScalar() { // Create a rational number with numerator (first) and denominator (second) auto frac = - std::make_pair(guest_tick_frequency_, Clock::host_tick_frequency()); + std::make_pair(guest_tick_frequency_, Clock::QueryHostTickFrequency()); // Doing it this way ensures we don't mess up our frequency scaling and // precisely controls the precision the guest_time_scalar_ can have. if (guest_time_scalar_ > 1.0) { @@ -104,6 +108,21 @@ inline uint64_t QueryGuestSystemTimeOffset() { return guest_tick_count * numerator / denominator; } +uint64_t Clock::QueryHostTickFrequency() { + if (cvars::clock_source_raw) { + return host_tick_frequency_raw(); + } else { + return host_tick_frequency_platform(); + } +} +uint64_t Clock::QueryHostTickCount() { + if (cvars::clock_source_raw) { + return host_tick_count_raw(); + } else { + return host_tick_count_platform(); + } +} + double Clock::guest_time_scalar() { return guest_time_scalar_; } void Clock::set_guest_time_scalar(double scalar) { diff --git a/src/xenia/base/clock.h b/src/xenia/base/clock.h index aec1cfcc6..00f1ff449 100644 --- a/src/xenia/base/clock.h +++ b/src/xenia/base/clock.h @@ -15,13 +15,22 @@ #include "xenia/base/cvar.h" DECLARE_bool(clock_no_scaling); +DECLARE_bool(clock_source_raw); namespace xe { class Clock { public: - // Host ticks-per-second. - static uint64_t host_tick_frequency(); + // Host ticks-per-second. Generally QueryHostTickFrequency should be used. + // Either from platform suplied time source or from hardware directly. + static uint64_t host_tick_frequency_platform(); + static uint64_t host_tick_frequency_raw(); + // Host tick count. Generally QueryHostTickCount() should be used. + static uint64_t host_tick_count_platform(); + static uint64_t host_tick_count_raw(); + + // Queries the host tick frequency. + static uint64_t QueryHostTickFrequency(); // Queries the current host tick count. static uint64_t QueryHostTickCount(); // Host time, in FILETIME format. @@ -43,6 +52,7 @@ class Clock { // Sets the guest time base, used for computing the system time. // By default this is the current system time. static void set_guest_system_time_base(uint64_t time_base); + // Queries the current guest tick count, accounting for frequency adjustment // and scaling. static uint64_t QueryGuestTickCount(); diff --git a/src/xenia/base/clock_posix.cc b/src/xenia/base/clock_posix.cc index e2f1cf627..f18082b0e 100644 --- a/src/xenia/base/clock_posix.cc +++ b/src/xenia/base/clock_posix.cc @@ -2,7 +2,7 @@ ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** - * Copyright 2017 Ben Vanik. All rights reserved. * + * Copyright 2019 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ @@ -14,14 +14,14 @@ namespace xe { -uint64_t Clock::host_tick_frequency() { +uint64_t Clock::host_tick_frequency_platform() { timespec res; clock_getres(CLOCK_MONOTONIC_RAW, &res); return uint64_t(res.tv_sec) + uint64_t(res.tv_nsec) * 1000000000ull; } -uint64_t Clock::QueryHostTickCount() { +uint64_t Clock::host_tick_count_platform() { timespec res; clock_gettime(CLOCK_MONOTONIC_RAW, &res); @@ -40,7 +40,7 @@ uint64_t Clock::QueryHostSystemTime() { } uint64_t Clock::QueryHostUptimeMillis() { - return QueryHostTickCount() / (host_tick_frequency() / 1000); + return host_tick_count_platform() * 1000 / host_tick_frequency_platform(); } } // namespace xe \ No newline at end of file diff --git a/src/xenia/base/clock_win.cc b/src/xenia/base/clock_win.cc index 7d33d0c93..4a0c8aeb5 100644 --- a/src/xenia/base/clock_win.cc +++ b/src/xenia/base/clock_win.cc @@ -13,13 +13,13 @@ namespace xe { -uint64_t Clock::host_tick_frequency() { +uint64_t Clock::host_tick_frequency_platform() { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); return frequency.QuadPart; } -uint64_t Clock::QueryHostTickCount() { +uint64_t Clock::host_tick_count_platform() { LARGE_INTEGER counter; uint64_t time = 0; if (QueryPerformanceCounter(&counter)) { @@ -35,7 +35,7 @@ uint64_t Clock::QueryHostSystemTime() { } uint64_t Clock::QueryHostUptimeMillis() { - return QueryHostTickCount() / (host_tick_frequency() / 1000); + return host_tick_count_platform() * 1000 / host_tick_frequency_platform(); } } // namespace xe diff --git a/src/xenia/base/clock_x64.cc b/src/xenia/base/clock_x64.cc new file mode 100644 index 000000000..72a250b9d --- /dev/null +++ b/src/xenia/base/clock_x64.cc @@ -0,0 +1,105 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2019 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/base/platform.h" + +#if XE_ARCH_AMD64 + +#include "xenia/base/clock.h" +#include "xenia/base/logging.h" + +// Wrap all these different cpu compiler intrinsics. +// So no inline assembler here and the compiler will remove the clutter. +#if XE_COMPILER_MSVC +#define xe_cpu_cpuid(level, eax, ebx, ecx, edx) \ + { \ + int __xe_cpuid_registers_[4]; \ + __cpuid(__xe_cpuid_registers_, (level)); \ + (eax) = static_cast(__xe_cpuid_registers_[0]); \ + (ebx) = static_cast(__xe_cpuid_registers_[1]); \ + (ecx) = static_cast(__xe_cpuid_registers_[2]); \ + (edx) = static_cast(__xe_cpuid_registers_[3]); \ + } +#define xe_cpu_rdtsc() __rdtsc() +#elif XE_COMPILER_CLANG || XE_COMPILER_GNUC +#include +#define xe_cpu_cpuid(level, eax, ebx, ecx, edx) \ + __cpuid((level), (eax), (ebx), (ecx), (edx)); +#define xe_cpu_rdtsc() __rdtsc() +#else +#error "No cpu instruction wrappers for current compiler implemented." +#endif + +#define CLOCK_FATAL(msg) \ + xe::FatalError( \ + "The raw clock source is not supported on your CPU. \n" \ + "%s \n" \ + "Set the cvar 'clock_source_raw' to 'false'.", \ + (msg)); + +namespace xe { +// Getting the TSC frequency can be a bit tricky. This method here only works on +// Intel as it seems. There is no easy way to get the frequency outside of ring0 +// on AMD, so we fail gracefully if not possible. +uint64_t Clock::host_tick_frequency_raw() { + uint32_t eax, ebx, ecx, edx; + + // 00H Get max supported cpuid level. + xe_cpu_cpuid(0x0, eax, ebx, ecx, edx); + auto max_cpuid = eax; + // 80000000H Get max extended cpuid level + xe_cpu_cpuid(0x80000000, eax, ebx, ecx, edx); + auto max_cpuid_ex = eax; + + // 80000007H Get extended power feature info + if (max_cpuid_ex >= 0x80000007) { + xe_cpu_cpuid(0x80000007, eax, ebx, ecx, edx); + // Invariant TSC bit at position 8 + auto tsc_invariant = edx & (1 << 8); + // If the TSC is not invariant it will change its frequency with power + // states and across cores. + if (!tsc_invariant) { + CLOCK_FATAL("The CPU has no invariant TSC."); + return 0; + } + } else { + CLOCK_FATAL("Unclear if the CPU has an invariant TSC.") + return 0; + } + + if (max_cpuid >= 0x15) { + // 15H Get TSC/Crystal ratio and Crystal Hz. + xe_cpu_cpuid(0x15, eax, ebx, ecx, edx); + uint64_t ratio_num = ebx; + uint64_t ratio_den = eax; + uint64_t cryst_freq = ecx; + // For some CPUs, Crystal frequency is not reported. + if (ratio_num && ratio_den && cryst_freq) { + // If it is, calculate the TSC frequency + auto tsc_freq = cryst_freq * ratio_num / ratio_den; + } + } + + if (max_cpuid >= 0x16) { + // 16H Get CPU base frequency MHz in EAX. + xe_cpu_cpuid(0x16, eax, ebx, ecx, edx); + uint64_t cpu_base_freq = static_cast(eax) * 1000000; + assert(cpu_base_freq); + return cpu_base_freq; + } + + CLOCK_FATAL("The clock frequency could not be determined."); + return 0; +} + +uint64_t Clock::host_tick_count_raw() { return xe_cpu_rdtsc(); } + +} // namespace xe + +#endif