Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v5 00/34] Cleaning up the KVM clock mess
@ 2026-06-08 14:47 David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 01/34] KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init() David Woodhouse
                   ` (33 more replies)
  0 siblings, 34 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

This is v5 of the series to clean up the KVM clock, rebased onto
tip/timers/ptp (which now includes Thomas's ktime snapshot series and
the read_snapshot patches for hyperv, kvmclock, and vmclock).

The KVM clock has historically suffered from three problems:

 1. Imprecision: get_kvmclock_ns() computed the clock from the *host*
    TSC without applying guest TSC scaling, causing systemic drift from
    the values the guest computes from its own TSC.

 2. Unnecessary discontinuities: gratuitous KVM_REQ_MASTERCLOCK_UPDATE
    requests caused the master clock reference point to be re-snapshotted,
    yanking the guest's clock due to arithmetic precision differences.

 3. No precise migration API: the existing KVM_[GS]ET_CLOCK only allows
    setting the clock at a given UTC reference time, which is necessarily
    imprecise. There was no way to preserve the exact arithmetic
    relationship between guest TSC and KVM clock across live migration.

This series addresses all three, and adds new APIs for precise clock 
migration and TSC frequency reporting. As an added bonus, it now rips 
out the whole pvclock_gtod_data hack which was shadowing the kernel's 
timekeeping, and uses ktime snapshots as $DEITY (well, Thomas) intended.

Changes since v4:
 - Rebased onto tip/timers/ptp (includes ktime snapshot infrastructure)
 - Dropped "WARN if kvm_get_walltime_and_clockread() fails" — the WARN
   was spurious during clocksource transitions
 - Dropped guest-side "Obtain TSC frequency from CPUID" patches (adopted
   by Sean for a separate series)
 - Dropped KVM_VCPU_TSC_EFFECTIVE_FREQ
 - Fixed false re-enabling of master clock when a single vCPU syncs
   multiple times at a mismatched frequency: introduced per-vCPU
   cur_tsc_freq_generation counter so each vCPU is counted exactly once
 - Unified nr_vcpus_matched_tsc and nr_vcpus_matched_freq to use the
   same counting convention (1-based, >= online_vcpus threshold)
 - "Avoid gratuitous global clock updates": kept global update in
   non-master-clock mode on vCPU load (CLOCK_MONOTONIC_RAW means no NTP
   drift but preserving the existing safety); only optimize master clock
 - "Xen runstate negative time": refined to update state but not account
   time on backwards clock, always update last_steal and guest shared page
 - Added "Activate master clock immediately on vCPU creation" to avoid
   unnecessary non-master-clock window during VM setup
 - New final patches: use ktime_get_snapshot_id() for master clock
   reference, then remove pvclock_gtod_data entirely (replaced by direct
   ktime_get_raw() + offs_boot computation)
 - Added masterclock_offset_test selftest (verifies kvmclock consistency
   across vCPUs with different TSC offsets)
 - Added xen_cpuid_timing_test selftest
 - Added pvclock_migration_test selftest
 - Addressed AI reviewer (Sashiko) feedback throughout:
   - get_kvmclock(): goto fallback on clock read failure instead of
     using uninitialized data; single #ifdef CONFIG_X86_64 block
   - kvm_synchronize_tsc(): changed ns to s64 to match function
     signature; moved time reads inside tsc_write_lock
   - Kill last_tsc fields: use kvm_scale_tsc() subtraction for
     backwards TSC instead of zeroing cur_tsc_write
   - KVM_[GS]ET_CLOCK_GUEST: validate padding fields, bounds-check
     tsc_shift
   - pvclock selftest: seqcount loop for torn-read safety, per-vCPU
     pvclock addresses, graceful skip when caps unavailable
   - KVM_VCPU_TSC_SCALE: return -ENXIO when !has_tsc_control
   - UAPI pvclock-abi: added -D__KERNEL__ to xen-hypercalls.sh
   - VMX: also clear SECONDARY_EXEC_TSC_SCALING from vmcs_config

David Woodhouse (31):
  KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init()
  KVM: x86: Improve accuracy of KVM clock when TSC scaling is in force
  KVM: x86: Explicitly disable TSC scaling without CONSTANT_TSC
  KVM: x86: Activate master clock immediately on vCPU creation
  KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration
  KVM: x86: Avoid NTP frequency skew for KVM clock on 32-bit host
  KVM: x86: Fold __get_kvmclock() into get_kvmclock()
  KVM: x86: Restructure get_kvmclock()
  KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling
  KVM: x86: Use get_kvmclock() in kvm_get_wall_clock_epoch()
  KVM: x86: Fix compute_guest_tsc() to handle negative time deltas
  KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling
  KVM: x86: Simplify and comment kvm_get_time_scale()
  KVM: x86: Remove implicit rdtsc() from kvm_compute_l1_tsc_offset()
  KVM: x86: Improve synchronization in kvm_synchronize_tsc()
  KVM: x86: Kill last_tsc_{nsec,write,offset} fields
  KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool
  KVM: x86: Allow KVM master clock mode when TSCs are offset from each other
  KVM: x86: Factor out kvm_use_master_clock()
  KVM: x86: Avoid gratuitous global clock updates
  KVM: x86/xen: Prevent runstate times from becoming negative
  KVM: x86: Avoid redundant masterclock updates from multiple vCPUs
  KVM: x86: Remove runtime Xen TSC frequency CPUID update
  KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ
  KVM: x86: Use ktime_get_snapshot_id() for master clock
  KVM: x86: Compute kvmclock base without pvclock_gtod_data
  KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean
  KVM: x86: Remove pvclock_gtod_data and private timekeeping code
  KVM: selftests: Add master clock offset test
  KVM: selftests: Add Xen/generic CPUID timing leaf test
  KVM: selftests: Add Xen runstate migration test

Jack Allister (3):
  UAPI: x86: Move pvclock-abi to UAPI for x86 platforms
  KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration
  KVM: selftests: Add KVM/PV clock selftest to prove timer correction

 Documentation/virt/kvm/api.rst                     |   37 +
 Documentation/virt/kvm/devices/vcpu.rst            |  119 ++-
 MAINTAINERS                                        |    4 +-
 arch/x86/include/asm/kvm_host.h                    |   16 +-
 arch/x86/include/uapi/asm/kvm.h                    |    6 +
 arch/x86/include/{ => uapi}/asm/pvclock-abi.h      |   27 +-
 arch/x86/kvm/cpuid.c                               |   16 -
 arch/x86/kvm/svm/svm.c                             |    3 +-
 arch/x86/kvm/vmx/vmx.c                             |    4 +-
 arch/x86/kvm/x86.c                                 | 1039 ++++++++++++--------
 arch/x86/kvm/xen.c                                 |   30 +-
 arch/x86/kvm/xen.h                                 |   13 -
 include/uapi/linux/kvm.h                           |    3 +
 scripts/xen-hypercalls.sh                          |    2 +-
 tools/testing/selftests/kvm/Makefile.kvm           |    4 +
 .../selftests/kvm/x86/masterclock_offset_test.c    |  180 ++++
 .../selftests/kvm/x86/pvclock_migration_test.c     |  382 +++++++
 tools/testing/selftests/kvm/x86/pvclock_test.c     |  441 +++++++++
 .../selftests/kvm/x86/xen_cpuid_timing_test.c      |  230 +++++
 .../testing/selftests/kvm/x86/xen_migration_test.c |  194 ++++
 20 files changed, 2263 insertions(+), 487 deletions(-)

base-commit: bc484a5096732cd858771cccd3164ec985bdc03d


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

* [PATCH v5 01/34] KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 02/34] KVM: x86: Improve accuracy of KVM clock when TSC scaling is in force David Woodhouse
                   ` (32 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The KVM clock is an interesting thing. It is defined as "nanoseconds
since the guest was created", but in practice it runs at two *different*
rates — or three different rates, if you count implementation bugs.

Definition A is that it runs synchronously with the CLOCK_MONOTONIC_RAW
of the host, with a delta of kvm->arch.kvmclock_offset.

But that version doesn't actually get used in the common case, where the
host has a reliable TSC and the guest TSCs are all running at the same
rate and in sync with each other, and kvm->arch.use_master_clock is set.

In that common case, definition B is used: There is a reference point in
time at kvm->arch.master_kernel_ns (again a CLOCK_MONOTONIC_RAW time),
and a corresponding host TSC value kvm->arch.master_cycle_now. This
fixed point in time is converted to guest units (the time offset by
kvmclock_offset and the TSC Value scaled and offset to be a guest TSC
value) and advertised to the guest in the pvclock structure. While in
this 'use_master_clock' mode, the fixed point in time never needs to be
changed, and the clock runs precisely in time with the guest TSC, at the
rate advertised in the pvclock structure.

The third definition C is implemented in kvm_get_wall_clock_epoch() and
__get_kvmclock(), using the master_cycle_now and master_kernel_ns fields
but converting the *host* TSC cycles directly to a value in nanoseconds
instead of scaling via the guest TSC.

One might naïvely think that all three definitions are identical, since
CLOCK_MONOTONIC_RAW is not skewed by NTP frequency corrections; all
three are just the result of counting the host TSC at a known frequency,
or the scaled guest TSC at a known precise fraction of the host's
frequency. The problem is with arithmetic precision, and the way that
frequency scaling is done in a division-free way by multiplying by a
scale factor, then shifting right. In practice, all three ways of
calculating the KVM clock will suffer a systemic drift from each other.

Eventually, definition C should just be eliminated. Commit 451a707813ae
("KVM: x86/xen: improve accuracy of Xen timers") worked around it for
the specific case of Xen timers, which are defined in terms of the KVM
clock and suffered from a continually increasing error in timer expiry
times. That commit notes that get_kvmclock_ns() is non-trivial to fix
and says "I'll come back to that", which remains true.

Definitions A and B do need to coexist, the former to handle the case
where the host or guest TSC is suboptimally configured. But KVM should
be more careful about switching between them, and the discontinuity in
guest time which could result.

In particular, KVM_REQ_MASTERCLOCK_UPDATE will take a new snapshot of
time as the reference in master_kernel_ns and master_cycle_now, yanking
the guest's clock back to match definition A at that moment.

When invoked from in 'use_master_clock' mode, kvm_update_masterclock()
should probably *adjust* kvm->arch.kvmclock_offset to account for the
drift, instead of yanking the clock back to definition A. But in the
meantime there are a bunch of places where it just doesn't need to be
invoked at all.

To start with: there is no need to do such an update when a Xen guest
populates the shared_info page. This seems to have been a hangover from
the very first implementation of shared_info which automatically
populated the vcpu_info structures at their default locations, but even
then it should just have raised KVM_REQ_CLOCK_UPDATE on each vCPU
instead of using KVM_REQ_MASTERCLOCK_UPDATE. And now that userspace is
expected to explicitly set the vcpu_info even in its default locations,
there's not even any need for that either.

Fixes: 629b5348841a ("KVM: x86/xen: update wallclock region")
Reviewed-by: Paul Durrant <paul@xen.org>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/xen.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 91fd3673c09a..82e34edbfdbd 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -98,8 +98,6 @@ static int kvm_xen_shared_info_init(struct kvm *kvm)
 	wc->version = wc_version + 1;
 	read_unlock_irq(&gpc->lock);
 
-	kvm_make_all_cpus_request(kvm, KVM_REQ_MASTERCLOCK_UPDATE);
-
 out:
 	srcu_read_unlock(&kvm->srcu, idx);
 	return ret;
-- 
2.54.0


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

* [PATCH v5 02/34] KVM: x86: Improve accuracy of KVM clock when TSC scaling is in force
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 01/34] KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 03/34] UAPI: x86: Move pvclock-abi to UAPI for x86 platforms David Woodhouse
                   ` (31 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The kvm_guest_time_update() function scales the host TSC frequency to
the guest's using kvm_scale_tsc() and the v->arch.l1_tsc_scaling_ratio
scaling ratio previously calculated for that vCPU. Then calculates the
scaling factors for the KVM clock itself based on that guest TSC
frequency.

However, it uses kHz as the unit when scaling, and then multiplies by
1000 only at the end.

With a host TSC frequency of 3000MHz and a guest set to 2500MHz, the
result of kvm_scale_tsc() will actually come out at 2,499,999kHz. So
the KVM clock advertised to the guest is based on a frequency of
2,499,999,000 Hz.

By using Hz as the unit from the beginning, the KVM clock would be based
on a more accurate frequency of 2,499,999,999 Hz in this example.

Use u64 for the hw_tsc_hz field since an unsigned int would overflow for
TSC frequencies above 4GHz. Use div_u64() for the Xen CPUID leaf to
play nice with 32-bit kernels.

Fixes: 78db6a503796 ("KVM: x86: rewrite handling of scaled TSC for kvmclock")
Reviewed-by: Paul Durrant <paul@xen.org>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/include/asm/kvm_host.h |  2 +-
 arch/x86/kvm/cpuid.c            |  2 +-
 arch/x86/kvm/x86.c              | 17 +++++++++--------
 3 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index c470e40a00aa..37264212c7df 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -950,7 +950,7 @@ struct kvm_vcpu_arch {
 	gpa_t time;
 	s8  pvclock_tsc_shift;
 	u32 pvclock_tsc_mul;
-	unsigned int hw_tsc_khz;
+	u64 hw_tsc_hz;
 	struct gfn_to_pfn_cache pv_time;
 	/* set guest stopped flag in pvclock flags field */
 	bool pvclock_set_guest_stopped_request;
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index e69156b54cff..621d950ec692 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -2131,7 +2131,7 @@ bool kvm_cpuid(struct kvm_vcpu *vcpu, u32 *eax, u32 *ebx,
 				*ecx = vcpu->arch.pvclock_tsc_mul;
 				*edx = vcpu->arch.pvclock_tsc_shift;
 			} else if (index == 2) {
-				*eax = vcpu->arch.hw_tsc_khz;
+				*eax = div_u64(vcpu->arch.hw_tsc_hz, 1000);
 			}
 		}
 	} else {
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0a1b63c63d1a..d9ef165df6a1 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3314,7 +3314,8 @@ static void kvm_setup_guest_pvclock(struct pvclock_vcpu_time_info *ref_hv_clock,
 int kvm_guest_time_update(struct kvm_vcpu *v)
 {
 	struct pvclock_vcpu_time_info hv_clock = {};
-	unsigned long flags, tgt_tsc_khz;
+	unsigned long flags;
+	u64 tgt_tsc_hz;
 	unsigned seq;
 	struct kvm_vcpu_arch *vcpu = &v->arch;
 	struct kvm_arch *ka = &v->kvm->arch;
@@ -3340,8 +3341,8 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
 
 	/* Keep irq disabled to prevent changes to the clock */
 	local_irq_save(flags);
-	tgt_tsc_khz = get_cpu_tsc_khz();
-	if (unlikely(tgt_tsc_khz == 0)) {
+	tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
+	if (unlikely(tgt_tsc_hz == 0)) {
 		local_irq_restore(flags);
 		kvm_make_request(KVM_REQ_CLOCK_UPDATE, v);
 		return 1;
@@ -3376,16 +3377,16 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
 	/* With all the info we got, fill in the values */
 
 	if (kvm_caps.has_tsc_control) {
-		tgt_tsc_khz = kvm_scale_tsc(tgt_tsc_khz,
+		tgt_tsc_hz = kvm_scale_tsc(tgt_tsc_hz,
 					    v->arch.l1_tsc_scaling_ratio);
-		tgt_tsc_khz = tgt_tsc_khz ? : 1;
+		tgt_tsc_hz = tgt_tsc_hz ? : 1;
 	}
 
-	if (unlikely(vcpu->hw_tsc_khz != tgt_tsc_khz)) {
-		kvm_get_time_scale(NSEC_PER_SEC, tgt_tsc_khz * 1000LL,
+	if (unlikely(vcpu->hw_tsc_hz != tgt_tsc_hz)) {
+		kvm_get_time_scale(NSEC_PER_SEC, tgt_tsc_hz,
 				   &vcpu->pvclock_tsc_shift,
 				   &vcpu->pvclock_tsc_mul);
-		vcpu->hw_tsc_khz = tgt_tsc_khz;
+		vcpu->hw_tsc_hz = tgt_tsc_hz;
 	}
 
 	hv_clock.tsc_shift = vcpu->pvclock_tsc_shift;
-- 
2.54.0


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

* [PATCH v5 03/34] UAPI: x86: Move pvclock-abi to UAPI for x86 platforms
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 01/34] KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init() David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 02/34] KVM: x86: Improve accuracy of KVM clock when TSC scaling is in force David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration David Woodhouse
                   ` (30 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: Jack Allister <jalliste@amazon.com>

A subsequent commit will provide a new KVM interface for performing a
fixup/correction of the KVM clock against the reference TSC. The
KVM_[GS]ET_CLOCK_GUEST API requires a pvclock_vcpu_time_info, as such
the caller must know about this definition.

Move the definition to the UAPI folder so that it is exported to
usermode and also change the type definitions to use the standard for
UAPI exports.

Signed-off-by: Jack Allister <jalliste@amazon.com>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 MAINTAINERS                                   |  4 +--
 arch/x86/include/{ => uapi}/asm/pvclock-abi.h | 27 ++++++++++---------
 scripts/xen-hypercalls.sh                     |  2 +-
 3 files changed, 18 insertions(+), 15 deletions(-)
 rename arch/x86/include/{ => uapi}/asm/pvclock-abi.h (82%)

diff --git a/MAINTAINERS b/MAINTAINERS
index 882214b0e7db..dc0f6516beb4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14402,7 +14402,7 @@ S:	Supported
 T:	git git://git.kernel.org/pub/scm/virt/kvm/kvm.git
 F:	arch/um/include/asm/kvm_para.h
 F:	arch/x86/include/asm/kvm_para.h
-F:	arch/x86/include/asm/pvclock-abi.h
+F:	arch/x86/include/uapi/asm/pvclock-abi.h
 F:	arch/x86/include/uapi/asm/kvm_para.h
 F:	arch/x86/kernel/kvm.c
 F:	arch/x86/kernel/kvmclock.c
@@ -29081,7 +29081,7 @@ R:	Boris Ostrovsky <boris.ostrovsky@oracle.com>
 L:	xen-devel@lists.xenproject.org (moderated for non-subscribers)
 S:	Supported
 F:	arch/x86/configs/xen.config
-F:	arch/x86/include/asm/pvclock-abi.h
+F:	arch/x86/include/uapi/asm/pvclock-abi.h
 F:	arch/x86/include/asm/xen/
 F:	arch/x86/platform/pvh/
 F:	arch/x86/xen/
diff --git a/arch/x86/include/asm/pvclock-abi.h b/arch/x86/include/uapi/asm/pvclock-abi.h
similarity index 82%
rename from arch/x86/include/asm/pvclock-abi.h
rename to arch/x86/include/uapi/asm/pvclock-abi.h
index b9fece5fc96d..6d70cf640362 100644
--- a/arch/x86/include/asm/pvclock-abi.h
+++ b/arch/x86/include/uapi/asm/pvclock-abi.h
@@ -1,6 +1,9 @@
-/* SPDX-License-Identifier: GPL-2.0 */
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
 #ifndef _ASM_X86_PVCLOCK_ABI_H
 #define _ASM_X86_PVCLOCK_ABI_H
+
+#include <linux/types.h>
+
 #ifndef __ASSEMBLER__
 
 /*
@@ -24,20 +27,20 @@
  */
 
 struct pvclock_vcpu_time_info {
-	u32   version;
-	u32   pad0;
-	u64   tsc_timestamp;
-	u64   system_time;
-	u32   tsc_to_system_mul;
-	s8    tsc_shift;
-	u8    flags;
-	u8    pad[2];
+	__u32   version;
+	__u32   pad0;
+	__u64   tsc_timestamp;
+	__u64   system_time;
+	__u32   tsc_to_system_mul;
+	__s8    tsc_shift;
+	__u8    flags;
+	__u8    pad[2];
 } __attribute__((__packed__)); /* 32 bytes */
 
 struct pvclock_wall_clock {
-	u32   version;
-	u32   sec;
-	u32   nsec;
+	__u32   version;
+	__u32   sec;
+	__u32   nsec;
 } __attribute__((__packed__));
 
 #define PVCLOCK_TSC_STABLE_BIT	(1 << 0)
diff --git a/scripts/xen-hypercalls.sh b/scripts/xen-hypercalls.sh
index f18b00843df3..51a722198997 100755
--- a/scripts/xen-hypercalls.sh
+++ b/scripts/xen-hypercalls.sh
@@ -5,7 +5,7 @@ shift
 in="$@"
 
 for i in $in; do
-	eval $CPP $LINUXINCLUDE -dD -imacros "$i" -x c /dev/null
+	eval $CPP -D__KERNEL__ $LINUXINCLUDE -dD -imacros "$i" -x c /dev/null
 done | \
 awk '$1 == "#define" && $2 ~ /__HYPERVISOR_[a-z][a-z_0-9]*/ { v[$3] = $2 }
 	END {   print "/* auto-generated by scripts/xen-hypercall.sh */"
-- 
2.54.0


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

* [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (2 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 03/34] UAPI: x86: Move pvclock-abi to UAPI for x86 platforms David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 15:33   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction David Woodhouse
                   ` (29 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: Jack Allister <jalliste@amazon.com>

In the common case (where kvm->arch.use_master_clock is true), the KVM
clock is defined as a simple arithmetic function of the guest TSC, based
on a reference point stored in kvm->arch.master_kernel_ns and
kvm->arch.master_cycle_now.

The existing KVM_[GS]ET_CLOCK functionality does not allow for this
relationship to be precisely saved and restored by userspace. All it can
currently do is set the KVM clock at a given UTC reference time, which
is necessarily imprecise.

So on live update, the guest TSC can remain cycle accurate at precisely
the same offset from the host TSC, but there is no way for userspace to
restore the KVM clock accurately.

Even on live migration to a new host, where the accuracy of the guest
time-keeping is fundamentally limited by the accuracy of wallclock
synchronization between the source and destination hosts, the clock jump
experienced by the guest's TSC and its KVM clock should at least be
*consistent*. Even when the guest TSC suffers a discontinuity, its KVM
clock should still remain the *same* arithmetic function of the guest
TSC, and not suffer an *additional* discontinuity.

To allow for accurate migration of the KVM clock, add per-vCPU ioctls
which save and restore the actual PV clock info in
pvclock_vcpu_time_info.

The restoration in KVM_SET_CLOCK_GUEST works by creating a new reference
point in time just as kvm_update_masterclock() does, and calculating the
corresponding guest TSC value. This guest TSC value is then passed
through the user-provided pvclock structure to generate the *intended*
KVM clock value at that point in time, and through the *actual* KVM
clock calculation. Then kvm->arch.kvmclock_offset is adjusted to
eliminate the difference.

Where kvm->arch.use_master_clock is false (because the host TSC is
unreliable, or the guest TSCs are configured strangely), the KVM clock
is *not* defined as a function of the guest TSC so KVM_GET_CLOCK_GUEST
returns an error. In this case, as documented, userspace shall use the
legacy KVM_GET_CLOCK ioctl. The loss of precision is acceptable in this
case since the clocks are imprecise in this mode anyway.

On *restoration*, if kvm->arch.use_master_clock is false, an error is
returned for similar reasons and userspace shall fall back to using
KVM_SET_CLOCK. This does mean that, as documented, userspace needs to
use *both* KVM_GET_CLOCK_GUEST and KVM_GET_CLOCK and send both results
with the migration data (unless the intent is to refuse to resume on a
host with bad TSC).

Co-developed-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Jack Allister <jalliste@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
Cc: Dongli Zhang <dongli.zhang@oracle.com>
---
 Documentation/virt/kvm/api.rst |  37 ++++++++
 arch/x86/kvm/x86.c             | 164 +++++++++++++++++++++++++++++++++
 include/uapi/linux/kvm.h       |   3 +
 3 files changed, 204 insertions(+)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 52bbbb553ce1..2268b4442df6 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6553,6 +6553,43 @@ KVM_S390_KEYOP_SSKE
   Sets the storage key for the guest address ``guest_addr`` to the key
   specified in ``key``, returning the previous value in ``key``.
 
+4.145 KVM_GET_CLOCK_GUEST
+----------------------------
+
+:Capability: none
+:Architectures: x86_64
+:Type: vcpu ioctl
+:Parameters: struct pvclock_vcpu_time_info (out)
+:Returns: 0 on success, <0 on error
+
+Retrieves the current time information structure used for KVM/PV clocks,
+in precisely the form advertised to the guest vCPU, which gives parameters
+for a direct conversion from a guest TSC value to nanoseconds.
+
+When the KVM clock is not in "master clock" mode, for example because the
+host TSC is unreliable or the guest TSCs are oddly configured, the KVM clock
+is actually defined by the host CLOCK_MONOTONIC_RAW instead of the guest TSC.
+In this case, the KVM_GET_CLOCK_GUEST ioctl returns -EINVAL.
+
+4.146 KVM_SET_CLOCK_GUEST
+----------------------------
+
+:Capability: none
+:Architectures: x86_64
+:Type: vcpu ioctl
+:Parameters: struct pvclock_vcpu_time_info (in)
+:Returns: 0 on success, <0 on error
+
+Sets the KVM clock (for the whole VM) in terms of the vCPU TSC, using the
+pvclock structure as returned by KVM_GET_CLOCK_GUEST. This allows the precise
+arithmetic relationship between guest TSC and KVM clock to be preserved by
+userspace across migration.
+
+When the KVM clock is not in "master clock" mode, and the KVM clock is actually
+defined by the host CLOCK_MONOTONIC_RAW, this ioctl returns -EINVAL. Userspace
+may choose to set the clock using the less precise KVM_SET_CLOCK ioctl, or may
+choose to fail, denying migration to a host whose TSC is misbehaving.
+
 .. _kvm_run:
 
 5. The kvm_run structure
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index d9ef165df6a1..b7e5f6e3dc6c 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6205,6 +6205,162 @@ static int kvm_get_reg_list(struct kvm_vcpu *vcpu,
 	return 0;
 }
 
+#ifdef CONFIG_X86_64
+static int kvm_vcpu_ioctl_get_clock_guest(struct kvm_vcpu *v, void __user *argp)
+{
+	struct pvclock_vcpu_time_info hv_clock = {};
+	struct kvm_vcpu_arch *vcpu = &v->arch;
+	struct kvm_arch *ka = &v->kvm->arch;
+	unsigned int seq;
+
+	/*
+	 * If KVM_REQ_CLOCK_UPDATE is already pending, or if the pvclock
+	 * has never been generated at all, call kvm_guest_time_update().
+	 */
+	if (kvm_check_request(KVM_REQ_CLOCK_UPDATE, v) || !vcpu->hw_tsc_hz) {
+		int idx = srcu_read_lock(&v->kvm->srcu);
+		int ret = kvm_guest_time_update(v);
+
+		srcu_read_unlock(&v->kvm->srcu, idx);
+		if (ret)
+			return -EINVAL;
+	}
+
+	/*
+	 * Reconstruct the pvclock from the master clock state, matching
+	 * exactly what kvm_guest_time_update() writes to the guest.
+	 */
+	do {
+		seq = read_seqcount_begin(&ka->pvclock_sc);
+
+		if (!ka->use_master_clock)
+			return -EINVAL;
+
+		hv_clock.tsc_timestamp = kvm_read_l1_tsc(v, ka->master_cycle_now);
+		hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
+	} while (read_seqcount_retry(&ka->pvclock_sc, seq));
+
+	hv_clock.tsc_shift = vcpu->pvclock_tsc_shift;
+	hv_clock.tsc_to_system_mul = vcpu->pvclock_tsc_mul;
+	hv_clock.flags = PVCLOCK_TSC_STABLE_BIT;
+
+	if (copy_to_user(argp, &hv_clock, sizeof(hv_clock)))
+		return -EFAULT;
+
+	return 0;
+}
+
+/*
+ * Reverse the calculation in the hv_clock definition.
+ *
+ * time_ns = ( (cycles << shift) * mul ) >> 32;
+ * (although shift can be negative, so that's bad C)
+ *
+ * So for a single second,
+ * NSEC_PER_SEC = ( ( FREQ_HZ << shift) * mul ) >> 32
+ * NSEC_PER_SEC << 32 = ( FREQ_HZ << shift ) * mul
+ * ( NSEC_PER_SEC << 32 ) / mul = FREQ_HZ << shift
+ * ( NSEC_PER_SEC << 32 ) / mul ) >> shift = FREQ_HZ
+ */
+static u64 hvclock_to_hz(u32 mul, s8 shift)
+{
+	u64 tm = NSEC_PER_SEC << 32;
+
+	/* Maximise precision. Shift right until the top bit is set */
+	tm <<= 2;
+	shift += 2;
+
+	/* While 'mul' is even, increase the shift *after* the division */
+	while (!(mul & 1)) {
+		shift++;
+		mul >>= 1;
+	}
+
+	tm /= mul;
+
+	if (shift > 0)
+		return tm >> shift;
+	else
+		return tm << -shift;
+}
+
+static int kvm_vcpu_ioctl_set_clock_guest(struct kvm_vcpu *v, void __user *argp)
+{
+	struct pvclock_vcpu_time_info user_hv_clock;
+	struct kvm *kvm = v->kvm;
+	struct kvm_arch *ka = &kvm->arch;
+	u64 curr_tsc_hz, user_tsc_hz;
+	u64 user_clk_ns;
+	u64 guest_tsc;
+	int rc = 0;
+
+	if (copy_from_user(&user_hv_clock, argp, sizeof(user_hv_clock)))
+		return -EFAULT;
+
+	if (user_hv_clock.pad0 || user_hv_clock.pad[0] || user_hv_clock.pad[1])
+		return -EINVAL;
+
+	if (!user_hv_clock.tsc_to_system_mul)
+		return -EINVAL;
+
+	if (user_hv_clock.tsc_shift < -32 || user_hv_clock.tsc_shift > 32)
+		return -EINVAL;
+
+	user_tsc_hz = hvclock_to_hz(user_hv_clock.tsc_to_system_mul,
+				    user_hv_clock.tsc_shift);
+
+	kvm_hv_request_tsc_page_update(kvm);
+
+	/*
+	 * kvm_start_pvclock_update() takes tsc_write_lock and opens
+	 * the pvclock seqcount; kvm_end_pvclock_update() closes both.
+	 * All clock state modifications between them are atomic with
+	 * respect to readers in kvm_guest_time_update().
+	 */
+	kvm_start_pvclock_update(kvm);
+	pvclock_update_vm_gtod_copy(kvm);
+
+	if (!ka->use_master_clock) {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	curr_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
+	if (unlikely(curr_tsc_hz == 0)) {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (kvm_caps.has_tsc_control)
+		curr_tsc_hz = kvm_scale_tsc(curr_tsc_hz,
+					    v->arch.l1_tsc_scaling_ratio);
+
+	/*
+	 * Allow for a discrepancy of 1 kHz either way between the TSC
+	 * frequency used to generate the user's pvclock and the current
+	 * host's measured frequency, since they may not precisely match.
+	 */
+	if (user_tsc_hz < curr_tsc_hz - 1000 ||
+	    user_tsc_hz > curr_tsc_hz + 1000) {
+		rc = -ERANGE;
+		goto out;
+	}
+
+	/*
+	 * Calculate the guest TSC at the new reference point, and the
+	 * corresponding KVM clock value according to user_hv_clock.
+	 * Adjust kvmclock_offset so both definitions agree.
+	 */
+	guest_tsc = kvm_read_l1_tsc(v, ka->master_cycle_now);
+	user_clk_ns = __pvclock_read_cycles(&user_hv_clock, guest_tsc);
+	ka->kvmclock_offset = user_clk_ns - ka->master_kernel_ns;
+
+out:
+	kvm_end_pvclock_update(kvm);
+	return rc;
+}
+#endif
+
 long kvm_arch_vcpu_ioctl(struct file *filp,
 			 unsigned int ioctl, unsigned long arg)
 {
@@ -6605,6 +6761,14 @@ long kvm_arch_vcpu_ioctl(struct file *filp,
 		srcu_read_unlock(&vcpu->kvm->srcu, idx);
 		break;
 	}
+#ifdef CONFIG_X86_64
+	case KVM_SET_CLOCK_GUEST:
+		r = kvm_vcpu_ioctl_set_clock_guest(vcpu, argp);
+		break;
+	case KVM_GET_CLOCK_GUEST:
+		r = kvm_vcpu_ioctl_get_clock_guest(vcpu, argp);
+		break;
+#endif
 #ifdef CONFIG_KVM_HYPERV
 	case KVM_GET_SUPPORTED_HV_CPUID:
 		r = kvm_ioctl_get_supported_hv_cpuid(vcpu, argp);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 6c8afa2047bf..9b50191b859c 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1669,4 +1669,7 @@ struct kvm_pre_fault_memory {
 	__u64 padding[5];
 };
 
+#define KVM_SET_CLOCK_GUEST	_IOW(KVMIO, 0xd6, struct pvclock_vcpu_time_info)
+#define KVM_GET_CLOCK_GUEST	_IOR(KVMIO, 0xd7, struct pvclock_vcpu_time_info)
+
 #endif /* __LINUX_KVM_H */
-- 
2.54.0


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

* [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (3 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 15:49   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 06/34] KVM: x86: Explicitly disable TSC scaling without CONSTANT_TSC David Woodhouse
                   ` (28 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: Jack Allister <jalliste@amazon.com>

A VM's KVM/PV clock has an inherent relationship to its TSC. When either
the host system live-updates or the VM is live-migrated this pairing of
the two clock sources should stay the same. In reality this is not the
case without some correction taking place.

The KVM_GET_CLOCK_GUEST/KVM_SET_CLOCK_GUEST ioctls can be used to
perform a correction on the PVTI (PV time information) structure held by
KVM to effectively fix up the kvmclock_offset prior to the guest VM
resuming in either a live-update/migration scenario.

This test proves that without the necessary fixup there is a perceived
change in the guest TSC and KVM/PV clock relationship before and after a
simulated LU/LM takes place, and that the correction eliminates it.

The test:
  1. Snapshots the PVTI at boot (PVTI0).
  2. Induces a change in PVTI data (KVM_REQ_MASTERCLOCK_UPDATE).
  3. Snapshots the PVTI after the change (PVTI1).
  4. Requests correction via KVM_SET_CLOCK_GUEST using PVTI0.
  5. Snapshots the PVTI after correction (PVTI2).

Then samples the TSC at a single point in time and calculates the KVM
clock using each PVTI snapshot. The corrected clock should match the
boot clock to within ±1ns.

The test enumerates multiple TSC frequencies from 1GHz to 5GHz at 500MHz
steps, crossing the 32-bit boundary, to exercise the scaling path at
various ratios. The sleep duration between snapshots is configurable via
the -s/--sleep command line option.

Co-developed-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Jack Allister <jalliste@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
Cc: Dongli Zhang <dongli.zhang@oracle.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../testing/selftests/kvm/x86/pvclock_test.c  | 440 ++++++++++++++++++
 2 files changed, 441 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/pvclock_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 9118a5a51b89..fb935ae3bf38 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -105,6 +105,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test
 TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
 TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
 TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
+TEST_GEN_PROGS_x86 += x86/pvclock_test
 TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
 TEST_GEN_PROGS_x86 += x86/set_sregs_test
 TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test
diff --git a/tools/testing/selftests/kvm/x86/pvclock_test.c b/tools/testing/selftests/kvm/x86/pvclock_test.c
new file mode 100644
index 000000000000..aecd62fc8a93
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/pvclock_test.c
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright © Amazon.com, Inc. or its affiliates.
+ *
+ * Tests for pvclock API
+ * KVM_SET_CLOCK_GUEST/KVM_GET_CLOCK_GUEST
+ */
+#include <getopt.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "apic.h"
+
+#include <asm/pvclock-abi.h>
+
+/*
+ * Reproduce the pvclock calculation the guest uses to convert TSC to
+ * nanoseconds. This must match the kernel's __pvclock_read_cycles().
+ */
+static inline uint64_t pvclock_scale_delta(uint64_t delta, uint32_t mul,
+					   int8_t shift)
+{
+	if (shift < 0)
+		delta >>= -shift;
+	else
+		delta <<= shift;
+	return ((__uint128_t)delta * mul) >> 32;
+}
+
+static inline uint64_t pvclock_read_cycles(struct pvclock_vcpu_time_info *src,
+					   uint64_t tsc)
+{
+	uint64_t delta = tsc - src->tsc_timestamp;
+
+	return src->system_time + pvclock_scale_delta(delta,
+						      src->tsc_to_system_mul,
+						      src->tsc_shift);
+}
+
+static inline void pvti_snapshot(struct pvclock_vcpu_time_info *dst,
+				 volatile struct pvclock_vcpu_time_info *src)
+{
+	uint32_t version;
+
+	do {
+		version = src->version;
+		__asm__ __volatile__("" ::: "memory");
+		*dst = *src;
+		__asm__ __volatile__("" ::: "memory");
+	} while ((src->version & 1) || src->version != version);
+}
+
+enum {
+	STAGE_FIRST_BOOT,
+	STAGE_UNCORRECTED,
+	STAGE_CORRECTED
+};
+
+#define KVMCLOCK_GPA	0xc0000000ull
+#define KVMCLOCK_SIZE	sizeof(struct pvclock_vcpu_time_info)
+
+static void trigger_pvti_update(void)
+{
+	/*
+	 * Toggle between KVM's old and new system time methods to coerce KVM
+	 * into updating the fields in the PV time info struct.
+	 */
+	wrmsr(MSR_KVM_SYSTEM_TIME, KVMCLOCK_GPA | KVM_MSR_ENABLED);
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, KVMCLOCK_GPA | KVM_MSR_ENABLED);
+}
+
+static void guest_code(void)
+{
+	struct pvclock_vcpu_time_info *pvti =
+		(void *)(unsigned long)KVMCLOCK_GPA;
+	struct pvclock_vcpu_time_info pvti_boot;
+	struct pvclock_vcpu_time_info pvti_uncorrected;
+	struct pvclock_vcpu_time_info pvti_corrected;
+	uint64_t tsc_guest;
+	uint64_t clk_boot, clk_uncorrected, clk_corrected;
+	int64_t delta_corrected;
+
+	/* Set up kvmclock and snapshot the initial pvclock parameters. */
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, KVMCLOCK_GPA | KVM_MSR_ENABLED);
+	pvti_snapshot(&pvti_boot, pvti);
+	GUEST_SYNC(STAGE_FIRST_BOOT);
+
+	/*
+	 * Trigger an update of the PVTI. Calculating the KVM clock using this
+	 * updated structure will show a delta from the original.
+	 */
+	trigger_pvti_update();
+	pvti_snapshot(&pvti_uncorrected, pvti);
+	GUEST_SYNC(STAGE_UNCORRECTED);
+
+	/*
+	 * Snapshot the corrected time (the host does KVM_SET_CLOCK_GUEST when
+	 * handling STAGE_UNCORRECTED).
+	 */
+	pvti_snapshot(&pvti_corrected, pvti);
+
+	/*
+	 * Sample the TSC at a single point in time, then calculate the
+	 * effective KVM clock using the PVTI from each stage. Verify that the
+	 * corrected clock matches the boot clock to within ±2ns.
+	 */
+	tsc_guest = rdtsc();
+
+	clk_boot = pvclock_read_cycles(&pvti_boot, tsc_guest);
+	clk_uncorrected = pvclock_read_cycles(&pvti_uncorrected, tsc_guest);
+	clk_corrected = pvclock_read_cycles(&pvti_corrected, tsc_guest);
+
+	delta_corrected = clk_boot - clk_corrected;
+
+	__GUEST_ASSERT(delta_corrected >= -2 && delta_corrected <= 2,
+		       "corrected delta %ld out of range (boot=%lu uncorrected=%lu corrected=%lu)",
+		       delta_corrected, clk_boot, clk_uncorrected, clk_corrected);
+
+	GUEST_SYNC(STAGE_CORRECTED);
+}
+
+static void run_test(struct kvm_vm *vm, struct kvm_vcpu *vcpu,
+		     unsigned int sleep_sec)
+{
+	struct pvclock_vcpu_time_info pvti_before;
+	struct ucall uc;
+
+	for (;;) {
+		vcpu_run(vcpu);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+		switch (get_ucall(vcpu, &uc)) {
+		case UCALL_ABORT:
+			REPORT_GUEST_ASSERT(uc);
+			break;
+		case UCALL_SYNC:
+			break;
+		default:
+			TEST_FAIL("Unexpected ucall");
+		}
+
+		switch (uc.args[1]) {
+		case STAGE_FIRST_BOOT:
+			/* Save the pvclock parameters before the update. */
+			vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &pvti_before);
+
+			/* Sleep to let the clocks diverge. */
+			sleep(sleep_sec);
+			break;
+
+		case STAGE_UNCORRECTED:
+			/* Restore the original pvclock parameters. */
+			vcpu_ioctl(vcpu, KVM_SET_CLOCK_GUEST, &pvti_before);
+			break;
+
+		case STAGE_CORRECTED:
+			/* Guest verified the delta in-guest. */
+			return;
+
+		default:
+			TEST_FAIL("Unknown stage %lu", uc.args[1]);
+		}
+	}
+}
+
+static void configure_pvclock(struct kvm_vm *vm)
+{
+	unsigned int nr_pages;
+
+	nr_pages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, getpagesize());
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    KVMCLOCK_GPA, 1, nr_pages, 0);
+	virt_map(vm, KVMCLOCK_GPA, KVMCLOCK_GPA, nr_pages);
+}
+
+static void run_at_frequency(uint64_t tsc_khz, unsigned int sleep_sec)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+
+	pr_info("Testing at TSC frequency %lu kHz\n", tsc_khz);
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	configure_pvclock(vm);
+	vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)tsc_khz);
+	run_test(vm, vcpu, sleep_sec);
+	kvm_vm_free(vm);
+}
+
+static void test_tsc_stable_bit(void);
+static void test_clock_guest_with_offsets(void);
+
+static void usage(const char *name)
+{
+	printf("Usage: %s [options]\n"
+	       "  -s, --sleep SEC     sleep duration between snapshots (default: 2)\n"
+	       "  -h, --help          show this help\n", name);
+}
+
+int main(int argc, char *argv[])
+{
+	static const struct option long_opts[] = {
+		{ "sleep", required_argument, NULL, 's' },
+		{ "help",  no_argument,       NULL, 'h' },
+		{ NULL,    0,                  NULL,  0  },
+	};
+	unsigned int sleep_sec = 2;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	uint64_t host_khz;
+	uint64_t freq;
+	int opt;
+
+	while ((opt = getopt_long(argc, argv, "s:h", long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 's':
+			sleep_sec = atoi(optarg);
+			break;
+		case 'h':
+		default:
+			usage(argv[0]);
+			return opt == 'h' ? 0 : 1;
+		}
+	}
+
+	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL));
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	configure_pvclock(vm);
+
+	/* Check KVM_GET_CLOCK_GUEST is supported */
+	{
+		struct pvclock_vcpu_time_info tmp;
+		int ret = __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &tmp);
+		TEST_REQUIRE(ret != -1 || errno != ENOTTY);
+	}
+
+	/* First run at native frequency (no scaling). */
+	run_test(vm, vcpu, sleep_sec);
+
+	/*
+	 * Then enumerate a range of TSC frequencies crossing the 32-bit
+	 * boundary, to exercise the scaling path at various ratios.
+	 */
+	host_khz = __vcpu_ioctl(vcpu, KVM_GET_TSC_KHZ, NULL);
+	kvm_vm_free(vm);
+
+	for (freq = 1000000; freq <= 5000000; freq += 500000) {
+		if (freq == host_khz)
+			continue;
+		run_at_frequency(freq, sleep_sec);
+	}
+
+	test_tsc_stable_bit();
+	test_clock_guest_with_offsets();
+
+	return 0;
+}
+
+static void guest_code_stable_bit(void)
+{
+	uint32_t apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
+	uint64_t gpa = KVMCLOCK_GPA + apic_id * sizeof(struct pvclock_vcpu_time_info);
+
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, gpa | KVM_MSR_ENABLED);
+	GUEST_SYNC(0);
+	GUEST_SYNC(0);
+	GUEST_SYNC(0);
+}
+
+static void set_tsc_offset(struct kvm_vcpu *vcpu, uint64_t offset)
+{
+	struct kvm_device_attr attr = {
+		.group = KVM_VCPU_TSC_CTRL,
+		.attr = KVM_VCPU_TSC_OFFSET,
+		.addr = (__u64)(uintptr_t)&offset,
+	};
+	vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &attr);
+}
+
+static void run_vcpu_once(struct kvm_vcpu *vcpu)
+{
+	struct ucall uc;
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+		break;
+	case UCALL_SYNC:
+		break;
+	default:
+		TEST_FAIL("Unexpected ucall");
+	}
+}
+
+static void test_tsc_stable_bit(void)
+{
+	struct pvclock_vcpu_time_info pvti;
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+	int ret;
+
+	pr_info("Testing PVCLOCK_TSC_STABLE_BIT with matched/unmatched TSCs\n");
+
+	vm = vm_create_with_vcpus(2, guest_code_stable_bit, vcpus);
+	configure_pvclock(vm);
+
+	/*
+	 * Case 1: All TSCs matched (same frequency and offset).
+	 * Master clock should be active, PVCLOCK_TSC_STABLE_BIT set.
+	 */
+	run_vcpu_once(vcpus[0]);
+
+	ret = __vcpu_ioctl(vcpus[0], KVM_GET_CLOCK_GUEST, &pvti);
+	TEST_ASSERT(!ret, "GET_CLOCK_GUEST should succeed with matched TSCs");
+	TEST_ASSERT(pvti.flags & PVCLOCK_TSC_STABLE_BIT,
+		    "PVCLOCK_TSC_STABLE_BIT should be set with matched TSCs");
+
+	/*
+	 * Case 2: Different TSC offset, same frequency.
+	 * Master clock should still be active (frequency matches), but
+	 * PVCLOCK_TSC_STABLE_BIT should be cleared (offsets differ).
+	 */
+	set_tsc_offset(vcpus[1], 12345678);
+	run_vcpu_once(vcpus[1]);
+	run_vcpu_once(vcpus[0]);
+
+	ret = __vcpu_ioctl(vcpus[0], KVM_GET_CLOCK_GUEST, &pvti);
+	if (ret) {
+		/* Master clock disabled by offset mismatch — old kernel */
+		pr_info("  Skipping offset tests (master clock requires matched offsets)\n");
+		goto out_stable;
+	}
+	TEST_ASSERT(!(pvti.flags & PVCLOCK_TSC_STABLE_BIT),
+		    "PVCLOCK_TSC_STABLE_BIT should be clear with offset-mismatched TSCs");
+
+	/*
+	 * Case 3: Different TSC frequency.
+	 * Master clock should be disabled entirely.
+	 */
+	vcpu_ioctl(vcpus[1], KVM_SET_TSC_KHZ,
+		   (void *)(unsigned long)(__vcpu_ioctl(vcpus[1], KVM_GET_TSC_KHZ, NULL) / 2));
+	/* Write TSC to trigger kvm_synchronize_tsc / kvm_track_tsc_matching */
+	vcpu_set_msr(vcpus[1], MSR_IA32_TSC, 0);
+	run_vcpu_once(vcpus[1]);
+
+	ret = __vcpu_ioctl(vcpus[0], KVM_GET_CLOCK_GUEST, &pvti);
+	TEST_ASSERT(ret && errno == EINVAL,
+		    "GET_CLOCK_GUEST should fail with frequency-mismatched TSCs, got %d (errno %d)",
+		    ret, errno);
+
+out_stable:
+	kvm_vm_free(vm);
+}
+
+static void test_clock_guest_with_offsets(void)
+{
+	struct pvclock_vcpu_time_info pvti0, pvti1, pvti1_after;
+	struct kvm_vcpu *vcpus[2];
+	struct kvm_vm *vm;
+	int64_t delta;
+	int ret;
+
+	pr_info("Testing KVM_[GS]ET_CLOCK_GUEST with different TSC offsets\n");
+
+	vm = vm_create_with_vcpus(2, guest_code_stable_bit, vcpus);
+	configure_pvclock(vm);
+
+	/* Set different TSC offsets on the two vCPUs */
+	set_tsc_offset(vcpus[0], 0);
+	set_tsc_offset(vcpus[1], 1000000000ull);
+
+	/* Run both to establish kvmclock */
+	run_vcpu_once(vcpus[0]);
+	run_vcpu_once(vcpus[1]);
+
+	/* GET_CLOCK_GUEST on both — should succeed (master clock active) */
+	ret = __vcpu_ioctl(vcpus[0], KVM_GET_CLOCK_GUEST, &pvti0);
+	if (ret) {
+		pr_info("  Skipping (master clock requires matched offsets on this kernel)\n");
+		kvm_vm_free(vm);
+		return;
+	}
+	ret = __vcpu_ioctl(vcpus[1], KVM_GET_CLOCK_GUEST, &pvti1);
+	TEST_ASSERT(!ret, "GET_CLOCK_GUEST on vcpu1 failed");
+
+	/* The tsc_timestamps should differ (different offsets) */
+	TEST_ASSERT(pvti0.tsc_timestamp != pvti1.tsc_timestamp,
+		    "tsc_timestamps should differ with different offsets");
+
+	/* Sleep to let time elapse, then restore vcpu0's clock */
+	sleep(1);
+	vcpu_ioctl(vcpus[0], KVM_SET_CLOCK_GUEST, &pvti0);
+
+	/* Run vcpu0 to process the clock update */
+	run_vcpu_once(vcpus[0]);
+
+	/* GET_CLOCK_GUEST on vcpu1 — should reflect the correction */
+	ret = __vcpu_ioctl(vcpus[1], KVM_GET_CLOCK_GUEST, &pvti1_after);
+	TEST_ASSERT(!ret, "GET_CLOCK_GUEST on vcpu1 after SET failed");
+
+	/*
+	 * After SET on vcpu0, verify the correction worked by getting
+	 * the clock on vcpu0 again. The mul/shift should be the same,
+	 * and computing kvmclock at the same TSC should give the same
+	 * result as the original (within ±2ns).
+	 */
+	{
+		struct pvclock_vcpu_time_info pvti0_after;
+		uint64_t tsc_now, clk_from_old, clk_from_new;
+
+		ret = __vcpu_ioctl(vcpus[0], KVM_GET_CLOCK_GUEST, &pvti0_after);
+		TEST_ASSERT(!ret, "GET_CLOCK_GUEST on vcpu0 after SET failed");
+
+		tsc_now = pvti0_after.tsc_timestamp;
+		clk_from_old = pvclock_read_cycles(&pvti0, tsc_now);
+		clk_from_new = pvclock_read_cycles(&pvti0_after, tsc_now);
+
+		delta = (int64_t)clk_from_new - (int64_t)clk_from_old;
+		TEST_ASSERT(delta >= -2 && delta <= 2,
+			    "clock correction delta should be <=2ns, got %ld ns",
+			    delta);
+	}
+
+	/*
+	 * Also verify that vcpu1's clock is still accessible (master
+	 * clock still active with different offsets).
+	 */
+	ret = __vcpu_ioctl(vcpus[1], KVM_GET_CLOCK_GUEST, &pvti1_after);
+	TEST_ASSERT(!ret, "GET_CLOCK_GUEST on vcpu1 after SET failed");
+
+	kvm_vm_free(vm);
+}
-- 
2.54.0


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

