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 <[email protected]>
This commit is contained in:
Augustin Cavalier
2023-10-25 00:53:55 +00:00
committed by waddlesplash
parent b8fbb723ea
commit bb2808d615
6 changed files with 55 additions and 23 deletions
@@ -17,7 +17,7 @@ extern "C" {
#endif #endif
void calculate_cpu_conversion_factor(uint8 channel); void determine_cpu_conversion_factor(uint8 channel);
status_t boot_arch_cpu_init(void); status_t boot_arch_cpu_init(void);
void arch_ucode_load(BootVolume& volume); void arch_ucode_load(BootVolume& volume);
+47 -17
View File
@@ -23,19 +23,6 @@
#include <string.h> #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; uint32 gTimeConversionFactor;
@@ -184,6 +171,22 @@ private:
#endif #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 static inline void
calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta, calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
double& conversionFactor, uint16& expired) double& conversionFactor, uint16& expired)
@@ -214,7 +217,7 @@ calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
} while (startHigh != 255); } while (startHigh != 255);
// Read in the first TSC value // Read in the first TSC value
uint64 startTSC = __rdtsc(); uint64 startTSC = rdtsc_fenced();
// Wait for the PIT to count down to our desired value // Wait for the PIT to count down to our desired value
uint8 endLow; uint8 endLow;
@@ -226,7 +229,7 @@ calibration_loop(uint8 desiredHighByte, uint8 channel, uint64& tscDelta,
} while (endHigh > desiredHighByte); } while (endHigh > desiredHighByte);
// And read the second TSC value // And read the second TSC value
uint64 endTSC = __rdtsc(); uint64 endTSC = rdtsc_fenced();
tscDelta = endTSC - startTSC; tscDelta = endTSC - startTSC;
expired = ((startHigh << 8) | startLow) - ((endHigh << 8) | endLow); 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) calculate_cpu_conversion_factor(uint8 channel)
{ {
// When using channel 2, enable the input and disable the speaker. // 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 void
ucode_load(BootVolume& volume) ucode_load(BootVolume& volume)
{ {
@@ -386,7 +416,7 @@ ucode_load(BootVolume& volume)
extern "C" bigtime_t extern "C" bigtime_t
system_time() system_time()
{ {
uint64 tsc = __rdtsc(); uint64 tsc = rdtsc_fenced();
uint64 lo = (uint32)tsc; uint64 lo = (uint32)tsc;
uint64 hi = tsc >> 32; uint64 hi = tsc >> 32;
return ((lo * gTimeConversionFactor) >> 32) + hi * gTimeConversionFactor; return ((lo * gTimeConversionFactor) >> 32) + hi * gTimeConversionFactor;
+1 -1
View File
@@ -65,7 +65,7 @@ cpu_init()
if (check_cpu_features() != B_OK) if (check_cpu_features() != B_OK)
panic("You need a Pentium or higher in order to boot!\n"); 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; gKernelArgs.num_cpus = 1;
// this will eventually be corrected later on // this will eventually be corrected later on
@@ -21,7 +21,7 @@ arch_timer_init(void)
{ {
// use PIT channel 2 for the calibration loop // use PIT channel 2 for the calibration loop
// as channel 0 is reserved for the UEFI firmware // as channel 0 is reserved for the UEFI firmware
calculate_cpu_conversion_factor(2); determine_cpu_conversion_factor(2);
hpet_init(); hpet_init();
} }
@@ -19,6 +19,6 @@
void void
arch_timer_init(void) arch_timer_init(void)
{ {
calculate_cpu_conversion_factor(2); determine_cpu_conversion_factor(2);
hpet_init(); hpet_init();
} }
+4 -2
View File
@@ -1648,8 +1648,10 @@ init_tsc(kernel_args* args)
// try to find the TSC frequency with CPUID // try to find the TSC frequency with CPUID
uint32 conversionFactor = args->arch_args.system_time_cv_factor; uint32 conversionFactor = args->arch_args.system_time_cv_factor;
init_tsc_with_cpuid(args, &conversionFactor); if (!x86_check_feature(IA32_FEATURE_EXT_HYPERVISOR, FEATURE_EXT)) {
init_tsc_with_msr(args, &conversionFactor); init_tsc_with_cpuid(args, &conversionFactor);
init_tsc_with_msr(args, &conversionFactor);
}
uint64 conversionFactorNsecs = (uint64)conversionFactor * 1000; uint64 conversionFactorNsecs = (uint64)conversionFactor * 1000;