diff --git a/xen/arch/x86/cpu/common.c b/xen/arch/x86/cpu/common.c index 4be86e1f7b..d045ed6b41 100644 --- a/xen/arch/x86/cpu/common.c +++ b/xen/arch/x86/cpu/common.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -444,6 +445,39 @@ void __init early_cpu_init(bool verbose) if (!(c->vendor & (X86_VENDOR_AMD | X86_VENDOR_HYGON))) park_offline_cpus = opt_mce; + + /* + * If nominal freq isn't available, use highest, thus causing NOW() + * output to move more slowly. See preset_tsc_scale(). + */ + if (c->cpuid_level >= 0x15) { + cpuid(0x15, &eax, &ebx, &ecx, &edx); + + if (ecx && ebx && eax) + preset_tsc_scale(DIV_ROUND_UP(ecx * 1UL * ebx, eax)); + else if (c->cpuid_level >= 0x16) { + /* Assume CPU base freq ≈ TSC freq. */ + cpuid(0x16, &eax, &ebx, &ecx, &edx); + if (eax) + preset_tsc_scale(eax * 1000000UL); + else if (ebx) + preset_tsc_scale(ebx * 1000000UL); + } + } else if (c->vendor & (X86_VENDOR_AMD | X86_VENDOR_HYGON)) { + unsigned int nom_mhz = 0, hi_mhz = 0; + + amd_process_freq(c, NULL, &nom_mhz, &hi_mhz); + if (nom_mhz) + preset_tsc_scale(nom_mhz * 1000000UL); + else if (hi_mhz) + preset_tsc_scale(hi_mhz * 1000000UL); + } else if (c->vendor & X86_VENDOR_INTEL) { + unsigned int hi_mhz = 0; + + intel_process_freq(c, NULL, &hi_mhz); + if (hi_mhz) + preset_tsc_scale(hi_mhz * 1000000UL); + } } void reset_cpuinfo(struct cpuinfo_x86 *c, bool keep_basic) diff --git a/xen/arch/x86/cpu/intel.c b/xen/arch/x86/cpu/intel.c index 5c07140695..1006126bc3 100644 --- a/xen/arch/x86/cpu/intel.c +++ b/xen/arch/x86/cpu/intel.c @@ -476,8 +476,8 @@ static int num_cpu_cores(struct cpuinfo_x86 *c) return 1; } -static void intel_process_freq(const struct cpuinfo_x86 *c, - unsigned int *min_mhz, unsigned int *max_mhz) +void intel_process_freq(const struct cpuinfo_x86 *c, + unsigned int *min_mhz, unsigned int *max_mhz) { uint64_t msrval; uint8_t max_ratio, min_ratio; diff --git a/xen/arch/x86/include/asm/processor.h b/xen/arch/x86/include/asm/processor.h index 4b200428be..4b17e13a97 100644 --- a/xen/arch/x86/include/asm/processor.h +++ b/xen/arch/x86/include/asm/processor.h @@ -417,6 +417,9 @@ static inline uint8_t get_cpu_family(uint32_t raw, uint8_t *model, return fam; } +void intel_process_freq(const struct cpuinfo_x86 *c, + unsigned int *min_mhz, unsigned int *max_mhz); + #ifdef CONFIG_INTEL extern int8_t opt_tsx; extern bool rtm_disabled; diff --git a/xen/arch/x86/include/asm/time.h b/xen/arch/x86/include/asm/time.h index ed763ebc1b..6c8f0fe58c 100644 --- a/xen/arch/x86/include/asm/time.h +++ b/xen/arch/x86/include/asm/time.h @@ -23,6 +23,7 @@ mktime (unsigned int year, unsigned int mon, int time_suspend(void); int time_resume(void); +void preset_tsc_scale(unsigned long freq); void init_percpu_time(void); void time_latch_stamps(void); diff --git a/xen/arch/x86/time.c b/xen/arch/x86/time.c index f35555f185..f37fddc61e 100644 --- a/xen/arch/x86/time.c +++ b/xen/arch/x86/time.c @@ -1713,6 +1713,9 @@ s_time_t get_s_time_fixed(uint64_t at_tsc) const struct cpu_time *t = &this_cpu(cpu_time); uint64_t tsc, delta; + /* scale_delta() degenerates when the scale wasn't set yet. */ + ASSERT(t->tsc_scale.mul_frac); + if ( at_tsc ) tsc = at_tsc; else @@ -1728,6 +1731,20 @@ s_time_t get_s_time_fixed(uint64_t at_tsc) s_time_t get_s_time(void) { + /* + * Before the TSC scale is set, avoid returning constant 0 (or whatever + * this_cpu(cpu_time).stamp.local_stime is set to). While the returned + * value is in no way representing time, it at least increases + * monotonically, thus avoiding e.g. waiting loops to degenerate to + * entirely infinite ones. + */ + if ( unlikely(!this_cpu(cpu_time).tsc_scale.mul_frac) ) + { + static s_time_t counter; + + return arch_fetch_and_add(&counter, 1); + } + return get_s_time_fixed(0); } @@ -2681,6 +2698,22 @@ int __init init_xen_time(void) return 0; } +/* BSP-only function to pre-set an approximate TSC scale. */ +void __init preset_tsc_scale(unsigned long freq) +{ + struct cpu_time *t = &this_cpu(cpu_time); + + /* + * The incoming frequency is only approximate (nominal). Increase it by + * 1% to make NOW() output rather a little too slow than too fast, thus + * avoiding a possible backwards jump once the final scale is set. + */ + freq += DIV_ROUND_UP(freq, 100); + + set_time_scale(&t->tsc_scale, freq); + t->stamp.local_tsc = boot_tsc_stamp; + NOW_good = true; +} /* Early init function. */ void __init early_time_init(void) @@ -2698,6 +2731,9 @@ void __init early_time_init(void) "TSC ADJUST set to %lx on boot CPU - clearing\n", tmp); wrmsrl(MSR_IA32_TSC_ADJUST, 0); boot_tsc_stamp -= tmp; + + if ( t->stamp.local_tsc ) + t->stamp.local_tsc -= tmp; } }