* [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz
@ 2026-10-24 17:45 Sasha Levin
0 siblings, 0 replies; only message in thread
From: Sasha Levin @ 2026-10-24 17:45 UTC (permalink / raw)
To: Sean Christopherson, Paolo Bonzini
Cc: Vitaly Kuznetsov, Jim Mattson, kvm, linux-kernel
KVM computes the highest frequency that KVM_SET_TSC_KHZ accepts once,
during vendor init, from the tsc_khz of the time. When KVM is built in,
that runs before tsc_refine_calibration_work() refines tsc_khz, about a
second later. If refinement raises tsc_khz, the cap stays below what the
maximum scaling ratio gives for the refined frequency, and
KVM_SET_TSC_KHZ rejects frequencies that set_tsc_khz() would accept, as
set_tsc_khz() computes the ratio from the current tsc_khz.
nested_tsc_scaling_test runs into this on AMD: its last case asks for
the frequency KVM_GET_TSC_KHZ reports times SVM's maximum ratio, and gets
-EINVAL on boots where refinement raised tsc_khz. KernelCI sees it
intermittently on a DL345 Gen11 (EPYC). On Intel the cap is limited to
INT_MAX, so the test does not hit it there.
Like commit 741e511b4208 ("KVM: x86: Don't snapshot "max" TSC if host TSC
is constant") did for the default frequency of new VMs, compute the cap
from tsc_khz when it is used instead of taking a snapshot at load time.
This covers KVM_SET_TSC_KHZ on both the vCPU and the VM file descriptor.
This also drops setting the cap to tsc_khz in the Hyper-V reenlightenment
notifier, which left no room for scaling up once the host TSC frequency
had changed; commit 0092e4346f49 ("x86/kvm: Support Hyper-V
reenlightenment") gives no reason for that limit. The cap now follows the
new frequency the same way. The notifier still does not recompute the
multipliers of vCPUs that already exist.
Fixes: 92a1f12d2598 ("KVM: X86: Implement userspace interface to set virtual_tsc_khz")
Link: https://d.kernelci.org/test/maestro:6adc828a55fda507607f79c3
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Reproduced on an AMD DL345 Gen11 booted with tsc_early_khz= 500 kHz below
the refined frequency, so that refinement always raises tsc_khz after KVM
is initialized. With KernelCI's x86 build of mainline 5b0764a53235,
nested_tsc_scaling_test failed 3 out of 3 times with KVM_SET_TSC_KHZ
returning -EINVAL. With the same config plus this patch it passed 3 out
of 3, and tsc_scaling_sync, tsc_msrs_test, kvm_clock_test and
nested_tsc_adjust_test still pass. A nested SVM guest with the same
tsc_early_khz trick gives the same result, and with tsc_early_khz above
the real frequency (refinement lowers tsc_khz) mainline passes.
This does not change the existing window between the cap check and
set_tsc_khz(), which read tsc_khz separately.
arch/x86/include/asm/kvm_host.h | 2 --
arch/x86/kvm/x86.c | 30 +++++++++++++++---------------
2 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 1ee4b127004df..35a74f0eb45e5 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -260,8 +260,6 @@ struct kvm_x86_pmu_event_filter;
struct kvm_caps {
/* control of guest tsc rate supported? */
bool has_tsc_control;
- /* maximum supported tsc_khz for guests */
- u32 max_guest_tsc_khz;
/* number of bits of the fractional part of the TSC scaling ratio */
u8 tsc_scaling_ratio_frac_bits;
/* maximum allowed value of TSC scaling ratio */
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 32563a91fba99..9e22072f39c18 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -1156,6 +1156,19 @@ static inline u64 __scale_tsc(u64 ratio, u64 tsc)
return mul_u64_u64_shr(tsc, ratio, kvm_caps.tsc_scaling_ratio_frac_bits);
}
+/*
+ * Use the current tsc_khz, not the one at load time, as the TSC frequency can
+ * be refined after KVM is initialized. Make sure the user can only configure
+ * tsc_khz values that fit into a signed integer, otherwise KVM_GET_TSC_KHZ
+ * would return a negative value and confuse userspace. A min value is not
+ * calculated because it will always be 1 on all machines.
+ */
+static u32 kvm_max_guest_tsc_khz(void)
+{
+ return min(0x7fffffffULL,
+ __scale_tsc(kvm_caps.max_tsc_scaling_ratio, tsc_khz));
+}
+
u64 kvm_scale_tsc(u64 tsc, u64 ratio)
{
u64 _tsc = tsc;
@@ -3728,7 +3741,7 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
user_tsc_khz = (u32)arg;
if (kvm_caps.has_tsc_control &&
- user_tsc_khz > kvm_caps.max_guest_tsc_khz)
+ user_tsc_khz > kvm_max_guest_tsc_khz())
goto out;
if (user_tsc_khz == 0)
@@ -4695,7 +4708,7 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
user_tsc_khz = (u32)arg;
if (kvm_caps.has_tsc_control &&
- user_tsc_khz > kvm_caps.max_guest_tsc_khz)
+ user_tsc_khz > kvm_max_guest_tsc_khz())
goto out;
if (user_tsc_khz == 0)
@@ -6749,7 +6762,6 @@ static void kvm_hyperv_tsc_notifier(void)
for_each_present_cpu(cpu)
per_cpu(cpu_tsc_khz, cpu) = tsc_khz;
}
- kvm_caps.max_guest_tsc_khz = tsc_khz;
list_for_each_entry(kvm, &vm_list, vm_list) {
__kvm_start_pvclock_update(kvm);
@@ -7188,18 +7200,6 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
if (!tdp_enabled)
kvm_caps.supported_quirks &= ~KVM_X86_QUIRK_IGNORE_GUEST_PAT;
- if (kvm_caps.has_tsc_control) {
- /*
- * Make sure the user can only configure tsc_khz values that
- * fit into a signed integer, otherwise KVM_GET_TSC_KHZ would
- * return a negative value and confuse userspace.
- * A min value is not calculated because it will always
- * be 1 on all machines.
- */
- u64 max = min(0x7fffffffULL,
- __scale_tsc(kvm_caps.max_tsc_scaling_ratio, tsc_khz));
- kvm_caps.max_guest_tsc_khz = max;
- }
kvm_caps.default_tsc_scaling_ratio = 1ULL << kvm_caps.tsc_scaling_ratio_frac_bits;
kvm_init_msr_lists();
return 0;
--
2.53.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-24 17:45 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 17:45 [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®