* [PATCH v5 06/34] KVM: x86: Explicitly disable TSC scaling without CONSTANT_TSC
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (4 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation David Woodhouse
                   ` (27 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

KVM does make an attempt to cope with non-constant TSC, and has
notifiers to handle host TSC frequency changes. However, it *only*
adjusts the KVM clock, and doesn't adjust TSC frequency scaling when
the host changes.

This is presumably because non-constant TSCs were fixed in hardware
long before TSC scaling was implemented, so there should never be real
CPUs which have TSC scaling but *not* CONSTANT_TSC.

Such a combination could potentially happen in some odd L1 nesting
environment, but it isn't worth trying to support it. Just make the
dependency explicit.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/svm/svm.c | 3 ++-
 arch/x86/kvm/vmx/vmx.c | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index e7fdd7a9c280..7817752533fe 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5546,7 +5546,8 @@ static __init int svm_hardware_setup(void)
 				     XFEATURE_MASK_BNDCSR);
 
 	if (tsc_scaling) {
-		if (!boot_cpu_has(X86_FEATURE_TSCRATEMSR)) {
+		if (!boot_cpu_has(X86_FEATURE_TSCRATEMSR) ||
+		    !boot_cpu_has(X86_FEATURE_CONSTANT_TSC)) {
 			tsc_scaling = false;
 		} else {
 			pr_info("TSC scaling supported\n");
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index a29896a9ef14..ed207cc7692d 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8672,7 +8672,7 @@ __init int vmx_hardware_setup(void)
 	if (!enable_apicv || !cpu_has_vmx_ipiv())
 		enable_ipiv = false;
 
-	if (cpu_has_vmx_tsc_scaling())
+	if (cpu_has_vmx_tsc_scaling() && boot_cpu_has(X86_FEATURE_CONSTANT_TSC))
 		kvm_caps.has_tsc_control = true;
 
 	kvm_caps.max_tsc_scaling_ratio = KVM_VMX_TSC_MULTIPLIER_MAX;
-- 
2.54.0


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

* [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (5 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 06/34] KVM: x86: Explicitly disable TSC scaling without CONSTANT_TSC David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 16:27   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration David Woodhouse
                   ` (26 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Previously, the master clock was only activated when the first vCPU
processed KVM_REQ_MASTERCLOCK_UPDATE during KVM_RUN. This meant that
KVM_GET_CLOCK could not return the host_tsc field until after the
first KVM_RUN, making it impossible for userspace to follow the
documented TSC migration procedure without a dummy vCPU run.

Fix this by calling kvm_update_masterclock() directly from
kvm_arch_vcpu_postcreate(), after kvm_synchronize_tsc() has already
set all_vcpus_matched_freq. This ensures the master clock is active
immediately, and KVM_GET_CLOCK returns a valid {host_tsc, realtime}
pair as soon as a vCPU exists.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index b7e5f6e3dc6c..c1897d939da9 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -13098,6 +13098,8 @@ void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu)
 		return;
 	vcpu_load(vcpu);
 	kvm_synchronize_tsc(vcpu, NULL);
+	if (!vcpu->kvm->arch.use_master_clock)
+		kvm_update_masterclock(vcpu->kvm);
 	vcpu_put(vcpu);
 
 	/* poll control enabled by default */
-- 
2.54.0


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

* [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (6 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 16:39   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 09/34] KVM: x86: Avoid NTP frequency skew for KVM clock on 32-bit host David Woodhouse
                   ` (25 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The documentation on TSC migration using KVM_VCPU_TSC_OFFSET is woefully
inadequate. It ignores TSC scaling, and ignores the fact that the host
TSC may differ from one host to the next (and in fact because of the way
the kernel calibrates it, it generally differs from one boot to the next
even on the same hardware).

Add KVM_VCPU_TSC_SCALE to extract the actual scale ratio and frac_bits,
and attempt to document the process that userspace needs to follow to
preserve the TSC across migration. Add a self test to function as an
exemplar.

Only enumerate KVM_VCPU_TSC_SCALE when kvm_caps.has_tsc_control is true,
since the scaling ratio is only meaningful when hardware TSC scaling is
supported.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 Documentation/virt/kvm/devices/vcpu.rst       | 119 ++++--
 arch/x86/include/uapi/asm/kvm.h               |   6 +
 arch/x86/kvm/x86.c                            |  22 +
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../kvm/x86/pvclock_migration_test.c          | 382 ++++++++++++++++++
 5 files changed, 500 insertions(+), 30 deletions(-)
 create mode 100644 tools/testing/selftests/kvm/x86/pvclock_migration_test.c

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index 5e3805820010..167aa4140d30 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -243,7 +243,10 @@ Returns:
 Specifies the guest's TSC offset relative to the host's TSC. The guest's
 TSC is then derived by the following equation:
 
-  guest_tsc = host_tsc + KVM_VCPU_TSC_OFFSET
+  guest_tsc = ((host_tsc * tsc_scale_ratio) >> tsc_scale_bits) + KVM_VCPU_TSC_OFFSET
+
+The values of tsc_scale_ratio and tsc_scale_bits can be obtained using
+the KVM_VCPU_TSC_SCALE attribute.
 
 This attribute is useful to adjust the guest's TSC on live migration,
 so that the TSC counts the time during which the VM was paused. The
@@ -251,44 +254,100 @@ following describes a possible algorithm to use for this purpose.
 
 From the source VMM process:
 
-1. Invoke the KVM_GET_CLOCK ioctl to record the host TSC (tsc_src),
+1. Invoke the KVM_GET_CLOCK ioctl to record the host TSC (host_tsc_src),
    kvmclock nanoseconds (guest_src), and host CLOCK_REALTIME nanoseconds
-   (host_src).
+   (time_src) at a given moment (Tsrc).
+
+2. For each vCPU[i]:
+
+   a. Read the KVM_VCPU_TSC_OFFSET attribute to record the guest TSC offset
+      (ofs_src[i]).
 
-2. Read the KVM_VCPU_TSC_OFFSET attribute for every vCPU to record the
-   guest TSC offset (ofs_src[i]).
+   b. Read the KVM_VCPU_TSC_SCALE attribute to record the guest TSC scaling
+      ratio (ratio_src[i], frac_bits_src[i]).
 
-3. Invoke the KVM_GET_TSC_KHZ ioctl to record the frequency of the
-   guest's TSC (freq).
+   c. Use host_tsc_src and the scaling/offset factors to calculate this
+      vCPU's TSC at time Tsrc:
+
+      tsc_src[i] = ((host_tsc_src * ratio_src[i]) >> frac_bits_src[i]) + ofs_src[i]
+
+3. Invoke the KVM_GET_CLOCK_GUEST ioctl on the boot vCPU to return the KVM
+   clock as a function of the guest TSC (pvti_src). (This ioctl may not
+   succeed if the host and guest TSCs are not consistent and well-behaved.)
 
 From the destination VMM process:
 
-4. Invoke the KVM_SET_CLOCK ioctl, providing the source nanoseconds from
-   kvmclock (guest_src) and CLOCK_REALTIME (host_src) in their respective
-   fields.  Ensure that the KVM_CLOCK_REALTIME flag is set in the provided
-   structure.
+4. Before creating the vCPUs, invoke the KVM_SET_TSC_KHZ ioctl on the VM, to
+   set the scaled frequency of the guest's TSC (freq).
+
+5. Invoke the KVM_GET_CLOCK ioctl to record the host TSC (host_tsc_dst) and
+   host CLOCK_REALTIME nanoseconds (time_dst) at a given moment (Tdst).
+
+6. Calculate the number of nanoseconds elapsed between Tsrc and Tdst:
+
+   ΔT = time_dst - time_src
+
+7. As each vCPU[i] is created:
+
+   a. Read the KVM_VCPU_TSC_SCALE attribute to record the guest TSC scaling
+      ratio (ratio_dst[i], frac_bits_dst[i]).
+
+   b. Calculate the intended guest TSC value at time Tdst:
+
+      tsc_dst[i] = tsc_src[i] + (ΔT * freq[i])
 
-   KVM will advance the VM's kvmclock to account for elapsed time since
-   recording the clock values.  Note that this will cause problems in
-   the guest (e.g., timeouts) unless CLOCK_REALTIME is synchronized
-   between the source and destination, and a reasonably short time passes
-   between the source pausing the VMs and the destination executing
-   steps 4-7.
+   c. Use host_tsc_dst and the scaling factors to calculate this vCPU's
+      raw scaled TSC at time Tdst without offsetting:
+
+      raw_dst[i] = ((host_tsc_dst * ratio_dst[i]) >> frac_bits_dst[i])
+
+   d. Calculate ofs_dst[i] = tsc_dst[i] - raw_dst[i] and set the resulting
+      offset using the KVM_VCPU_TSC_OFFSET attribute.
+
+8. If pvti_src was provided, invoke the KVM_SET_CLOCK_GUEST ioctl on the boot
+   vCPU to restore the KVM clock as a precise function of the guest TSC.
+
+9. If KVM_SET_CLOCK_GUEST was not available or failed (e.g. because the
+   master clock is not active), fall back to the KVM_SET_CLOCK ioctl,
+   providing the source nanoseconds from kvmclock (guest_src) and
+   CLOCK_REALTIME (time_src) in their respective fields. Ensure that the
+   KVM_CLOCK_REALTIME flag is set in the provided structure.
+
+   KVM will restore the VM's kvmclock, accounting for elapsed time since
+   the clock values were recorded. Note that this will cause problems in
+   the guest (e.g., timeouts) unless CLOCK_REALTIME is synchronized between
+   the source and destination, and a reasonably short time passes between
+   the source pausing the VMs and the destination resuming them.
+   Due to the KVM_[SG]ET_CLOCK API using CLOCK_REALTIME instead of
+   CLOCK_TAI, leap seconds during the migration may also introduce errors.
+
+4.2 ATTRIBUTE: KVM_VCPU_TSC_SCALE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:Parameters: struct kvm_vcpu_tsc_scale
+
+Returns:
+
+	 ======= ======================================
+	 -EFAULT Error reading the provided parameter
+		 address.
+	 -ENXIO  Attribute not supported (no TSC scaling)
+	 -EINVAL Invalid request to write the attribute
+	 ======= ======================================
 
-5. Invoke the KVM_GET_CLOCK ioctl to record the host TSC (tsc_dest) and
-   kvmclock nanoseconds (guest_dest).
+This read-only attribute reports the guest's TSC scaling factor, in the form
+of a fixed-point number represented by the following structure::
 
-6. Adjust the guest TSC offsets for every vCPU to account for (1) time
-   elapsed since recording state and (2) difference in TSCs between the
-   source and destination machine:
+  struct kvm_vcpu_tsc_scale {
+	__u64 tsc_ratio;
+	__u64 tsc_frac_bits;
+  };
 
-   ofs_dst[i] = ofs_src[i] -
-     (guest_src - guest_dest) * freq +
-     (tsc_src - tsc_dest)
+The tsc_frac_bits field indicates the location of the fixed point, such that
+host TSC values are converted to guest TSC using the formula:
 
-   ("ofs[i] + tsc - guest * freq" is the guest TSC value corresponding to
-   a time of 0 in kvmclock.  The above formula ensures that it is the
-   same on the destination as it was on the source).
+  guest_tsc = ((host_tsc * tsc_ratio) >> tsc_frac_bits) + offset
 
-7. Write the KVM_VCPU_TSC_OFFSET attribute for every vCPU with the
-   respective value derived in the previous step.
+Userspace can use this to precisely calculate the guest TSC from the host
+TSC at any given moment. This is needed for accurate migration of guests,
+as described in the documentation for the KVM_VCPU_TSC_OFFSET attribute.
diff --git a/arch/x86/include/uapi/asm/kvm.h b/arch/x86/include/uapi/asm/kvm.h
index 5f2b30d0405c..384be9a53395 100644
--- a/arch/x86/include/uapi/asm/kvm.h
+++ b/arch/x86/include/uapi/asm/kvm.h
@@ -961,6 +961,12 @@ struct kvm_hyperv_eventfd {
 /* for KVM_{GET,SET,HAS}_DEVICE_ATTR */
 #define KVM_VCPU_TSC_CTRL 0 /* control group for the timestamp counter (TSC) */
 #define   KVM_VCPU_TSC_OFFSET 0 /* attribute for the TSC offset */
+#define   KVM_VCPU_TSC_SCALE  1 /* attribute for TSC scaling factor */
+
+struct kvm_vcpu_tsc_scale {
+	__u64 tsc_ratio;
+	__u64 tsc_frac_bits;
+};
 
 /* x86-specific KVM_EXIT_HYPERCALL flags. */
 #define KVM_EXIT_HYPERCALL_LONG_MODE	_BITULL(0)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c1897d939da9..6337f9b9d7ac 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -5930,6 +5930,9 @@ static int kvm_arch_tsc_has_attr(struct kvm_vcpu *vcpu,
 	case KVM_VCPU_TSC_OFFSET:
 		r = 0;
 		break;
+	case KVM_VCPU_TSC_SCALE:
+		r = kvm_caps.has_tsc_control ? 0 : -ENXIO;
+		break;
 	default:
 		r = -ENXIO;
 	}
@@ -5950,6 +5953,22 @@ static int kvm_arch_tsc_get_attr(struct kvm_vcpu *vcpu,
 			break;
 		r = 0;
 		break;
+	case KVM_VCPU_TSC_SCALE: {
+		struct kvm_vcpu_tsc_scale scale;
+
+		if (!kvm_caps.has_tsc_control) {
+			r = -ENXIO;
+			break;
+		}
+
+		scale.tsc_ratio = vcpu->arch.l1_tsc_scaling_ratio;
+		scale.tsc_frac_bits = kvm_caps.tsc_scaling_ratio_frac_bits;
+		r = -EFAULT;
+		if (copy_to_user(uaddr, &scale, sizeof(scale)))
+			break;
+		r = 0;
+		break;
+	}
 	default:
 		r = -ENXIO;
 	}
@@ -5989,6 +6008,9 @@ static int kvm_arch_tsc_set_attr(struct kvm_vcpu *vcpu,
 		r = 0;
 		break;
 	}
+	case KVM_VCPU_TSC_SCALE:
+		r = -EINVAL; /* Read only */
+		break;
 	default:
 		r = -ENXIO;
 	}
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index fb935ae3bf38..90568ab631d7 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -106,6 +106,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
 TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
 TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
 TEST_GEN_PROGS_x86 += x86/pvclock_test
+TEST_GEN_PROGS_x86 += x86/pvclock_migration_test
 TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
 TEST_GEN_PROGS_x86 += x86/set_sregs_test
 TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test
diff --git a/tools/testing/selftests/kvm/x86/pvclock_migration_test.c b/tools/testing/selftests/kvm/x86/pvclock_migration_test.c
new file mode 100644
index 000000000000..6a7eaf627d1a
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/pvclock_migration_test.c
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test KVM clock precision across simulated live migration.
+ *
+ * Verifies that the documented TSC migration procedure (using
+ * KVM_VCPU_TSC_OFFSET, KVM_VCPU_TSC_SCALE, KVM_GET_CLOCK, and
+ * KVM_SET_CLOCK_GUEST) preserves the kvmclock's relationship to
+ * CLOCK_MONOTONIC_RAW.
+ *
+ * The test:
+ * 1. Creates a VM, runs the guest to enable kvmclock
+ * 2. Does a PTP-like ABA measurement of kvmclock vs CLOCK_MONOTONIC_RAW
+ * 3. Follows the documented migration procedure (same host, 1s pause)
+ * 4. Does the same ABA measurement on the destination VM
+ * 5. Verifies the kvmclock-vs-monotonic delta is preserved
+ */
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#include <asm/pvclock-abi.h>
+
+#define KVMCLOCK_GPA	0xc0000000ULL
+
+static void guest_code(void)
+{
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, KVMCLOCK_GPA | 1);
+	GUEST_SYNC(0);
+	GUEST_SYNC(1);
+}
+
+static uint64_t read_kvmclock_ns(struct kvm_vm *vm)
+{
+	struct kvm_clock_data data = {};
+
+	vm_ioctl(vm, KVM_GET_CLOCK, &data);
+	return data.clock;
+}
+
+static uint64_t pvclock_read_cycles(struct pvclock_vcpu_time_info *src,
+				    uint64_t tsc)
+{
+	uint64_t delta = tsc - src->tsc_timestamp;
+	uint64_t ns;
+
+	if (src->tsc_shift >= 0)
+		delta <<= src->tsc_shift;
+	else
+		delta >>= -(int32_t)src->tsc_shift;
+
+	ns = (unsigned __int128)delta * src->tsc_to_system_mul >> 32;
+	return src->system_time + ns;
+}
+
+/*
+ * ABA measurement: read CLOCK_MONOTONIC_RAW, kvmclock, CLOCK_MONOTONIC_RAW.
+ * Repeat 3 times, keep the reading with the smallest spread.
+ */
+static void aba_reading(struct kvm_vm *vm, uint64_t *lo, uint64_t *kvm_ns,
+			uint64_t *hi)
+{
+	uint64_t best_spread = UINT64_MAX;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		struct timespec ts1, ts2;
+		uint64_t m1, m2, clk;
+
+		clock_gettime(CLOCK_MONOTONIC_RAW, &ts1);
+		clk = read_kvmclock_ns(vm);
+		clock_gettime(CLOCK_MONOTONIC_RAW, &ts2);
+
+		m1 = ts1.tv_sec * 1000000000ULL + ts1.tv_nsec;
+		m2 = ts2.tv_sec * 1000000000ULL + ts2.tv_nsec;
+
+		if (m2 - m1 < best_spread) {
+			best_spread = m2 - m1;
+			*lo = m1;
+			*kvm_ns = clk;
+			*hi = m2;
+		}
+	}
+}
+
+static struct kvm_vm *create_vm(struct kvm_vcpu **vcpu)
+{
+	struct kvm_vm *vm = vm_create_with_one_vcpu(vcpu, guest_code);
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    KVMCLOCK_GPA, 1, 1, 0);
+	virt_map(vm, KVMCLOCK_GPA, KVMCLOCK_GPA, 1);
+	return vm;
+}
+
+int main(void)
+{
+	struct pvclock_vcpu_time_info pvti_src;
+	struct kvm_clock_data clock_src, clock_dst;
+	struct kvm_vcpu_tsc_scale scale_src, scale_dst;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	uint64_t mono_before, kvm_before, kvm_after;
+	int64_t delta_before;
+	uint64_t ofs_src, tsc_src, tsc_dst, raw_dst, ofs_dst;
+	uint64_t host_tsc_src, host_tsc_dst;
+	uint64_t time_src, time_dst;
+	int64_t delta_t;
+	uint32_t freq_khz = 1500000; /* 1.5 GHz — forces TSC scaling */
+	int ret;
+
+	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
+
+	/* === SOURCE SIDE === */
+	pr_info("=== Source VM ===\n");
+	vm = create_vm(&vcpu);
+
+	/* Set guest TSC frequency (may trigger scaling) */
+	vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)(unsigned long)freq_khz);
+
+	/* Run guest to enable kvmclock */
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+
+	/* ABA measurement: kvmclock vs CLOCK_MONOTONIC_RAW */
+	uint64_t src_mono_lo, src_mono_hi;
+	aba_reading(vm, &src_mono_lo, &kvm_before, &src_mono_hi);
+	mono_before = (src_mono_lo + src_mono_hi) / 2;
+	delta_before = (int64_t)(kvm_before - mono_before);
+	pr_info("  kvmclock - MONOTONIC_RAW = %" PRId64 " ns (±%" PRIu64 " ns)\n",
+		delta_before, (src_mono_hi - src_mono_lo) / 2);
+
+	/* Step 1: KVM_GET_CLOCK for atomic {host_tsc, realtime} */
+	memset(&clock_src, 0, sizeof(clock_src));
+	clock_src.flags = KVM_CLOCK_REALTIME;
+	vm_ioctl(vm, KVM_GET_CLOCK, &clock_src);
+	host_tsc_src = clock_src.host_tsc;
+	time_src = clock_src.realtime;
+
+	/* Step 2: Save TSC offset and scale */
+	{
+		struct kvm_device_attr attr = {
+			.group = KVM_VCPU_TSC_CTRL,
+			.attr = KVM_VCPU_TSC_OFFSET,
+			.addr = (uint64_t)(uintptr_t)&ofs_src,
+		};
+		vcpu_ioctl(vcpu, KVM_GET_DEVICE_ATTR, &attr);
+	}
+	{
+		struct kvm_device_attr attr = {
+			.group = KVM_VCPU_TSC_CTRL,
+			.attr = KVM_VCPU_TSC_SCALE,
+			.addr = (uint64_t)(uintptr_t)&scale_src,
+		};
+		memset(&scale_src, 0, sizeof(scale_src));
+		__vcpu_ioctl(vcpu, KVM_GET_DEVICE_ATTR, &attr);
+	}
+
+	/* Compute guest TSC at Tsrc */
+	if (scale_src.tsc_frac_bits)
+		tsc_src = ((unsigned __int128)host_tsc_src * scale_src.tsc_ratio
+			   >> scale_src.tsc_frac_bits) + ofs_src;
+	else
+		tsc_src = host_tsc_src + ofs_src;
+
+	/* Step 3: KVM_GET_CLOCK_GUEST */
+	ret = __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &pvti_src);
+	TEST_ASSERT(!ret, "KVM_GET_CLOCK_GUEST failed");
+
+	pr_info("  TSC freq=%u kHz, offset=%" PRId64 "\n", freq_khz, (int64_t)ofs_src);
+
+	kvm_vm_release(vm);
+
+	/* === PAUSE (simulate migration) === */
+	pr_info("=== Pausing 1 second ===\n");
+	sleep(1);
+
+	/* === DESTINATION SIDE === */
+	pr_info("=== Destination VM ===\n");
+	vm = create_vm(&vcpu);
+
+	/* Step 4: KVM_SET_TSC_KHZ */
+	vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)(unsigned long)freq_khz);
+
+	/* Step 5: KVM_GET_CLOCK for atomic {host_tsc, realtime} pair.
+	 * Master clock is active from vCPU creation.
+	 */
+	memset(&clock_dst, 0, sizeof(clock_dst));
+	vm_ioctl(vm, KVM_GET_CLOCK, &clock_dst);
+	host_tsc_dst = clock_dst.host_tsc;
+	time_dst = clock_dst.realtime;
+
+	/* Step 6: ΔT */
+	delta_t = (int64_t)(time_dst - time_src);
+
+	/* Step 7: Compute destination offset */
+	{
+		struct kvm_device_attr attr = {
+			.group = KVM_VCPU_TSC_CTRL,
+			.attr = KVM_VCPU_TSC_SCALE,
+			.addr = (uint64_t)(uintptr_t)&scale_dst,
+		};
+		memset(&scale_dst, 0, sizeof(scale_dst));
+		__vcpu_ioctl(vcpu, KVM_GET_DEVICE_ATTR, &attr);
+	}
+
+	tsc_dst = tsc_src + (uint64_t)((int64_t)freq_khz * 1000 * delta_t / 1000000000LL);
+
+	if (scale_dst.tsc_frac_bits)
+		raw_dst = (unsigned __int128)host_tsc_dst * scale_dst.tsc_ratio
+			  >> scale_dst.tsc_frac_bits;
+	else
+		raw_dst = host_tsc_dst;
+
+	ofs_dst = tsc_dst - raw_dst;
+
+	/*
+	 * The TSC offset delta introduced by using CLOCK_REALTIME to
+	 * estimate elapsed time. On same host, the correct offset is
+	 * ofs_src; the difference is the CLOCK_REALTIME-vs-TSC error.
+	 */
+	int64_t tsc_ofs_delta = (int64_t)(ofs_dst - ofs_src);
+	int64_t tsc_ofs_delta_ns = tsc_ofs_delta * 1000000000LL / ((int64_t)freq_khz * 1000);
+	pr_info("  Destination TSC offset=%" PRId64
+		", imprecision from CLOCK_REALTIME: %" PRId64 " cycles = %"
+		PRId64 " ns\n", (int64_t)ofs_dst, tsc_ofs_delta, tsc_ofs_delta_ns);
+
+	/* Set TSC offset */
+	{
+		struct kvm_device_attr attr = {
+			.group = KVM_VCPU_TSC_CTRL,
+			.attr = KVM_VCPU_TSC_OFFSET,
+			.addr = (uint64_t)(uintptr_t)&ofs_dst,
+		};
+		vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &attr);
+	}
+
+	/* Step 8: KVM_SET_CLOCK_GUEST */
+	ret = __vcpu_ioctl(vcpu, KVM_SET_CLOCK_GUEST, &pvti_src);
+	TEST_ASSERT(!ret, "KVM_SET_CLOCK_GUEST failed: errno %d", errno);
+
+	/* Run guest to update pvclock page on destination */
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+
+	/* ABA measurement on destination */
+	uint64_t mono_lo, mono_hi;
+	aba_reading(vm, &mono_lo, &kvm_after, &mono_hi);
+
+	/*
+	 * The kvmclock is tied to the guest TSC via SET_CLOCK_GUEST.
+	 * The guest TSC is offset from the correct value by tsc_ofs_delta_ns
+	 * (due to CLOCK_REALTIME imprecision). So the kvmclock should be
+	 * offset from CLOCK_MONOTONIC_RAW by exactly:
+	 *   (original delta) + tsc_ofs_delta_ns
+	 *
+	 * The "original delta" has uncertainty from the source ABA spread,
+	 * and the measurement has uncertainty from the destination ABA spread.
+	 * Verify the expected value falls within the combined bounds.
+	 */
+	int64_t delta_before_lo = (int64_t)(kvm_before - src_mono_hi);
+	int64_t delta_before_hi = (int64_t)(kvm_before - src_mono_lo);
+	int64_t expected_lo = delta_before_lo + tsc_ofs_delta_ns;
+	int64_t expected_hi = delta_before_hi + tsc_ofs_delta_ns;
+	int64_t actual_lo = (int64_t)(kvm_after - mono_hi);
+	int64_t actual_hi = (int64_t)(kvm_after - mono_lo);
+
+	/* Show the shift relative to the source measurement */
+	int64_t expected_mid = tsc_ofs_delta_ns;
+	int64_t expected_err = (int64_t)(src_mono_hi - src_mono_lo) / 2;
+	int64_t actual_mid = ((actual_lo + actual_hi) / 2) - delta_before;
+	int64_t actual_err = (int64_t)(mono_hi - mono_lo) / 2;
+	pr_info("  kvmclock-mono shift: expected %" PRId64 " ns (±%" PRId64
+		"), measured %" PRId64 " ns (±%" PRId64 ")\n",
+		expected_mid, expected_err, actual_mid, actual_err);
+
+	/* The ranges must overlap */
+	TEST_ASSERT(expected_hi >= actual_lo && expected_lo <= actual_hi,
+		    "Ranges don't overlap: expected [%" PRId64 ", %" PRId64
+		    "] measured [%" PRId64 ", %" PRId64 "]",
+		    expected_lo, expected_hi, actual_lo, actual_hi);
+
+	/*
+	 * Direct pvclock verification: read the destination pvclock page
+	 * and verify that computing kvmclock from pvti_src and pvti_dst
+	 * at the same guest TSC gives the same result.
+	 *
+	 * Get an atomic {host_tsc, kvmclock} pair, scale host_tsc to
+	 * guest TSC using KVM_VCPU_TSC_SCALE, then compute kvmclock
+	 * from both pvclock structs.
+	 */
+	struct kvm_clock_data clock_now = {};
+	vm_ioctl(vm, KVM_GET_CLOCK, &clock_now);
+
+	struct pvclock_vcpu_time_info *pvti_dst = addr_gpa2hva(vm, KVMCLOCK_GPA);
+	uint64_t host_tsc_now = clock_now.host_tsc;
+	uint64_t guest_tsc_now;
+
+	if (scale_dst.tsc_frac_bits)
+		guest_tsc_now = ((unsigned __int128)host_tsc_now *
+				 scale_dst.tsc_ratio >> scale_dst.tsc_frac_bits)
+				+ ofs_dst;
+	else
+		guest_tsc_now = host_tsc_now + ofs_dst;
+
+	uint64_t clk_from_src = pvclock_read_cycles(&pvti_src, guest_tsc_now);
+	uint64_t clk_from_dst = pvclock_read_cycles(pvti_dst, guest_tsc_now);
+	int64_t pvclock_delta = (int64_t)(clk_from_src - clk_from_dst);
+
+	pr_info("  Pvclock direct: src=%" PRIu64 " dst=%" PRIu64
+		" delta=%" PRId64 " ns\n", clk_from_src, clk_from_dst, pvclock_delta);
+	pr_info("  KVM_GET_CLOCK:  %" PRIu64 " ns\n", (uint64_t)clock_now.clock);
+
+	TEST_ASSERT(pvclock_delta >= -1 && pvclock_delta <= 1,
+		    "pvclock src vs dst disagree by %" PRId64 " ns", pvclock_delta);
+
+	/*
+	 * Tight ABA: compare pvclock_read() directly (no ioctl) against
+	 * CLOCK_MONOTONIC_RAW. The spread should be much smaller since
+	 * there's no syscall between the two clock_gettime calls — just
+	 * rdtsc + userspace mul/shift.
+	 */
+	uint64_t tight_mono_lo = 0, tight_mono_hi = 0, tight_kvm = 0;
+	uint64_t tight_best_spread = UINT64_MAX;
+	for (int i = 0; i < 3; i++) {
+		struct timespec ts1, ts2;
+		uint64_t m1, m2, tsc, clk;
+
+		clock_gettime(CLOCK_MONOTONIC_RAW, &ts1);
+		tsc = rdtsc();
+		clock_gettime(CLOCK_MONOTONIC_RAW, &ts2);
+
+		m1 = ts1.tv_sec * 1000000000ULL + ts1.tv_nsec;
+		m2 = ts2.tv_sec * 1000000000ULL + ts2.tv_nsec;
+
+		/* Scale host TSC to guest TSC */
+		if (scale_dst.tsc_frac_bits)
+			tsc = ((unsigned __int128)tsc * scale_dst.tsc_ratio
+			       >> scale_dst.tsc_frac_bits) + ofs_dst;
+		else
+			tsc += ofs_dst;
+
+		clk = pvclock_read_cycles(pvti_dst, tsc);
+
+		if (m2 - m1 < tight_best_spread) {
+			tight_best_spread = m2 - m1;
+			tight_mono_lo = m1;
+			tight_mono_hi = m2;
+			tight_kvm = clk;
+		}
+	}
+	pr_info("  Tight ABA spread: %" PRIu64 " ns (best of 3)\n", tight_best_spread);
+
+	int64_t tight_expected_lo = delta_before_lo + tsc_ofs_delta_ns;
+	int64_t tight_expected_hi = delta_before_hi + tsc_ofs_delta_ns;
+	int64_t tight_actual_lo = (int64_t)(tight_kvm - tight_mono_hi);
+	int64_t tight_actual_hi = (int64_t)(tight_kvm - tight_mono_lo);
+	int64_t tight_actual_mid = ((tight_actual_lo + tight_actual_hi) / 2) - delta_before;
+	int64_t tight_actual_err = (int64_t)(tight_mono_hi - tight_mono_lo) / 2;
+
+	pr_info("  Tight kvmclock-mono shift: expected %" PRId64
+		" ns (±%" PRId64 "), measured %" PRId64 " ns (±%" PRId64 ")\n",
+		expected_mid, expected_err, tight_actual_mid, tight_actual_err);
+
+	TEST_ASSERT(tight_expected_hi >= tight_actual_lo &&
+		    tight_expected_lo <= tight_actual_hi,
+		    "Tight ABA ranges don't overlap");
+
+	kvm_vm_release(vm);
+	pr_info("PASS: kvmclock offset matches TSC delta from CLOCK_REALTIME"
+		" (%" PRId64 " ns) within ABA bounds\n", tsc_ofs_delta_ns);
+	return 0;
+}
-- 
2.54.0


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

* [PATCH v5 09/34] KVM: x86: Avoid NTP frequency skew for KVM clock on 32-bit host
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (7 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 10/34] KVM: x86: Fold __get_kvmclock() into get_kvmclock() David Woodhouse
                   ` (24 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Commit 53fafdbb8b21 ("KVM: x86: switch KVMCLOCK base to monotonic raw
clock") did so only for 64-bit hosts, by capturing the boot offset from
within the existing clocksource notifier update_pvclock_gtod().

That notifier was added in commit 16e8d74d2da9 ("KVM: x86: notifier for
clocksource changes") but only on x86_64, because its original purpose
was just to disable the "master clock" mode which is only supported on
x86_64.

Now that the notifier is used for more than disabling master clock mode,
enable it for the 32-bit build too so that get_kvmclock_base_ns() can be
unaffected by NTP sync on 32-bit too.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/x86.c | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 6337f9b9d7ac..50bd2871b051 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2342,7 +2342,6 @@ static int do_set_msr(struct kvm_vcpu *vcpu, unsigned index, u64 *data)
 	return kvm_set_msr_ignored_check(vcpu, index, *data, true);
 }
 
-#ifdef CONFIG_X86_64
 struct pvclock_clock {
 	int vclock_mode;
 	u64 cycle_last;
@@ -2400,13 +2399,6 @@ static s64 get_kvmclock_base_ns(void)
 	/* Count up from boot time, but with the frequency of the raw clock.  */
 	return ktime_to_ns(ktime_add(ktime_get_raw(), pvclock_gtod_data.offs_boot));
 }
-#else
-static s64 get_kvmclock_base_ns(void)
-{
-	/* Master clock not used, so we can just use CLOCK_BOOTTIME.  */
-	return ktime_get_boottime_ns();
-}
-#endif
 
 static void kvm_write_wall_clock(struct kvm *kvm, gpa_t wall_clock, int sec_hi_ofs)
 {
@@ -10173,6 +10165,7 @@ static void pvclock_irq_work_fn(struct irq_work *w)
 }
 
 static DEFINE_IRQ_WORK(pvclock_irq_work, pvclock_irq_work_fn);
+#endif
 
 /*
  * Notification about pvclock gtod data update.
@@ -10180,26 +10173,26 @@ static DEFINE_IRQ_WORK(pvclock_irq_work, pvclock_irq_work_fn);
 static int pvclock_gtod_notify(struct notifier_block *nb, unsigned long unused,
 			       void *priv)
 {
-	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
 	struct timekeeper *tk = priv;
 
 	update_pvclock_gtod(tk);
 
+#ifdef CONFIG_X86_64
 	/*
 	 * Disable master clock if host does not trust, or does not use,
 	 * TSC based clocksource. Delegate queue_work() to irq_work as
 	 * this is invoked with tk_core.seq write held.
 	 */
-	if (!gtod_is_based_on_tsc(gtod->clock.vclock_mode) &&
+	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode) &&
 	    atomic_read(&kvm_guest_has_master_clock) != 0)
 		irq_work_queue(&pvclock_irq_work);
+#endif
 	return 0;
 }
 
 static struct notifier_block pvclock_gtod_notifier = {
 	.notifier_call = pvclock_gtod_notify,
 };
-#endif
 
 void kvm_setup_xss_caps(void)
 {
@@ -10388,9 +10381,9 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 
 	if (pi_inject_timer == -1)
 		pi_inject_timer = housekeeping_enabled(HK_TYPE_TIMER);
-#ifdef CONFIG_X86_64
 	pvclock_gtod_register_notifier(&pvclock_gtod_notifier);
 
+#ifdef CONFIG_X86_64
 	if (hypervisor_is_type(X86_HYPER_MS_HYPERV))
 		set_hv_tscchange_cb(kvm_hyperv_tsc_notifier);
 #endif
@@ -10447,8 +10440,8 @@ void kvm_x86_vendor_exit(void)
 					    CPUFREQ_TRANSITION_NOTIFIER);
 		cpuhp_remove_state_nocalls(CPUHP_AP_X86_KVM_CLK_ONLINE);
 	}
-#ifdef CONFIG_X86_64
 	pvclock_gtod_unregister_notifier(&pvclock_gtod_notifier);
+#ifdef CONFIG_X86_64
 	irq_work_sync(&pvclock_irq_work);
 	cancel_work_sync(&pvclock_gtod_work);
 #endif
-- 
2.54.0


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

* [PATCH v5 10/34] KVM: x86: Fold __get_kvmclock() into get_kvmclock()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (8 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 09/34] KVM: x86: Avoid NTP frequency skew for KVM clock on 32-bit host David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 11/34] KVM: x86: Restructure get_kvmclock() David Woodhouse
                   ` (23 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

There is no need for the separate __get_kvmclock() helper; just inline
its body into get_kvmclock() within the seqcount retry loop.

No functional change.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 63 +++++++++++++++++++++-------------------------
 1 file changed, 28 insertions(+), 35 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 50bd2871b051..fce898811fe7 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3200,50 +3200,43 @@ static unsigned long get_cpu_tsc_khz(void)
 		return __this_cpu_read(cpu_tsc_khz);
 }
 
-/* Called within read_seqcount_begin/retry for kvm->pvclock_sc.  */
-static void __get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
+static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
 {
 	struct kvm_arch *ka = &kvm->arch;
 	struct pvclock_vcpu_time_info hv_clock;
+	unsigned int seq;
 
-	/* both __this_cpu_read() and rdtsc() should be on the same cpu */
-	get_cpu();
+	do {
+		seq = read_seqcount_begin(&ka->pvclock_sc);
 
-	data->flags = 0;
-	if (ka->use_master_clock &&
-	    (static_cpu_has(X86_FEATURE_CONSTANT_TSC) || __this_cpu_read(cpu_tsc_khz))) {
+		/* both __this_cpu_read() and rdtsc() should be on the same cpu */
+		get_cpu();
+
+		data->flags = 0;
+		if (ka->use_master_clock &&
+		    (static_cpu_has(X86_FEATURE_CONSTANT_TSC) || __this_cpu_read(cpu_tsc_khz))) {
 #ifdef CONFIG_X86_64
-		struct timespec64 ts;
+			struct timespec64 ts;
 
-		if (kvm_get_walltime_and_clockread(&ts, &data->host_tsc)) {
-			data->realtime = ts.tv_nsec + NSEC_PER_SEC * ts.tv_sec;
-			data->flags |= KVM_CLOCK_REALTIME | KVM_CLOCK_HOST_TSC;
-		} else
+			if (kvm_get_walltime_and_clockread(&ts, &data->host_tsc)) {
+				data->realtime = ts.tv_nsec + NSEC_PER_SEC * ts.tv_sec;
+				data->flags |= KVM_CLOCK_REALTIME | KVM_CLOCK_HOST_TSC;
+			} else
 #endif
-		data->host_tsc = rdtsc();
-
-		data->flags |= KVM_CLOCK_TSC_STABLE;
-		hv_clock.tsc_timestamp = ka->master_cycle_now;
-		hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
-		kvm_get_time_scale(NSEC_PER_SEC, get_cpu_tsc_khz() * 1000LL,
-				   &hv_clock.tsc_shift,
-				   &hv_clock.tsc_to_system_mul);
-		data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
-	} else {
-		data->clock = get_kvmclock_base_ns() + ka->kvmclock_offset;
-	}
-
-	put_cpu();
-}
-
-static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
-{
-	struct kvm_arch *ka = &kvm->arch;
-	unsigned seq;
+			data->host_tsc = rdtsc();
+
+			data->flags |= KVM_CLOCK_TSC_STABLE;
+			hv_clock.tsc_timestamp = ka->master_cycle_now;
+			hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
+			kvm_get_time_scale(NSEC_PER_SEC, get_cpu_tsc_khz() * 1000LL,
+					   &hv_clock.tsc_shift,
+					   &hv_clock.tsc_to_system_mul);
+			data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
+		} else {
+			data->clock = get_kvmclock_base_ns() + ka->kvmclock_offset;
+		}
 
-	do {
-		seq = read_seqcount_begin(&ka->pvclock_sc);
-		__get_kvmclock(kvm, data);
+		put_cpu();
 	} while (read_seqcount_retry(&ka->pvclock_sc, seq));
 }
 
-- 
2.54.0


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

* [PATCH v5 11/34] KVM: x86: Restructure get_kvmclock()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (9 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 10/34] KVM: x86: Fold __get_kvmclock() into get_kvmclock() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling David Woodhouse
                   ` (22 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Move get/put_cpu inside the use_master_clock branch since they are only
needed there (for RDTSC and get_cpu_tsc_khz() to be on the same CPU).

Simplify the use_master_clock condition: the open-coded CONSTANT_TSC ||
cpu_tsc_khz check is unnecessary since use_master_clock can only be true
when the TSC is usable.

Wrap the entire use_master_clock block in #ifdef CONFIG_X86_64, since
use_master_clock is never true on 32-bit (host_tsc_clocksource is only
set under CONFIG_X86_64).

When the clock read fails (e.g. clocksource transitioning away from
TSC), fall back to the non-master-clock path (get_kvmclock_base_ns)
rather than proceeding with uninitialised data or spinning in the
seqcount loop.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 34 +++++++++++++++++++++++-----------
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index fce898811fe7..6983a7494fcd 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3209,21 +3209,30 @@ static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
 	do {
 		seq = read_seqcount_begin(&ka->pvclock_sc);
 
-		/* both __this_cpu_read() and rdtsc() should be on the same cpu */
-		get_cpu();
-
 		data->flags = 0;
-		if (ka->use_master_clock &&
-		    (static_cpu_has(X86_FEATURE_CONSTANT_TSC) || __this_cpu_read(cpu_tsc_khz))) {
 #ifdef CONFIG_X86_64
+		if (ka->use_master_clock) {
 			struct timespec64 ts;
 
+			/*
+			 * The RDTSC and get_cpu_tsc_khz() must happen on
+			 * the same CPU.
+			 */
+			get_cpu();
+
 			if (kvm_get_walltime_and_clockread(&ts, &data->host_tsc)) {
 				data->realtime = ts.tv_nsec + NSEC_PER_SEC * ts.tv_sec;
 				data->flags |= KVM_CLOCK_REALTIME | KVM_CLOCK_HOST_TSC;
-			} else
-#endif
-			data->host_tsc = rdtsc();
+			} else {
+				/*
+				 * Clock read failed (e.g. clocksource is
+				 * transitioning away from TSC). Fall back to
+				 * the non-master-clock path rather than
+				 * spinning.
+				 */
+				put_cpu();
+				goto fallback;
+			}
 
 			data->flags |= KVM_CLOCK_TSC_STABLE;
 			hv_clock.tsc_timestamp = ka->master_cycle_now;
@@ -3232,11 +3241,14 @@ static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
 					   &hv_clock.tsc_shift,
 					   &hv_clock.tsc_to_system_mul);
 			data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
-		} else {
+
+			put_cpu();
+		} else
+#endif
+		{
+fallback:
 			data->clock = get_kvmclock_base_ns() + ka->kvmclock_offset;
 		}
-
-		put_cpu();
 	} while (read_seqcount_retry(&ka->pvclock_sc, seq));
 }
 
-- 
2.54.0


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

* [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (10 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 11/34] KVM: x86: Restructure get_kvmclock() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 17:39   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 13/34] KVM: x86: Use get_kvmclock() in kvm_get_wall_clock_epoch() David Woodhouse
                   ` (21 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

When in master clock mode, the KVM clock is defined in terms of the
guest TSC. But get_kvmclock() was computing it from the host TSC
without applying TSC scaling, leading to a systemic drift from the
values the guest computes from its own TSC.

Store the VM's TSC scaling ratio in kvm_arch and precompute the
guest-TSC-based mul/shift in pvclock_update_vm_gtod_copy(). Use these
in get_kvmclock() to scale the host TSC delta to guest TSC before
converting to nanoseconds.

This avoids "definition C" of the KVM clock described in the
earlier commit "KVM: x86/xen: Do not corrupt KVM clock in
kvm_xen_shared_info_init()".

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/include/asm/kvm_host.h |  4 +++
 arch/x86/kvm/x86.c              | 52 +++++++++++++++++++++++++++++----
 2 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 37264212c7df..5348fd5ea3f3 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1490,6 +1490,7 @@ struct kvm_arch {
 	u64 last_tsc_write;
 	u32 last_tsc_khz;
 	u64 last_tsc_offset;
+	u64 last_tsc_scaling_ratio;
 	u64 cur_tsc_nsec;
 	u64 cur_tsc_write;
 	u64 cur_tsc_offset;
@@ -1504,6 +1505,9 @@ struct kvm_arch {
 	bool use_master_clock;
 	u64 master_kernel_ns;
 	u64 master_cycle_now;
+	u64 master_tsc_scaling_ratio;
+	s8  master_tsc_shift;
+	u32 master_tsc_mul;
 
 #ifdef CONFIG_KVM_HYPERV
 	struct kvm_hv hyperv;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 6983a7494fcd..7ae6a7705353 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2781,6 +2781,7 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 	kvm->arch.last_tsc_write = tsc;
 	kvm->arch.last_tsc_khz = vcpu->arch.virtual_tsc_khz;
 	kvm->arch.last_tsc_offset = offset;
+	kvm->arch.last_tsc_scaling_ratio = vcpu->arch.l1_tsc_scaling_ratio;
 
 	vcpu->arch.last_guest_tsc = tsc;
 
@@ -3109,6 +3110,8 @@ static bool kvm_get_walltime_and_clockread(struct timespec64 *ts,
  *
  */
 
+static unsigned long get_cpu_tsc_khz(void);
+
 static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 {
 #ifdef CONFIG_X86_64
@@ -3132,9 +3135,30 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 				&& !ka->backwards_tsc_observed
 				&& !ka->boot_vcpu_runs_old_kvmclock;
 
-	if (ka->use_master_clock)
+	if (ka->use_master_clock) {
+		u64 tsc_hz;
+
 		atomic_set(&kvm_guest_has_master_clock, 1);
 
+		/*
+		 * Copy the scaling ratio and precompute the mul/shift for
+		 * converting guest TSC to nanoseconds. These are used by
+		 * get_kvmclock() to compute kvmclock from the host TSC
+		 * without needing a vCPU reference.
+		 */
+		ka->master_tsc_scaling_ratio = ka->last_tsc_scaling_ratio;
+		tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
+		if (tsc_hz && kvm_caps.has_tsc_control)
+			tsc_hz = kvm_scale_tsc(tsc_hz,
+					       ka->master_tsc_scaling_ratio);
+		if (tsc_hz)
+			kvm_get_time_scale(NSEC_PER_SEC, tsc_hz,
+					   &ka->master_tsc_shift,
+					   &ka->master_tsc_mul);
+		else
+			ka->use_master_clock = false;
+	}
+
 	vclock_mode = pvclock_gtod_data.clock.vclock_mode;
 	trace_kvm_update_master_clock(ka->use_master_clock, vclock_mode,
 					vcpus_matched);
@@ -3237,10 +3261,28 @@ static void get_kvmclock(struct kvm *kvm, struct kvm_clock_data *data)
 			data->flags |= KVM_CLOCK_TSC_STABLE;
 			hv_clock.tsc_timestamp = ka->master_cycle_now;
 			hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
-			kvm_get_time_scale(NSEC_PER_SEC, get_cpu_tsc_khz() * 1000LL,
-					   &hv_clock.tsc_shift,
-					   &hv_clock.tsc_to_system_mul);
-			data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
+
+			/*
+			 * Use the precomputed guest-TSC-based mul/shift
+			 * so that the kvmclock value matches what the
+			 * guest computes from its own TSC.
+			 */
+			hv_clock.tsc_shift = ka->master_tsc_shift;
+			hv_clock.tsc_to_system_mul = ka->master_tsc_mul;
+
+			if (kvm_caps.has_tsc_control) {
+				u64 tsc_delta = data->host_tsc - ka->master_cycle_now;
+
+				tsc_delta = kvm_scale_tsc(tsc_delta,
+							  ka->master_tsc_scaling_ratio);
+				data->clock = hv_clock.system_time +
+					pvclock_scale_delta(tsc_delta,
+							    hv_clock.tsc_to_system_mul,
+							    hv_clock.tsc_shift);
+			} else {
+				data->clock = __pvclock_read_cycles(&hv_clock,
+								    data->host_tsc);
+			}
 
 			put_cpu();
 		} else
-- 
2.54.0


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

* [PATCH v5 13/34] KVM: x86: Use get_kvmclock() in kvm_get_wall_clock_epoch()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (11 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas David Woodhouse
                   ` (20 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Now that get_kvmclock() correctly handles TSC scaling and captures both
wallclock and kvmclock from the same TSC reading,
kvm_get_wall_clock_epoch() can simply call it instead of duplicating
the pvclock computation.

This eliminates the last instance of the "definition C" kvmclock
calculation that computed nanoseconds directly from the host TSC
without accounting for guest TSC scaling.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 59 +++++++---------------------------------------
 1 file changed, 9 insertions(+), 50 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 7ae6a7705353..fc9366b83912 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3494,63 +3494,22 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
  * wallclock and kvmclock times, and subtracting one from the other.
  *
  * Fall back to using their values at slightly different moments by
- * calling ktime_get_real_ns() and get_kvmclock_ns() separately.
+ * calling ktime_get_real_ns() and get_kvmclock() separately.
  */
 uint64_t kvm_get_wall_clock_epoch(struct kvm *kvm)
 {
-#ifdef CONFIG_X86_64
-	struct pvclock_vcpu_time_info hv_clock;
-	struct kvm_arch *ka = &kvm->arch;
-	unsigned long seq, local_tsc_khz;
-	struct timespec64 ts;
-	uint64_t host_tsc;
-
-	do {
-		seq = read_seqcount_begin(&ka->pvclock_sc);
-
-		local_tsc_khz = 0;
-		if (!ka->use_master_clock)
-			break;
-
-		/*
-		 * The TSC read and the call to get_cpu_tsc_khz() must happen
-		 * on the same CPU.
-		 */
-		get_cpu();
-
-		local_tsc_khz = get_cpu_tsc_khz();
-
-		if (local_tsc_khz &&
-		    !kvm_get_walltime_and_clockread(&ts, &host_tsc))
-			local_tsc_khz = 0; /* Fall back to old method */
-
-		put_cpu();
-
-		/*
-		 * These values must be snapshotted within the seqcount loop.
-		 * After that, it's just mathematics which can happen on any
-		 * CPU at any time.
-		 */
-		hv_clock.tsc_timestamp = ka->master_cycle_now;
-		hv_clock.system_time = ka->master_kernel_ns + ka->kvmclock_offset;
+	struct kvm_clock_data data;
 
-	} while (read_seqcount_retry(&ka->pvclock_sc, seq));
+	get_kvmclock(kvm, &data);
 
 	/*
-	 * If the conditions were right, and obtaining the wallclock+TSC was
-	 * successful, calculate the KVM clock at the corresponding time and
-	 * subtract one from the other to get the guest's epoch in nanoseconds
-	 * since 1970-01-01.
+	 * If get_kvmclock() captured both wallclock and kvmclock from the
+	 * same TSC reading, use them for a precise epoch calculation.
 	 */
-	if (local_tsc_khz) {
-		kvm_get_time_scale(NSEC_PER_SEC, local_tsc_khz * NSEC_PER_USEC,
-				   &hv_clock.tsc_shift,
-				   &hv_clock.tsc_to_system_mul);
-		return ts.tv_nsec + NSEC_PER_SEC * ts.tv_sec -
-			__pvclock_read_cycles(&hv_clock, host_tsc);
-	}
-#endif
-	return ktime_get_real_ns() - get_kvmclock_ns(kvm);
+	if (data.flags & KVM_CLOCK_REALTIME)
+		return data.realtime - data.clock;
+
+	return ktime_get_real_ns() - data.clock;
 }
 
 /*
-- 
2.54.0


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

* [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (12 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 13/34] KVM: x86: Use get_kvmclock() in kvm_get_wall_clock_epoch() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 17:59   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling David Woodhouse
                   ` (19 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The compute_guest_tsc() function computes the guest TSC at a given
kernel_ns timestamp. When the master clock reference point
(master_kernel_ns) is earlier than vcpu->arch.this_tsc_nsec, the delta
is negative. Since pvclock_scale_delta() takes a u64, the negative
value wraps to a huge positive number, producing a wildly wrong result.

Handle negative deltas explicitly by negating the delta, scaling it,
and subtracting from this_tsc_write.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index fc9366b83912..8aae22401046 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2588,11 +2588,21 @@ static int kvm_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
 
 static u64 compute_guest_tsc(struct kvm_vcpu *vcpu, s64 kernel_ns)
 {
-	u64 tsc = pvclock_scale_delta(kernel_ns-vcpu->arch.this_tsc_nsec,
-				      vcpu->arch.virtual_tsc_mult,
-				      vcpu->arch.virtual_tsc_shift);
-	tsc += vcpu->arch.this_tsc_write;
-	return tsc;
+	s64 delta_ns = kernel_ns - vcpu->arch.this_tsc_nsec;
+	u64 tsc;
+
+	/* Handle negative deltas gracefully (master clock ref may be earlier) */
+	if (delta_ns < 0) {
+		tsc = pvclock_scale_delta(-delta_ns,
+					  vcpu->arch.virtual_tsc_mult,
+					  vcpu->arch.virtual_tsc_shift);
+		return vcpu->arch.this_tsc_write - tsc;
+	}
+
+	tsc = pvclock_scale_delta(delta_ns,
+				  vcpu->arch.virtual_tsc_mult,
+				  vcpu->arch.virtual_tsc_shift);
+	return vcpu->arch.this_tsc_write + tsc;
 }
 
 #ifdef CONFIG_X86_64
-- 
2.54.0


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

