Re: [PATCH] KVM: x86: Compute the max guest TSC frequency from the current tsc_khz

From: Nikunj A Dadhania

Date: Tue Oct 27 2026 - 02:41:21 EST


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@xxxxxxxxxx>
> ---

Reviewed-by: Nikunj A Dadhania <nikunj@xxxxxxx>
Tested-by: Nikunj A Dadhania <nikunj@xxxxxxx>

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;