Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Sean Christopherson <seanjc@google.com>
To: Jim Mattson <jmattson@google.com>
Cc: pbonzini@redhat.com, chao.gao@intel.com, kvm@vger.kernel.org
Subject: Re: [PATCH v5 2/2] KVM: VMX: Cap VMX preemption timer to work around Intel erratum
Date: Tue, 28 Jul 2026 08:03:31 -0700	[thread overview]
Message-ID: <amjEw9QcQN7owHKF@google.com> (raw)
In-Reply-To: <20260724234914.987987-3-jmattson@google.com>

On Fri, Jul 24, 2026, Jim Mattson wrote:

Sorry for the late review, I didn't actually look at the code in the previous
versions.

> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index a07faa066ef0..ebe83a641473 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -153,6 +153,7 @@ module_param(dump_invalid_vmcs, bool, 0644);
>  #ifdef CONFIG_X86_64
>  static int __read_mostly cpu_preemption_timer_multi;
>  static bool __read_mostly enable_preemption_timer = 1;
> +static u64 __ro_after_init preemption_timer_limit;
>  module_param_named(preemption_timer, enable_preemption_timer, bool, S_IRUGO);
>  #else
>  #define enable_preemption_timer false
> @@ -8306,6 +8307,26 @@ static inline int u64_shl_div_u64(u64 a, unsigned int shift,
>  	return 0;
>  }
>  
> +/*
> + * Workaround for a widespread Intel erratum (e.g. EMR158) where the
> + * VMX-preemption timer may expire earlier than expected when programmed
> + * with large values. The workaround is to cap the timer value to strictly
> + * less than 2^25 * CPUID.15H:EBX / CPUID.15H:EAX.
> + */
> +static __init u64 calc_preemption_timer_limit(void)
> +{
> +	u32 eax, ebx, ecx, edx;
> +
> +	if (cpuid_eax(0) < 0x15)
> +		return 1ULL << 32;

Tracking an exclusive limit is cumbersome because all of KVM's usage deals with
the max value, i.e. it forces all usage to effectively do "- 1".  I also think
we should go with "max_value" instead of "limit" so that there's less chance for
confusion around whether the limit is exclusive or inclusive.

And we should provide a local "const u64" for maximum architectural value.  The
other option would be to initialize the global to the max architectural value,
and then do early returns here, but I like explicitly setting the global before
checking it against use_timer_freq.

> +
> +	cpuid(0x15, &eax, &ebx, &ecx, &edx);
> +	if (!eax || !ebx)
> +		return 1ULL << 32;
> +
> +	return min_t(u64, 1ULL << 32, ((u64)ebx << 25) / eax);

While I appreciate Sashiko's paranoia about "((u64)ebx << 25) / eax)" yielding
zero, I think we should treat that as a WARNable offence.  And to avoid a false
positive due to running as a VM of a misconfigured hypervisor, I think we should
only apply the erratum workaround on bare metal, i.e. use the maximum value if
KVM detects X86_FEATURE_HYPERVISOR.

So this as fixup?  If this looks good to you, I'm happy to post v6 since I've
already got it locally and tested on CLX, ICX, and EMR.

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index ebe83a641473..dae41842a9ce 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -153,7 +153,7 @@ module_param(dump_invalid_vmcs, bool, 0644);
 #ifdef CONFIG_X86_64
 static int __read_mostly cpu_preemption_timer_multi;
 static bool __read_mostly enable_preemption_timer = 1;
-static u64 __ro_after_init preemption_timer_limit;
+static u64 __ro_after_init preemption_timer_max_value;
 module_param_named(preemption_timer, enable_preemption_timer, bool, S_IRUGO);
 #else
 #define enable_preemption_timer false
@@ -8313,18 +8313,25 @@ static inline int u64_shl_div_u64(u64 a, unsigned int shift,
  * with large values. The workaround is to cap the timer value to strictly
  * less than 2^25 * CPUID.15H:EBX / CPUID.15H:EAX.
  */
-static __init u64 calc_preemption_timer_limit(void)
+static __init u64 calc_preemption_timer_max_value(void)
 {
+       const u64 ARCHITECTURAL_MAX_VALUE = UINT_MAX;
        u32 eax, ebx, ecx, edx;
 
+       if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR))
+               return ARCHITECTURAL_MAX_VALUE;
+
        if (cpuid_eax(0) < 0x15)
-               return 1ULL << 32;
+               return ARCHITECTURAL_MAX_VALUE;
 
        cpuid(0x15, &eax, &ebx, &ecx, &edx);
        if (!eax || !ebx)
-               return 1ULL << 32;
+               return ARCHITECTURAL_MAX_VALUE;
 
-       return min_t(u64, 1ULL << 32, ((u64)ebx << 25) / eax);
+       if (WARN_ON_ONCE(!(((u64)ebx << 25) / eax)))
+               return ARCHITECTURAL_MAX_VALUE;
+
+       return (((u64)ebx << 25) / eax) - 1;
 }
 
 static __init void vmx_setup_preemption_timer(void)