* [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (13 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 18:13   ` sashiko-bot
  2026-06-08 14:47 ` [PATCH v5 16/34] KVM: x86: Simplify and comment kvm_get_time_scale() David Woodhouse
                   ` (18 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Restructure kvm_guest_time_update() so that kernel_ns/host_tsc are
always "now" when doing TSC catchup, then swap in the master clock
reference values afterward for the hv_clock.

This makes the TSC upscaling code considerably simpler: the catchup
adjustment is computed as the delta between what the guest TSC *should*
be at "now" and what it actually is, rather than mixing "now" and
"master clock reference" timestamps.

The seqcount loop now also contains the kvm_get_time_and_clockread()
call (matching get_kvmclock's pattern), with the same WARN for
unexpected failure.

Based on a suggestion by Sean Christopherson.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 74 +++++++++++++++++++++++++++++++++-------------
 1 file changed, 53 insertions(+), 21 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 8aae22401046..92e32d720523 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3363,46 +3363,63 @@ static void kvm_setup_guest_pvclock(struct pvclock_vcpu_time_info *ref_hv_clock,
 int kvm_guest_time_update(struct kvm_vcpu *v)
 {
 	struct pvclock_vcpu_time_info hv_clock = {};
-	unsigned long flags;
 	u64 tgt_tsc_hz;
 	unsigned seq;
 	struct kvm_vcpu_arch *vcpu = &v->arch;
 	struct kvm_arch *ka = &v->kvm->arch;
 	s64 kernel_ns;
 	u64 tsc_timestamp, host_tsc;
+	u64 master_host_tsc = 0;
+	s64 master_kernel_ns = 0;
 	bool use_master_clock;
 
-	kernel_ns = 0;
-	host_tsc = 0;
-
 	/*
 	 * If the host uses TSC clock, then passthrough TSC as stable
 	 * to the guest.
 	 */
 	do {
 		seq = read_seqcount_begin(&ka->pvclock_sc);
+
 		use_master_clock = ka->use_master_clock;
-		if (use_master_clock) {
-			host_tsc = ka->master_cycle_now;
-			kernel_ns = ka->master_kernel_ns;
-		}
+
+		/*
+		 * The TSC read and the call to get_cpu_tsc_khz() must happen
+		 * on the same CPU.
+		 */
+		get_cpu();
+
+		tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
+
+#ifdef CONFIG_X86_64
+		if (use_master_clock &&
+		    !kvm_get_time_and_clockread(&kernel_ns, &host_tsc) &&
+		    !read_seqcount_retry(&ka->pvclock_sc, seq))
+			use_master_clock = false;
+#endif
+
+		put_cpu();
+
+		if (!use_master_clock)
+			break;
+
+		master_host_tsc = ka->master_cycle_now;
+		master_kernel_ns = ka->master_kernel_ns;
 	} while (read_seqcount_retry(&ka->pvclock_sc, seq));
 
-	/* Keep irq disabled to prevent changes to the clock */
-	local_irq_save(flags);
-	tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
 	if (unlikely(tgt_tsc_hz == 0)) {
-		local_irq_restore(flags);
 		kvm_make_request(KVM_REQ_CLOCK_UPDATE, v);
 		return 1;
 	}
+
 	if (!use_master_clock) {
+		unsigned long flags;
+
+		local_irq_save(flags);
 		host_tsc = rdtsc();
 		kernel_ns = get_kvmclock_base_ns();
+		local_irq_restore(flags);
 	}
 
-	tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);
-
 	/*
 	 * We may have to catch up the TSC to match elapsed wall clock
 	 * time for two reasons, even if kvmclock is used.
@@ -3411,17 +3428,32 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
 	 *      entry to avoid unknown leaps of TSC even when running
 	 *      again on the same CPU.  This may cause apparent elapsed
 	 *      time to disappear, and the guest to stand still or run
-	 *	very slowly.
+	 *      very slowly.
 	 */
 	if (vcpu->tsc_catchup) {
-		u64 tsc = compute_guest_tsc(v, kernel_ns);
-		if (tsc > tsc_timestamp) {
-			adjust_tsc_offset_guest(v, tsc - tsc_timestamp);
-			tsc_timestamp = tsc;
-		}
+		s64 adjustment;
+
+		/*
+		 * Calculate the delta between what the guest TSC *should* be
+		 * and what it actually is according to kvm_read_l1_tsc().
+		 */
+		adjustment = compute_guest_tsc(v, kernel_ns) -
+			     kvm_read_l1_tsc(v, host_tsc);
+		if (adjustment > 0)
+			adjust_tsc_offset_guest(v, adjustment);
 	}
 
-	local_irq_restore(flags);
+	/*
+	 * Now that TSC upscaling is out of the way, the remaining calculations
+	 * are all relative to the reference time that's placed in hv_clock.
+	 * If the master clock is NOT in use, the reference time is "now".  If
+	 * master clock is in use, the reference time comes from there.
+	 */
+	if (use_master_clock) {
+		host_tsc = master_host_tsc;
+		kernel_ns = master_kernel_ns;
+	}
+	tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);
 
 	/* With all the info we got, fill in the values */
 
-- 
2.54.0


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

* [PATCH v5 16/34] KVM: x86: Simplify and comment kvm_get_time_scale()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (14 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 17/34] KVM: x86: Remove implicit rdtsc() from kvm_compute_l1_tsc_offset() David Woodhouse
                   ` (17 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The kvm_get_time_scale() function was entirely opaque. Add comments
explaining what it does: compute a fixed-point multiplier and shift for
converting TSC ticks to nanoseconds via pvclock_scale_delta().

Rename the local variables from the cryptic tps64/tps32/scaled64 to
base_hz_u64/base32/scaled_hz_u64 to make the code self-documenting.
The "tps32" name stood for "Ticks Per Second" but was misleading since
it held the shifted base frequency, not a tick count.

No functional change.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/x86.c | 55 +++++++++++++++++++++++++++++++++-------------
 1 file changed, 40 insertions(+), 15 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 92e32d720523..a6c31a0d9955 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2472,32 +2472,57 @@ static uint32_t div_frac(uint32_t dividend, uint32_t divisor)
 	return dividend;
 }
 
-static void kvm_get_time_scale(uint64_t scaled_hz, uint64_t base_hz,
+static void kvm_get_time_scale(u64 scaled_hz, u64 base_hz,
 			       s8 *pshift, u32 *pmultiplier)
 {
-	uint64_t scaled64;
-	int32_t  shift = 0;
-	uint64_t tps64;
-	uint32_t tps32;
+	u64 scaled_hz_u64 = scaled_hz;
+	s32 shift = 0;
+	u64 base_hz_u64;
+	u32 base32;
 
-	tps64 = base_hz;
-	scaled64 = scaled_hz;
-	while (tps64 > scaled64*2 || tps64 & 0xffffffff00000000ULL) {
-		tps64 >>= 1;
+	/*
+	 * This function calculates a fixed-point multiplier and shift such
+	 * that:
+	 *   time_ns = (tsc_cycles << shift) * multiplier >> 32
+	 *
+	 * Where tsc_cycles tick at base_hz, and time_ns should count at
+	 * scaled_hz (typically NSEC_PER_SEC for a TSC→nanoseconds conversion).
+	 *
+	 * The multiplier is: (scaled_hz << 32) / base_hz, adjusted by shift
+	 * to keep everything in range.
+	 */
+
+	base_hz_u64 = base_hz;
+
+	/*
+	 * Start by shifting base_hz right until it fits in 32 bits, and
+	 * is lower than double the target rate. This introduces a negative
+	 * shift value which would result in pvclock_scale_delta() shifting
+	 * the actual tick count right before performing the multiplication.
+	 */
+	while (base_hz_u64 > scaled_hz_u64 * 2 || base_hz_u64 >> 32) {
+		base_hz_u64 >>= 1;
 		shift--;
 	}
 
-	tps32 = (uint32_t)tps64;
-	while (tps32 <= scaled64 || scaled64 & 0xffffffff00000000ULL) {
-		if (scaled64 & 0xffffffff00000000ULL || tps32 & 0x80000000)
-			scaled64 >>= 1;
+	/* Now the shifted base_hz fits in 32 bits. */
+	base32 = (u32)base_hz_u64;
+
+	/*
+	 * Next, shift scaled_hz right until it fits in 32 bits, and ensure
+	 * that the shifted base_hz is strictly larger (so that the result of the
+	 * final division also fits in 32 bits).
+	 */
+	while (base32 <= scaled_hz_u64 || scaled_hz_u64 >> 32) {
+		if (scaled_hz_u64 >> 32 || base32 & BIT(31))
+			scaled_hz_u64 >>= 1;
 		else
-			tps32 <<= 1;
+			base32 <<= 1;
 		shift++;
 	}
 
 	*pshift = shift;
-	*pmultiplier = div_frac(scaled64, tps32);
+	*pmultiplier = div_frac(scaled_hz_u64, base32);
 }
 
 #ifdef CONFIG_X86_64
-- 
2.54.0


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

* [PATCH v5 17/34] KVM: x86: Remove implicit rdtsc() from kvm_compute_l1_tsc_offset()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (15 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 16/34] KVM: x86: Simplify and comment kvm_get_time_scale() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 14:47 ` [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc() David Woodhouse
                   ` (16 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Let the callers pass the host TSC value in as an explicit parameter.
This leaves some fairly obviously stupid code, which is using this
function to compare the guest TSC at some *other* time, with the
newly-minted TSC value from rdtsc(). Unless it's being used to measure
*elapsed* time, that isn't very sensible.

In this case, "obviously stupid" is an improvement over being
non-obviously so.

No functional change intended.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/x86.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index a6c31a0d9955..bce4c7a6a6fe 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2693,11 +2693,12 @@ u64 kvm_scale_tsc(u64 tsc, u64 ratio)
 	return _tsc;
 }
 
-static u64 kvm_compute_l1_tsc_offset(struct kvm_vcpu *vcpu, u64 target_tsc)
+static u64 kvm_compute_l1_tsc_offset(struct kvm_vcpu *vcpu, u64 host_tsc,
+				     u64 target_tsc)
 {
 	u64 tsc;
 
-	tsc = kvm_scale_tsc(rdtsc(), vcpu->arch.l1_tsc_scaling_ratio);
+	tsc = kvm_scale_tsc(host_tsc, vcpu->arch.l1_tsc_scaling_ratio);
 
 	return target_tsc - tsc;
 }
@@ -2859,7 +2860,7 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 	bool synchronizing = false;
 
 	raw_spin_lock_irqsave(&kvm->arch.tsc_write_lock, flags);
-	offset = kvm_compute_l1_tsc_offset(vcpu, data);
+	offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data);
 	ns = get_kvmclock_base_ns();
 	elapsed = ns - kvm->arch.last_tsc_nsec;
 
@@ -2908,7 +2909,7 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 		} else {
 			u64 delta = nsec_to_cycles(vcpu, elapsed);
 			data += delta;
-			offset = kvm_compute_l1_tsc_offset(vcpu, data);
+			offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data);
 		}
 		matched = true;
 	}
@@ -4155,7 +4156,8 @@ int kvm_set_msr_common(struct kvm_vcpu *vcpu, struct msr_data *msr_info)
 		if (msr_info->host_initiated) {
 			kvm_synchronize_tsc(vcpu, &data);
 		} else if (!vcpu->arch.guest_tsc_protected) {
-			u64 adj = kvm_compute_l1_tsc_offset(vcpu, data) - vcpu->arch.l1_tsc_offset;
+			u64 adj = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data) -
+				  vcpu->arch.l1_tsc_offset;
 			adjust_tsc_offset_guest(vcpu, adj);
 			vcpu->arch.ia32_tsc_adjust_msr += adj;
 		}
@@ -5279,7 +5281,7 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 			mark_tsc_unstable("KVM discovered backwards TSC");
 
 		if (kvm_check_tsc_unstable()) {
-			u64 offset = kvm_compute_l1_tsc_offset(vcpu,
+			u64 offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(),
 						vcpu->arch.last_guest_tsc);
 			kvm_vcpu_write_tsc_offset(vcpu, offset);
 			if (!vcpu->arch.guest_tsc_protected)
-- 
2.54.0


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

* [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (16 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 17/34] KVM: x86: Remove implicit rdtsc() from kvm_compute_l1_tsc_offset() David Woodhouse
@ 2026-06-08 14:47 ` David Woodhouse
  2026-06-08 18:39   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields David Woodhouse
                   ` (15 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:47 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

When synchronizing to an existing TSC (either by explicitly writing
zero, or the legacy hack where the TSC is written within one second's
worth of the previously written TSC), the last_tsc_write and
last_tsc_nsec values were being misrecorded by __kvm_synchronize_tsc().
The *unsynchronized* value of the TSC (perhaps even zero) was being
recorded, along with the current time at which kvm_synchronize_tsc()
was called. This could cause *subsequent* writes to fail to synchronize
correctly.

Fix that by resetting {data, ns} to the previous values before passing
them to __kvm_synchronize_tsc() when synchronization is detected.
Except in the case where the TSC is unstable and *has* to be synthesised
from the host clock, in which case attempt to create a nsec/tsc pair
which is on the correct line.

Furthermore, there were *three* different TSC reads used for calculating
the "current" time, all slightly different from each other. Fix that by
using kvm_get_time_and_clockread() where possible and using the same
host_tsc value in all cases.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/x86.c | 33 +++++++++++++++++++++++++++++----
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index bce4c7a6a6fe..c8c0633263fb 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -203,6 +203,9 @@ module_param(mitigate_smt_rsb, bool, 0444);
  * usermode, e.g. SYSCALL MSRs and TSC_AUX, can be deferred until the CPU
  * returns to userspace, i.e. the kernel can run with the guest's value.
  */
+#ifdef CONFIG_X86_64
+static bool kvm_get_time_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp);
+#endif
 #define KVM_MAX_NR_USER_RETURN_MSRS 16
 
 struct kvm_user_return_msrs {
@@ -2854,14 +2857,23 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 {
 	u64 data = user_value ? *user_value : 0;
 	struct kvm *kvm = vcpu->kvm;
-	u64 offset, ns, elapsed;
+	u64 offset, host_tsc, elapsed;
+	s64 ns;
 	unsigned long flags;
 	bool matched = false;
 	bool synchronizing = false;
 
 	raw_spin_lock_irqsave(&kvm->arch.tsc_write_lock, flags);
-	offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data);
-	ns = get_kvmclock_base_ns();
+
+#ifdef CONFIG_X86_64
+	if (!kvm_get_time_and_clockread(&ns, &host_tsc))
+#endif
+	{
+		host_tsc = rdtsc();
+		ns = get_kvmclock_base_ns();
+	}
+
+	offset = kvm_compute_l1_tsc_offset(vcpu, host_tsc, data);
 	elapsed = ns - kvm->arch.last_tsc_nsec;
 
 	if (vcpu->arch.virtual_tsc_khz) {
@@ -2904,12 +2916,25 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
          */
 	if (synchronizing &&
 	    vcpu->arch.virtual_tsc_khz == kvm->arch.last_tsc_khz) {
+		/*
+		 * If synchronizing, the "last written" TSC value/time
+		 * recorded by __kvm_synchronize_tsc() should not change
+		 * (i.e. should be precisely the same as the existing
+		 * generation).
+		 */
+		data = kvm->arch.last_tsc_write;
+
 		if (!kvm_check_tsc_unstable()) {
 			offset = kvm->arch.cur_tsc_offset;
+			ns = kvm->arch.cur_tsc_nsec;
 		} else {
+			/*
+			 * ...unless the TSC is unstable and has to be
+			 * synthesised from the host clock in nanoseconds.
+			 */
 			u64 delta = nsec_to_cycles(vcpu, elapsed);
 			data += delta;
-			offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data);
+			offset = kvm_compute_l1_tsc_offset(vcpu, host_tsc, data);
 		}
 		matched = true;
 	}
-- 
2.54.0


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

* [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (17 preceding siblings ...)
  2026-06-08 14:47 ` [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc() David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 18:53   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 20/34] KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool David Woodhouse
                   ` (14 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

These pointlessly duplicate the cur_tsc_{nsec,write,offset} values.
The only place they were used was where the TSC is stable and a new
vCPU is being synchronized to the previous setting, in which case the
cur_tsc_* value is definitely identical.

Rename last_tsc_khz and last_tsc_scaling_ratio to cur_tsc_khz and
cur_tsc_scaling_ratio respectively, since they are properties of the
current TSC generation.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/include/asm/kvm_host.h |  7 ++----
 arch/x86/kvm/x86.c              | 42 ++++++++++++++++-----------------
 2 files changed, 22 insertions(+), 27 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 5348fd5ea3f3..59298a8f78eb 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1486,11 +1486,8 @@ struct kvm_arch {
 	 * preemption-disabled region, so it must be a raw spinlock.
 	 */
 	raw_spinlock_t tsc_write_lock;
-	u64 last_tsc_nsec;
-	u64 last_tsc_write;
-	u32 last_tsc_khz;
-	u64 last_tsc_offset;
-	u64 last_tsc_scaling_ratio;
+	u32 cur_tsc_khz;
+	u64 cur_tsc_scaling_ratio;
 	u64 cur_tsc_nsec;
 	u64 cur_tsc_write;
 	u64 cur_tsc_offset;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c8c0633263fb..bbd642e0dc54 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2813,14 +2813,12 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 		vcpu->kvm->arch.user_set_tsc = true;
 
 	/*
-	 * We also track th most recent recorded KHZ, write and time to
-	 * allow the matching interval to be extended at each write.
+	 * Track the TSC frequency, scaling ratio, and offset for the current
+	 * generation. These are used to detect matching TSC writes and to
+	 * compute the guest TSC from the host clock.
 	 */
-	kvm->arch.last_tsc_nsec = ns;
-	kvm->arch.last_tsc_write = tsc;
-	kvm->arch.last_tsc_khz = vcpu->arch.virtual_tsc_khz;
-	kvm->arch.last_tsc_offset = offset;
-	kvm->arch.last_tsc_scaling_ratio = vcpu->arch.l1_tsc_scaling_ratio;
+	kvm->arch.cur_tsc_khz = vcpu->arch.virtual_tsc_khz;
+	kvm->arch.cur_tsc_scaling_ratio = vcpu->arch.l1_tsc_scaling_ratio;
 
 	vcpu->arch.last_guest_tsc = tsc;
 
@@ -2833,8 +2831,6 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 		 * nanosecond time, offset, and write, so if TSCs are in
 		 * sync, we can match exact offset, and if not, we can match
 		 * exact software computation in compute_guest_tsc()
-		 *
-		 * These values are tracked in kvm->arch.cur_xxx variables.
 		 */
 		kvm->arch.cur_tsc_generation++;
 		kvm->arch.cur_tsc_nsec = ns;
@@ -2874,7 +2870,7 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 	}
 
 	offset = kvm_compute_l1_tsc_offset(vcpu, host_tsc, data);
-	elapsed = ns - kvm->arch.last_tsc_nsec;
+	elapsed = ns - kvm->arch.cur_tsc_nsec;
 
 	if (vcpu->arch.virtual_tsc_khz) {
 		if (data == 0) {
@@ -2884,7 +2880,7 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 			 */
 			synchronizing = true;
 		} else if (kvm->arch.user_set_tsc) {
-			u64 tsc_exp = kvm->arch.last_tsc_write +
+			u64 tsc_exp = kvm->arch.cur_tsc_write +
 						nsec_to_cycles(vcpu, elapsed);
 			u64 tsc_hz = vcpu->arch.virtual_tsc_khz * 1000LL;
 			/*
@@ -2915,14 +2911,14 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
 	 * it's better to try to match offsets from the beginning.
          */
 	if (synchronizing &&
-	    vcpu->arch.virtual_tsc_khz == kvm->arch.last_tsc_khz) {
+	    vcpu->arch.virtual_tsc_khz == kvm->arch.cur_tsc_khz) {
 		/*
 		 * If synchronizing, the "last written" TSC value/time
 		 * recorded by __kvm_synchronize_tsc() should not change
 		 * (i.e. should be precisely the same as the existing
 		 * generation).
 		 */
-		data = kvm->arch.last_tsc_write;
+		data = kvm->arch.cur_tsc_write;
 
 		if (!kvm_check_tsc_unstable()) {
 			offset = kvm->arch.cur_tsc_offset;
@@ -3207,7 +3203,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 		 * get_kvmclock() to compute kvmclock from the host TSC
 		 * without needing a vCPU reference.
 		 */
-		ka->master_tsc_scaling_ratio = ka->last_tsc_scaling_ratio;
+		ka->master_tsc_scaling_ratio = ka->cur_tsc_scaling_ratio;
 		tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
 		if (tsc_hz && kvm_caps.has_tsc_control)
 			tsc_hz = kvm_scale_tsc(tsc_hz,
@@ -6088,8 +6084,8 @@ static int kvm_arch_tsc_set_attr(struct kvm_vcpu *vcpu,
 		raw_spin_lock_irqsave(&kvm->arch.tsc_write_lock, flags);
 
 		matched = (vcpu->arch.virtual_tsc_khz &&
-			   kvm->arch.last_tsc_khz == vcpu->arch.virtual_tsc_khz &&
-			   kvm->arch.last_tsc_offset == offset);
+			   kvm->arch.cur_tsc_khz == vcpu->arch.virtual_tsc_khz &&
+			   kvm->arch.cur_tsc_offset == offset);
 
 		tsc = kvm_scale_tsc(rdtsc(), vcpu->arch.l1_tsc_scaling_ratio) + offset;
 		ns = get_kvmclock_base_ns();
@@ -13543,13 +13539,15 @@ int kvm_arch_enable_virtualization_cpu(void)
 			}
 
 			/*
-			 * We have to disable TSC offset matching.. if you were
-			 * booting a VM while issuing an S4 host suspend....
-			 * you may have some problem.  Solving this issue is
-			 * left as an exercise to the reader.
+			 * Adjust the TSC matching reference by the same
+			 * delta applied to each vCPU's offset, so that
+			 * future KVM_SET_TSC / vCPU creation still matches
+			 * correctly against the adjusted TSC timeline.
+			 * Scale from host to guest TSC rate.
 			 */
-			kvm->arch.last_tsc_nsec = 0;
-			kvm->arch.last_tsc_write = 0;
+			kvm->arch.cur_tsc_write -=
+				kvm_scale_tsc(delta_cyc,
+					      kvm->arch.cur_tsc_scaling_ratio);
 		}
 
 	}
-- 
2.54.0


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

* [PATCH v5 20/34] KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (18 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other David Woodhouse
                   ` (13 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Using a count and comparing with kvm->online_vcpus was always racy
because a new vCPU could be created while kvm_track_tsc_matching() was
running and comparing with kvm->online_vcpus. That variable is only
atomic with respect to itself; kvm_arch_vcpu_create() runs before
kvm->online_vcpus is incremented for the new vCPU.

Replace the count with a boolean that is set in kvm_track_tsc_matching()
after comparing the count, and cleared when a new TSC generation starts.
The boolean is consumed by pvclock_update_vm_gtod_copy() under the
tsc_write_lock, which serializes against __kvm_synchronize_tsc().

Keep the count for now as it's still used in the trace event.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/include/asm/kvm_host.h |  1 +
 arch/x86/kvm/x86.c              | 10 ++++++----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 59298a8f78eb..eb81f90284ba 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1492,6 +1492,7 @@ struct kvm_arch {
 	u64 cur_tsc_write;
 	u64 cur_tsc_offset;
 	u64 cur_tsc_generation;
+	bool all_vcpus_matched_tsc;
 	int nr_vcpus_matched_tsc;
 
 	u32 default_tsc_khz;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index bbd642e0dc54..ac66f8e7116f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2651,8 +2651,10 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 	 * and all vCPUs must have matching TSCs.  Note, the count for matching
 	 * vCPUs doesn't include the reference vCPU, hence "+1".
 	 */
-	bool use_master_clock = (ka->nr_vcpus_matched_tsc + 1 ==
-				 atomic_read(&vcpu->kvm->online_vcpus)) &&
+	ka->all_vcpus_matched_tsc = (ka->nr_vcpus_matched_tsc + 1 ==
+				     atomic_read(&vcpu->kvm->online_vcpus));
+
+	bool use_master_clock = ka->all_vcpus_matched_tsc &&
 				gtod_is_based_on_tsc(gtod->clock.vclock_mode);
 
 	/*
@@ -2837,6 +2839,7 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 		kvm->arch.cur_tsc_write = tsc;
 		kvm->arch.cur_tsc_offset = offset;
 		kvm->arch.nr_vcpus_matched_tsc = 0;
+		kvm->arch.all_vcpus_matched_tsc = false;
 	} else if (vcpu->arch.this_tsc_generation != kvm->arch.cur_tsc_generation) {
 		kvm->arch.nr_vcpus_matched_tsc++;
 	}
@@ -3177,8 +3180,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 	bool host_tsc_clocksource, vcpus_matched;
 
 	lockdep_assert_held(&kvm->arch.tsc_write_lock);
-	vcpus_matched = (ka->nr_vcpus_matched_tsc + 1 ==
-			atomic_read(&kvm->online_vcpus));
+	vcpus_matched = ka->all_vcpus_matched_tsc;
 
 	/*
 	 * If the host uses TSC clock, then passthrough TSC as stable
-- 
2.54.0


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

* [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (19 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 20/34] KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 19:15   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 22/34] KVM: selftests: Add master clock offset test David Woodhouse
                   ` (12 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Previously, a guest writing different TSC values on different vCPUs could
force KVM out of master clock mode. With this change, only a frequency
mismatch disables master clock. The only ways for non-master-clock mode
to happen now are archaic hardware without a TSC-based clocksource, or a
VMM that sets different TSC frequencies across vCPUs.

Running at a different frequency would lead to a systemic skew between
the clock(s) as observed by different vCPUs due to arithmetic precision
in the scaling. So that should indeed force the clock to be based on the
host's CLOCK_MONOTONIC_RAW instead of being in masterclock mode where it
is defined by the guest TSC.

But when the vCPUs merely have a different TSC *offset*, that's not a
problem. The offset is applied to that vCPU's kvmclock->tsc_timestamp
field, and it all comes out in the wash.

Track frequency matching separately from offset matching using a
dedicated freq generation counter (cur_tsc_freq_generation) that only
bumps on actual frequency changes. Each vCPU is counted exactly once per
freq generation via a per-vCPU this_tsc_freq_generation field, preventing
repeated syncs of the same vCPU from falsely re-enabling master clock.

Note that the generation-based counting has a known limitation: if all
vCPUs are in sync and one changes away and then back again, the other
vCPUs are still at the old generation and won't be counted until they
sync again (which may never happen). This was always the case for the
offset tracking and isn't expected VMM behaviour — although it is the
scenario that the VM-wide KVM_SET_TSC_KHZ ioctl was introduced to handle
cleanly.

While at it, restructure the existing TSC offset generation tracking to
use the same pattern: reset counter to zero on new generation, then
unconditionally count vCPUs that haven't been seen in this generation.
Both counters now use a consistent >= online_vcpus threshold (1-based
counting where the reference vCPU is included in the count).

Use frequency match for master clock eligibility, and full TSC match
(including offset) only for PVCLOCK_TSC_STABLE_BIT, which tells the
guest it is safe to skip cross-vCPU monotonicity enforcement.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/include/asm/kvm_host.h |  4 ++
 arch/x86/kvm/x86.c              | 68 ++++++++++++++++++++++++++-------
 2 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index eb81f90284ba..699a1a197194 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -970,6 +970,7 @@ struct kvm_vcpu_arch {
 	u64 this_tsc_nsec;
 	u64 this_tsc_write;
 	u64 this_tsc_generation;
+	u64 this_tsc_freq_generation;
 	bool tsc_catchup;
 	bool tsc_always_catchup;
 	s8 virtual_tsc_shift;
@@ -1493,6 +1494,9 @@ struct kvm_arch {
 	u64 cur_tsc_offset;
 	u64 cur_tsc_generation;
 	bool all_vcpus_matched_tsc;
+	bool all_vcpus_matched_freq;
+	int nr_vcpus_matched_freq;
+	u64 cur_tsc_freq_generation;
 	int nr_vcpus_matched_tsc;
 
 	u32 default_tsc_khz;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index ac66f8e7116f..86c30be4c5d2 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2647,14 +2647,37 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
 
 	/*
-	 * To use the masterclock, the host clocksource must be based on TSC
-	 * and all vCPUs must have matching TSCs.  Note, the count for matching
-	 * vCPUs doesn't include the reference vCPU, hence "+1".
+	 * Track whether all vCPUs have matching TSC offsets (for
+	 * PVCLOCK_TSC_STABLE_BIT) and matching frequencies (for
+	 * master clock eligibility).
+	 */
+
+	/*
+	 * A new vCPU might already have incremented ->online_vcpus
+	 * and cause a temporary false negative here. But will then
+	 * call kvm_synchronize_tsc() from kvm_arch_vcpu_postcreate()
+	 * and finish the job.
 	 */
-	ka->all_vcpus_matched_tsc = (ka->nr_vcpus_matched_tsc + 1 ==
-				     atomic_read(&vcpu->kvm->online_vcpus));
+	int online = atomic_read(&vcpu->kvm->online_vcpus);
 
-	bool use_master_clock = ka->all_vcpus_matched_tsc &&
+	ka->all_vcpus_matched_tsc = (ka->nr_vcpus_matched_tsc >= online);
+	/*
+	 * all_vcpus_matched_freq starts true and is cleared when
+	 * __kvm_synchronize_tsc() detects a frequency mismatch.
+	 * Re-enable when all vCPUs have synced with matching frequency.
+	 * If all offsets also match, that implies frequencies match too.
+	 */
+	if (ka->all_vcpus_matched_tsc ||
+	    ka->nr_vcpus_matched_freq >= online)
+		ka->all_vcpus_matched_freq = true;
+
+	/*
+	 * To use the masterclock, the host clocksource must be based on TSC
+	 * and all vCPUs must have matching TSC *frequency*. Different offsets
+	 * are fine — each vCPU's pvclock has its own tsc_timestamp that
+	 * accounts for its offset.
+	 */
+	bool use_master_clock = ka->all_vcpus_matched_freq &&
 				gtod_is_based_on_tsc(gtod->clock.vclock_mode);
 
 	/*
@@ -2818,7 +2841,22 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 	 * Track the TSC frequency, scaling ratio, and offset for the current
 	 * generation. These are used to detect matching TSC writes and to
 	 * compute the guest TSC from the host clock.
+	 *
+	 * If the frequency changed, master clock mode can no longer be used
+	 * since the kvmclock scaling factors differ between vCPUs.
 	 */
+	if (vcpu->arch.virtual_tsc_khz != kvm->arch.cur_tsc_khz) {
+		kvm->arch.cur_tsc_freq_generation++;
+		kvm->arch.all_vcpus_matched_freq = false;
+		kvm->arch.nr_vcpus_matched_freq = 0;
+	}
+
+	/* Count each vCPU once per freq generation */
+	if (vcpu->arch.this_tsc_freq_generation != kvm->arch.cur_tsc_freq_generation) {
+		vcpu->arch.this_tsc_freq_generation = kvm->arch.cur_tsc_freq_generation;
+		kvm->arch.nr_vcpus_matched_freq++;
+	}
+
 	kvm->arch.cur_tsc_khz = vcpu->arch.virtual_tsc_khz;
 	kvm->arch.cur_tsc_scaling_ratio = vcpu->arch.l1_tsc_scaling_ratio;
 
@@ -2835,17 +2873,18 @@ static void __kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 offset, u64 tsc,
 		 * exact software computation in compute_guest_tsc()
 		 */
 		kvm->arch.cur_tsc_generation++;
+		kvm->arch.all_vcpus_matched_tsc = false;
+		kvm->arch.nr_vcpus_matched_tsc = 0;
 		kvm->arch.cur_tsc_nsec = ns;
 		kvm->arch.cur_tsc_write = tsc;
 		kvm->arch.cur_tsc_offset = offset;
-		kvm->arch.nr_vcpus_matched_tsc = 0;
-		kvm->arch.all_vcpus_matched_tsc = false;
-	} else if (vcpu->arch.this_tsc_generation != kvm->arch.cur_tsc_generation) {
+	}
+
+	if (vcpu->arch.this_tsc_generation != kvm->arch.cur_tsc_generation) {
+		vcpu->arch.this_tsc_generation = kvm->arch.cur_tsc_generation;
 		kvm->arch.nr_vcpus_matched_tsc++;
 	}
 
-	/* Keep track of which generation this VCPU has synchronized to */
-	vcpu->arch.this_tsc_generation = kvm->arch.cur_tsc_generation;
 	vcpu->arch.this_tsc_nsec = kvm->arch.cur_tsc_nsec;
 	vcpu->arch.this_tsc_write = kvm->arch.cur_tsc_write;
 
@@ -3180,7 +3219,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 	bool host_tsc_clocksource, vcpus_matched;
 
 	lockdep_assert_held(&kvm->arch.tsc_write_lock);
-	vcpus_matched = ka->all_vcpus_matched_tsc;
+	vcpus_matched = ka->all_vcpus_matched_freq;
 
 	/*
 	 * If the host uses TSC clock, then passthrough TSC as stable
@@ -3527,7 +3566,7 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
 
 	/* If the host uses TSC clocksource, then it is stable */
 	hv_clock.flags = 0;
-	if (use_master_clock)
+	if (use_master_clock && ka->all_vcpus_matched_tsc)
 		hv_clock.flags |= PVCLOCK_TSC_STABLE_BIT;
 
 	if (vcpu->pv_time.active) {
@@ -6354,7 +6393,7 @@ static int kvm_vcpu_ioctl_get_clock_guest(struct kvm_vcpu *v, void __user *argp)
 
 	hv_clock.tsc_shift = vcpu->pvclock_tsc_shift;
 	hv_clock.tsc_to_system_mul = vcpu->pvclock_tsc_mul;
-	hv_clock.flags = PVCLOCK_TSC_STABLE_BIT;
+	hv_clock.flags = ka->all_vcpus_matched_tsc ? PVCLOCK_TSC_STABLE_BIT : 0;
 
 	if (copy_to_user(argp, &hv_clock, sizeof(hv_clock)))
 		return -EFAULT;
@@ -13649,6 +13688,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
 	mutex_init(&kvm->arch.apic_map_lock);
 	seqcount_raw_spinlock_init(&kvm->arch.pvclock_sc, &kvm->arch.tsc_write_lock);
 	kvm->arch.kvmclock_offset = -get_kvmclock_base_ns();
+	kvm->arch.all_vcpus_matched_freq = true;
 
 	raw_spin_lock_irqsave(&kvm->arch.tsc_write_lock, flags);
 	pvclock_update_vm_gtod_copy(kvm);
-- 
2.54.0


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

* [PATCH v5 22/34] KVM: selftests: Add master clock offset test
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (20 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 19:26   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 23/34] KVM: x86: Factor out kvm_use_master_clock() David Woodhouse
                   ` (11 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Verify that KVM master clock mode remains active when vCPUs have
different TSC offsets but the same frequency. Creates three vCPUs,
sets one to a different TSC value, and confirms:

 - KVM_CLOCK_HOST_TSC is set (master clock active)
 - KVM_CLOCK_TSC_STABLE is NOT set (offsets differ)

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro (claude-opus-4.6-1m)
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../kvm/x86/masterclock_offset_test.c         | 180 ++++++++++++++++++
 2 files changed, 181 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/masterclock_offset_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 90568ab631d7..7ecaaf82056e 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -106,6 +106,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
 TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
 TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
 TEST_GEN_PROGS_x86 += x86/pvclock_test
+TEST_GEN_PROGS_x86 += x86/masterclock_offset_test
 TEST_GEN_PROGS_x86 += x86/pvclock_migration_test
 TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
 TEST_GEN_PROGS_x86 += x86/set_sregs_test
diff --git a/tools/testing/selftests/kvm/x86/masterclock_offset_test.c b/tools/testing/selftests/kvm/x86/masterclock_offset_test.c
new file mode 100644
index 000000000000..88e2bd2edab5
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/masterclock_offset_test.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test that KVM master clock mode works with different TSC offsets
+ * as long as all vCPUs have the same TSC frequency.
+ */
+#include <stdint.h>
+#include <string.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#include <asm/pvclock-abi.h>
+
+#define KVMCLOCK_GPA	0xc0000000ull
+#define TSC_OFFSET	(1000000000ULL)
+
+static uint64_t pvclock_calc(struct pvclock_vcpu_time_info *pvti, uint64_t guest_tsc)
+{
+	uint64_t delta = guest_tsc - pvti->tsc_timestamp;
+
+	if (pvti->tsc_shift >= 0)
+		delta <<= pvti->tsc_shift;
+	else
+		delta >>= -(int)pvti->tsc_shift;
+
+	return pvti->system_time + ((__uint128_t)delta * pvti->tsc_to_system_mul >> 32);
+}
+
+static void guest_code(void)
+{
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, KVMCLOCK_GPA | KVM_MSR_ENABLED);
+	for (;;)
+		GUEST_SYNC(0);
+}
+
+int main(void)
+{
+	struct kvm_vcpu *vcpus[3];
+	struct kvm_clock_data clock;
+	struct pvclock_vcpu_time_info pvti[3];
+	struct kvm_vm *vm;
+	uint64_t offset0, host_tsc, clk0, clk2;
+	int i;
+
+	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
+
+	vm = vm_create_with_vcpus(3, guest_code, vcpus);
+
+	TEST_REQUIRE(!__vcpu_has_device_attr(vcpus[0], KVM_VCPU_TSC_CTRL,
+					     KVM_VCPU_TSC_OFFSET));
+
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    KVMCLOCK_GPA, 1,
+				    vm_calc_num_guest_pages(VM_MODE_DEFAULT,
+							   getpagesize()), 0);
+	virt_map(vm, KVMCLOCK_GPA, KVMCLOCK_GPA,
+		 vm_calc_num_guest_pages(VM_MODE_DEFAULT, getpagesize()));
+
+	/* Get vCPU 0's default offset and set vCPU 2's offset higher */
+	vcpu_device_attr_get(vcpus[0], KVM_VCPU_TSC_CTRL,
+			     KVM_VCPU_TSC_OFFSET, &offset0);
+	uint64_t offset2 = offset0 + TSC_OFFSET;
+	vcpu_device_attr_set(vcpus[2], KVM_VCPU_TSC_CTRL,
+			     KVM_VCPU_TSC_OFFSET, &offset2);
+
+	/* Run each vCPU to enable kvmclock (with offset already set) */
+	for (i = 0; i < 3; i++) {
+		vcpu_run(vcpus[i]);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpus[i], KVM_EXIT_IO);
+	}
+
+	/* Check master clock is active */
+	memset(&clock, 0, sizeof(clock));
+	vm_ioctl(vm, KVM_GET_CLOCK, &clock);
+	pr_info("KVM_GET_CLOCK flags: 0x%x\n", clock.flags);
+	TEST_ASSERT(clock.flags & KVM_CLOCK_HOST_TSC,
+		    "Master clock should be active, flags=0x%x", clock.flags);
+	TEST_ASSERT(clock.flags & KVM_CLOCK_TSC_STABLE,
+		    "KVM_CLOCK_TSC_STABLE should be set, flags=0x%x", clock.flags);
+
+	/* Get per-vCPU pvclock in order 0, 2, 1 */
+	int order[] = {0, 2, 1};
+	for (i = 0; i < 3; i++) {
+		int idx = order[i];
+		__vcpu_ioctl(vcpus[idx], KVM_GET_CLOCK_GUEST, &pvti[idx]);
+		pr_info("vCPU %d: tsc_timestamp=%lu system_time=%lu "
+			"mul=%u shift=%d flags=0x%x\n",
+			idx, (unsigned long)pvti[idx].tsc_timestamp,
+			(unsigned long)pvti[idx].system_time,
+			pvti[idx].tsc_to_system_mul, pvti[idx].tsc_shift,
+			pvti[idx].flags);
+	}
+
+	/* Read guest TSCs: should see (0+OFF) < 2 < (1+OFF) */
+	uint64_t gtsc0 = vcpu_get_msr(vcpus[0], MSR_IA32_TSC);
+	uint64_t gtsc2 = vcpu_get_msr(vcpus[2], MSR_IA32_TSC);
+	uint64_t gtsc1 = vcpu_get_msr(vcpus[1], MSR_IA32_TSC);
+	pr_info("Guest TSCs: vcpu0=%lu vcpu2=%lu vcpu1=%lu\n",
+		(unsigned long)gtsc0, (unsigned long)gtsc2, (unsigned long)gtsc1);
+	pr_info("vcpu0+OFF=%lu vcpu1+OFF=%lu\n",
+		(unsigned long)(gtsc0 + TSC_OFFSET),
+		(unsigned long)(gtsc1 + TSC_OFFSET));
+	TEST_ASSERT(gtsc0 + TSC_OFFSET < gtsc2 && gtsc2 < gtsc1 + TSC_OFFSET,
+		    "Expected (vcpu0+OFF) < vcpu2 < (vcpu1+OFF)");
+
+	/* PVCLOCK_TSC_STABLE_BIT should NOT be set (offsets differ) */
+	TEST_ASSERT(!(pvti[2].flags & PVCLOCK_TSC_STABLE_BIT),
+		    "PVCLOCK_TSC_STABLE_BIT should NOT be set, flags=0x%x",
+		    pvti[2].flags);
+
+	/* Same mul/shift */
+	TEST_ASSERT(pvti[0].tsc_to_system_mul == pvti[2].tsc_to_system_mul &&
+		    pvti[0].tsc_shift == pvti[2].tsc_shift,
+		    "All vCPUs should have same mul/shift");
+
+	/*
+	 * Read host TSC once. At this instant:
+	 *   vCPU 0 guest TSC = host_tsc + offset0
+	 *   vCPU 2 guest TSC = host_tsc + offset0 + TSC_OFFSET
+	 * Feed each through its pvclock. Expect the same kvmclock.
+	 */
+	host_tsc = rdtsc();
+	clk0 = pvclock_calc(&pvti[0], host_tsc + offset0);
+	clk2 = pvclock_calc(&pvti[2], host_tsc + offset0 + TSC_OFFSET);
+
+	pr_info("kvmclock via vCPU 0: %lu ns\n", (unsigned long)clk0);
+	pr_info("kvmclock via vCPU 2: %lu ns\n", (unsigned long)clk2);
+	TEST_ASSERT(clk0 == clk2,
+		    "kvmclock from offset vCPUs should match exactly, "
+		    "diff=%ld ns", (long)(clk2 - clk0));
+
+	pr_info("PASSED: pvclock consistent across offset vCPUs\n");
+
+	/*
+	 * Now add an hour to the VM kvmclock via KVM_SET_CLOCK, run each
+	 * vCPU to pick up the update, and check they're still in sync.
+	 */
+	{
+#define ONE_HOUR_NS (3600ULL * NSEC_PER_SEC)
+		struct kvm_clock_data setclk = { .clock = clock.clock + ONE_HOUR_NS };
+
+		vm_ioctl(vm, KVM_SET_CLOCK, &setclk);
+	}
+
+	/* Guest code does GUEST_SYNC then exits — run each to see update */
+	for (i = 0; i < 3; i++) {
+		vcpu_run(vcpus[order[i]]);
+		TEST_ASSERT_KVM_EXIT_REASON(vcpus[order[i]], KVM_EXIT_IO);
+	}
+
+	/* Re-read pvclocks */
+	for (i = 0; i < 3; i++)
+		__vcpu_ioctl(vcpus[order[i]], KVM_GET_CLOCK_GUEST, &pvti[order[i]]);
+
+	pr_info("After +1h: vCPU 0 system_time=%lu, vCPU 2 system_time=%lu\n",
+		(unsigned long)pvti[0].system_time,
+		(unsigned long)pvti[2].system_time);
+	TEST_ASSERT(pvti[0].system_time == pvti[2].system_time,
+		    "system_time should still match after KVM_SET_CLOCK");
+
+	host_tsc = rdtsc();
+	clk0 = pvclock_calc(&pvti[0], host_tsc + offset0);
+	clk2 = pvclock_calc(&pvti[2], host_tsc + offset0 + TSC_OFFSET);
+
+	pr_info("After +1h: kvmclock via vCPU 0: %lu ns\n", (unsigned long)clk0);
+	pr_info("After +1h: kvmclock via vCPU 2: %lu ns\n", (unsigned long)clk2);
+	TEST_ASSERT(clk0 == clk2,
+		    "After +1h: kvmclock should still match, diff=%ld ns",
+		    (long)(clk2 - clk0));
+
+	/* Verify the clock actually moved by ~1 hour */
+	TEST_ASSERT(clk0 > ONE_HOUR_NS,
+		    "Clock should be > 1 hour after set, got %lu ns",
+		    (unsigned long)clk0);
+
+	pr_info("PASSED: pvclock still consistent after KVM_SET_CLOCK +1h\n");
+	kvm_vm_free(vm);
+	return 0;
+}
-- 
2.54.0


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

* [PATCH v5 23/34] KVM: x86: Factor out kvm_use_master_clock()
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (21 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 22/34] KVM: selftests: Add master clock offset test David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 24/34] KVM: x86: Avoid gratuitous global clock updates David Woodhouse
                   ` (10 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Both kvm_track_tsc_matching() and pvclock_update_vm_gtod_copy() make a
decision about whether the KVM clock should be in master clock mode.
They used *different* criteria for the decision though. This isn't
really a problem; it only has the potential to cause unnecessary
invocations of KVM_REQ_MASTERCLOCK_UPDATE if the masterclock was
disabled due to TSC going backwards, or the guest using the old MSR.
But it isn't pretty.

Factor the decision out to a single function. And document the
historical reason why it's disabled for guests that use the old
MSR_KVM_SYSTEM_TIME.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/x86.c | 40 ++++++++++++++++++++++++++++++----------
 1 file changed, 30 insertions(+), 10 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 86c30be4c5d2..72fb4620a5ba 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2638,11 +2638,30 @@ static inline bool gtod_is_based_on_tsc(int mode)
 {
 	return mode == VDSO_CLOCKMODE_TSC || mode == VDSO_CLOCKMODE_HVCLOCK;
 }
-#endif
+
+static bool kvm_use_master_clock(struct kvm *kvm)
+{
+	struct kvm_arch *ka = &kvm->arch;
+
+	/*
+	 * The 'old kvmclock' check is a workaround (from 2015) for a
+	 * SUSE 2.6.16 kernel that didn't boot if the system_time in
+	 * its kvmclock was too far behind the current time. So the
+	 * mode of just setting the reference point and allowing time
+	 * to proceed linearly from there makes it fail to boot.
+	 * Despite that being kind of the *point* of the way the clock
+	 * is exposed to the guest. By coincidence, the offending
+	 * kernels used the old MSR_KVM_SYSTEM_TIME, which was moved
+	 * only because it resided in the wrong number range. So the
+	 * workaround is activated for *all* guests using the old MSR.
+	 */
+	return ka->all_vcpus_matched_freq &&
+		!ka->backwards_tsc_observed &&
+		!ka->boot_vcpu_runs_old_kvmclock;
+}
 
 static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 {
-#ifdef CONFIG_X86_64
 	struct kvm_arch *ka = &vcpu->kvm->arch;
 	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
 
@@ -2677,7 +2696,7 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 	 * are fine — each vCPU's pvclock has its own tsc_timestamp that
 	 * accounts for its offset.
 	 */
-	bool use_master_clock = ka->all_vcpus_matched_freq &&
+	bool use_master_clock = kvm_use_master_clock(vcpu->kvm) &&
 				gtod_is_based_on_tsc(gtod->clock.vclock_mode);
 
 	/*
@@ -2693,8 +2712,11 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 	trace_kvm_track_tsc(vcpu->vcpu_id, ka->nr_vcpus_matched_tsc,
 			    atomic_read(&vcpu->kvm->online_vcpus),
 		            ka->use_master_clock, gtod->clock.vclock_mode);
-#endif
 }
+#else
+static inline void kvm_track_tsc_matching(struct kvm_vcpu *vcpu,
+					  bool new_generation) {}
+#endif
 
 /*
  * Multiply tsc by a fixed point number represented by ratio.
@@ -3216,10 +3238,9 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 #ifdef CONFIG_X86_64
 	struct kvm_arch *ka = &kvm->arch;
 	int vclock_mode;
-	bool host_tsc_clocksource, vcpus_matched;
+	bool host_tsc_clocksource;
 
 	lockdep_assert_held(&kvm->arch.tsc_write_lock);
-	vcpus_matched = ka->all_vcpus_matched_freq;
 
 	/*
 	 * If the host uses TSC clock, then passthrough TSC as stable
@@ -3229,9 +3250,8 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 					&ka->master_kernel_ns,
 					&ka->master_cycle_now);
 
-	ka->use_master_clock = host_tsc_clocksource && vcpus_matched
-				&& !ka->backwards_tsc_observed
-				&& !ka->boot_vcpu_runs_old_kvmclock;
+	ka->use_master_clock = host_tsc_clocksource &&
+				kvm_use_master_clock(kvm);
 
 	if (ka->use_master_clock) {
 		u64 tsc_hz;
@@ -3259,7 +3279,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 
 	vclock_mode = pvclock_gtod_data.clock.vclock_mode;
 	trace_kvm_update_master_clock(ka->use_master_clock, vclock_mode,
-					vcpus_matched);
+					ka->all_vcpus_matched_freq);
 #endif
 }
 
-- 
2.54.0


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

* [PATCH v5 24/34] KVM: x86: Avoid gratuitous global clock updates
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (22 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 23/34] KVM: x86: Factor out kvm_use_master_clock() David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative David Woodhouse
                   ` (9 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Eliminate two sources of unnecessary KVM_REQ_GLOBAL_CLOCK_UPDATE:

1. kvm_write_system_time(): The global clock update was a workaround for
   ever-drifting clocks based on the host's CLOCK_MONOTONIC subject to
   NTP skew. Now that the KVM clock uses CLOCK_MONOTONIC_RAW, the clock
   does not drift with NTP corrections and there is no need to
   synchronize all vCPUs on boot or resume. Use KVM_REQ_CLOCK_UPDATE on
   the vCPU itself, and only when the clock is being enabled, not
   disabled.

2. kvm_arch_vcpu_load(): In master clock mode, migration between pCPUs
   does not require any clock update since the master clock reference is
   shared. Only request a local KVM_REQ_CLOCK_UPDATE for the vCPU's
   first-ever load (vcpu->cpu == -1) to generate initial pvclock params.
   In non-master-clock mode, keep the global update to synchronize all
   vCPUs.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 72fb4620a5ba..4fc21d701588 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2457,13 +2457,13 @@ static void kvm_write_system_time(struct kvm_vcpu *vcpu, gpa_t system_time,
 	}
 
 	vcpu->arch.time = system_time;
-	kvm_make_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu);
 
 	/* we verify if the enable bit is set... */
-	if (system_time & 1)
+	if (system_time & 1) {
 		kvm_gpc_activate(&vcpu->arch.pv_time, system_time & ~1ULL,
 				 sizeof(struct pvclock_vcpu_time_info));
-	else
+		kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
+	} else
 		kvm_gpc_deactivate(&vcpu->arch.pv_time);
 
 	return;
@@ -5377,8 +5377,10 @@ void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu)
 		 * On a host with synchronized TSC, there is no need to update
 		 * kvmclock on vcpu->cpu migration
 		 */
-		if (!vcpu->kvm->arch.use_master_clock || vcpu->cpu == -1)
+		if (!vcpu->kvm->arch.use_master_clock)
 			kvm_make_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu);
+		else if (vcpu->cpu == -1)
+			kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
 		if (vcpu->cpu != cpu)
 			kvm_make_request(KVM_REQ_MIGRATE_TIMER, vcpu);
 		vcpu->cpu = cpu;
-- 
2.54.0


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

* [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (23 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 24/34] KVM: x86: Avoid gratuitous global clock updates David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 19:58   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs David Woodhouse
                   ` (8 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

When kvm_xen_update_runstate() is invoked to set a vCPU's runstate, the
time spent in the previous runstate is accounted. This is based on the
delta between the current KVM clock time, and the previous value stored
in vcpu->arch.xen.runstate_entry_time.

If the KVM clock goes backwards, that delta will be negative. Or, since
it's an unsigned 64-bit integer, very *large*. Linux guests deal with
that particularly badly, reporting 100% steal time for ever more (well,
for *centuries* at least, until the delta has been consumed).

So when a negative delta is detected, just refrain from updating the
runstate times until the KVM clock catches up with runstate_entry_time
again.

Also clamp steal_ns to delta_ns to prevent steal time from exceeding
the total elapsed time, and handle negative steal_ns (which can happen
if run_delay goes backwards across a scheduler update).

The userspace APIs for setting the runstate times do not allow them to
be set past the current KVM clock, but userspace can still adjust the
KVM clock *after* setting the runstate times, which would cause this
situation to occur.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 arch/x86/kvm/xen.c | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 82e34edbfdbd..b1d67ece5db3 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -586,29 +586,45 @@ void kvm_xen_update_runstate(struct kvm_vcpu *v, int state)
 {
 	struct kvm_vcpu_xen *vx = &v->arch.xen;
 	u64 now = get_kvmclock_ns(v->kvm);
-	u64 delta_ns = now - vx->runstate_entry_time;
 	u64 run_delay = current->sched_info.run_delay;
+	s64 delta_ns = now - vx->runstate_entry_time;
+	s64 steal_ns = run_delay - vx->last_steal;
 
+	/*
+	 * If the vCPU was never run before, its prior state should
+	 * be considered RUNSTATE_offline.
+	 */
 	if (unlikely(!vx->runstate_entry_time))
 		vx->current_runstate = RUNSTATE_offline;
 
+	/*
+	 * If KVM clock went backwards, just update the current runstate
+	 * but don't account any time. Leave entry_time unchanged so the
+	 * next positive delta covers the full period once the clock
+	 * catches up. Update last_steal every time so stolen time only
+	 * reflects the interval since the most recent call.
+	 */
+	if (delta_ns < 0)
+		goto update_guest;
+
 	/*
 	 * Time waiting for the scheduler isn't "stolen" if the
 	 * vCPU wasn't running anyway.
 	 */
-	if (vx->current_runstate == RUNSTATE_running) {
-		u64 steal_ns = run_delay - vx->last_steal;
+	if (vx->current_runstate == RUNSTATE_running && steal_ns > 0) {
+		if (steal_ns > delta_ns)
+			steal_ns = delta_ns;
 
 		delta_ns -= steal_ns;
-
 		vx->runstate_times[RUNSTATE_runnable] += steal_ns;
 	}
-	vx->last_steal = run_delay;
 
 	vx->runstate_times[vx->current_runstate] += delta_ns;
-	vx->current_runstate = state;
 	vx->runstate_entry_time = now;
 
+ update_guest:
+	vx->current_runstate = state;
+	vx->last_steal = run_delay;
 	if (vx->runstate_cache.active)
 		kvm_xen_update_runstate_guest(v, state == RUNSTATE_runnable);
 }
-- 
2.54.0


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

* [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (24 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 20:11   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 27/34] KVM: x86: Remove runtime Xen TSC frequency CPUID update David Woodhouse
                   ` (7 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

When a masterclock update is triggered (e.g. by the clocksource change
notifier), KVM_REQ_MASTERCLOCK_UPDATE is set on all vCPUs. Without this
fix, each vCPU independently processes the request and redundantly
re-executes the entire pvclock_update_vm_gtod_copy() sequence, serialized
only by tsc_write_lock. Each redundant re-snapshot of the master clock
reference point introduces potential clock drift.

Fix this by having __kvm_start_pvclock_update() check, after acquiring
the lock, whether the requesting vCPU's KVM_REQ_MASTERCLOCK_UPDATE is
still set. If another vCPU already did the update and cleared it, bail
out. Otherwise, clear the request on all other vCPUs before proceeding.

The caller in vcpu_enter_guest() now uses kvm_test_request() (non-clearing)
since the clearing is done inside __kvm_start_pvclock_update() under the
lock.

Suggested-by: Dongli Zhang <dongli.zhang@oracle.com>
Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 60 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 46 insertions(+), 14 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4fc21d701588..54d4b1b3cfe4 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3288,10 +3288,39 @@ static void kvm_make_mclock_inprogress_request(struct kvm *kvm)
 	kvm_make_all_cpus_request(kvm, KVM_REQ_MCLOCK_INPROGRESS);
 }
 
-static void __kvm_start_pvclock_update(struct kvm *kvm)
+static void kvm_clear_mclock_inprogress_request(struct kvm *kvm)
 {
+	struct kvm_vcpu *vcpu;
+	unsigned long i;
+
+	kvm_for_each_vcpu(i, vcpu, kvm)
+		kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
+}
+
+static bool __kvm_start_pvclock_update(struct kvm *kvm, struct kvm_vcpu *requesting_vcpu)
+{
+	struct kvm_vcpu *vcpu;
+	unsigned long i;
+
 	raw_spin_lock_irq(&kvm->arch.tsc_write_lock);
+
+	/*
+	 * If another vCPU already did the update while we were waiting
+	 * for the lock, our request will have been cleared. Bail out.
+	 */
+	if (requesting_vcpu &&
+	    !kvm_test_request(KVM_REQ_MASTERCLOCK_UPDATE, requesting_vcpu)) {
+		kvm_clear_mclock_inprogress_request(kvm);
+		raw_spin_unlock_irq(&kvm->arch.tsc_write_lock);
+		return false;
+	}
+
+	/* The update is VM-wide; prevent other vCPUs from redoing it. */
+	kvm_for_each_vcpu(i, vcpu, kvm)
+		kvm_clear_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);
+
 	write_seqcount_begin(&kvm->arch.pvclock_sc);
+	return true;
 }
 
 static void kvm_start_pvclock_update(struct kvm *kvm)
@@ -3299,7 +3328,7 @@ static void kvm_start_pvclock_update(struct kvm *kvm)
 	kvm_make_mclock_inprogress_request(kvm);
 
 	/* no guest entries from this point */
-	__kvm_start_pvclock_update(kvm);
+	__kvm_start_pvclock_update(kvm, NULL);
 }
 
 static void kvm_end_pvclock_update(struct kvm *kvm)
@@ -3308,22 +3337,25 @@ static void kvm_end_pvclock_update(struct kvm *kvm)
 	struct kvm_vcpu *vcpu;
 	unsigned long i;
 
-	write_seqcount_end(&ka->pvclock_sc);
-	raw_spin_unlock_irq(&ka->tsc_write_lock);
 	kvm_for_each_vcpu(i, vcpu, kvm)
 		kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
 
 	/* guest entries allowed */
-	kvm_for_each_vcpu(i, vcpu, kvm)
-		kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
+	kvm_clear_mclock_inprogress_request(kvm);
+
+	write_seqcount_end(&ka->pvclock_sc);
+	raw_spin_unlock_irq(&ka->tsc_write_lock);
 }
 
-static void kvm_update_masterclock(struct kvm *kvm)
+static void kvm_update_masterclock(struct kvm *kvm, struct kvm_vcpu *vcpu)
 {
 	kvm_hv_request_tsc_page_update(kvm);
-	kvm_start_pvclock_update(kvm);
-	pvclock_update_vm_gtod_copy(kvm);
-	kvm_end_pvclock_update(kvm);
+	kvm_make_mclock_inprogress_request(kvm);
+
+	if (__kvm_start_pvclock_update(kvm, vcpu)) {
+		pvclock_update_vm_gtod_copy(kvm);
+		kvm_end_pvclock_update(kvm);
+	}
 }
 
 /*
@@ -10157,7 +10189,7 @@ static void kvm_hyperv_tsc_notifier(void)
 	kvm_caps.max_guest_tsc_khz = tsc_khz;
 
 	list_for_each_entry(kvm, &vm_list, vm_list) {
-		__kvm_start_pvclock_update(kvm);
+		__kvm_start_pvclock_update(kvm, NULL);
 		pvclock_update_vm_gtod_copy(kvm);
 		kvm_end_pvclock_update(kvm);
 	}
@@ -11535,8 +11567,8 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
 			kvm_mmu_free_obsolete_roots(vcpu);
 		if (kvm_check_request(KVM_REQ_MIGRATE_TIMER, vcpu))
 			__kvm_migrate_timers(vcpu);
-		if (kvm_check_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu))
-			kvm_update_masterclock(vcpu->kvm);
+		if (kvm_test_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu))
+			kvm_update_masterclock(vcpu->kvm, vcpu);
 		if (kvm_check_request(KVM_REQ_GLOBAL_CLOCK_UPDATE, vcpu))
 			kvm_gen_kvmclock_update(vcpu);
 		if (kvm_check_request(KVM_REQ_CLOCK_UPDATE, vcpu)) {
@@ -13273,7 +13305,7 @@ void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu)
 	vcpu_load(vcpu);
 	kvm_synchronize_tsc(vcpu, NULL);
 	if (!vcpu->kvm->arch.use_master_clock)
-		kvm_update_masterclock(vcpu->kvm);
+		kvm_update_masterclock(vcpu->kvm, NULL);
 	vcpu_put(vcpu);
 
 	/* poll control enabled by default */
-- 
2.54.0


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

* [PATCH v5 27/34] KVM: x86: Remove runtime Xen TSC frequency CPUID update
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (25 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test David Woodhouse
                   ` (6 subsequent siblings)
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Remove the code in kvm_cpuid() that dynamically updates the Xen TSC
info CPUID leaf at runtime. This code was updating the wrong sub-leaf
anyway (0x40000x03/2 EAX is the *host* TSC frequency per the Xen ABI,
not the guest frequency which belongs in 0x40000x03/0 ECX).

Userspace now has all the information it needs to populate the Xen TSC
info leaves (and the generic 0x40000010 timing leaf) at vCPU setup time:

  - KVM_GET_CLOCK_GUEST returns the pvclock_vcpu_time_info structure
    containing tsc_to_system_mul and tsc_shift (Xen leaf index 1)
  - KVM_VCPU_TSC_SCALE returns the effective TSC and bus
    frequencies in kHz (Xen leaf index 2, and 0x40000010)
  - KVM_VCPU_TSC_SCALE returns the raw hardware scaling ratio for
    precise arithmetic (VMClock)

This eliminates the last instance of KVM modifying guest CPUID entries
at runtime for timing information.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/cpuid.c | 16 ----------------
 arch/x86/kvm/xen.h   | 13 -------------
 2 files changed, 29 deletions(-)

diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index 621d950ec692..826637a0b72d 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -2117,22 +2117,6 @@ bool kvm_cpuid(struct kvm_vcpu *vcpu, u32 *eax, u32 *ebx,
 		} else if (function == 0x80000007) {
 			if (kvm_hv_invtsc_suppressed(vcpu))
 				*edx &= ~feature_bit(CONSTANT_TSC);
-		} else if (IS_ENABLED(CONFIG_KVM_XEN) &&
-			   kvm_xen_is_tsc_leaf(vcpu, function)) {
-			/*
-			 * Update guest TSC frequency information if necessary.
-			 * Ignore failures, there is no sane value that can be
-			 * provided if KVM can't get the TSC frequency.
-			 */
-			if (kvm_check_request(KVM_REQ_CLOCK_UPDATE, vcpu))
-				kvm_guest_time_update(vcpu);
-
-			if (index == 1) {
-				*ecx = vcpu->arch.pvclock_tsc_mul;
-				*edx = vcpu->arch.pvclock_tsc_shift;
-			} else if (index == 2) {
-				*eax = div_u64(vcpu->arch.hw_tsc_hz, 1000);
-			}
 		}
 	} else {
 		*eax = *ebx = *ecx = *edx = 0;
diff --git a/arch/x86/kvm/xen.h b/arch/x86/kvm/xen.h
index 59e6128a7bd3..f372855857a8 100644
--- a/arch/x86/kvm/xen.h
+++ b/arch/x86/kvm/xen.h
@@ -50,14 +50,6 @@ static inline void kvm_xen_sw_enable_lapic(struct kvm_vcpu *vcpu)
 		kvm_xen_inject_vcpu_vector(vcpu);
 }
 
-static inline bool kvm_xen_is_tsc_leaf(struct kvm_vcpu *vcpu, u32 function)
-{
-	return static_branch_unlikely(&kvm_xen_enabled.key) &&
-	       vcpu->arch.xen.cpuid.base &&
-	       function <= vcpu->arch.xen.cpuid.limit &&
-	       function == (vcpu->arch.xen.cpuid.base | XEN_CPUID_LEAF(3));
-}
-
 static inline bool kvm_xen_msr_enabled(struct kvm *kvm)
 {
 	return static_branch_unlikely(&kvm_xen_enabled.key) &&
@@ -177,11 +169,6 @@ static inline bool kvm_xen_timer_enabled(struct kvm_vcpu *vcpu)
 {
 	return false;
 }
-
-static inline bool kvm_xen_is_tsc_leaf(struct kvm_vcpu *vcpu, u32 function)
-{
-	return false;
-}
 #endif
 
 int kvm_xen_hypercall(struct kvm_vcpu *vcpu);
-- 
2.54.0


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

* [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (26 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 27/34] KVM: x86: Remove runtime Xen TSC frequency CPUID update David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-09  0:27   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ David Woodhouse
                   ` (5 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Verify that userspace can correctly populate Xen and generic CPUID
timing leaves using the KVM_VCPU_TSC_SCALE and
KVM_VCPU_TSC_SCALE attributes.

This validates that the removal of KVM's runtime Xen CPUID modification
doesn't break guests: userspace queries the effective TSC and bus
frequencies, computes the pvclock mul/shift, populates the CPUID leaves,
and the guest verifies the values match.

The test exercises:
 - KVM_VCPU_TSC_SCALE at native and scaled frequencies
 - KVM_VCPU_TSC_SCALE ratio verification against effective frequency
 - Generic timing leaf 0x40000010 (EAX=tsc_khz, EBX=bus_khz)
 - Xen leaf 3 sub-leaf 0 (ECX=guest TSC kHz)
 - Xen leaf 3 sub-leaf 1 (ECX=mul, EDX=shift)

Gracefully skips TSC scaling tests on hardware without support.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/x86/xen_cpuid_timing_test.c | 230 ++++++++++++++++++
 2 files changed, 231 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 7ecaaf82056e..58aac2980cdf 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -141,6 +141,7 @@ TEST_GEN_PROGS_x86 += x86/xss_msr_test
 TEST_GEN_PROGS_x86 += x86/debug_regs
 TEST_GEN_PROGS_x86 += x86/tsc_msrs_test
 TEST_GEN_PROGS_x86 += x86/vmx_pmu_caps_test
+TEST_GEN_PROGS_x86 += x86/xen_cpuid_timing_test
 TEST_GEN_PROGS_x86 += x86/xen_shinfo_test
 TEST_GEN_PROGS_x86 += x86/xen_vmcall_test
 TEST_GEN_PROGS_x86 += x86/sev_init2_tests
diff --git a/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c b/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c
new file mode 100644
index 000000000000..a0c262b8db89
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test that userspace can correctly populate Xen and generic CPUID
+ * timing leaves using KVM_GET_TSC_KHZ and KVM_VCPU_TSC_SCALE.
+ *
+ * This validates that the removal of KVM's runtime Xen CPUID modification
+ * doesn't break guests, because userspace has all the information needed.
+ */
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#include <asm/pvclock-abi.h>
+
+#define XEN_CPUID_BASE		0x40000100
+#define XEN_CPUID_LEAF(n)	(XEN_CPUID_BASE + (n))
+#define GENERIC_TIMING_LEAF	0x40000010
+
+/* Values set by host, verified by guest */
+static uint32_t expected_tsc_khz;
+static uint32_t expected_bus_khz;
+static uint32_t expected_tsc_mul;
+static int8_t   expected_tsc_shift;
+static uint64_t host_khz;
+
+static void guest_code(void)
+{
+	uint32_t eax, ebx, ecx, edx;
+
+	/* Check generic timing leaf 0x40000010 */
+	__cpuid(GENERIC_TIMING_LEAF, 0, &eax, &ebx, &ecx, &edx);
+	GUEST_ASSERT_EQ(eax, expected_tsc_khz);
+	GUEST_ASSERT_EQ(ebx, expected_bus_khz);
+
+	/* Check Xen leaf 3, sub-leaf 0: ECX = guest TSC frequency */
+	__cpuid(XEN_CPUID_LEAF(3), 0, &eax, &ebx, &ecx, &edx);
+	GUEST_ASSERT_EQ(ecx, expected_tsc_khz);
+
+	/* Check Xen leaf 3, sub-leaf 1: ECX = mul, EDX = shift */
+	__cpuid(XEN_CPUID_LEAF(3), 1, &eax, &ebx, &ecx, &edx);
+	GUEST_ASSERT_EQ(ecx, expected_tsc_mul);
+	GUEST_ASSERT_EQ((int8_t)edx, expected_tsc_shift);
+
+	GUEST_SYNC(0);
+}
+
+static void add_cpuid_entry(struct kvm_vcpu *vcpu, uint32_t function,
+			    uint32_t index, uint32_t eax, uint32_t ebx,
+			    uint32_t ecx, uint32_t edx)
+{
+	struct kvm_cpuid2 *cpuid = vcpu->cpuid;
+	struct kvm_cpuid_entry2 *entry;
+	int n = cpuid->nent;
+
+	vcpu->cpuid = realloc(vcpu->cpuid,
+			      sizeof(*cpuid) + (n + 1) * sizeof(*entry));
+	cpuid = vcpu->cpuid;
+	cpuid->nent = n + 1;
+
+	entry = &cpuid->entries[n];
+	memset(entry, 0, sizeof(*entry));
+	entry->function = function;
+	entry->index = index;
+	entry->flags = KVM_CPUID_FLAG_SIGNIFCANT_INDEX;
+	entry->eax = eax;
+	entry->ebx = ebx;
+	entry->ecx = ecx;
+	entry->edx = edx;
+}
+
+/*
+ * Compute pvclock mul/shift from frequency, matching kvm_get_time_scale().
+ */
+static void compute_tsc_mul_shift(uint64_t tsc_hz, uint32_t *mul, int8_t *shift)
+{
+	uint64_t scaled = 1000000000ULL;
+	uint64_t base = tsc_hz;
+	int32_t s = 0;
+	uint32_t base32;
+
+	while (base > scaled * 2 || base >> 32) {
+		base >>= 1;
+		s--;
+	}
+	base32 = (uint32_t)base;
+	while (base32 <= scaled || scaled >> 32) {
+		if (scaled >> 32 || base32 & (1U << 31))
+			scaled >>= 1;
+		else
+			base32 <<= 1;
+		s++;
+	}
+	*mul = (uint32_t)((scaled << 32) / base32);
+	*shift = (int8_t)s;
+}
+
+static void run_test(uint64_t tsc_khz)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	uint32_t effective_tsc_khz, effective_bus_khz;
+	int bus_cycle_ns;
+
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+
+	if (tsc_khz) {
+		pr_info("Testing at TSC frequency %lu kHz\n", tsc_khz);
+		vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)(unsigned long)tsc_khz);
+	} else {
+		pr_info("Testing at native TSC frequency\n");
+	}
+
+	effective_tsc_khz = __vcpu_ioctl(vcpu, KVM_GET_TSC_KHZ, NULL);
+	bus_cycle_ns = vm_check_cap(vm, KVM_CAP_X86_APIC_BUS_CYCLES_NS);
+	effective_bus_khz = bus_cycle_ns > 0 ? 1000000 / bus_cycle_ns : 1000000;
+
+	/* If scaling wasn't applied, skip this frequency */
+	if (tsc_khz && effective_tsc_khz == host_khz) {
+		pr_info("  TSC scaling not available, skipping\n");
+		kvm_vm_release(vm);
+		return;
+	}
+
+	pr_info("  Effective TSC: %u kHz, Bus: %u kHz\n", effective_tsc_khz, effective_bus_khz);
+
+	/* Also exercise KVM_VCPU_TSC_SCALE if available */
+	{
+		struct { uint64_t ratio; uint64_t frac_bits; } scale;
+		struct kvm_device_attr scale_attr = {
+			.group = KVM_VCPU_TSC_CTRL,
+			.attr = 1, /* KVM_VCPU_TSC_SCALE */
+			.addr = (uint64_t)(uintptr_t)&scale,
+		};
+
+		if (!__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &scale_attr)) {
+			vcpu_ioctl(vcpu, KVM_GET_DEVICE_ATTR, &scale_attr);
+			pr_info("  TSC scale: ratio=%lu frac_bits=%lu\n",
+				scale.ratio, scale.frac_bits);
+
+			/*
+			 * Verify: applying the ratio to the host TSC frequency
+			 * should give approximately the effective frequency.
+			 */
+			if (tsc_khz) {
+				uint64_t computed = ((__uint128_t)host_khz * scale.ratio) >> scale.frac_bits;
+				int64_t diff = (int64_t)computed - (int64_t)effective_tsc_khz;
+
+				TEST_ASSERT(diff >= -1 && diff <= 1,
+					    "TSC_SCALE ratio mismatch: computed %lu vs effective %u (diff %ld)",
+					    computed, effective_tsc_khz, diff);
+			}
+		}
+	}
+
+	compute_tsc_mul_shift((uint64_t)effective_tsc_khz * 1000,
+			      &expected_tsc_mul, &expected_tsc_shift);
+
+	expected_tsc_khz = effective_tsc_khz;
+	expected_bus_khz = effective_bus_khz;
+
+	sync_global_to_guest(vm, expected_tsc_khz);
+	sync_global_to_guest(vm, expected_bus_khz);
+	sync_global_to_guest(vm, expected_tsc_mul);
+	sync_global_to_guest(vm, expected_tsc_shift);
+
+	/* Populate CPUID leaves as a VMM would */
+	add_cpuid_entry(vcpu, GENERIC_TIMING_LEAF, 0,
+			effective_tsc_khz, effective_bus_khz, 0, 0);
+	add_cpuid_entry(vcpu, XEN_CPUID_LEAF(3), 0,
+			0, 0, effective_tsc_khz, 0);
+	add_cpuid_entry(vcpu, XEN_CPUID_LEAF(3), 1,
+			0, 0, expected_tsc_mul,
+			(uint32_t)(uint8_t)expected_tsc_shift);
+
+	vcpu_set_cpuid(vcpu);
+
+	pr_info("  pvclock mul=%u shift=%d\n", expected_tsc_mul, expected_tsc_shift);
+
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+		break;
+	case UCALL_SYNC:
+		break;
+	default:
+		TEST_FAIL("Unexpected ucall");
+	}
+
+	kvm_vm_release(vm);
+}
+
+int main(void)
+{
+	uint64_t freq;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct kvm_device_attr attr = {
+		.group = KVM_VCPU_TSC_CTRL,
+		.attr = KVM_VCPU_TSC_SCALE,
+	};
+
+	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
+
+	/* Check KVM_VCPU_TSC_SCALE is supported (implies TSC scaling) */
+	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
+	TEST_REQUIRE(!__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &attr));
+	host_khz = __vcpu_ioctl(vcpu, KVM_GET_TSC_KHZ, NULL);
+	kvm_vm_release(vm);
+
+	/* Native frequency */
+	run_test(0);
+
+	/* Scaled frequencies — skip if TSC scaling not available */
+	for (freq = 1000000; freq <= 4000000; freq += 1000000) {
+		if (freq == host_khz)
+			continue;
+		run_test(freq);
+	}
+
+	pr_info("PASS: All CPUID timing leaf tests passed\n");
+	return 0;
+}
-- 
2.54.0


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

* [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (27 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-09  0:37   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test David Woodhouse
                   ` (4 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

KVM_SET_TSC_KHZ changes the vCPU's TSC scaling ratio but does not
update the VM-wide cur_tsc_scaling_ratio used by get_kvmclock().
This causes get_kvmclock() to use a stale (default 1:1) ratio when
computing the KVM clock, leading to drift between the host-side
kvmclock and what the guest observes.

Fix this by calling kvm_synchronize_tsc() after changing the TSC
frequency. This:
 - Updates cur_tsc_scaling_ratio (consumed by pvclock_update_vm_gtod_copy)
 - Ensures the TSC value is continuous across the frequency change
 - Triggers kvm_track_tsc_matching() for proper masterclock handling
 - Allows subsequent vCPUs to synchronize via the 1-second slop hack

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 arch/x86/kvm/x86.c | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 54d4b1b3cfe4..96250264d403 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -206,6 +206,7 @@ module_param(mitigate_smt_rsb, bool, 0444);
 #ifdef CONFIG_X86_64
 static bool kvm_get_time_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp);
 #endif
+static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value);
 #define KVM_MAX_NR_USER_RETURN_MSRS 16
 
 struct kvm_user_return_msrs {
@@ -2611,7 +2612,20 @@ static int kvm_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
 			 user_tsc_khz, thresh_lo, thresh_hi);
 		use_scaling = 1;
 	}
-	return set_tsc_khz(vcpu, user_tsc_khz, use_scaling);
+	if (set_tsc_khz(vcpu, user_tsc_khz, use_scaling))
+		return -1;
+
+	/*
+	 * Re-synchronize the TSC after changing frequency. This ensures
+	 * cur_tsc_scaling_ratio is updated (used by get_kvmclock) and
+	 * the TSC value is continuous across the frequency change.
+	 */
+	{
+		u64 tsc = kvm_read_l1_tsc(vcpu, rdtsc());
+
+		kvm_synchronize_tsc(vcpu, &tsc);
+	}
+	return 0;
 }
 
 static u64 compute_guest_tsc(struct kvm_vcpu *vcpu, s64 kernel_ns)
-- 
2.54.0


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

* [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (28 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-09  0:50   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock David Woodhouse
                   ` (3 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Test that Xen runstate (steal time) is correctly accounted across a
simulated live migration using KVM_XEN_VCPU_ATTR and KVM_[GS]ET_CLOCK_GUEST.

The test simulates what a real VMM does during migration:
1. Creates a VM with Xen HVM config and runstate tracking
2. Runs the guest to accumulate some kvmclock time
3. Saves clock (KVM_GET_CLOCK_GUEST), TSC offset, and runstate
4. Marks the saved state as RUNSTATE_runnable (vCPU not running)
5. Destroys the source VM
6. Sleeps 10ms (simulating migration network transfer time)
7. Creates a new VM and restores all state precisely as saved
8. Runs the guest and verifies the migration gap appears as steal

The kernel accounts the gap because: on vcpu_load, it transitions from
RUNSTATE_runnable to RUNSTATE_running, computing delta = kvmclock_now -
state_entry_time. Since kvmclock has advanced past the saved entry time
(real time elapsed during migration), the delta is added to time_runnable.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 .../selftests/kvm/x86/xen_migration_test.c    | 194 ++++++++++++++++++
 1 file changed, 194 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/xen_migration_test.c

diff --git a/tools/testing/selftests/kvm/x86/xen_migration_test.c b/tools/testing/selftests/kvm/x86/xen_migration_test.c
new file mode 100644
index 000000000000..37e8ace00611
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/xen_migration_test.c
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Test Xen runstate (steal time) preservation across simulated migration.
+ *
+ * Verifies that the kernel correctly accounts the migration gap as
+ * steal time (runnable) when runstate data is saved and restored
+ * precisely, but real time elapses during the migration.
+ *
+ * The key insight: userspace saves the runstate with state=RUNSTATE_runnable
+ * (the vCPU is not running during migration). On restore, the kernel sees
+ * that kvmclock has advanced past state_entry_time, and accounts the
+ * difference as time spent in the runnable state.
+ */
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#include <asm/pvclock-abi.h>
+
+#define SHINFO_GPA	0xc0000000ULL
+#define RUNSTATE_GPA	(SHINFO_GPA + 0x1000)
+
+#define RUNSTATE_running  0
+#define RUNSTATE_runnable 1
+#define RUNSTATE_blocked  2
+#define RUNSTATE_offline  3
+
+struct vcpu_runstate_info {
+	uint32_t state;
+	uint64_t state_entry_time;
+	uint64_t time[4];
+} __attribute__((packed));
+
+static void guest_code(void)
+{
+	volatile struct vcpu_runstate_info *rs =
+		(void *)(unsigned long)RUNSTATE_GPA;
+
+	/* Report runstate times — no need to enable kvmclock MSR,
+	 * the kernel writes runstate using its internal kvmclock. */
+	GUEST_SYNC_ARGS(0, rs->time[RUNSTATE_runnable],
+			rs->time[RUNSTATE_running], 0, 0);
+}
+
+static struct kvm_vm *create_xen_vm(struct kvm_vcpu **vcpu)
+{
+	struct kvm_vm *vm;
+	int xen_caps;
+
+	vm = vm_create_with_one_vcpu(vcpu, guest_code);
+
+	xen_caps = kvm_check_cap(KVM_CAP_XEN_HVM);
+	TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_SHARED_INFO);
+	TEST_REQUIRE(xen_caps & KVM_XEN_HVM_CONFIG_RUNSTATE);
+
+	/* Map pages */
+	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
+				    SHINFO_GPA, 1, 2, 0);
+	virt_map(vm, SHINFO_GPA, SHINFO_GPA, 2);
+
+	/* Enable Xen HVM with MSR interception (enables runstate tracking) */
+	struct kvm_xen_hvm_config cfg = {
+		.flags = KVM_XEN_HVM_CONFIG_INTERCEPT_HCALL,
+		.msr = 0x40000000,
+	};
+	vm_ioctl(vm, KVM_XEN_HVM_CONFIG, &cfg);
+
+	/* Set shared_info */
+	struct kvm_xen_hvm_attr ha = {
+		.type = KVM_XEN_ATTR_TYPE_SHARED_INFO,
+		.u.shared_info.gfn = SHINFO_GPA >> 12,
+	};
+	vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &ha);
+
+	/* Set runstate address */
+	struct kvm_xen_vcpu_attr rs_addr = {
+		.type = KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_ADDR,
+		.u.gpa = RUNSTATE_GPA,
+	};
+	vcpu_ioctl(*vcpu, KVM_XEN_VCPU_SET_ATTR, &rs_addr);
+
+	return vm;
+}
+
+int main(void)
+{
+	struct pvclock_vcpu_time_info pvti;
+	struct kvm_xen_vcpu_attr runstate_save;
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	struct ucall uc;
+	uint64_t tsc_offset;
+	int ret;
+
+	/* === SOURCE SIDE === */
+	pr_info("=== Source: create VM and run guest ===\n");
+	vm = create_xen_vm(&vcpu);
+
+	/* Run guest once to accumulate some runstate time */
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+
+	pr_info("  Guest sees: runnable=%" PRIu64 " running=%" PRIu64 "\n",
+		uc.args[2], uc.args[3]);
+
+	/* Save clock state */
+	ret = __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &pvti);
+	TEST_ASSERT(!ret, "KVM_GET_CLOCK_GUEST failed");
+
+	/* Save TSC offset */
+	tsc_offset = vcpu_get_msr(vcpu, MSR_IA32_TSC_ADJUST);
+
+	/* Save runstate — the vCPU is now "runnable" (not running) */
+	runstate_save.type = KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_DATA;
+	vcpu_ioctl(vcpu, KVM_XEN_VCPU_GET_ATTR, &runstate_save);
+
+	/*
+	 * Transition to runnable state before saving — the vCPU is
+	 * not running during migration.
+	 */
+	runstate_save.u.runstate.state = RUNSTATE_runnable;
+
+	pr_info("  Saved runstate: running=%" PRIu64 " runnable=%" PRIu64
+		" entry=%" PRIu64 "\n",
+		(uint64_t)runstate_save.u.runstate.time_running,
+		(uint64_t)runstate_save.u.runstate.time_runnable,
+		(uint64_t)runstate_save.u.runstate.state_entry_time);
+
+	uint64_t saved_runnable = runstate_save.u.runstate.time_runnable;
+
+	kvm_vm_release(vm);
+
+	/* === MIGRATION GAP === */
+	pr_info("=== Simulating migration (sleeping 10ms) ===\n");
+	usleep(10000);
+
+	/* === DESTINATION SIDE === */
+	pr_info("=== Destination: create new VM and restore ===\n");
+	vm = create_xen_vm(&vcpu);
+
+	/* Restore TSC offset */
+	vcpu_set_msr(vcpu, MSR_IA32_TSC_ADJUST, tsc_offset);
+
+	/* Restore clock — kvmclock will now be ~10ms ahead of the snapshot */
+	vcpu_ioctl(vcpu, KVM_SET_CLOCK_GUEST, &pvti);
+
+	/* Restore runstate exactly as saved (state=runnable) */
+	runstate_save.type = KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_DATA;
+	ret = __vcpu_ioctl(vcpu, KVM_XEN_VCPU_SET_ATTR, &runstate_save);
+	TEST_ASSERT(!ret, "Restore runstate failed: errno %d", errno);
+
+	/*
+	 * Run the guest. When the vCPU enters vcpu_run, the kernel
+	 * transitions from RUNSTATE_runnable to RUNSTATE_running.
+	 * It computes: delta = kvmclock_now - state_entry_time
+	 * This delta (which includes the migration gap) is added to
+	 * time_runnable (steal time).
+	 */
+	vcpu_run(vcpu);
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+	TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_SYNC);
+
+	uint64_t guest_runnable = uc.args[2];
+	uint64_t guest_running = uc.args[3];
+
+	pr_info("  Guest sees: runnable=%" PRIu64 " running=%" PRIu64 "\n",
+		guest_runnable, guest_running);
+
+	uint64_t steal_increase = guest_runnable - saved_runnable;
+	pr_info("  Steal time increase: %" PRIu64 " ns (migration gap)\n",
+		steal_increase);
+
+	/*
+	 * The steal time increase should be at least 10ms (the sleep)
+	 * but not more than 5s (allowing for VM creation overhead).
+	 * The actual gap is from the source's state_entry_time to the
+	 * destination's kvmclock "now" at vcpu_load time.
+	 */
+	TEST_ASSERT(steal_increase >= 10000000ULL &&
+		    steal_increase < 5000000000ULL,
+		    "Steal time increase %" PRIu64 " ns not in expected range "
+		    "[10ms, 5s]", steal_increase);
+
+	kvm_vm_release(vm);
+	pr_info("PASS: Migration gap correctly accounted as steal time\n");
+	return 0;
+}
-- 
2.54.0


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

* [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (29 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-09  1:03   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 32/34] KVM: x86: Compute kvmclock base without pvclock_gtod_data David Woodhouse
                   ` (2 subsequent siblings)
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Replace the KVM-private vgettsc()/do_kvmclock_base()/do_monotonic()/
do_realtime() timekeeping reimplementation with calls to the generic
ktime_get_snapshot_id() interface.

The snapshot provides both the system time and the raw_cycles (TSC)
atomically paired. When raw_cycles is zero, the clocksource could not
provide a raw hardware counter value, which is equivalent to the
previous vgettsc() returning VDSO_CLOCKMODE_NONE.

For kvm_get_time_and_clockread(), the kvmclock base time is
CLOCK_MONOTONIC_RAW + offs_boot. The snapshot provides the raw time
atomically paired with the TSC; offs_boot is added separately as it
only changes at suspend/resume boundaries.

This is a step towards eliminating the pvclock_gtod_data private copy
of timekeeping state and the associated notifier callback.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 arch/x86/kvm/x86.c | 46 +++++++++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 11 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 96250264d403..2713aebb96ae 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -35,6 +35,7 @@
 #include "smm.h"
 
 #include <linux/clocksource.h>
+#include <linux/timekeeping.h>
 #include <linux/interrupt.h>
 #include <linux/kvm.h>
 #include <linux/fs.h>
@@ -3162,14 +3163,32 @@ static int do_realtime(struct timespec64 *ts, u64 *tsc_timestamp)
  * reports the TSC value from which it do so. Returns true if host is
  * using TSC based clocksource.
  */
+static bool kvm_snapshot_has_tsc(struct system_time_snapshot *snap,
+				u64 *tsc_timestamp)
+{
+	if (snap->cs_id == CSID_X86_TSC) {
+		*tsc_timestamp = snap->cycles;
+		return true;
+	}
+
+	if (snap->hw_csid == CSID_X86_TSC && snap->hw_cycles) {
+		*tsc_timestamp = snap->hw_cycles;
+		return true;
+	}
+
+	return false;
+}
+
 static bool kvm_get_time_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp)
 {
-	/* checked again under seqlock below */
-	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode))
+	struct system_time_snapshot snap;
+
+	ktime_get_snapshot_id(CLOCK_MONOTONIC_RAW, &snap);
+	if (!kvm_snapshot_has_tsc(&snap, tsc_timestamp))
 		return false;
 
-	return gtod_is_based_on_tsc(do_kvmclock_base(kernel_ns,
-						     tsc_timestamp));
+	*kernel_ns = ktime_to_ns(ktime_mono_to_any(snap.systime, TK_OFFS_BOOT));
+	return true;
 }
 
 /*
@@ -3178,12 +3197,14 @@ static bool kvm_get_time_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp)
  */
 bool kvm_get_monotonic_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp)
 {
-	/* checked again under seqlock below */
-	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode))
+	struct system_time_snapshot snap;
+
+	ktime_get_snapshot_id(CLOCK_MONOTONIC, &snap);
+	if (!kvm_snapshot_has_tsc(&snap, tsc_timestamp))
 		return false;
 
-	return gtod_is_based_on_tsc(do_monotonic(kernel_ns,
-						 tsc_timestamp));
+	*kernel_ns = ktime_to_ns(snap.systime);
+	return true;
 }
 
 /*
@@ -3196,11 +3217,14 @@ bool kvm_get_monotonic_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp)
 static bool kvm_get_walltime_and_clockread(struct timespec64 *ts,
 					   u64 *tsc_timestamp)
 {
-	/* checked again under seqlock below */
-	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode))
+	struct system_time_snapshot snap;
+
+	ktime_get_snapshot_id(CLOCK_REALTIME, &snap);
+	if (!kvm_snapshot_has_tsc(&snap, tsc_timestamp))
 		return false;
 
-	return gtod_is_based_on_tsc(do_realtime(ts, tsc_timestamp));
+	*ts = ktime_to_timespec64(snap.systime);
+	return true;
 }
 #endif
 
-- 
2.54.0


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

* [PATCH v5 32/34] KVM: x86: Compute kvmclock base without pvclock_gtod_data
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (30 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean David Woodhouse
  2026-06-08 14:48 ` [PATCH v5 34/34] KVM: x86: Remove pvclock_gtod_data and private timekeeping code David Woodhouse
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

get_kvmclock_base_ns() needs CLOCK_MONOTONIC_RAW + offs_boot. Compute
this directly rather than reading offs_boot from the pvclock_gtod_data
private copy. offs_boot only changes at suspend/resume so does not
need to be atomically paired with the raw clock read.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 arch/x86/kvm/x86.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 2713aebb96ae..c18947c5b63f 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2402,7 +2402,7 @@ static void update_pvclock_gtod(struct timekeeper *tk)
 static s64 get_kvmclock_base_ns(void)
 {
 	/* Count up from boot time, but with the frequency of the raw clock.  */
-	return ktime_to_ns(ktime_add(ktime_get_raw(), pvclock_gtod_data.offs_boot));
+	return ktime_to_ns(ktime_mono_to_any(ktime_get_raw(), TK_OFFS_BOOT));
 }
 
 static void kvm_write_wall_clock(struct kvm *kvm, gpa_t wall_clock, int sec_hi_ofs)
-- 
2.54.0


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

* [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (31 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 32/34] KVM: x86: Compute kvmclock base without pvclock_gtod_data David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  2026-06-09  1:23   ` sashiko-bot
  2026-06-08 14:48 ` [PATCH v5 34/34] KVM: x86: Remove pvclock_gtod_data and private timekeeping code David Woodhouse
  33 siblings, 1 reply; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

The remaining users of pvclock_gtod_data only need to know whether
the host clocksource is TSC-based. Replace all vclock_mode checks
with a simple kvm_host_has_tsc_clocksource boolean, updated by the
pvclock_gtod_notify callback.

This is inherently racy (as it always was — kvm_track_tsc_matching
never held the gtod seqcount), relying on eventual consistency: the
notifier fires on every timekeeping update and will correct any
transient inconsistency within one tick.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 arch/x86/kvm/x86.c | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c18947c5b63f..93a428c37847 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2649,6 +2649,8 @@ static u64 compute_guest_tsc(struct kvm_vcpu *vcpu, s64 kernel_ns)
 }
 
 #ifdef CONFIG_X86_64
+static bool kvm_host_has_tsc_clocksource;
+
 static inline bool gtod_is_based_on_tsc(int mode)
 {
 	return mode == VDSO_CLOCKMODE_TSC || mode == VDSO_CLOCKMODE_HVCLOCK;
@@ -2678,7 +2680,6 @@ static bool kvm_use_master_clock(struct kvm *kvm)
 static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 {
 	struct kvm_arch *ka = &vcpu->kvm->arch;
-	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
 
 	/*
 	 * Track whether all vCPUs have matching TSC offsets (for
@@ -2712,7 +2713,7 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 	 * accounts for its offset.
 	 */
 	bool use_master_clock = kvm_use_master_clock(vcpu->kvm) &&
-				gtod_is_based_on_tsc(gtod->clock.vclock_mode);
+				kvm_host_has_tsc_clocksource;
 
 	/*
 	 * Request a masterclock update if the masterclock needs to be toggled
@@ -2726,7 +2727,7 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
 
 	trace_kvm_track_tsc(vcpu->vcpu_id, ka->nr_vcpus_matched_tsc,
 			    atomic_read(&vcpu->kvm->online_vcpus),
-		            ka->use_master_clock, gtod->clock.vclock_mode);
+		            ka->use_master_clock, kvm_host_has_tsc_clocksource);
 }
 #else
 static inline void kvm_track_tsc_matching(struct kvm_vcpu *vcpu,
@@ -2850,7 +2851,7 @@ static inline bool kvm_check_tsc_unstable(void)
 	 * TSC is marked unstable when we're running on Hyper-V,
 	 * 'TSC page' clocksource is good.
 	 */
-	if (pvclock_gtod_data.clock.vclock_mode == VDSO_CLOCKMODE_HVCLOCK)
+	if (kvm_host_has_tsc_clocksource)
 		return false;
 #endif
 	return check_tsc_unstable();
@@ -3315,7 +3316,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
 			ka->use_master_clock = false;
 	}
 
-	vclock_mode = pvclock_gtod_data.clock.vclock_mode;
+	vclock_mode = kvm_host_has_tsc_clocksource;
 	trace_kvm_update_master_clock(ka->use_master_clock, vclock_mode,
 					ka->all_vcpus_matched_freq);
 #endif
@@ -10407,12 +10408,15 @@ static int pvclock_gtod_notify(struct notifier_block *nb, unsigned long unused,
 	update_pvclock_gtod(tk);
 
 #ifdef CONFIG_X86_64
+	kvm_host_has_tsc_clocksource =
+		gtod_is_based_on_tsc(tk->tkr_mono.clock->vdso_clock_mode);
+
 	/*
 	 * Disable master clock if host does not trust, or does not use,
 	 * TSC based clocksource. Delegate queue_work() to irq_work as
 	 * this is invoked with tk_core.seq write held.
 	 */
-	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode) &&
+	if (!kvm_host_has_tsc_clocksource &&
 	    atomic_read(&kvm_guest_has_master_clock) != 0)
 		irq_work_queue(&pvclock_irq_work);
 #endif
-- 
2.54.0


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

* [PATCH v5 34/34] KVM: x86: Remove pvclock_gtod_data and private timekeeping code
  2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
                   ` (32 preceding siblings ...)
  2026-06-08 14:48 ` [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean David Woodhouse
@ 2026-06-08 14:48 ` David Woodhouse
  33 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 14:48 UTC (permalink / raw)
  To: Paolo Bonzini, Jonathan Corbet, Shuah Khan, Sean Christopherson,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	H. Peter Anvin, Vitaly Kuznetsov, Juergen Gross, Boris Ostrovsky,
	David Woodhouse, Paul Durrant, Jonathan Cameron, Sascha Bischoff,
	Marc Zyngier, Joey Gouly, Jack Allister, Dongli Zhang, joe.jin,
	kvm, linux-doc, linux-kernel, xen-devel, linux-kselftest

From: David Woodhouse <dwmw@amazon.co.uk>

Remove the now-unused KVM-private timekeeping infrastructure:

 - struct pvclock_clock and struct pvclock_gtod_data
 - update_pvclock_gtod() and its seqcount-protected state copy
 - read_tsc() (KVM's private TSC reader with cycle_last clamping)
 - vgettsc() (KVM's private clocksource interpolation)
 - do_kvmclock_base(), do_monotonic(), do_realtime()

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Assisted-by: Kiro:claude-opus-4.6-1m
---
 Documentation/virt/kvm/devices/vcpu.rst       |   4 +-
 arch/x86/kvm/vmx/vmx.c                        |   2 +
 arch/x86/kvm/x86.c                            | 177 +-----------------
 .../testing/selftests/kvm/x86/pvclock_test.c  |   7 +-
 4 files changed, 9 insertions(+), 181 deletions(-)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index 167aa4140d30..3d1a89c2b4f7 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -243,9 +243,9 @@ Returns:
 Specifies the guest's TSC offset relative to the host's TSC. The guest's
 TSC is then derived by the following equation:
 
-  guest_tsc = ((host_tsc * tsc_scale_ratio) >> tsc_scale_bits) + KVM_VCPU_TSC_OFFSET
+  guest_tsc = ((host_tsc * tsc_ratio) >> tsc_frac_bits) + KVM_VCPU_TSC_OFFSET
 
-The values of tsc_scale_ratio and tsc_scale_bits can be obtained using
+The values of tsc_ratio and tsc_frac_bits can be obtained using
 the KVM_VCPU_TSC_SCALE attribute.
 
 This attribute is useful to adjust the guest's TSC on live migration,
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index ed207cc7692d..1aaf3924a799 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -8674,6 +8674,8 @@ __init int vmx_hardware_setup(void)
 
 	if (cpu_has_vmx_tsc_scaling() && boot_cpu_has(X86_FEATURE_CONSTANT_TSC))
 		kvm_caps.has_tsc_control = true;
+	else
+		vmcs_config.cpu_based_2nd_exec_ctrl &= ~SECONDARY_EXEC_TSC_SCALING;
 
 	kvm_caps.max_tsc_scaling_ratio = KVM_VMX_TSC_MULTIPLIER_MAX;
 	kvm_caps.tsc_scaling_ratio_frac_bits = 48;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 93a428c37847..966057913366 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -2347,58 +2347,6 @@ static int do_set_msr(struct kvm_vcpu *vcpu, unsigned index, u64 *data)
 	return kvm_set_msr_ignored_check(vcpu, index, *data, true);
 }
 
-struct pvclock_clock {
-	int vclock_mode;
-	u64 cycle_last;
-	u64 mask;
-	u32 mult;
-	u32 shift;
-	u64 base_cycles;
-	u64 offset;
-};
-
-struct pvclock_gtod_data {
-	seqcount_t	seq;
-
-	struct pvclock_clock clock; /* extract of a clocksource struct */
-	struct pvclock_clock raw_clock; /* extract of a clocksource struct */
-
-	ktime_t		offs_boot;
-	u64		wall_time_sec;
-};
-
-static struct pvclock_gtod_data pvclock_gtod_data;
-
-static void update_pvclock_gtod(struct timekeeper *tk)
-{
-	struct pvclock_gtod_data *vdata = &pvclock_gtod_data;
-
-	write_seqcount_begin(&vdata->seq);
-
-	/* copy pvclock gtod data */
-	vdata->clock.vclock_mode	= tk->tkr_mono.clock->vdso_clock_mode;
-	vdata->clock.cycle_last		= tk->tkr_mono.cycle_last;
-	vdata->clock.mask		= tk->tkr_mono.mask;
-	vdata->clock.mult		= tk->tkr_mono.mult;
-	vdata->clock.shift		= tk->tkr_mono.shift;
-	vdata->clock.base_cycles	= tk->tkr_mono.xtime_nsec;
-	vdata->clock.offset		= tk->tkr_mono.base;
-
-	vdata->raw_clock.vclock_mode	= tk->tkr_raw.clock->vdso_clock_mode;
-	vdata->raw_clock.cycle_last	= tk->tkr_raw.cycle_last;
-	vdata->raw_clock.mask		= tk->tkr_raw.mask;
-	vdata->raw_clock.mult		= tk->tkr_raw.mult;
-	vdata->raw_clock.shift		= tk->tkr_raw.shift;
-	vdata->raw_clock.base_cycles	= tk->tkr_raw.xtime_nsec;
-	vdata->raw_clock.offset		= tk->tkr_raw.base;
-
-	vdata->wall_time_sec            = tk->xtime_sec;
-
-	vdata->offs_boot		= tk->offs_boot;
-
-	write_seqcount_end(&vdata->seq);
-}
-
 static s64 get_kvmclock_base_ns(void)
 {
 	/* Count up from boot time, but with the frequency of the raw clock.  */
@@ -3037,128 +2985,6 @@ static inline void adjust_tsc_offset_host(struct kvm_vcpu *vcpu, s64 adjustment)
 
 #ifdef CONFIG_X86_64
 
-static u64 read_tsc(void)
-{
-	u64 ret = (u64)rdtsc_ordered();
-	u64 last = pvclock_gtod_data.clock.cycle_last;
-
-	if (likely(ret >= last))
-		return ret;
-
-	/*
-	 * GCC likes to generate cmov here, but this branch is extremely
-	 * predictable (it's just a function of time and the likely is
-	 * very likely) and there's a data dependence, so force GCC
-	 * to generate a branch instead.  I don't barrier() because
-	 * we don't actually need a barrier, and if this function
-	 * ever gets inlined it will generate worse code.
-	 */
-	asm volatile ("");
-	return last;
-}
-
-static inline u64 vgettsc(struct pvclock_clock *clock, u64 *tsc_timestamp,
-			  int *mode)
-{
-	u64 tsc_pg_val;
-	long v;
-
-	switch (clock->vclock_mode) {
-	case VDSO_CLOCKMODE_HVCLOCK:
-		if (hv_read_tsc_page_tsc(hv_get_tsc_page(),
-					 tsc_timestamp, &tsc_pg_val)) {
-			/* TSC page valid */
-			*mode = VDSO_CLOCKMODE_HVCLOCK;
-			v = (tsc_pg_val - clock->cycle_last) &
-				clock->mask;
-		} else {
-			/* TSC page invalid */
-			*mode = VDSO_CLOCKMODE_NONE;
-		}
-		break;
-	case VDSO_CLOCKMODE_TSC:
-		*mode = VDSO_CLOCKMODE_TSC;
-		*tsc_timestamp = read_tsc();
-		v = (*tsc_timestamp - clock->cycle_last) &
-			clock->mask;
-		break;
-	default:
-		*mode = VDSO_CLOCKMODE_NONE;
-	}
-
-	if (*mode == VDSO_CLOCKMODE_NONE)
-		*tsc_timestamp = v = 0;
-
-	return v * clock->mult;
-}
-
-/*
- * As with get_kvmclock_base_ns(), this counts from boot time, at the
- * frequency of CLOCK_MONOTONIC_RAW (hence adding gtos->offs_boot).
- */
-static int do_kvmclock_base(s64 *t, u64 *tsc_timestamp)
-{
-	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
-	unsigned long seq;
-	int mode;
-	u64 ns;
-
-	do {
-		seq = read_seqcount_begin(&gtod->seq);
-		ns = gtod->raw_clock.base_cycles;
-		ns += vgettsc(&gtod->raw_clock, tsc_timestamp, &mode);
-		ns >>= gtod->raw_clock.shift;
-		ns += ktime_to_ns(ktime_add(gtod->raw_clock.offset, gtod->offs_boot));
-	} while (unlikely(read_seqcount_retry(&gtod->seq, seq)));
-	*t = ns;
-
-	return mode;
-}
-
-/*
- * This calculates CLOCK_MONOTONIC at the time of the TSC snapshot, with
- * no boot time offset.
- */
-static int do_monotonic(s64 *t, u64 *tsc_timestamp)
-{
-	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
-	unsigned long seq;
-	int mode;
-	u64 ns;
-
-	do {
-		seq = read_seqcount_begin(&gtod->seq);
-		ns = gtod->clock.base_cycles;
-		ns += vgettsc(&gtod->clock, tsc_timestamp, &mode);
-		ns >>= gtod->clock.shift;
-		ns += ktime_to_ns(gtod->clock.offset);
-	} while (unlikely(read_seqcount_retry(&gtod->seq, seq)));
-	*t = ns;
-
-	return mode;
-}
-
-static int do_realtime(struct timespec64 *ts, u64 *tsc_timestamp)
-{
-	struct pvclock_gtod_data *gtod = &pvclock_gtod_data;
-	unsigned long seq;
-	int mode;
-	u64 ns;
-
-	do {
-		seq = read_seqcount_begin(&gtod->seq);
-		ts->tv_sec = gtod->wall_time_sec;
-		ns = gtod->clock.base_cycles;
-		ns += vgettsc(&gtod->clock, tsc_timestamp, &mode);
-		ns >>= gtod->clock.shift;
-	} while (unlikely(read_seqcount_retry(&gtod->seq, seq)));
-
-	ts->tv_sec += __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
-	ts->tv_nsec = ns;
-
-	return mode;
-}
-
 /*
  * Calculates the kvmclock_base_ns (CLOCK_MONOTONIC_RAW + boot time) and
  * reports the TSC value from which it do so. Returns true if host is
@@ -6231,7 +6057,7 @@ static int kvm_arch_tsc_set_attr(struct kvm_vcpu *vcpu,
 		break;
 	}
 	case KVM_VCPU_TSC_SCALE:
-		r = -EINVAL; /* Read only */
+		r = kvm_caps.has_tsc_control ? -EINVAL : -ENXIO;
 		break;
 	default:
 		r = -ENXIO;
@@ -10405,7 +10231,6 @@ static int pvclock_gtod_notify(struct notifier_block *nb, unsigned long unused,
 {
 	struct timekeeper *tk = priv;
 
-	update_pvclock_gtod(tk);
 
 #ifdef CONFIG_X86_64
 	kvm_host_has_tsc_clocksource =
diff --git a/tools/testing/selftests/kvm/x86/pvclock_test.c b/tools/testing/selftests/kvm/x86/pvclock_test.c
index aecd62fc8a93..4c1869fa482e 100644
--- a/tools/testing/selftests/kvm/x86/pvclock_test.c
+++ b/tools/testing/selftests/kvm/x86/pvclock_test.c
@@ -14,7 +14,6 @@
 #include "test_util.h"
 #include "kvm_util.h"
 #include "processor.h"
-#include "apic.h"
 
 #include <asm/pvclock-abi.h>
 
@@ -262,10 +261,12 @@ int main(int argc, char *argv[])
 	return 0;
 }
 
+static volatile uint32_t vcpu_counter;
+
 static void guest_code_stable_bit(void)
 {
-	uint32_t apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
-	uint64_t gpa = KVMCLOCK_GPA + apic_id * sizeof(struct pvclock_vcpu_time_info);
+	uint32_t idx = __atomic_fetch_add(&vcpu_counter, 1, __ATOMIC_SEQ_CST);
+	uint64_t gpa = KVMCLOCK_GPA + idx * sizeof(struct pvclock_vcpu_time_info);
 
 	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, gpa | KVM_MSR_ENABLED);
 	GUEST_SYNC(0);
-- 
2.54.0


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

* Re: [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration
  2026-06-08 14:47 ` [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration David Woodhouse
@ 2026-06-08 15:33   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 15:33 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

New issues:
- [High] Unbounded bitwise shift in `hvclock_to_hz()` triggered by user-controlled input can cause Undefined Behavior (shift out-of-bounds) and a kernel panic (DoS).
- [Low] The KVM_[GS]ET_CLOCK_GUEST ioctls cause compilation failures in userspace due to an incomplete type in the generic UAPI header.

Pre-existing issues:
- [High] Race condition in `kvm_guest_time_update` reads `kvmclock_offset` unsynchronized, leading to corrupt guest `system_time`.
--

commit fdd3f3e2e20605997654a24ac2e941cc677cf87b
Author: Jack Allister <jalliste@amazon.com>

KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration

This commit adds per-vCPU ioctls (KVM_GET_CLOCK_GUEST and KVM_SET_CLOCK_GUEST)
to allow userspace to precisely save and restore the arithmetic relationship
between the guest TSC and the KVM clock across live migrations.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index d9ef165df6a14..b7e5f6e3dc6c1 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> +static u64 hvclock_to_hz(u32 mul, s8 shift)
> +{
> +	u64 tm = NSEC_PER_SEC << 32;
> +
> +	/* Maximise precision. Shift right until the top bit is set */
> +	tm <<= 2;
> +	shift += 2;
> +
> +	/* While 'mul' is even, increase the shift *after* the division */
> +	while (!(mul & 1)) {
> +		shift++;
> +		mul >>= 1;
> +	}
> +
> +	tm /= mul;
> +
> +	if (shift > 0)
> +		return tm >> shift;

[Severity: High]
Can this result in an undefined bitwise shift? If userspace provides a
tsc_shift of 32 and a tsc_to_system_mul with 31 trailing zeroes (like
0x80000000), the loop will increment the shift 31 times. This results in a
total shift amount of 65. Since tm is a 64-bit integer, shifting it by 65
bits triggers undefined behavior. Does the validation in
kvm_vcpu_ioctl_set_clock_guest() need to enforce a limit on the combined
shift amount?

> +	else
> +		return tm << -shift;
> +}

[ ... ]

> +static int kvm_vcpu_ioctl_set_clock_guest(struct kvm_vcpu *v, void __user *argp)
> +{

[ ... ]

> +	/*
> +	 * Calculate the guest TSC at the new reference point, and the
> +	 * corresponding KVM clock value according to user_hv_clock.
> +	 * Adjust kvmclock_offset so both definitions agree.
> +	 */
> +	guest_tsc = kvm_read_l1_tsc(v, ka->master_cycle_now);
> +	user_clk_ns = __pvclock_read_cycles(&user_hv_clock, guest_tsc);
> +	ka->kvmclock_offset = user_clk_ns - ka->master_kernel_ns;

[Severity: High]
This is a pre-existing issue, but is it possible for concurrent updates to
cause a torn read in kvm_guest_time_update() now that this ioctl modifies
kvmclock_offset?

In kvm_guest_time_update(), master_kernel_ns is read safely inside the
pvclock_sc seqcount loop, but kvmclock_offset is read outside of it:

kvm_guest_time_update() {
        do {
                seq = read_seqcount_begin(&ka->pvclock_sc);
                ...
                master_kernel_ns = ka->master_kernel_ns;
        } while (read_seqcount_retry(&ka->pvclock_sc, seq));
        ...
        hv_clock.system_time = kernel_ns + v->kvm->arch.kvmclock_offset;
}

If kvm_vcpu_ioctl_set_clock_guest() modifies both variables under the seqcount
write lock after the reader finishes the loop but before reading
kvmclock_offset, couldn't the guest receive a stale kernel_ns combined with a
new kvmclock_offset?

> +
> +out:
> +	kvm_end_pvclock_update(kvm);
> +	return rc;
> +}

[ ... ]

> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index 6c8afa2047bf3..9b50191b859cd 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -1669,4 +1669,7 @@ struct kvm_pre_fault_memory {
>  	__u64 padding[5];
>  };
>  
> +#define KVM_SET_CLOCK_GUEST	_IOW(KVMIO, 0xd6, struct pvclock_vcpu_time_info)
> +#define KVM_GET_CLOCK_GUEST	_IOR(KVMIO, 0xd7, struct pvclock_vcpu_time_info)
> +

[Severity: Low]
Does this cause compilation failures for userspace applications? The ioctls
use struct pvclock_vcpu_time_info which is defined in asm/pvclock-abi.h, but
that header is not included by linux/kvm.h. Since UAPI headers should be
self-contained, could this result in an 'invalid application of sizeof to
incomplete type' error?

>  #endif /* __LINUX_KVM_H */

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=4

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

* Re: [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction
  2026-06-08 14:47 ` [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction David Woodhouse
@ 2026-06-08 15:49   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 15:49 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Flawed API/Feature Check for KVM_GET_CLOCK_GUEST support.
- [Medium] Missing Capability Check for KVM_VCPU_TSC_OFFSET device attribute.
--

commit 26a6d06b6a0ac05e89b1ed831e29fd555de24ff8
Author: Jack Allister <jalliste@amazon.com>

KVM: selftests: Add KVM/PV clock selftest to prove timer correction

This commit adds a KVM/PV clock selftest to verify timer correction during
simulated live update or migration. It verifies that using KVM_GET_CLOCK_GUEST
and KVM_SET_CLOCK_GUEST maintains the relationship between a guest's TSC and
KVM/PV clock.

> diff --git a/tools/testing/selftests/kvm/x86/pvclock_test.c b/tools/testing/selftests/kvm/x86/pvclock_test.c
> new file mode 100644
> index 0000000000000..aecd62fc8a93c
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/pvclock_test.c

[ ... ]

> +int main(int argc, char *argv[])
> +{

[ ... ]

> +	/* Check KVM_GET_CLOCK_GUEST is supported */
> +	{
> +		struct pvclock_vcpu_time_info tmp;
> +		int ret = __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &tmp);
> +		TEST_REQUIRE(ret != -1 || errno != ENOTTY);

[Severity: Medium]
Does this condition correctly detect unsupported ioctls on x86?

Looking at the KVM implementation, kvm_arch_vcpu_ioctl() on x86 returns
-EINVAL for unsupported ioctls rather than -ENOTTY. On an older kernel where
the ioctl is not supported, wouldn't errno be EINVAL, making this condition
true and causing the test to proceed and fail later instead of safely
skipping?

> +	}

[ ... ]

> +static void set_tsc_offset(struct kvm_vcpu *vcpu, uint64_t offset)
> +{
> +	struct kvm_device_attr attr = {
> +		.group = KVM_VCPU_TSC_CTRL,
> +		.attr = KVM_VCPU_TSC_OFFSET,
> +		.addr = (__u64)(uintptr_t)&offset,
> +	};
> +	vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &attr);

[Severity: Medium]
Will this unconditionally fail on older kernels that lack support for the
KVM_VCPU_TSC_OFFSET device attribute?

While the test requires KVM_CAP_TSC_CONTROL earlier in main(), the
KVM_VCPU_TSC_OFFSET attribute is a separate, newer feature. Should this
include a capability check using __vcpu_has_device_attr() before invoking
vcpu_ioctl() to ensure the test safely skips on older kernels?

> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=5

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

* Re: [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation
  2026-06-08 14:47 ` [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation David Woodhouse
@ 2026-06-08 16:27   ` sashiko-bot
  2026-06-08 23:29     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 16:27 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unconditional masterclock update causes O(N^2) overhead during VM creation
--

commit 8f4737a975ef2b2f510c7316ea3baa233e41c647
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Activate master clock immediately on vCPU creation

This commit updates vCPU creation to activate the master clock immediately,
rather than waiting for the first vCPU to process KVM_REQ_MASTERCLOCK_UPDATE.
This allows KVM_GET_CLOCK to return a valid host_tsc and realtime pair right
away without requiring a dummy vCPU run.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index b7e5f6e3dc6c1..c1897d939da91 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -13098,6 +13098,8 @@ void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu)
>  		return;
>  	vcpu_load(vcpu);
>  	kvm_synchronize_tsc(vcpu, NULL);
> +	if (!vcpu->kvm->arch.use_master_clock)
> +		kvm_update_masterclock(vcpu->kvm);

[Severity: High]
Does this cause an O(N^2) scaling issue during VM creation if the host 
clocksource doesn't support the masterclock?

If the host lacks masterclock support (for example, missing a TSC 
clocksource), vcpu->kvm->arch.use_master_clock remains false after the 
update. This means every subsequent vCPU creation will re-trigger 
kvm_update_masterclock() unnecessarily.

Since kvm_update_masterclock() makes requests for all existing vCPUs 
using kvm_make_all_cpus_request() and loops over them, creating N vCPUs 
would result in O(N^2) lock acquisitions and iterations. Could this 
cause noticeable latency regressions or soft lockups when booting large VMs?

>  	vcpu_put(vcpu);
>  
>  	/* poll control enabled by default */

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=7

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

* Re: [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration
  2026-06-08 14:47 ` [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration David Woodhouse
@ 2026-06-08 16:39   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 16:39 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Missing capability check for TSC scaling in `pvclock_migration_test` causes unconditional test failure on unsupported hardware.
- [Medium] Integer overflow in `tsc_dst` and `tsc_ofs_delta_ns` calculations in `pvclock_migration_test`.
--

commit 672fb23aded51a56922a91383781eb9402e95560
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration

Adds KVM_VCPU_TSC_SCALE to extract the scale ratio and frac_bits, and
documents the process for userspace to preserve the TSC across migration.
Adds a selftest to verify KVM clock precision across simulated live migration.

> diff --git a/tools/testing/selftests/kvm/x86/pvclock_migration_test.c b/tools/testing/selftests/kvm/x86/pvclock_migration_test.c
> new file mode 100644
> index 0000000000000..6a7eaf627d1a7
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/pvclock_migration_test.c

[ ... ]

> +int main(void)
> +{

[ ... ]

> +	uint32_t freq_khz = 1500000; /* 1.5 GHz — forces TSC scaling */
> +	int ret;
> +
> +	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
> +
> +	/* === SOURCE SIDE === */
> +	pr_info("=== Source VM ===\n");
> +	vm = create_vm(&vcpu);
> +
> +	/* Set guest TSC frequency (may trigger scaling) */
> +	vcpu_ioctl(vcpu, KVM_SET_TSC_KHZ, (void *)(unsigned long)freq_khz);

[Severity: Medium]
Will this unconditionally fail on hardware that lacks TSC scaling?

The test unconditionally forces a 1.5 GHz guest TSC frequency. If the host
lacks hardware TSC scaling (kvm_caps.has_tsc_control is false) and the
physical TSC frequency is greater than 1.5 GHz, KVM returns -EINVAL.

Should there be a check like TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL))
before forcing this, or a way to gracefully handle the error?

[ ... ]

> +	/* Step 6: ΔT */
> +	delta_t = (int64_t)(time_dst - time_src);
> +
> +	/* Step 7: Compute destination offset */
> +	{
> +		struct kvm_device_attr attr = {
> +			.group = KVM_VCPU_TSC_CTRL,
> +			.attr = KVM_VCPU_TSC_SCALE,
> +			.addr = (uint64_t)(uintptr_t)&scale_dst,
> +		};
> +		memset(&scale_dst, 0, sizeof(scale_dst));
> +		__vcpu_ioctl(vcpu, KVM_GET_DEVICE_ATTR, &attr);
> +	}
> +
> +	tsc_dst = tsc_src + (uint64_t)((int64_t)freq_khz * 1000 * delta_t / 1000000000LL);

[Severity: Medium]
Can this calculation overflow under heavy load?

With freq_khz set to 1500000, the intermediate multiplication is
1.5e9 * delta_t. This exceeds INT64_MAX if delta_t is greater than ~6.14
seconds. While the test uses sleep(1), VM teardown and creation under heavy
CI load might cause the time delta to exceed this.

Could this be rearranged to avoid intermediate overflow, for example:
delta_t * freq_khz / 1000000LL?

> +
> +	if (scale_dst.tsc_frac_bits)
> +		raw_dst = (unsigned __int128)host_tsc_dst * scale_dst.tsc_ratio
> +			  >> scale_dst.tsc_frac_bits;
> +	else
> +		raw_dst = host_tsc_dst;
> +
> +	ofs_dst = tsc_dst - raw_dst;
> +
> +	/*
> +	 * The TSC offset delta introduced by using CLOCK_REALTIME to
> +	 * estimate elapsed time. On same host, the correct offset is
> +	 * ofs_src; the difference is the CLOCK_REALTIME-vs-TSC error.
> +	 */
> +	int64_t tsc_ofs_delta = (int64_t)(ofs_dst - ofs_src);
> +	int64_t tsc_ofs_delta_ns = tsc_ofs_delta * 1000000000LL / ((int64_t)freq_khz * 1000);

[Severity: Medium]
Could a similar integer overflow happen here if the TSC offset delta cycles
are abnormally large?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=8

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

* Re: [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling
  2026-06-08 14:47 ` [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling David Woodhouse
@ 2026-06-08 17:39   ` sashiko-bot
  2026-06-08 23:43     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 17:39 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Precision loss discrepancy between master clock and vCPU pvclock causes monotonic time jumps on live migration.
--

commit 47c298cb974d0bc994c9dfd80ba585010b2ad459
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling

This patch fixes a systemic drift in the KVM clock when using TSC scaling.
It precomputes the guest-TSC-based multiplier and shift in
pvclock_update_vm_gtod_copy() and applies them in get_kvmclock() to correctly
scale the host TSC delta to the guest TSC before converting to nanoseconds.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 6983a7494fcdd..7ae6a77053530 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> @@ -3132,9 +3135,30 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
>  				&& !ka->backwards_tsc_observed
>  				&& !ka->boot_vcpu_runs_old_kvmclock;
>  
> -	if (ka->use_master_clock)
> +	if (ka->use_master_clock) {
> +		u64 tsc_hz;
> +
>  		atomic_set(&kvm_guest_has_master_clock, 1);
>  
> +		/*
> +		 * Copy the scaling ratio and precompute the mul/shift for
> +		 * converting guest TSC to nanoseconds. These are used by
> +		 * get_kvmclock() to compute kvmclock from the host TSC
> +		 * without needing a vCPU reference.
> +		 */
> +		ka->master_tsc_scaling_ratio = ka->last_tsc_scaling_ratio;
> +		tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
> +		if (tsc_hz && kvm_caps.has_tsc_control)
> +			tsc_hz = kvm_scale_tsc(tsc_hz,
> +					       ka->master_tsc_scaling_ratio);

[Severity: High]
Does calculating tsc_hz by multiplying by 1000 before scaling create a
precision discrepancy with the guest's pvclock?

In kvm_guest_time_update(), the guest's pvclock frequency is calculated
by first scaling the host frequency in kHz and then multiplying by 1000 to
get Hz:

    tgt_tsc_khz = kvm_scale_tsc(tgt_tsc_khz, v->arch.l1_tsc_scaling_ratio);
    tgt_tsc_khz = tgt_tsc_khz ? : 1;
    ...
    kvm_get_time_scale(NSEC_PER_SEC, tgt_tsc_khz * 1000LL, ...);

This truncates up to 999 Hz of precision. The new calculation here scales
the Hz directly, resulting in a slightly larger tsc_hz and correspondingly
smaller multiplier than the vCPU's pvclock_tsc_mul.

Because the host uses this smaller multiplier in get_kvmclock(), it will
calculate a slightly smaller elapsed time than the guest has internally
observed. Upon live migration, reading this lagging clock via KVM_GET_CLOCK
could cause the guest's monotonic time to jump backwards when it resumes.

Would it be better to match the guest's exact computation, or update the
guest's calculation to match this higher precision?

> +		if (tsc_hz)
> +			kvm_get_time_scale(NSEC_PER_SEC, tsc_hz,
> +					   &ka->master_tsc_shift,
> +					   &ka->master_tsc_mul);
> +		else
> +			ka->use_master_clock = false;
> +	}
> +
>  	vclock_mode = pvclock_gtod_data.clock.vclock_mode;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=12

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

* Re: [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas
  2026-06-08 14:47 ` [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas David Woodhouse
@ 2026-06-08 17:59   ` sashiko-bot
  2026-06-09  0:02     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 17:59 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [High] Unsafe unsigned comparison in `kvm_guest_time_update` defeats the monotonicity guard when `compute_guest_tsc` mathematically wraps below zero.
- [High] `__pvclock_read_cycles` passes a wrapped negative delta to `pvclock_scale_delta()`, resulting in a wildly corrupted scaled clock offset.
--

commit 2283f250a7b73885318539a7dbf8a833e3f3f4ce
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: handle negative deltas in compute_guest_tsc()

The compute_guest_tsc() function computes the guest TSC at a given
kernel_ns timestamp. When the master clock reference point is earlier
than vcpu->arch.this_tsc_nsec, the delta wraps to a huge positive number.
This commit handles negative deltas explicitly by negating, scaling,
and subtracting from this_tsc_write.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index fc9366b83912..8aae22401046 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -2588,11 +2588,21 @@ static int kvm_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
>  
>  static u64 compute_guest_tsc(struct kvm_vcpu *vcpu, s64 kernel_ns)
>  {
> -	u64 tsc = pvclock_scale_delta(kernel_ns-vcpu->arch.this_tsc_nsec,
> -				      vcpu->arch.virtual_tsc_mult,
> -				      vcpu->arch.virtual_tsc_shift);
> -	tsc += vcpu->arch.this_tsc_write;
> -	return tsc;
> +	s64 delta_ns = kernel_ns - vcpu->arch.this_tsc_nsec;
> +	u64 tsc;
> +
> +	/* Handle negative deltas gracefully (master clock ref may be earlier) */
> +	if (delta_ns < 0) {
> +		tsc = pvclock_scale_delta(-delta_ns,
> +					  vcpu->arch.virtual_tsc_mult,
> +					  vcpu->arch.virtual_tsc_shift);
> +		return vcpu->arch.this_tsc_write - tsc;

[Severity: High]
This isn't a bug introduced by this patch, but does this incomplete fix expose
a logic error in the caller when the subtraction mathematically wraps below
zero?

If this_tsc_write is smaller than the scaled tsc (for example, if the guest
recently wrote 0 to the TSC), the return value wraps to a huge unsigned u64.

In the caller kvm_guest_time_update(), we see this unsigned comparison:

arch/x86/kvm/x86.c:kvm_guest_time_update() {
    ...
    u64 tsc = compute_guest_tsc(v, kernel_ns);
    if (tsc > tsc_timestamp) {
        adjust_tsc_offset_guest(v, tsc - tsc_timestamp);
        tsc_timestamp = tsc;
    }
    ...
}

Because the wrapped negative tsc appears as a huge positive number, this
evaluates to true. adjust_tsc_offset_guest() is then called with the wrapped
value, forcing the guest TSC to leap backwards.

Can this break the strict monotonicity guarantee and cause time anomalies or
guest kernel panics?

> +	}
> +
> +	tsc = pvclock_scale_delta(delta_ns,
> +				  vcpu->arch.virtual_tsc_mult,
> +				  vcpu->arch.virtual_tsc_shift);

[Severity: High]
This isn't a bug introduced by this patch, but does __pvclock_read_cycles()
suffer from the exact same scaling corruption?

When __get_kvmclock() calls __pvclock_read_cycles(), it uses rdtsc() for the
local TSC and ka->master_cycle_now for the timestamp:

arch/x86/kvm/x86.c:__get_kvmclock() {
    ...
    data->clock = __pvclock_read_cycles(&hv_clock, data->host_tsc);
    ...
}

If the local CPU's TSC is slightly behind the CPU that recorded the master
clock due to normal cross-CPU TSC skew, tsc < src->tsc_timestamp can occur:

arch/x86/include/asm/pvclock.h:__pvclock_read_cycles() {
    ...
    u64 delta = tsc - src->tsc_timestamp;
    u64 offset = pvclock_scale_delta(delta, src->tsc_to_system_mul,
                                     src->tsc_shift);
    ...
}

This causes delta to mathematically wrap to a huge unsigned number, which is
then passed directly to pvclock_scale_delta().

Could this produce an astronomical clock offset that gets returned to
userspace via the KVM_GET_CLOCK ioctl?

> +	return vcpu->arch.this_tsc_write + tsc;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=14

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

* Re: [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling
  2026-06-08 14:47 ` [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling David Woodhouse
@ 2026-06-08 18:13   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 18:13 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Dropping the `local_irq_save()` protection around the TSC catchup calculation exposes an unprotected Read-Modify-Write (RMW) of `l1_tsc_offset`, leading to guest TSC corruption on systems with unstable TSC.
- [Medium] The commit message claims a WARN is added for `kvm_get_time_and_clockread()` unexpected failures, but the implementation silently handles it.
--

commit f05935e83e34b920673a8039b5e1b6c7443ffe07
Author: David Woodhouse <dwmw@amazon.co.uk>

Restructure kvm_guest_time_update() so that kernel_ns/host_tsc are
always "now" when doing TSC catchup, then swap in the master clock
reference values afterward for the hv_clock.

This makes the TSC upscaling code considerably simpler: the catchup
adjustment is computed as the delta between what the guest TSC *should*
be at "now" and what it actually is, rather than mixing "now" and
"master clock reference" timestamps.

> The seqcount loop now also contains the kvm_get_time_and_clockread()
> call (matching get_kvmclock's pattern), with the same WARN for
> unexpected failure.

[Severity: Medium]
The commit message mentions that a WARN is added for unexpected failures of
kvm_get_time_and_clockread(), but the implementation inside the
CONFIG_X86_64 block seems to silently handle the failure by clearing
use_master_clock. Was the WARN intentionally omitted from the patch?

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 8aae22401046..92e32d720523 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -3363,46 +3363,63 @@ static void kvm_setup_guest_pvclock(struct pvclock_vcpu_time_info *ref_hv_clock,
[ ... ]
> -	/* Keep irq disabled to prevent changes to the clock */
> -	local_irq_save(flags);
> -	tgt_tsc_hz = (u64)get_cpu_tsc_khz() * 1000;
>  	if (unlikely(tgt_tsc_hz == 0)) {
> -		local_irq_restore(flags);
>  		kvm_make_request(KVM_REQ_CLOCK_UPDATE, v);
>  		return 1;
>  	}
> +
>  	if (!use_master_clock) {
> +		unsigned long flags;
> +
> +		local_irq_save(flags);
>  		host_tsc = rdtsc();
>  		kernel_ns = get_kvmclock_base_ns();
> +		local_irq_restore(flags);
>  	}
>  
> -	tsc_timestamp = kvm_read_l1_tsc(v, host_tsc);
> -
>  	/*
>  	 * We may have to catch up the TSC to match elapsed wall clock
>  	 * time for two reasons, even if kvmclock is used.
> @@ -3411,17 +3428,32 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
>  	 *      entry to avoid unknown leaps of TSC even when running
>  	 *      again on the same CPU.  This may cause apparent elapsed
>  	 *      time to disappear, and the guest to stand still or run
> -	 *	very slowly.
> +	 *      very slowly.
>  	 */
>  	if (vcpu->tsc_catchup) {
> -		u64 tsc = compute_guest_tsc(v, kernel_ns);
> -		if (tsc > tsc_timestamp) {
> -			adjust_tsc_offset_guest(v, tsc - tsc_timestamp);
> -			tsc_timestamp = tsc;
> -		}
> +		s64 adjustment;
> +
> +		/*
> +		 * Calculate the delta between what the guest TSC *should* be
> +		 * and what it actually is according to kvm_read_l1_tsc().
> +		 */
> +		adjustment = compute_guest_tsc(v, kernel_ns) -
> +			     kvm_read_l1_tsc(v, host_tsc);
> +		if (adjustment > 0)
> +			adjust_tsc_offset_guest(v, adjustment);

[Severity: High]
Does dropping the local_irq_save() protection around the TSC catchup calculation
expose an unprotected read-modify-write of l1_tsc_offset?

If preemption occurs inside adjust_tsc_offset_guest() after reading the offset:

arch/x86/kvm/x86.c:adjust_tsc_offset_guest() {
    u64 tsc_offset = vcpu->arch.l1_tsc_offset;
    /* <--- preemption here */
    kvm_vcpu_write_tsc_offset(vcpu, tsc_offset + adjustment);
}

And the vCPU migrates to a new CPU with an unstable TSC, KVM's preempt
notifiers would call kvm_arch_vcpu_load() and write a new compensation
offset. When this thread resumes, wouldn't it write back the stale base
offset and overwrite the preempt notifier's compensation?

Since the code only corrects positive adjustments, the corrupted offset
would remain, potentially causing the guest TSC to leap forward.

>  	}
>  
> -	local_irq_restore(flags);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=15

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

* Re: [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc()
  2026-06-08 14:47 ` [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc() David Woodhouse
@ 2026-06-08 18:39   ` sashiko-bot
  2026-06-09  0:14     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 18:39 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The patch breaks the 1-second TSC matching interval extension, causing severe TSC synchronization failures when restoring large VMs.
--

commit 36ab17132ec1bae1eb23cb9d908fb9bf60b2d36f
Author: David Woodhouse <dwmw@amazon.co.uk>

x86/kvm: Fix misrecorded TSC synchronization values

This commit modifies kvm_synchronize_tsc() to reset the written data and
timestamp to previous values when a synchronization match is detected. It
also unifies the TSC reads used for calculating the current time.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index bce4c7a6a6fe..c8c0633263fb 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> @@ -2904,12 +2916,25 @@ static void kvm_synchronize_tsc(struct kvm_vcpu *vcpu, u64 *user_value)
>           */
>  	if (synchronizing &&
>  	    vcpu->arch.virtual_tsc_khz == kvm->arch.last_tsc_khz) {
> +		/*
> +		 * If synchronizing, the "last written" TSC value/time
> +		 * recorded by __kvm_synchronize_tsc() should not change
> +		 * (i.e. should be precisely the same as the existing
> +		 * generation).
> +		 */
> +		data = kvm->arch.last_tsc_write;
> +
>  		if (!kvm_check_tsc_unstable()) {
>  			offset = kvm->arch.cur_tsc_offset;
> +			ns = kvm->arch.cur_tsc_nsec;

[Severity: High]
Does forcing ns and data back to the start of the generation break the
1-second TSC matching interval extension for large VMs?

During VM restore, userspace sequentially writes the exact same saved TSC
value to all vCPUs. KVM's legacy behavior updated the tracking anchor to the
time and value of each matched write, creating a sliding window that allowed
the restoration process to exceed 1 second in total, as long as each
individual vCPU was restored within 1 second of the previous one.

By freezing the anchor to the start of the generation (T0) here, this sliding
window appears to be lost. If a large VM takes more than 1 second to restore,
a vCPU restored at T_N (where T_N - T0 > 1s) will compute an elapsed time
greater than 1s.

Since userspace writes the same static saved TSC value, the expected TSC
will be more than 1s ahead of the written value. Would this cause the
synchronization check to fail, creating a new TSC generation with a
misaligned offset compared to the rest of the guest?

>  		} else {
> +			/*
> +			 * ...unless the TSC is unstable and has to be
> +			 * synthesised from the host clock in nanoseconds.
> +			 */
>  			u64 delta = nsec_to_cycles(vcpu, elapsed);
>  			data += delta;
> -			offset = kvm_compute_l1_tsc_offset(vcpu, rdtsc(), data);
> +			offset = kvm_compute_l1_tsc_offset(vcpu, host_tsc, data);
>  		}
>  		matched = true;
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=18

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

* Re: [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields
  2026-06-08 14:48 ` [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields David Woodhouse
@ 2026-06-08 18:53   ` sashiko-bot
  2026-06-09  0:34     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 18:53 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [High] Mathematical flaw in S4 host resume TSC adjustment permanently breaks cross-vCPU synchronization.
- [High] Lockless read-modify-write of `cur_tsc_write` introduces a race condition with concurrent `KVM_SET_TSC` ioctls.
- [Medium] The commit silently changes the logic for TSC matching after an S4 resume without documenting the functional change.

Pre-existing issues:
- [Critical] Unprotected iteration over `vm_list` in CPU hotplug path leads to Use-After-Free.
--

commit d9bfcd24b7d69aa51689545f7274022cd36bbee8
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Remove last_tsc_{nsec,write,khz,offset,scaling_ratio}

This commit removes the redundant last_tsc_* variables, as they pointlessly
duplicate the cur_tsc_* values used for TSC generation tracking. The patch
renames the remaining properties to reflect the current generation and
updates the S4 host suspend logic.

> These pointlessly duplicate the cur_tsc_{nsec,write,offset} values.
> The only place they were used was where the TSC is stable and a new
> vCPU is being synchronized to the previous setting, in which case the
> cur_tsc_* value is definitely identical.
> 
> Rename last_tsc_khz and last_tsc_scaling_ratio to cur_tsc_khz and
> cur_tsc_scaling_ratio respectively, since they are properties of the
> current TSC generation.

[Severity: Medium]
Does this commit also introduce a functional change to the S4 host suspend
logic?

The commit message describes a pure cleanup of duplicated variables, but the
patch removes the explicit zeroing of last_tsc_nsec and last_tsc_write (which
disabled TSC matching) and replaces it with a math adjustment to
cur_tsc_write. Should this behavioral change be documented in the commit
message?

[ ... ]

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index c8c0633263fb..bbd642e0dc54 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> @@ -13543,13 +13539,15 @@ int kvm_arch_enable_virtualization_cpu(void)
>  			}
>  
>  			/*
> -			 * We have to disable TSC offset matching.. if you were
> -			 * booting a VM while issuing an S4 host suspend....
> -			 * you may have some problem.  Solving this issue is
> -			 * left as an exercise to the reader.
> +			 * Adjust the TSC matching reference by the same
> +			 * delta applied to each vCPU's offset, so that
> +			 * future KVM_SET_TSC / vCPU creation still matches
> +			 * correctly against the adjusted TSC timeline.
> +			 * Scale from host to guest TSC rate.
>  			 */

[Severity: Critical]
This isn't a bug introduced by this patch, but does the iteration over vm_list
in kvm_arch_enable_virtualization_cpu() need to hold kvm_lock?

Since kvm_arch_enable_virtualization_cpu() is called from the CPU hotplug
callback, it runs under the CPU hotplug lock but doesn't seem to serialize
against the kvm_lock mutex. If a VM is destroyed concurrently via
kvm_destroy_vm(), could this lockless iteration dereference a freed KVM
instance?

> -			kvm->arch.last_tsc_nsec = 0;
> -			kvm->arch.last_tsc_write = 0;
> +			kvm->arch.cur_tsc_write -=
> +				kvm_scale_tsc(delta_cyc,
> +					      kvm->arch.cur_tsc_scaling_ratio);

[Severity: High]
Does this S4 resume adjustment to cur_tsc_write permanently break cross-vCPU
synchronization by omitting the symmetrical adjustment to cur_tsc_offset?

During S4 resume, existing vCPUs compensate their offsets via delta_cyc.
When a new vCPU is subsequently created or KVM_SET_TSC is called,
kvm_synchronize_tsc() assigns the new vCPU's offset directly from
kvm->arch.cur_tsc_offset:

kvm_synchronize_tsc() {
    ...
    offset = kvm->arch.cur_tsc_offset;
    ...
}

Because cur_tsc_offset lacks the delta_cyc compensation applied to existing
vCPUs, won't the new vCPU's TSC timeline be permanently shifted backward by
delta_cyc relative to the existing vCPUs?


[Severity: High]
Can this lockless read-modify-write operation race with concurrent KVM_SET_TSC
ioctls?

Since CPU hotplug can occur while KVM is running VMs, if the platform
registers a backwards TSC during CPU bringup, this path executes concurrently
with userspace ioctls like KVM_SET_TSC.

KVM_SET_TSC properly acquires kvm->arch.tsc_write_lock before updating
cur_tsc_write and offsets. Could this lockless subtraction tear the 64-bit
value or overwrite a brand-new TSC generation state from KVM_SET_TSC?

>  		}
>  
>  	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=19

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

* Re: [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other
  2026-06-08 14:48 ` [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other David Woodhouse
@ 2026-06-08 19:15   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 19:15 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Guest pvclocks are not updated when all vCPUs re-synchronize their TSC offsets, permanently depriving the guest of the PVCLOCK_TSC_STABLE_BIT optimization.
--

commit b05710eb6e19ddef655898395dce759387bbe84e
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Track frequency matching separately from offset matching

Previously, a guest writing different TSC values on different vCPUs could
force KVM out of master clock mode. This patch separates frequency tracking
from offset tracking, allowing master clock to persist despite offset
mismatches, while maintaining PVCLOCK_TSC_STABLE_BIT semantics.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index ac66f8e7116f..86c30be4c5d2 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -2647,14 +2647,37 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)

[ ... ]

> +	/*
> +	 * To use the masterclock, the host clocksource must be based on TSC
> +	 * and all vCPUs must have matching TSC *frequency*. Different offsets
> +	 * are fine — each vCPU's pvclock has its own tsc_timestamp that
> +	 * accounts for its offset.
> +	 */
> +	bool use_master_clock = ka->all_vcpus_matched_freq &&
>  				gtod_is_based_on_tsc(gtod->clock.vclock_mode);

[Severity: Medium]
Will guest pvclocks be correctly updated when the final vCPU synchronizes its
TSC offset to match the others?

Because all_vcpus_matched_tsc is now decoupled from use_master_clock, the
broadcast that restores PVCLOCK_TSC_STABLE_BIT across all vCPUs appears to be
broken, causing a performance regression for guest vDSO time reads.

When the final vCPU synchronizes its offset, new_generation evaluates to false
and use_master_clock remains true.

Therefore, the existing condition further down in kvm_track_tsc_matching():

if ((ka->use_master_clock && new_generation) ||
    (ka->use_master_clock != use_master_clock))
        kvm_make_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);

will fail to trigger the KVM_REQ_MASTERCLOCK_UPDATE request.

Other vCPUs will not get the broadcast to run kvm_guest_time_update() and will
be permanently deprived of the PVCLOCK_TSC_STABLE_BIT optimization until an
unrelated event forces a global clock update.

[ ... ]

> @@ -3527,7 +3566,7 @@ int kvm_guest_time_update(struct kvm_vcpu *v)
>  
>  	/* If the host uses TSC clocksource, then it is stable */
>  	hv_clock.flags = 0;
> -	if (use_master_clock)
> +	if (use_master_clock && ka->all_vcpus_matched_tsc)
>  		hv_clock.flags |= PVCLOCK_TSC_STABLE_BIT;

[Severity: Medium]
Without the KVM_REQ_MASTERCLOCK_UPDATE broadcast mentioned above, will other
vCPUs ever reach this code to restore their PVCLOCK_TSC_STABLE_BIT flag once
ka->all_vcpus_matched_tsc becomes true again?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=21

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

* Re: [PATCH v5 22/34] KVM: selftests: Add master clock offset test
  2026-06-08 14:48 ` [PATCH v5 22/34] KVM: selftests: Add master clock offset test David Woodhouse
@ 2026-06-08 19:26   ` sashiko-bot
  2026-06-09  0:50     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 19:26 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] The test uses an undefined, hallucinated ioctl macro `KVM_GET_CLOCK_GUEST` and fails to check the return value of `__vcpu_ioctl`, leading to compilation errors and potential use of uninitialized stack memory.
- [Medium] The test incorrectly assumes KVM master clock mode remains active when vCPUs have different TSC offsets.
- [Medium] The test code's assertion for `KVM_CLOCK_TSC_STABLE` directly contradicts the commit message.
- [Medium] The test incorrectly expects `PVCLOCK_TSC_STABLE_BIT` to be cleared when the master clock is active.
--

commit 810ed346c3086cf1091b3ee7294755bdf43c9973
Author: David Woodhouse <dwmw@amazon.co.uk>

Verify that KVM master clock mode remains active when vCPUs have different TSC offsets

This commit adds a test to verify that KVM master clock mode remains active
when vCPUs have different TSC offsets but the same frequency. It creates
three vCPUs, offsets one of them, and confirms the KVM_CLOCK_HOST_TSC
behavior.

>  - KVM_CLOCK_TSC_STABLE is NOT set (offsets differ)

[Severity: Medium]
Does this commit message contradict the implementation? The message states
that KVM_CLOCK_TSC_STABLE should NOT be set, but the test code explicitly
asserts that it is set.

> diff --git a/tools/testing/selftests/kvm/x86/masterclock_offset_test.c b/tools/testing/selftests/kvm/x86/masterclock_offset_test.c
> new file mode 100644
> index 000000000000..88e2bd2edab5
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/masterclock_offset_test.c
[ ... ]
> @@ -0,0 +1,180 @@
[ ... ]
> +	/* Check master clock is active */
> +	memset(&clock, 0, sizeof(clock));
> +	vm_ioctl(vm, KVM_GET_CLOCK, &clock);
> +	pr_info("KVM_GET_CLOCK flags: 0x%x\n", clock.flags);
> +	TEST_ASSERT(clock.flags & KVM_CLOCK_HOST_TSC,
> +		    "Master clock should be active, flags=0x%x", clock.flags);

[Severity: Medium]
Will this assertion fail? KVM disables the master clock (use_master_clock)
if all online vCPUs do not have matching TSC offsets and generations. Since
vcpus[2] is explicitly set to a different offset earlier in the test,
KVM_CLOCK_HOST_TSC shouldn't be set here.

> +	TEST_ASSERT(clock.flags & KVM_CLOCK_TSC_STABLE,
> +		    "KVM_CLOCK_TSC_STABLE should be set, flags=0x%x", clock.flags);

[Severity: Medium]
Is this assertion correct? The commit message claims that
KVM_CLOCK_TSC_STABLE is NOT set, but this code asserts that it is present
in clock.flags.

> +
> +	/* Get per-vCPU pvclock in order 0, 2, 1 */
> +	int order[] = {0, 2, 1};
> +	for (i = 0; i < 3; i++) {
> +		int idx = order[i];
> +		__vcpu_ioctl(vcpus[idx], KVM_GET_CLOCK_GUEST, &pvti[idx]);

[Severity: High]
Does the KVM_GET_CLOCK_GUEST macro exist? It doesn't appear to be defined
in the KVM UAPI or selftests headers, which would cause a compilation error.

Additionally, __vcpu_ioctl() returns an error code instead of asserting on
failure. Since the return value isn't checked, any failure leaves the pvti
array uninitialized. Could we use vcpu_ioctl() or check the return value
to ensure we don't operate on uninitialized memory during pvclock_calc()?

[ ... ]
> +	/* PVCLOCK_TSC_STABLE_BIT should NOT be set (offsets differ) */
> +	TEST_ASSERT(!(pvti[2].flags & PVCLOCK_TSC_STABLE_BIT),
> +		    "PVCLOCK_TSC_STABLE_BIT should NOT be set, flags=0x%x",
> +		    pvti[2].flags);

[Severity: Medium]
Is this expectation correct when the master clock is active? In KVM, when
use_master_clock is true (which is required for KVM_CLOCK_HOST_TSC above),
kvm_guest_time_update() unconditionally sets PVCLOCK_TSC_STABLE_BIT in the
pvclock structure. If the master clock were actually active, this assertion
would always fail.

[ ... ]
> +	/* Re-read pvclocks */
> +	for (i = 0; i < 3; i++)
> +		__vcpu_ioctl(vcpus[order[i]], KVM_GET_CLOCK_GUEST, &pvti[order[i]]);

[Severity: High]
As mentioned above, KVM_GET_CLOCK_GUEST appears to be undefined, and the
unchecked __vcpu_ioctl() return value risks leaving pvti uninitialized
before it is read again.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=22

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

* Re: [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative
  2026-06-08 14:48 ` [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative David Woodhouse
@ 2026-06-08 19:58   ` sashiko-bot
  2026-06-09  1:02     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 19:58 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] Double-counting and state corruption of KVM Xen runstate times due to reentrancy from preempt notifiers.
--

commit 4a9cda06784af6de072f170463bdc1b39cf8245c
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86/xen: Handle retrograde KVM clock in runstate accounting

When kvm_xen_update_runstate() is invoked to set a vCPU's runstate, the
time spent in the previous runstate is accounted. This commit handles
cases where the KVM clock goes backwards by refraining from updating
runstate times until the clock catches up with runstate_entry_time.

> diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
> index 82e34edbfdbd..b1d67ece5db3 100644
> --- a/arch/x86/kvm/xen.c
> +++ b/arch/x86/kvm/xen.c

[ ... ]

> @@ -586,29 +586,45 @@ void kvm_xen_update_runstate(struct kvm_vcpu *v, int state)

[ ... ]

>  	vx->runstate_times[vx->current_runstate] += delta_ns;
> -	vx->current_runstate = state;
>  	vx->runstate_entry_time = now;
>  
> + update_guest:
> +	vx->current_runstate = state;

[Severity: High]
This isn't a bug introduced by this patch, but can this read-modify-write
sequence be safely executed with preemption enabled?

If record_steal_time() calls kvm_xen_update_runstate() with preemption
enabled, and the thread is preempted in the middle of these updates:

Original thread starts:
    vx->runstate_times[vx->current_runstate] += delta_ns;
    <preempted here>

Preempt notifier kvm_sched_out() triggers:
    kvm_xen_runstate_set_preempted() re-enters kvm_xen_update_runstate()
    updates vx->runstate_entry_time and vx->current_runstate

Original thread resumes:
    vx->runstate_entry_time = now;
    vx->current_runstate = state;

Would this cause the original execution to overwrite the state variables
with its old, pre-preemption timestamps and state? It seems this would
obliterate the preempt notifier's update and force runstate_entry_time
backwards, potentially causing KVM to double-count elapsed time on the
next accounting cycle.

Does this state tracking need to be protected by disabling preemption or
using a lock to avoid this reentrancy problem?

> +	vx->last_steal = run_delay;
>  	if (vx->runstate_cache.active)
>  		kvm_xen_update_runstate_guest(v, state == RUNSTATE_runnable);
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=25

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

* Re: [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs
  2026-06-08 14:48 ` [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs David Woodhouse
@ 2026-06-08 20:11   ` sashiko-bot
  2026-06-09  1:34     ` David Woodhouse
  0 siblings, 1 reply; 61+ messages in thread
From: sashiko-bot @ 2026-06-08 20:11 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] O(N) vCPU iteration loops moved inside a raw spinlock and seqcount writer critical section, causing severe O(N^2) latency spikes.
- [High] Unilateral clearing of KVM_REQ_MCLOCK_INPROGRESS in the bailout path creates a race condition allowing premature guest entries.
--

commit c617628fa6cacec7b404452ff34e7359195be099
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM: x86: Fix redundant pvclock updates

When a masterclock update is triggered, KVM_REQ_MASTERCLOCK_UPDATE is set on
all vCPUs. This patch adds a check in __kvm_start_pvclock_update() to prevent
redundant updates by having it bail out if another vCPU already processed the
request. It also clears the request on all other vCPUs once the lock is
acquired.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -3288,10 +3288,39 @@ static void kvm_make_mclock_inprogress_request(struct kvm *kvm)
>  	kvm_make_all_cpus_request(kvm, KVM_REQ_MCLOCK_INPROGRESS);
>  }
>  
> -static void __kvm_start_pvclock_update(struct kvm *kvm)
> +static void kvm_clear_mclock_inprogress_request(struct kvm *kvm)
>  {
> +	struct kvm_vcpu *vcpu;
> +	unsigned long i;
> +
> +	kvm_for_each_vcpu(i, vcpu, kvm)
> +		kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
> +}
> +
> +static bool __kvm_start_pvclock_update(struct kvm *kvm, struct kvm_vcpu *requesting_vcpu)
> +{
> +	struct kvm_vcpu *vcpu;
> +	unsigned long i;
> +
>  	raw_spin_lock_irq(&kvm->arch.tsc_write_lock);
> +
> +	/*
> +	 * If another vCPU already did the update while we were waiting
> +	 * for the lock, our request will have been cleared. Bail out.
> +	 */
> +	if (requesting_vcpu &&
> +	    !kvm_test_request(KVM_REQ_MASTERCLOCK_UPDATE, requesting_vcpu)) {
> +		kvm_clear_mclock_inprogress_request(kvm);
> +		raw_spin_unlock_irq(&kvm->arch.tsc_write_lock);
> +		return false;
> +	}

[Severity: High]
Does this bailout path risk prematurely clearing the protection set by
another concurrent update?

If a concurrent masterclock update is initiated (for example, by
kvm_arch_vcpu_postcreate() calling kvm_update_masterclock() with a NULL
requesting_vcpu), that thread will set KVM_REQ_MCLOCK_INPROGRESS to
prevent guest entries.

If the bailing vCPU holds the lock, sees its bailout condition met, and
calls kvm_clear_mclock_inprogress_request(), could this clear the newly
set protection flag globally? This might leave the system without
protection and allow other vCPUs to enter the guest without
KVM_REQ_CLOCK_UPDATE being set, causing them to observe an inconsistent
clock state.

[Severity: High]
Is it safe to execute this O(N) vCPU iteration loop inside the
tsc_write_lock raw spinlock?

Since kvm_clear_mclock_inprogress_request() iterates over all vCPUs, and
up to N vCPUs might sequentially take this bailout path, could this lead
to O(N^2) lock hold times with IRQs disabled? In large VMs, this might
result in significant latency spikes and potential soft lockups.

> +
> +	/* The update is VM-wide; prevent other vCPUs from redoing it. */
> +	kvm_for_each_vcpu(i, vcpu, kvm)
> +		kvm_clear_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu);

[Severity: High]
Similarly, does adding this O(N) vCPU iteration inside the raw spinlock
create a risk of latency spikes on the fast path for large VMs?

[ ... ]

>  static void kvm_end_pvclock_update(struct kvm *kvm)
>  {
>  	struct kvm_arch *ka = &kvm->arch;
>  	struct kvm_vcpu *vcpu;
>  	unsigned long i;
>  
> -	write_seqcount_end(&ka->pvclock_sc);
> -	raw_spin_unlock_irq(&ka->tsc_write_lock);
>  	kvm_for_each_vcpu(i, vcpu, kvm)
>  		kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
>  
>  	/* guest entries allowed */
> -	kvm_for_each_vcpu(i, vcpu, kvm)
> -		kvm_clear_request(KVM_REQ_MCLOCK_INPROGRESS, vcpu);
> +	kvm_clear_mclock_inprogress_request(kvm);
> +
> +	write_seqcount_end(&ka->pvclock_sc);
> +	raw_spin_unlock_irq(&ka->tsc_write_lock);
>  }

[Severity: High]
Does moving write_seqcount_end() and raw_spin_unlock_irq() after the two
O(N) vCPU iteration loops risk stalling seqcount readers indefinitely?

Performing O(N) atomic bit operations inside the seqcount writer critical
section and with IRQs disabled could cause severe latency spikes, RT
constraint violations, and potentially stall readers spinning on the
seqcount in NMI context.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=26

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

* Re: [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation
  2026-06-08 16:27   ` sashiko-bot
@ 2026-06-08 23:29     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 23:29 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 556 bytes --]

On Mon, 2026-06-08 at 16:27 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Does this cause an O(N²) scaling issue during VM creation if the host
> clocksource doesn't support the masterclock?

Good catch. Fixed in v6 by replacing the synchronous
kvm_update_masterclock() call with a simple
kvm_make_request(KVM_REQ_MASTERCLOCK_UPDATE, vcpu).

The vCPU will process the request on its first run,
avoiding the O(N) iteration over all existing vCPUs
that made vCPU creation O(N²) on archaic hardware
without a TSC-based clocksource.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling
  2026-06-08 17:39   ` sashiko-bot
@ 2026-06-08 23:43     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-08 23:43 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 642 bytes --]

On Mon, 2026-06-08 at 17:39 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Does calculating tsc_hz by multiplying by 1000 before scaling create
> a precision discrepancy with the guest's pvclock?

No. Patch 02 in this series ("Improve accuracy of KVM clock when TSC
scaling is in force") already changed kvm_guest_time_update() to use
the same order of operations: multiply to Hz first, then scale.

The old code did kvm_scale_tsc(khz) then multiplied by 1000, which
loses up to 999 Hz of precision from the >>48 truncation. Both this
patch and patch 02 now compute khz * 1000 first then scale, so they
match exactly.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas
  2026-06-08 17:59   ` sashiko-bot
@ 2026-06-09  0:02     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  0:02 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 1049 bytes --]

On Mon, 2026-06-08 at 17:59 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Can this result in ... the caller kvm_guest_time_update() ...
> adjust_tsc_offset_guest() is then called with the wrapped value

I don't think so... the very next patch in the series refactors that so
it looks like this:

		adjustment = compute_guest_tsc(v, kernel_ns) -
			     kvm_read_l1_tsc(v, host_tsc);
		if (adjustment > 0)
			adjust_tsc_offset_guest(v, adjustment);

> [Severity: High]
> does __pvclock_read_cycles() suffer from the exact same scaling
> corruption?

I think it's OK in that case as it's only used in masterclock mode, and
we do expect the TSC to be strictly monotonic.

(ISTR I experimented with letting the masterclock reference time be in
the future, and ... didn't like it. Even KVM_SET_CLOCK_GUEST doesn't
let you do that.)

Huh, probably do need to make KVM_SET_CLOCK_GUEST reject the case where
guest_tsc < user_hv_clock.tsc_timestamp, or that *will* have the same
problem with __pvclock_read_cycles()...

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc()
  2026-06-08 18:39   ` sashiko-bot
@ 2026-06-09  0:14     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  0:14 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 1247 bytes --]

On Mon, 2026-06-08 at 18:39 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Does forcing ns and data back to the start of the generation break
> the 1-second TSC matching interval extension for large VMs?

Good point. Fixed in v6 by always advancing the reference point
to "now" in the matched path, unifying the stable/unstable cases:

    if (synchronizing &&
        vcpu->arch.virtual_tsc_khz == kvm->arch.cur_tsc_khz) {
            /*
             * If synchronizing, advance the reference point to "now"
             * so the matching window slides forward with each vCPU.
             */
            u64 delta = nsec_to_cycles(vcpu, elapsed);

            data = kvm->arch.cur_tsc_write + delta;

            if (!kvm_check_tsc_unstable())
                    offset = kvm->arch.cur_tsc_offset;
            else
                    offset = kvm_compute_l1_tsc_offset(vcpu, host_tsc, data);
            matched = true;
    }

This maintains the sliding window behaviour (ns stays as the
current time, data advances by elapsed TSC cycles) so the 1s
matching threshold is relative to the most recent matched
write, not the generation start.

Subject to full testing once I'm done on this fixup pass...

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test
  2026-06-08 14:48 ` [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test David Woodhouse
@ 2026-06-09  0:27   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-09  0:27 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Using kvm_vm_release() instead of kvm_vm_free() leaks the struct kvm_vm and its memory tracking structures on each test iteration.
- [Low] KVM_VCPU_TSC_SCALE is undeclared in UAPI headers, causing a build failure.
--

commit 520a5187b17e7c7df17d704853e3e3ea37d182f3
Author: David Woodhouse <dwmw@amazon.co.uk>

Verify that userspace can correctly populate Xen and generic CPUID
timing leaves using the KVM_VCPU_TSC_SCALE and
KVM_VCPU_TSC_SCALE attributes.

This patch adds a KVM selftest to validate that removing KVM's runtime
Xen CPUID modifications does not break guests. It queries effective
frequencies, computes pvclock variables, and verifies that the guest CPUID
instructions return the expected values.

> diff --git a/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c b/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c
> new file mode 100644
> index 000000000000..a0c262b8db89
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/xen_cpuid_timing_test.c

[ ... ]

> +static void run_test(uint64_t tsc_khz)
> +{

[ ... ]

> +	/* If scaling wasn't applied, skip this frequency */
> +	if (tsc_khz && effective_tsc_khz == host_khz) {
> +		pr_info("  TSC scaling not available, skipping\n");
> +		kvm_vm_release(vm);

[Severity: Medium]
Should this be a call to kvm_vm_free() instead of kvm_vm_release()?

In KVM selftests, kvm_vm_release() only closes file descriptors.

Calling it here leaves the struct kvm_vm and its associated memory structures
dynamically allocated, which causes a memory leak on each skipped or
completed test iteration.

> +		return;
> +	}

[ ... ]

> +	switch (get_ucall(vcpu, &uc)) {
> +	case UCALL_ABORT:
> +		REPORT_GUEST_ASSERT(uc);
> +		break;
> +	case UCALL_SYNC:
> +		break;
> +	default:
> +		TEST_FAIL("Unexpected ucall");
> +	}
> +
> +	kvm_vm_release(vm);

[Severity: Medium]
As mentioned above, does this code leak the struct kvm_vm and its tracking
structures?

Using kvm_vm_free() here would free the tracking structures in addition to
closing the fds.

> +}
> +
> +int main(void)
> +{
> +	uint64_t freq;
> +	struct kvm_vcpu *vcpu;
> +	struct kvm_vm *vm;
> +	struct kvm_device_attr attr = {
> +		.group = KVM_VCPU_TSC_CTRL,
> +		.attr = KVM_VCPU_TSC_SCALE,

[Severity: Low]
Does this break the build due to KVM_VCPU_TSC_SCALE being undeclared?

It appears KVM_VCPU_TSC_SCALE isn't defined in the UAPI headers.

There is a hardcoded fallback 1 used earlier in run_test() that avoids
this issue, but it seems to have been missed here.

> +	};
> +
> +	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
> +
> +	/* Check KVM_VCPU_TSC_SCALE is supported (implies TSC scaling) */
> +	vm = vm_create_with_one_vcpu(&vcpu, guest_code);
> +	TEST_REQUIRE(!__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &attr));
> +	host_khz = __vcpu_ioctl(vcpu, KVM_GET_TSC_KHZ, NULL);
> +	kvm_vm_release(vm);

[Severity: Medium]
Similarly, could this leak the capability check VM?

It seems kvm_vm_free() is the intended cleanup function here.

> +
> +	/* Native frequency */
> +	run_test(0);

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=28

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

* Re: [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields
  2026-06-08 18:53   ` sashiko-bot
@ 2026-06-09  0:34     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  0:34 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 1717 bytes --]

On Mon, 2026-06-08 at 18:53 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Does this S4 resume adjustment to cur_tsc_write permanently break
> cross-vCPU synchronization by omitting the symmetrical adjustment
> to cur_tsc_offset?

Yes, good catch. Fixed in v6 by also adjusting cur_tsc_offset +=
delta_cyc alongside the cur_tsc_write adjustment, so new vCPUs
created after a backwards TSC event get the correct offset.

> [Severity: High]
> Can this lockless read-modify-write operation race with concurrent
> KVM_SET_TSC ioctls?

Also fixed in v6 — the cur_tsc_write adjustment is now done under
tsc_write_lock. This path runs on CPU hotplug (not just S4) so
it can genuinely race with concurrent vCPU operations.

Theoretically I guess we could store the delta_cyc in the kvm
somewhere, and let the vCPU which wins the race to call
kvm_update_masterclock() ingest them? But for an esoteric case on
ancient crappy hardware than I'm hoping we delete soon... can I be
bothered?

> [Severity: Medium]
> The commit silently changes the logic for TSC matching after an S4
> resume without documenting the functional change.

Will update the commit message in v6. Basically, if we adjust the
fields correctly we shouldn't *need* to disable matching, and that TODO
about VM creation across S4 suspend can go away... although I wasn't
actually planning to test that case.

> [Severity: Critical]
> Unprotected iteration over vm_list in CPU hotplug path

I guess that's because we can't take a mutex in a hotplug routine?
I'm going to note it as preexisting and try to ignore it for now.
I shall probably fail (to ignore it) in the long run... but let's see
how I get on.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ
  2026-06-08 14:48 ` [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ David Woodhouse
@ 2026-06-09  0:37   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-09  0:37 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Calling `kvm_read_l1_tsc` after `set_tsc_khz` calculates the current TSC using the newly set scaling ratio but the old offset, causing a massive discontinuity in the guest's observed TSC instead of preserving continuity.
--

commit c224c306d9d055ced6e04bed3638e47c8d31fb69
Author: David Woodhouse <dwmw@amazon.co.uk>

KVM_SET_TSC_KHZ changes the vCPU's TSC scaling ratio but does not update

This commit fixes an issue where get_kvmclock() uses a stale (1:1) ratio
when computing the KVM clock. It calls kvm_synchronize_tsc() after
changing the TSC frequency to ensure cur_tsc_scaling_ratio is updated
and the TSC value remains continuous across the frequency change.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 54d4b1b3cfe4..96250264d403 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> @@ -2611,7 +2612,20 @@ static int kvm_set_tsc_khz(struct kvm_vcpu *vcpu, u32 user_tsc_khz)
>  			 user_tsc_khz, thresh_lo, thresh_hi);
>  		use_scaling = 1;
>  	}
> -	return set_tsc_khz(vcpu, user_tsc_khz, use_scaling);
> +	if (set_tsc_khz(vcpu, user_tsc_khz, use_scaling))
> +		return -1;
> +
> +	/*
> +	 * Re-synchronize the TSC after changing frequency. This ensures
> +	 * cur_tsc_scaling_ratio is updated (used by get_kvmclock) and
> +	 * the TSC value is continuous across the frequency change.
> +	 */
> +	{
> +		u64 tsc = kvm_read_l1_tsc(vcpu, rdtsc());
> +
> +		kvm_synchronize_tsc(vcpu, &tsc);

[Severity: High]
Will this cause a discontinuity in the guest's observed TSC instead of
preserving continuity?

If kvm_read_l1_tsc() is called after set_tsc_khz() has already updated the
scaling ratio, the current guest TSC is calculated using the newly set
scaling ratio combined with the old offset:

tsc = old_offset + rdtsc() * new_ratio

When kvm_synchronize_tsc() is then called to compute the new offset:

new_offset = tsc - rdtsc() * new_ratio

This algebraically simplifies back to the old offset. Because the offset
remains essentially unchanged while the scaling ratio has changed, the
guest's observed TSC would immediately jump by the difference between the
old and new ratios multiplied by rdtsc().

Since rdtsc() represents the host's uptime in cycles, could this cause a
massive time jump that breaks guest timekeeping or leads to lockups?

Would it be safer to evaluate kvm_read_l1_tsc(vcpu, rdtsc()) before
calling set_tsc_khz(), so the old scaling ratio is used to establish the
baseline for continuity?

> +	}
> +	return 0;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=29

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

* Re: [PATCH v5 22/34] KVM: selftests: Add master clock offset test
  2026-06-08 19:26   ` sashiko-bot
@ 2026-06-09  0:50     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  0:50 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 1235 bytes --]

On Mon, 2026-06-08 at 19:26 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> Does the KVM_GET_CLOCK_GUEST macro exist?

Yes — it's defined in include/uapi/linux/kvm.h by patch 04
in this series.

> [Severity: Medium]
> Will this assertion fail? KVM disables the master clock if all
> online vCPUs do not have matching TSC offsets

No — patch 21 in this series decouples master clock from offset
matching. Master clock only requires matching *frequency*.
Different offsets are fine. That's kind of the point in the test!

> [Severity: Medium]
> The commit message claims KVM_CLOCK_TSC_STABLE is NOT set, but
> the code asserts it is present.

You're right — the commit message is wrong. KVM_CLOCK_TSC_STABLE
(VM-level) IS set in master clock mode. What is NOT set is
PVCLOCK_TSC_STABLE_BIT (per-vCPU), which requires matching
offsets. Will fix the commit message in v6.

> [Severity: Medium]
> PVCLOCK_TSC_STABLE_BIT should be set when master clock is active

No — patch 21 gates PVCLOCK_TSC_STABLE_BIT on
all_vcpus_matched_tsc (full offset match), not just
use_master_clock. This is the intended decoupling, as guests use
PVCLOCK_TSC_STABLE_BIT to infer that TSC is stable across vCPUs.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test
  2026-06-08 14:48 ` [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test David Woodhouse
@ 2026-06-09  0:50   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-09  0:50 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Memory leak of the VM metadata structures and userspace memory regions.
- [Low] The new test file is missing from the KVM selftests Makefile, preventing it from being compiled.
- [Low] The test uses undefined KVM UAPI ioctls, causing compilation failures.
--

commit 0780d159e1213b2e5be1d76bb2647a1007af3fdf
Author: David Woodhouse <dwmw@amazon.co.uk>

Test that Xen runstate (steal time) is correctly accounted across a
simulated live migration using KVM_XEN_VCPU_ATTR and KVM_[GS]ET_CLOCK_GUEST.

This commit adds a new KVM selftest to verify Xen runstate tracking across
a simulated VM migration. It creates a VM with Xen HVM configuration,
accumulates runstate time, saves and restores the VM state, and verifies
that the elapsed time during migration is properly accounted as steal time.

>  .../selftests/kvm/x86/xen_migration_test.c    | 194 ++++++++++++++++++
>  1 file changed, 194 insertions(+)
>  create mode 100644 tools/testing/selftests/kvm/x86/xen_migration_test.c

[Severity: Low]
Is the new xen_migration_test.c file missing from the KVM selftests Makefile?
Without adding it to TEST_GEN_PROGS_x86 (or an equivalent target) in
tools/testing/selftests/kvm/Makefile, the test is ignored by the build system
and will never be compiled or executed.

> diff --git a/tools/testing/selftests/kvm/x86/xen_migration_test.c b/tools/testing/selftests/kvm/x86/xen_migration_test.c
> new file mode 100644
> index 000000000000..37e8ace00611
> --- /dev/null
> +++ b/tools/testing/selftests/kvm/x86/xen_migration_test.c
[ ... ]
> +int main(void)
> +{
> +	struct pvclock_vcpu_time_info pvti;
> +	struct kvm_xen_vcpu_attr runstate_save;
[ ... ]
> +	pr_info("  Guest sees: runnable=%" PRIu64 " running=%" PRIu64 "\n",
> +		uc.args[2], uc.args[3]);
> +
> +	/* Save clock state */
> +	ret = __vcpu_ioctl(vcpu, KVM_GET_CLOCK_GUEST, &pvti);

[Severity: Low]
Does this code fail to compile because KVM_GET_CLOCK_GUEST is an undeclared
identifier? It does not appear to exist in the standard KVM UAPI headers.

> +	TEST_ASSERT(!ret, "KVM_GET_CLOCK_GUEST failed");
> +
> +	/* Save TSC offset */
[ ... ]
> +	uint64_t saved_runnable = runstate_save.u.runstate.time_runnable;
> +
> +	kvm_vm_release(vm);

[Severity: Medium]
Does calling kvm_vm_release() here instead of kvm_vm_free() leak the 
struct kvm_vm object and its mapped userspace memory regions?

kvm_vm_release() is an internal helper that releases vCPUs and file
descriptors, but it leaves the VM structure and memory maps allocated
right before the vm pointer is overwritten with a new VM.

> +
> +	/* === MIGRATION GAP === */
> +	pr_info("=== Simulating migration (sleeping 10ms) ===\n");
> +	usleep(10000);
> +
> +	/* === DESTINATION SIDE === */
> +	pr_info("=== Destination: create new VM and restore ===\n");
> +	vm = create_xen_vm(&vcpu);
> +
> +	/* Restore TSC offset */
> +	vcpu_set_msr(vcpu, MSR_IA32_TSC_ADJUST, tsc_offset);
> +
> +	/* Restore clock — kvmclock will now be ~10ms ahead of the snapshot */
> +	vcpu_ioctl(vcpu, KVM_SET_CLOCK_GUEST, &pvti);

[Severity: Low]
Similarly, does this fail to compile because KVM_SET_CLOCK_GUEST does not
exist in the standard KVM UAPI headers?

> +
> +	/* Restore runstate exactly as saved (state=runnable) */
[ ... ]
> +	TEST_ASSERT(steal_increase >= 10000000ULL &&
> +		    steal_increase < 5000000000ULL,
> +		    "Steal time increase %" PRIu64 " ns not in expected range "
> +		    "[10ms, 5s]", steal_increase);
> +
> +	kvm_vm_release(vm);

[Severity: Medium]
Does this also permanently leak the second VM's metadata structures and
memory maps before the test exits?

> +	pr_info("PASS: Migration gap correctly accounted as steal time\n");
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=30

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

* Re: [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative
  2026-06-08 19:58   ` sashiko-bot
@ 2026-06-09  1:02     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  1:02 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 424 bytes --]

On Mon, 2026-06-08 at 19:58 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> can this read-modify-write sequence be safely executed with
> preemption enabled?

This is hurting my brain and I might need to come back to it.
The reentrancy concern is pre-existing and out of scope for
this series — the patch only adds handling for retrograde clock
values, it doesn't change the calling context or locking.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

* Re: [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock
  2026-06-08 14:48 ` [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock David Woodhouse
@ 2026-06-09  1:03   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-09  1:03 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Uninitialized stack memory used on failure path in KVM timekeeping.
- [Low] Unused static functions left in the code, causing `-Wunused-function` warnings.
--

commit 25989314a763f4ae38067d7da21492c86ffa8fa4
Author: David Woodhouse <dwmw@amazon.co.uk>

Replace the KVM-private vgettsc()/do_kvmclock_base()/do_monotonic()/
do_realtime() timekeeping reimplementation with calls to the generic
ktime_get_snapshot_id() interface.

This patch replaces the KVM-private timekeeping functions with generic
ktime_get_snapshot_id() calls. It provides system time and raw cycles 
atomically paired to simplify the timekeeping state.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 96250264d403..2713aebb96ae 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -3162,14 +3163,32 @@ static int do_realtime(struct timespec64 *ts, u64 *tsc_timestamp)

[Severity: Low]
The patch description mentions replacing the private timekeeping functions
(vgettsc, do_kvmclock_base, do_monotonic, and do_realtime), but it appears
they were not removed from the source code.

Does leaving these unused static functions trigger -Wunused-function
compiler warnings during the build?

>   * reports the TSC value from which it do so. Returns true if host is
>   * using TSC based clocksource.
>   */
> +static bool kvm_snapshot_has_tsc(struct system_time_snapshot *snap,
> +				u64 *tsc_timestamp)
> +{
> +	if (snap->cs_id == CSID_X86_TSC) {
> +		*tsc_timestamp = snap->cycles;
> +		return true;
> +	}
> +
> +	if (snap->hw_csid == CSID_X86_TSC && snap->hw_cycles) {
> +		*tsc_timestamp = snap->hw_cycles;
> +		return true;
> +	}
> +
> +	return false;
> +}
> +
>  static bool kvm_get_time_and_clockread(s64 *kernel_ns, u64 *tsc_timestamp)
>  {
> -	/* checked again under seqlock below */
> -	if (!gtod_is_based_on_tsc(pvclock_gtod_data.clock.vclock_mode))
> +	struct system_time_snapshot snap;
> +
> +	ktime_get_snapshot_id(CLOCK_MONOTONIC_RAW, &snap);
> +	if (!kvm_snapshot_has_tsc(&snap, tsc_timestamp))

[Severity: Critical]
If ktime_get_snapshot_id() encounters a failure (such as when timekeeping is
suspended), it only sets snap.valid = false and returns immediately, leaving
the rest of the struct uninitialized.

Since snap.valid is not checked before calling kvm_snapshot_has_tsc(), could
this pass uninitialized stack memory into the helper?

If the uninitialized snap->cs_id happens to match CSID_X86_TSC, could KVM
expose uninitialized host kernel stack data to the guest VM?

>  		return false;
>  
> -	return gtod_is_based_on_tsc(do_kvmclock_base(kernel_ns,
> -						     tsc_timestamp));
> +	*kernel_ns = ktime_to_ns(ktime_mono_to_any(snap.systime, TK_OFFS_BOOT));
> +	return true;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=31

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

* Re: [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean
  2026-06-08 14:48 ` [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean David Woodhouse
@ 2026-06-09  1:23   ` sashiko-bot
  0 siblings, 0 replies; 61+ messages in thread
From: sashiko-bot @ 2026-06-09  1:23 UTC (permalink / raw)
  To: David Woodhouse; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] `kvm_check_tsc_unstable()` incorrectly masks TSC instability when the host uses a native TSC clocksource, breaking backward TSC compensation.
- [Medium] Tracepoints expect a clock mode enum but now receive a collapsed boolean value, changing trace output and breaking observability tooling.
--

commit e7585c139257b84c3968a9648d71f5c5478115f9
Author: David Woodhouse <dwmw@amazon.co.uk>
Subject: KVM: x86: Replace vclock_mode checks with kvm_host_has_tsc_clocksource

This commit replaces remaining users of pvclock_gtod_data with a simple
kvm_host_has_tsc_clocksource boolean. This removes the need for tracking
the exact vclock_mode when determining if the host clocksource is
TSC-based.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index c18947c5b63f..93a428c37847 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
[ ... ]
> @@ -2726,7 +2727,7 @@ static void kvm_track_tsc_matching(struct kvm_vcpu *vcpu, bool new_generation)
>  
>  	trace_kvm_track_tsc(vcpu->vcpu_id, ka->nr_vcpus_matched_tsc,
>  			    atomic_read(&vcpu->kvm->online_vcpus),
> -		            ka->use_master_clock, gtod->clock.vclock_mode);
> +		            ka->use_master_clock, kvm_host_has_tsc_clocksource);

[Severity: Medium]
Will this change break ABI for observability tooling that expects the
tracepoint to log the original vclock_mode integer enum?

Since kvm_host_has_tsc_clocksource is a boolean, a true value will
implicitly cast to 1 (VDSO_CLOCKMODE_TSC). If the actual mode is
VDSO_CLOCKMODE_HVCLOCK (3), the boolean still evaluates to 1, causing the
trace output to erroneously log 'tsc' instead of the expected integer 3.

[ ... ]
> @@ -2850,7 +2851,7 @@ static inline bool kvm_check_tsc_unstable(void)
>  	 * TSC is marked unstable when we're running on Hyper-V,
>  	 * 'TSC page' clocksource is good.
>  	 */
> -	if (pvclock_gtod_data.clock.vclock_mode == VDSO_CLOCKMODE_HVCLOCK)
> +	if (kvm_host_has_tsc_clocksource)
>  		return false;

[Severity: High]
Does this change unintentionally mask TSC instability when the host uses a
native TSC clocksource?

Previously, this check bypassed check_tsc_unstable() only when the clocksource
was VDSO_CLOCKMODE_HVCLOCK. Now, it evaluates to true for both HVCLOCK and
native TSC clocksources.

When using a native TSC clocksource, KVM detects backwards TSCs in
kvm_arch_vcpu_load() and synchronously calls mark_tsc_unstable(). However,
because kvm_host_has_tsc_clocksource remains true until the timekeeper
notifier fires, the subsequent call to kvm_check_tsc_unstable() will
immediately return false.

Will this completely bypass the TSC offset catchup logic, directly exposing
the backwards TSC jump to the guest?

[ ... ]
> @@ -3315,7 +3316,7 @@ static void pvclock_update_vm_gtod_copy(struct kvm *kvm)
>  			ka->use_master_clock = false;
>  	}
>  
> -	vclock_mode = pvclock_gtod_data.clock.vclock_mode;
> +	vclock_mode = kvm_host_has_tsc_clocksource;
>  	trace_kvm_update_master_clock(ka->use_master_clock, vclock_mode,
>  					ka->all_vcpus_matched_freq);
>  #endif

[Severity: Medium]
Similar to trace_kvm_track_tsc above, does passing a boolean here break
tools expecting the original vclock_mode enum for
trace_kvm_update_master_clock?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260608145455.89187-1-dwmw2@infradead.org?part=33

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

* Re: [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs
  2026-06-08 20:11   ` sashiko-bot
@ 2026-06-09  1:34     ` David Woodhouse
  0 siblings, 0 replies; 61+ messages in thread
From: David Woodhouse @ 2026-06-09  1:34 UTC (permalink / raw)
  To: sashiko-bot; +Cc: dwmw2, kvm, sashiko-reviews

[-- Attachment #1: Type: text/plain, Size: 2436 bytes --]

On Mon, 2026-06-08 at 20:11 +0000, sashiko-bot@kernel.org wrote:
> [Severity: High]
> O(N) vCPU iteration loops moved inside a raw spinlock

This is an existing pattern in KVM — kvm_end_pvclock_update()
always iterated over all vCPUs under the lock. The per-vCPU
operations are just atomic bit set/clear, so the hold time
is bounded by N * (one atomic op) which is a few microseconds
even for very large VMs.

> [Severity: High]
> Unilateral clearing of KVM_REQ_MCLOCK_INPROGRESS in the bailout
> path creates a race condition

Hm, I don't think so.

All vCPUs might react to KVM_REQ_MASTERCLOCK_UPDATE in parallel and
each set KVM_REQ_MCLOCK_INPROGRESS on the others (which can't be done
under spinlock as it has to kick), then they all try to take the lock
in __kvm_start_pvclock_update().

The *winner* will actually do the work, then clear INPROGRESS on the
rest. The *losers* then each take the lock, realise there's nothing to
do, and also clear INPROGRESS (just in case they were late to the party
and they had *set* it themselves, long after everyone else was done).

Could that bailout clearing of INPROGRESS race with a *new* round of
KVM_REQ_MASTERCLOCK_UPDATE? Not without the requesting vCPU actually
having that REQ set again... in which case it wouldn't take the bailout
at all, would it?

Hm, I think there's a different, pre-existing race here, with a vCPU
that was *going* to bail out, but actually it sees a
KVM_REQ_MASTERCLOCK_UPDATE for a *next* generation, and processes it
even though MCLOCK_INPROGRESS already got *cleared* again for the last
generation, and hasn't been reasserted yet?

That existed in the old code where everyone was a winner: the *last*
winner could be clearing MCLOCK_INPROGRESS, just after the first winner
has set it for the next round.

I *think* it can be resolved by taking the bailout path if
MCLOCK_INPROGRESS is already clear, as well as if MASTERCLOCK_UPDATE is
already clear. But it's pre-existing, makes my head hurt, and is for a
very rare condition of back-to-back updates. So it's going on the 'for
later' list.


> [Severity: High]
> Does moving write_seqcount_end() after the loops risk stalling
> seqcount readers?

The loops are O(N) atomic bit operations. On the guest side,
readers that observe the seqcount mid-write simply retry —
they don't spin indefinitely. The window is microseconds
even for large VMs.

[-- Attachment #2: smime.p7s --]
[-- Type: application/pkcs7-signature, Size: 5069 bytes --]

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

end of thread, other threads:[~2026-06-09  1:34 UTC | newest]

Thread overview: 61+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-08 14:47 [PATCH v5 00/34] Cleaning up the KVM clock mess David Woodhouse
2026-06-08 14:47 ` [PATCH v5 01/34] KVM: x86/xen: Do not corrupt KVM clock in kvm_xen_shared_info_init() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 02/34] KVM: x86: Improve accuracy of KVM clock when TSC scaling is in force David Woodhouse
2026-06-08 14:47 ` [PATCH v5 03/34] UAPI: x86: Move pvclock-abi to UAPI for x86 platforms David Woodhouse
2026-06-08 14:47 ` [PATCH v5 04/34] KVM: x86: Add KVM_[GS]ET_CLOCK_GUEST for accurate KVM clock migration David Woodhouse
2026-06-08 15:33   ` sashiko-bot
2026-06-08 14:47 ` [PATCH v5 05/34] KVM: selftests: Add KVM/PV clock selftest to prove timer correction David Woodhouse
2026-06-08 15:49   ` sashiko-bot
2026-06-08 14:47 ` [PATCH v5 06/34] KVM: x86: Explicitly disable TSC scaling without CONSTANT_TSC David Woodhouse
2026-06-08 14:47 ` [PATCH v5 07/34] KVM: x86: Activate master clock immediately on vCPU creation David Woodhouse
2026-06-08 16:27   ` sashiko-bot
2026-06-08 23:29     ` David Woodhouse
2026-06-08 14:47 ` [PATCH v5 08/34] KVM: x86: Add KVM_VCPU_TSC_SCALE and fix the documentation on TSC migration David Woodhouse
2026-06-08 16:39   ` sashiko-bot
2026-06-08 14:47 ` [PATCH v5 09/34] KVM: x86: Avoid NTP frequency skew for KVM clock on 32-bit host David Woodhouse
2026-06-08 14:47 ` [PATCH v5 10/34] KVM: x86: Fold __get_kvmclock() into get_kvmclock() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 11/34] KVM: x86: Restructure get_kvmclock() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 12/34] KVM: x86: Fix KVM clock precision in get_kvmclock() with TSC scaling David Woodhouse
2026-06-08 17:39   ` sashiko-bot
2026-06-08 23:43     ` David Woodhouse
2026-06-08 14:47 ` [PATCH v5 13/34] KVM: x86: Use get_kvmclock() in kvm_get_wall_clock_epoch() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 14/34] KVM: x86: Fix compute_guest_tsc() to handle negative time deltas David Woodhouse
2026-06-08 17:59   ` sashiko-bot
2026-06-09  0:02     ` David Woodhouse
2026-06-08 14:47 ` [PATCH v5 15/34] KVM: x86: Restructure kvm_guest_time_update() for TSC upscaling David Woodhouse
2026-06-08 18:13   ` sashiko-bot
2026-06-08 14:47 ` [PATCH v5 16/34] KVM: x86: Simplify and comment kvm_get_time_scale() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 17/34] KVM: x86: Remove implicit rdtsc() from kvm_compute_l1_tsc_offset() David Woodhouse
2026-06-08 14:47 ` [PATCH v5 18/34] KVM: x86: Improve synchronization in kvm_synchronize_tsc() David Woodhouse
2026-06-08 18:39   ` sashiko-bot
2026-06-09  0:14     ` David Woodhouse
2026-06-08 14:48 ` [PATCH v5 19/34] KVM: x86: Kill last_tsc_{nsec,write,offset} fields David Woodhouse
2026-06-08 18:53   ` sashiko-bot
2026-06-09  0:34     ` David Woodhouse
2026-06-08 14:48 ` [PATCH v5 20/34] KVM: x86: Replace nr_vcpus_matched_tsc count with all_vcpus_matched_tsc bool David Woodhouse
2026-06-08 14:48 ` [PATCH v5 21/34] KVM: x86: Allow KVM master clock mode when TSCs are offset from each other David Woodhouse
2026-06-08 19:15   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 22/34] KVM: selftests: Add master clock offset test David Woodhouse
2026-06-08 19:26   ` sashiko-bot
2026-06-09  0:50     ` David Woodhouse
2026-06-08 14:48 ` [PATCH v5 23/34] KVM: x86: Factor out kvm_use_master_clock() David Woodhouse
2026-06-08 14:48 ` [PATCH v5 24/34] KVM: x86: Avoid gratuitous global clock updates David Woodhouse
2026-06-08 14:48 ` [PATCH v5 25/34] KVM: x86/xen: Prevent runstate times from becoming negative David Woodhouse
2026-06-08 19:58   ` sashiko-bot
2026-06-09  1:02     ` David Woodhouse
2026-06-08 14:48 ` [PATCH v5 26/34] KVM: x86: Avoid redundant masterclock updates from multiple vCPUs David Woodhouse
2026-06-08 20:11   ` sashiko-bot
2026-06-09  1:34     ` David Woodhouse
2026-06-08 14:48 ` [PATCH v5 27/34] KVM: x86: Remove runtime Xen TSC frequency CPUID update David Woodhouse
2026-06-08 14:48 ` [PATCH v5 28/34] KVM: selftests: Add Xen/generic CPUID timing leaf test David Woodhouse
2026-06-09  0:27   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 29/34] KVM: x86: Re-synchronize TSC after KVM_SET_TSC_KHZ David Woodhouse
2026-06-09  0:37   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 30/34] KVM: selftests: Add Xen runstate migration test David Woodhouse
2026-06-09  0:50   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 31/34] KVM: x86: Use ktime_get_snapshot_id() for master clock David Woodhouse
2026-06-09  1:03   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 32/34] KVM: x86: Compute kvmclock base without pvclock_gtod_data David Woodhouse
2026-06-08 14:48 ` [PATCH v5 33/34] KVM: x86: Replace pvclock_gtod_data vclock_mode with boolean David Woodhouse
2026-06-09  1:23   ` sashiko-bot
2026-06-08 14:48 ` [PATCH v5 34/34] KVM: x86: Remove pvclock_gtod_data and private timekeeping code David Woodhouse

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