summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Linn <jl@conductive.de>2019-11-20 22:08:41 +0100
committerRick Gibbed <rick@gibbed.us>2019-12-01 17:11:58 -0600
commit15d422d9883129c8ba69610fd9acdeede9ba7a5c (patch)
tree2c56e78232ab7dd1c1fe2c380bac09fc81b26aad
parentf88d46ceadf4a18b5857b99d3c944ff480b09e40 (diff)
[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.
-rw-r--r--src/xenia/base/clock.cc23
-rw-r--r--src/xenia/base/clock.h14
-rw-r--r--src/xenia/base/clock_posix.cc8
-rw-r--r--src/xenia/base/clock_win.cc6
-rw-r--r--src/xenia/base/clock_x64.cc105
5 files changed, 145 insertions, 11 deletions
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<uint32_t>(__xe_cpuid_registers_[0]); \
+ (ebx) = static_cast<uint32_t>(__xe_cpuid_registers_[1]); \
+ (ecx) = static_cast<uint32_t>(__xe_cpuid_registers_[2]); \
+ (edx) = static_cast<uint32_t>(__xe_cpuid_registers_[3]); \
+ }
+#define xe_cpu_rdtsc() __rdtsc()
+#elif XE_COMPILER_CLANG || XE_COMPILER_GNUC
+#include <cpuid.h>
+#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<uint64_t>(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