@@ -8338,7 +8345,7 @@ static __init void vmx_setup_preemption_timer(void)
                cpu_preemption_timer_multi =
                        vmx_misc_preemption_timer_rate(vmcs_config.misc);
 
-               preemption_timer_limit = calc_preemption_timer_limit();
+               preemption_timer_max_value = calc_preemption_timer_max_value();
 
                if (tsc_khz)
                        use_timer_freq = (u64)tsc_khz * 1000;
@@ -8349,8 +8356,7 @@ static __init void vmx_setup_preemption_timer(void)
                 * value.  Don't use the timer if it might cause spurious exits
                 * at a rate faster than 0.1 Hz (of uninterrupted guest time).
                 */
-               if (!preemption_timer_limit ||
-                   use_timer_freq > (preemption_timer_limit - 1) / 10)
+               if (use_timer_freq > preemption_timer_max_value / 10)
                        enable_preemption_timer = false;
        }
 
@@ -8392,7 +8398,7 @@ int vmx_set_hv_timer(struct kvm_vcpu *vcpu, u64 guest_deadline_tsc,
         * It's possible that it fits on later vmentries, but checking
         * on every vmentry is costly so we just use an hrtimer.
         */
-       if ((delta_tsc >> cpu_preemption_timer_multi) >= preemption_timer_limit)
+       if ((delta_tsc >> cpu_preemption_timer_multi) > preemption_timer_max_value)
                return -ERANGE;
 
        vmx->hv_deadline_tsc = tscl + delta_tsc;
@@ -8426,7 +8432,7 @@ static void vmx_update_hv_timer(struct kvm_vcpu *vcpu, bool force_immediate_exit
                vmcs_write32(VMX_PREEMPTION_TIMER_VALUE, delta_tsc);
                vmx->loaded_vmcs->hv_timer_soft_disabled = false;
        } else if (!vmx->loaded_vmcs->hv_timer_soft_disabled) {
-               vmcs_write32(VMX_PREEMPTION_TIMER_VALUE, preemption_timer_limit - 1);
+               vmcs_write32(VMX_PREEMPTION_TIMER_VALUE, preemption_timer_max_value);
                vmx->loaded_vmcs->hv_timer_soft_disabled = true;
        }
 }

  parent reply	other threads:[~2026-07-28 15:03 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-24 23:49 [PATCH v5 0/2] Compute safe bound for VMX preemption timer Jim Mattson
2026-07-24 23:49 ` [PATCH v5 1/2] KVM: VMX: Bury all of the VMX preemption timer code under CONFIG_X86_64=y Jim Mattson
2026-07-28  5:16   ` Chao Gao
2026-07-28  7:13   ` Binbin Wu
2026-07-24 23:49 ` [PATCH v5 2/2] KVM: VMX: Cap VMX preemption timer to work around Intel erratum Jim Mattson
2026-07-28  7:22   ` Binbin Wu
2026-07-28 15:03   ` Sean Christopherson [this message]
2026-07-28 15:28     ` Jim Mattson
2026-07-28 15:40       ` Sean Christopherson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=amjEw9QcQN7owHKF@google.com \
    --to=seanjc@google.com \
    --cc=chao.gao@intel.com \
    --cc=jmattson@google.com \
    --cc=kvm@vger.kernel.org \
    --cc=pbonzini@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox