Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz
@ 2026-10-24 17:45 Sasha Levin
  2026-10-27  6:40 ` Nikunj A Dadhania
  0 siblings, 1 reply; 2+ messages 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 related	[flat|nested] 2+ messages in thread

* Re: [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz
  2026-10-24 17:45 [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz Sasha Levin
@ 2026-10-27  6:40 ` Nikunj A Dadhania
  0 siblings, 0 replies; 2+ messages in thread
From: Nikunj A Dadhania @ 2026-10-27  6:40 UTC (permalink / raw)
  To: Sasha Levin, Sean Christopherson, Paolo Bonzini
  Cc: Vitaly Kuznetsov, Jim Mattson, kvm, linux-kernel

On 10/24/2026 11:15 PM, Sasha Levin wrote:
> 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>
> ---

Reviewed-by: Nikunj A Dadhania <nikunj@amd.com>
Tested-by: Nikunj A Dadhania <nikunj@amd.com>

Tested on an AMD EPYC 9996 (Venice) with KVM built in (CONFIG_KVM_AMD=y),
booting with tsc_early_khz set to the refined tsc_khz minus 500.

The Hyper-V notifier path is only reachable when KVM runs as a Hyper-V
guest; I haven't tested that path.

> 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;


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-10-27  6:41 UTC | newest]

Thread overview: 2+ messages (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
2026-10-27  6:40 ` Nikunj A Dadhania

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox