105 lines
3.7 KiB
C++
105 lines
3.7 KiB
C++
/**
|
|
******************************************************************************
|
|
* 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<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" 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.
|
|
XE_NOINLINE
|
|
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
|
|
return 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;
|
|
}
|
|
XE_NOINLINE
|
|
uint64_t Clock::host_tick_count_raw() { return xe_cpu_rdtsc(); }
|
|
|
|
} // namespace xe
|
|
|
|
#endif
|