bootloader: Implement TSC calibration via hypervisor CPUID leaf.
While debugging some problems on the HaikuPorts build VMs, mmlr noticed their clocks had an alarming amount of drift. This prompted an investigation into TSC calibration mechanisms, and the discovery that there is a VM-specific one which we did not implement. This mechanism is more accurate than counting cycles on VMs where cycles can be "stolen" (the probable cause of the aforementioned clock drift.) Tested in VMware (works out of the box) and on QEMU/KVM (may need TSC frequency specified or a host with invariant TSC.) Change-Id: I4ccfdb2e4e2621404ec9026e7106c02bf96faf18 Reviewed-on: https://review.haiku-os.org/c/haiku/+/7063 Reviewed-by: waddlesplash <waddlesplash@gmail.com>
This commit is contained in:
parent
b8fbb723ea
commit
bb2808d615
@ -17,7 +17,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
void calculate_cpu_conversion_factor(uint8 channel);
|
||||
void determine_cpu_conversion_factor(uint8 channel);
|
||||
status_t boot_arch_cpu_init(void);
|
||||
void arch_ucode_load(BootVolume& volume);
|
||||
|
||||
|
@ -23,19 +23,6 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if __GNUC__ > 2
|
||||
#include <x86intrin.h>
|
||||
#else
|
||||
static inline uint64_t __rdtsc()
|
||||
{
|
||||
uint64 tsc;
|
||||
|
||||
asm volatile ("rdtsc\n" : "=A"(tsc));
|
||||
|
||||
return tsc;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
uint32 gTimeConversionFactor;
|
||||
|
||||
@ -184,6 +171,22 @@ private:
|
||||
#endif
|
||||
|
||||
|
||||
static inline uint64_t
|
||||
rdtsc_fenced()
|
||||
{
|
||||
uint64 tsc;
|
||||
|
||||
// RDTSC is not serializing, nor does it drain the instruction stream.
|
||||
// RDTSCP does, but is not available everywhere. Other OSes seem to use
|
||||
// "CPUID" rather than MFENCE/LFENCE for serializing here during boot.
|
||||
asm volatile ("cpuid" : : : "eax", "ebx", "ecx", "edx");
|
||||
|
||||
asm volatile ("rdtsc" : "=A"(tsc));
|
||||
|
||||
return tsc;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
|
||||
double& conversionFactor, uint16& expired)
|
||||
@ -214,7 +217,7 @@ calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
|
||||
} while (startHigh != 255);
|
||||
|
||||
// Read in the first TSC value
|
||||
uint64 startTSC = __rdtsc();
|
||||
uint64 startTSC = rdtsc_fenced();
|
||||
|
||||
// Wait for the PIT to count down to our desired value
|
||||
uint8 endLow;
|
||||
@ -226,7 +229,7 @@ calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
|
||||
} while (endHigh > desiredHighByte);
|
||||
|
||||
// And read the second TSC value
|
||||
uint64 endTSC = __rdtsc();
|
||||
uint64 endTSC = rdtsc_fenced();
|
||||
|
||||
tscDelta = endTSC - startTSC;
|
||||
expired = ((startHigh << 8) | startLow) - ((endHigh << 8) | endLow);
|
||||
@ -234,7 +237,7 @@ calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
static void
|
||||
calculate_cpu_conversion_factor(uint8 channel)
|
||||
{
|
||||
// When using channel 2, enable the input and disable the speaker.
|
||||
@ -317,6 +320,33 @@ slower_sample:
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
determine_cpu_conversion_factor(uint8 channel)
|
||||
{
|
||||
// Before using the calibration loop, check if we are on a hypervisor.
|
||||
cpuid_info info;
|
||||
if (get_current_cpuid(&info, 1, 0) == B_OK
|
||||
&& (info.regs.ecx & IA32_FEATURE_EXT_HYPERVISOR) != 0) {
|
||||
get_current_cpuid(&info, 0x40000000, 0);
|
||||
const uint32 maxVMM = info.regs.eax;
|
||||
if (maxVMM >= 0x40000010) {
|
||||
get_current_cpuid(&info, 0x40000010, 0);
|
||||
|
||||
uint64 clockSpeed = uint64(info.regs.eax) * 1000;
|
||||
gTimeConversionFactor = (uint64(1000) << 32) / info.regs.eax;
|
||||
|
||||
gKernelArgs.arch_args.system_time_cv_factor = gTimeConversionFactor;
|
||||
gKernelArgs.arch_args.cpu_clock_speed = clockSpeed;
|
||||
|
||||
dprintf("TSC frequency read from hypervisor CPUID leaf\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
calculate_cpu_conversion_factor(channel);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ucode_load(BootVolume& volume)
|
||||
{
|
||||
@ -386,7 +416,7 @@ ucode_load(BootVolume& volume)
|
||||
extern "C" bigtime_t
|
||||
system_time()
|
||||
{
|
||||
uint64 tsc = __rdtsc();
|
||||
uint64 tsc = rdtsc_fenced();
|
||||
uint64 lo = (uint32)tsc;
|
||||
uint64 hi = tsc >> 32;
|
||||
return ((lo * gTimeConversionFactor) >> 32) + hi * gTimeConversionFactor;
|
||||
|
@ -65,7 +65,7 @@ cpu_init()
|
||||
if (check_cpu_features() != B_OK)
|
||||
panic("You need a Pentium or higher in order to boot!\n");
|
||||
|
||||
calculate_cpu_conversion_factor(0);
|
||||
determine_cpu_conversion_factor(0);
|
||||
|
||||
gKernelArgs.num_cpus = 1;
|
||||
// this will eventually be corrected later on
|
||||
|
@ -21,7 +21,7 @@ arch_timer_init(void)
|
||||
{
|
||||
// use PIT channel 2 for the calibration loop
|
||||
// as channel 0 is reserved for the UEFI firmware
|
||||
calculate_cpu_conversion_factor(2);
|
||||
determine_cpu_conversion_factor(2);
|
||||
|
||||
hpet_init();
|
||||
}
|
||||
|
@ -19,6 +19,6 @@
|
||||
void
|
||||
arch_timer_init(void)
|
||||
{
|
||||
calculate_cpu_conversion_factor(2);
|
||||
determine_cpu_conversion_factor(2);
|
||||
hpet_init();
|
||||
}
|
||||
|
@ -1648,8 +1648,10 @@ init_tsc(kernel_args* args)
|
||||
|
||||
// try to find the TSC frequency with CPUID
|
||||
uint32 conversionFactor = args->arch_args.system_time_cv_factor;
|
||||
init_tsc_with_cpuid(args, &conversionFactor);
|
||||
init_tsc_with_msr(args, &conversionFactor);
|
||||
if (!x86_check_feature(IA32_FEATURE_EXT_HYPERVISOR, FEATURE_EXT)) {
|
||||
init_tsc_with_cpuid(args, &conversionFactor);
|
||||
init_tsc_with_msr(args, &conversionFactor);
|
||||
}
|
||||
uint64 conversionFactorNsecs = (uint64)conversionFactor * 1000;
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user