[PATCH v3 04/10] KVM: x86/xen: Punt singleshot timer hcalls to userspace if Xen vCPU ID isn't set

From: Sean Christopherson

Date: Thu Jun 25 2026 - 18:38:05 EST


Explicitly invalidate KVM's internal Xen vCPU ID during vCPU creation
instead of *trying* to set the Xen ID to the vCPU index by default, and
forward singleshot timer hypercalls to userspace if the VMM hasn't set the
Xen ID via KVM_XEN_VCPU_ATTR_TYPE_VCPU_ID. Using the vCPU's index as its
default Xen ID is reasonable in concept, but in practice is horribly flawed
as the index is left as '0' until after vCPU initialization completes, i.e.
every vCPU gets a Xen ID of '0' by default.

Forward hypercalls to userspace instead of trying to salvage any kind of
default behavior, as all userspace implementations that support multiple
vCPUs either don't enable the timer, are guaranteed to set Xen ID, or work
only because *all* guests also screw up the singleshot timer hypercalls.
The last scenarios is extremely unlikely given that Linux-as-a-guest uses
the actual Xen vCPU ID when making timer hypercalls. In other words, for
all intents and purposes, KVM's ABI is already that userspace must set the
Xen vCPU ID, so just commit to that ABI.

Note, KVM's handling of KVM_XEN_VCPU_ATTR_TYPE_VCPU_ID restricts the ID to
KVM_MAX_VCPUS, so there's no chance of a valid ID colliding with -1u.

Link: https://lore.kernel.org/all/20260612233017.1F9771F000E9@xxxxxxxxxxxxxxx
Suggested-by: David Woodhouse <dwmw2@xxxxxxxxxxxxx>
Signed-off-by: Sean Christopherson <seanjc@xxxxxxxxxx>
---
arch/x86/kvm/xen.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 694b31c1fcc9..27c1aeeab8af 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -22,6 +22,7 @@
#include <xen/interface/version.h>
#include <xen/interface/event_channel.h>
#include <xen/interface/sched.h>
+#include <xen/xen-ops.h>

#include <asm/xen/cpuid.h>
#include <asm/pvclock.h>
@@ -1610,6 +1611,9 @@ static bool kvm_xen_hcall_vcpu_op(struct kvm_vcpu *vcpu, bool longmode, int cmd,
if (!kvm_xen_timer_enabled(vcpu))
return false;

+ if (vcpu->arch.xen.vcpu_id == XEN_VCPU_ID_INVALID)
+ return false;
+
switch (cmd) {
case VCPUOP_set_singleshot_timer:
if (vcpu->arch.xen.vcpu_id != vcpu_id) {
@@ -2299,7 +2303,7 @@ static bool kvm_xen_hcall_evtchn_send(struct kvm_vcpu *vcpu, u64 param, u64 *r)

void kvm_xen_init_vcpu(struct kvm_vcpu *vcpu)
{
- vcpu->arch.xen.vcpu_id = vcpu->vcpu_idx;
+ vcpu->arch.xen.vcpu_id = XEN_VCPU_ID_INVALID;
vcpu->arch.xen.poll_evtchn = 0;

timer_setup(&vcpu->arch.xen.poll_timer, cancel_evtchn_poll, 0);
--
2.55.0.rc0.799.gd6f94ed593-goog