From: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
To: khorenko@virtuozzo.com
Cc: devel@openvz.org
Subject: [Devel] [PATCH hci-8.1 v2] KVM: nVMX: Keep the emulated VMX-preemption timer on L1's TSC
Date: Fri, 6 Nov 2026 17:09:10 +0200 [thread overview]
Message-ID: <20261106151535.138563-1-eva.kurchatova@virtuozzo.com> (raw)
KVM emulates L2's VMX-preemption timer with an hrtimer, but the hrtimer
runs on CLOCK_MONOTONIC, which NTP slews against the TSC. The VM-exit
then comes late or early in L1's TSC, and vmx_preemption_timer_test
fails.
Arm the hrtimer an eighth early while more than 100us are left, and re-arm
it from its callback until L1's TSC reaches the deadline kept since
commit 850448f35aaf ("KVM: nVMX: Fix VMX preemption timer migration").
Count the timer value left from that deadline too, and wait for a
running callback when moving the timer to another CPU.
Fixes: f4124500c2c1 ("KVM: nVMX: Fully emulate preemption timer")
https://virtuozzo.atlassian.net/browse/VSTOR-152991
Feature: fix ms/kvm
Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
---
Changes in v2:
- Read L1's TSC once in vmx_calc_preemption_timer_value(), so that the
value it returns is counted from the same sample as the deadline it
sets.
- Use hrtimer_cancel() in vmx_migrate_timers(): the callback may now
re-arm the timer, and hrtimer_try_to_cancel() gives up on a running
callback, which left the timer on the CPU the vCPU had left.
- Keep vmx_get_preemption_timer_value(), now counted from the deadline,
so that its caller is unchanged.
- Correct naming, and that the skew only stretches the last 100us.
- Shorter commit message.
Link to v1: http://lore.virtuozzo.com/kernel/010701a1b11e8c72-26d4b079-ff6d-48b4-8769-7d3abff2e701-000000@eu-central-1.amazonses.com/T/#t
arch/x86/kvm/vmx/nested.c | 73 ++++++++++++++++++++++++++++-----------
arch/x86/kvm/vmx/vmx.c | 3 +-
2 files changed, 54 insertions(+), 22 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 8c771d8c35e3..85161d3ee80d 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -2206,15 +2206,59 @@ void nested_sync_vmcs12_to_shadow(struct kvm_vcpu *vcpu)
vmx->nested.need_vmcs12_to_shadow_sync = false;
}
+/* L1's TSC in ticks of the emulated timer */
+static u64 vmx_preemption_timer_now(struct kvm_vcpu *vcpu)
+{
+ return kvm_read_l1_tsc(vcpu, rdtsc()) >>
+ VMX_MISC_EMULATED_PREEMPTION_TIMER_RATE;
+}
+
+/* Ticks left from @now to the deadline, at most the 32-bit timer value */
+static u64 vmx_preemption_timer_left(struct kvm_vcpu *vcpu, u64 now)
+{
+ u64 deadline = to_vmx(vcpu)->nested.preemption_timer_deadline;
+
+ return deadline > now ? min(deadline - now, (u64)U32_MAX) : 0;
+}
+
+/*
+ * The hrtimer runs on CLOCK_MONOTONIC, which adjtimex may skew against the
+ * TSC by up to 10%: fire an eighth early while more than 100us are left, and
+ * let the callback re-arm until the deadline. The skew then only stretches
+ * the last 100us.
+ */
+static ktime_t vmx_preemption_timer_expiry(struct kvm_vcpu *vcpu, u64 ticks)
+{
+ u64 ns = (ticks << VMX_MISC_EMULATED_PREEMPTION_TIMER_RATE) * 1000000;
+
+ do_div(ns, vcpu->arch.virtual_tsc_khz);
+ if (ns > 100 * NSEC_PER_USEC)
+ ns -= ns >> 3;
+ return ktime_add_ns(ktime_get(), ns);
+}
+
+static void vmx_preemption_timer_expire(struct kvm_vcpu *vcpu)
+{
+ to_vmx(vcpu)->nested.preemption_timer_expired = true;
+ kvm_make_request(KVM_REQ_EVENT, vcpu);
+ kvm_vcpu_kick(vcpu);
+}
+
static enum hrtimer_restart vmx_preemption_timer_fn(struct hrtimer *timer)
{
struct vcpu_vmx *vmx =
container_of(timer, struct vcpu_vmx, nested.preemption_timer);
+ struct kvm_vcpu *vcpu = &vmx->vcpu;
+ u64 now = vmx_preemption_timer_now(vcpu);
+ u64 left = vmx_preemption_timer_left(vcpu, now);
- vmx->nested.preemption_timer_expired = true;
- kvm_make_request(KVM_REQ_EVENT, &vmx->vcpu);
- kvm_vcpu_kick(&vmx->vcpu);
+ if (left) {
+ hrtimer_set_expires(timer,
+ vmx_preemption_timer_expiry(vcpu, left));
+ return HRTIMER_RESTART;
+ }
+ vmx_preemption_timer_expire(vcpu);
return HRTIMER_NORESTART;
}
@@ -2223,15 +2267,14 @@ static u64 vmx_calc_preemption_timer_value(struct kvm_vcpu *vcpu)
struct vcpu_vmx *vmx = to_vmx(vcpu);
struct vmcs12 *vmcs12 = get_vmcs12(vcpu);
- u64 l1_scaled_tsc = kvm_read_l1_tsc(vcpu, rdtsc()) >>
- VMX_MISC_EMULATED_PREEMPTION_TIMER_RATE;
+ u64 l1_scaled_tsc = vmx_preemption_timer_now(vcpu);
if (!vmx->nested.has_preemption_timer_deadline) {
vmx->nested.preemption_timer_deadline =
vmcs12->vmx_preemption_timer_value + l1_scaled_tsc;
vmx->nested.has_preemption_timer_deadline = true;
}
- return vmx->nested.preemption_timer_deadline - l1_scaled_tsc;
+ return vmx_preemption_timer_left(vcpu, l1_scaled_tsc);
}
static void vmx_start_preemption_timer(struct kvm_vcpu *vcpu,
@@ -2244,18 +2287,15 @@ static void vmx_start_preemption_timer(struct kvm_vcpu *vcpu,
* a VMExit prior to executing any instructions in the guest.
*/
if (preemption_timeout == 0) {
- vmx_preemption_timer_fn(&vmx->nested.preemption_timer);
+ vmx_preemption_timer_expire(vcpu);
return;
}
if (vcpu->arch.virtual_tsc_khz == 0)
return;
- preemption_timeout <<= VMX_MISC_EMULATED_PREEMPTION_TIMER_RATE;
- preemption_timeout *= 1000000;
- do_div(preemption_timeout, vcpu->arch.virtual_tsc_khz);
hrtimer_start(&vmx->nested.preemption_timer,
- ktime_add_ns(ktime_get(), preemption_timeout),
+ vmx_preemption_timer_expiry(vcpu, preemption_timeout),
HRTIMER_MODE_ABS_PINNED);
}
@@ -4430,16 +4470,7 @@ static int vmx_check_nested_events(struct kvm_vcpu *vcpu)
static u32 vmx_get_preemption_timer_value(struct kvm_vcpu *vcpu)
{
- ktime_t remaining =
- hrtimer_get_remaining(&to_vmx(vcpu)->nested.preemption_timer);
- u64 value;
-
- if (ktime_to_ns(remaining) <= 0)
- return 0;
-
- value = ktime_to_ns(remaining) * vcpu->arch.virtual_tsc_khz;
- do_div(value, 1000000);
- return value >> VMX_MISC_EMULATED_PREEMPTION_TIMER_RATE;
+ return vmx_preemption_timer_left(vcpu, vmx_preemption_timer_now(vcpu));
}
static bool is_vmcs12_ext_field(unsigned long field)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 00ec97314037..ca554e79ae53 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8182,7 +8182,8 @@ void vmx_migrate_timers(struct kvm_vcpu *vcpu)
if (is_guest_mode(vcpu)) {
struct hrtimer *timer = &to_vmx(vcpu)->nested.preemption_timer;
- if (hrtimer_try_to_cancel(timer) == 1)
+ /* Wait for a running callback, it may re-arm the timer */
+ if (hrtimer_cancel(timer))
hrtimer_start_expires(timer, HRTIMER_MODE_ABS_PINNED);
}
}
--
2.56.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
next reply other threads:[~2026-11-06 15:17 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 15:09 Eva Kurchatova [this message]
2026-11-06 15:22 ` no-reply
2026-11-06 16:35 ` Denis V. Lunev
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=20261106151535.138563-1-eva.kurchatova@virtuozzo.com \
--to=eva.kurchatova@virtuozzo.com \
--cc=devel@openvz.org \
--cc=khorenko@virtuozzo.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