[RFC PATCH 06/13] KVM: planes: Fan out per-plane requests to twin vCPUs

From: David Woodhouse

Date: Fri Nov 13 2026 - 14:40:07 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

A request raised on one plane's vCPU for a VM-wide reason (TLB flush
after a host-side zap, APIC page reload, obsolete-roots release) is
consumed by whichever twin runs first, so the other planes keep stale
translations. The plane-1 twin of a Windows VSM guest then runs on a
zapped root and the guest bugchecks.

Mark those three requests per-plane and make kvm_make_vcpu_request()
replicate any KVM_REQUEST_PER_PLANE request to every twin of the
target vCPU through vcpu_for_each_plane(). Mode and wait live in the
shared common and the kick targets the running twin's pCPU, so the
existing kick logic already reaches the twins.

The x86 kvm_exit and powerpc kvm_check_requests tracepoints logged
only the shared request word, so the three requests would vanish from
them. OR in the vCPU's plane requests instead; a separate field would
say which word a bit came from, but changes the tracepoint format for
little gain.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
---
arch/powerpc/kvm/trace.h | 3 ++-
arch/x86/include/asm/kvm_host.h | 6 ++++--
arch/x86/kvm/mmu/mmu.c | 13 ++++++++-----
arch/x86/kvm/trace.h | 3 ++-
include/linux/kvm_host.h | 3 ++-
virt/kvm/kvm_main.c | 20 +++++++++++++++++++-
6 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/arch/powerpc/kvm/trace.h b/arch/powerpc/kvm/trace.h
index 35c000d918bb..2fc60a4e159f 100644
--- a/arch/powerpc/kvm/trace.h
+++ b/arch/powerpc/kvm/trace.h
@@ -108,7 +108,8 @@ TRACE_EVENT(kvm_check_requests,

TP_fast_assign(
__entry->cpu_nr = vcpu->vcpu_id;
- __entry->requests = vcpu->common->requests;
+ __entry->requests = READ_ONCE(vcpu->common->requests) |
+ READ_ONCE(vcpu->plane_requests);
),

TP_printk("vcpu=%x requests=%x",
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index d4628ab99197..596be8ad7154 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -107,7 +107,8 @@
KVM_ARCH_REQ_FLAGS(15, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
#define KVM_REQ_GLOBAL_CLOCK_UPDATE KVM_ARCH_REQ(16)
#define KVM_REQ_APIC_PAGE_RELOAD \
- KVM_ARCH_REQ_FLAGS(17, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
+ KVM_ARCH_REQ_FLAGS(17, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP | \
+ KVM_REQUEST_PER_PLANE)
#define KVM_REQ_HV_CRASH KVM_ARCH_REQ(18)
#define KVM_REQ_IOAPIC_EOI_EXIT KVM_ARCH_PLANE_REQ(19)
#define KVM_REQ_HV_RESET KVM_ARCH_REQ(20)
@@ -125,7 +126,8 @@
#define KVM_REQ_UPDATE_CPU_DIRTY_LOGGING \
KVM_ARCH_REQ_FLAGS(30, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
#define KVM_REQ_MMU_FREE_OBSOLETE_ROOTS \
- KVM_ARCH_REQ_FLAGS(31, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
+ KVM_ARCH_REQ_FLAGS(31, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP | \
+ KVM_REQUEST_PER_PLANE)
#define KVM_REQ_HV_TLB_FLUSH \
KVM_ARCH_REQ_FLAGS(32, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
#define KVM_REQ_VMSA_PAGE_RELOAD \
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e8eb1e48df36..ef6df08f5a99 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6405,11 +6405,14 @@ void kvm_mmu_free_obsolete_roots(struct kvm_vcpu *vcpu)
int i;

/*
- * KVM_REQ_MMU_FREE_OBSOLETE_ROOTS is tracked in the request set
- * shared by all of the vCPU's planes, so the plane that consumes
- * the request is not necessarily the plane whose root is obsolete.
- * Service every plane: the inactive twins cannot be running (all
- * planes of a vCPU share one thread), so their MMUs are stable.
+ * KVM_REQ_MMU_FREE_OBSOLETE_ROOTS fans out to every plane twin,
+ * but each twin only consumes its own copy when it next runs, and
+ * a plane that userspace never re-enters would pin its obsolete
+ * roots indefinitely. Service every plane from whichever twin
+ * consumes its request first: the inactive twins cannot be
+ * running (all planes of a vCPU share one thread), so their MMUs
+ * are stable, and a twin's own later request finds nothing left
+ * to free.
*/
vcpu_for_each_plane(vcpu->common, i, v) {
__kvm_mmu_free_obsolete_roots(vcpu->kvm, &v->arch.root_mmu);
diff --git a/arch/x86/kvm/trace.h b/arch/x86/kvm/trace.h
index 7038e596019a..ad901836c08a 100644
--- a/arch/x86/kvm/trace.h
+++ b/arch/x86/kvm/trace.h
@@ -409,7 +409,8 @@ TRACE_EVENT(name, \
__entry->guest_rip = tracing_kvm_rip_read(vcpu); \
__entry->isa = isa; \
__entry->vcpu_id = vcpu->vcpu_id; \
- __entry->requests = READ_ONCE(vcpu->common->requests); \
+ __entry->requests = READ_ONCE(vcpu->common->requests) | \
+ READ_ONCE(vcpu->plane_requests); \
kvm_x86_call(get_exit_info)(vcpu, \
&__entry->exit_reason, \
&__entry->info1, \
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index b30982fcd3e7..6c50c1dc5819 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -168,7 +168,8 @@ static inline bool kvm_is_error_gpa(gpa_t gpa)
* Architecture-independent vcpu->requests bit members
* Bits 3-7 are reserved for more arch-independent bits.
*/
-#define KVM_REQ_TLB_FLUSH (0 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
+#define KVM_REQ_TLB_FLUSH \
+ (0 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP | KVM_REQUEST_PER_PLANE)
#define KVM_REQ_VM_DEAD (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
#define KVM_REQ_UNBLOCK 2
#define KVM_REQ_DIRTY_RING_SOFT_FULL 3
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 6a00654490ae..c323d067256e 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -237,9 +237,27 @@ static void kvm_make_vcpu_request(struct kvm_vcpu *vcpu, unsigned int req,
{
int cpu;

- if (likely(!(req & KVM_REQUEST_NO_ACTION)))
+ if (likely(!(req & KVM_REQUEST_NO_ACTION))) {
__kvm_make_request(req, vcpu);

+ /*
+ * A per-plane request raised on "the vCPU" (TLB flush after
+ * a host-side zap, obsolete-roots reload, ...) concerns
+ * every plane: fan it out to the twins, or the plane that
+ * runs next consumes a bit the others still need. Mode and
+ * wait live in the common and the kick below targets the
+ * running twin's pCPU, so it covers the twins too.
+ */
+ if (req & KVM_REQUEST_PER_PLANE) {
+ struct kvm_vcpu *twin;
+ int p;
+
+ vcpu_for_each_plane(vcpu->common, p, twin)
+ if (twin != vcpu)
+ __kvm_make_request(req, twin);
+ }
+ }
+
if (!(req & KVM_REQUEST_NO_WAKEUP) && kvm_vcpu_wake_up(vcpu))
return;

--
2.50.1