Skip to content

Commit d8f2f49

Browse files
dvrabel-ntnxrkrcmar
authored andcommitted
x86/kvm: fix LAPIC timer drift when guest uses periodic mode
Since 4.10, commit 8003c9a (KVM: LAPIC: add APIC Timer periodic/oneshot mode VMX preemption timer support), guests using periodic LAPIC timers (such as FreeBSD 8.4) would see their timers drift significantly over time. Differences in the underlying clocks and numerical errors means the periods of the two timers (hv and sw) are not the same. This difference will accumulate with every expiry resulting in a large error between the hv and sw timer. This means the sw timer may be running slow when compared to the hv timer. When the timer is switched from hv to sw, the now active sw timer will expire late. The guest VCPU is reentered and it switches to using the hv timer. This timer catches up, injecting multiple IRQs into the guest (of which the guest only sees one as it does not get to run until the hv timer has caught up) and thus the guest's timer rate is low (and becomes increasing slower over time as the sw timer lags further and further behind). I believe a similar problem would occur if the hv timer is the slower one, but I have not observed this. Fix this by synchronizing the deadlines for both timers to the same time source on every tick. This prevents the errors from accumulating. Fixes: 8003c9a Cc: Wanpeng Li <wanpeng.li@hotmail.com> Signed-off-by: David Vrabel <david.vrabel@nutanix.com> Cc: stable@vger.kernel.org Reviewed-by: Paolo Bonzini <pbonzini@redhat.com> Reviewed-by: Wanpeng Li <wanpengli@tencent.com> Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
1 parent b09efdc commit d8f2f49

File tree

1 file changed

+14
-2
lines changed

1 file changed

+14
-2
lines changed

arch/x86/kvm/lapic.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,11 +1522,23 @@ static bool set_target_expiration(struct kvm_lapic *apic)
15221522

15231523
static void advance_periodic_target_expiration(struct kvm_lapic *apic)
15241524
{
1525-
apic->lapic_timer.tscdeadline +=
1526-
nsec_to_cycles(apic->vcpu, apic->lapic_timer.period);
1525+
ktime_t now = ktime_get();
1526+
u64 tscl = rdtsc();
1527+
ktime_t delta;
1528+
1529+
/*
1530+
* Synchronize both deadlines to the same time source or
1531+
* differences in the periods (caused by differences in the
1532+
* underlying clocks or numerical approximation errors) will
1533+
* cause the two to drift apart over time as the errors
1534+
* accumulate.
1535+
*/
15271536
apic->lapic_timer.target_expiration =
15281537
ktime_add_ns(apic->lapic_timer.target_expiration,
15291538
apic->lapic_timer.period);
1539+
delta = ktime_sub(apic->lapic_timer.target_expiration, now);
1540+
apic->lapic_timer.tscdeadline = kvm_read_l1_tsc(apic->vcpu, tscl) +
1541+
nsec_to_cycles(apic->vcpu, delta);
15301542
}
15311543

15321544
static void start_sw_period(struct kvm_lapic *apic)

0 commit comments

Comments
 (0)