/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2020 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/base/clock.h" #include "xenia/base/platform.h" #if XE_ARCH_AMD64 && XE_CLOCK_RAW_AVAILABLE #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" msg \ "\n" \ "Set the cvar 'clock_source_raw' to 'false'."); 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