[RFC PATCH 28/55] KVM: x86: inhibit APICv when VM planes are enabled

From: David Woodhouse

Date: Sat Oct 17 2026 - 08:33:05 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

With APICv live on plane-0 vCPUs while their plane-1 twins use
software injection, the twins trip the APICv coherency checks: stale
vcpu-vs-VM activation state at vcpu_enter_guest(), and timer interrupt
injection against a vCPU that is not the running one. Cross-plane
event delivery (forced VTL entry, twin kicks, NMI folding) depends on
software-injection bookkeeping that hardware acceleration bypasses.

Add APICV_INHIBIT_REASON_PLANES, set it when a plane above 0 is
created, and include it in the VMX and AVIC required-inhibit masks;
__kvm_set_or_clear_apicv_inhibit() silently ignores a reason the
vendor mask does not carry. The reference VSM hypervisor likewise
force-disables APICv when VTLs are enabled.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
arch/x86/include/asm/kvm_host.h | 10 +++++++++-
arch/x86/kvm/svm/svm.h | 3 ++-
arch/x86/kvm/vmx/main.c | 3 ++-
arch/x86/kvm/x86.c | 10 ++++++++++
4 files changed, 23 insertions(+), 3 deletions(-)

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 1ba77d9375cd..5945b57c091d 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1436,6 +1436,13 @@ enum kvm_apicv_inhibit {
*/
APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG,

+ /*
+ * APICv is inhibited when the VM has multiple planes (Hyper-V VTLs).
+ * Cross-plane event delivery relies on software injection; hardware
+ * acceleration on one twin would bypass it.
+ */
+ APICV_INHIBIT_REASON_PLANES,
+
NR_APICV_INHIBIT_REASONS,
};

@@ -1455,7 +1462,8 @@ enum kvm_apicv_inhibit {
__APICV_INHIBIT_REASON(PIT_REINJ), \
__APICV_INHIBIT_REASON(SEV), \
__APICV_INHIBIT_REASON(LOGICAL_ID_ALIASED), \
- __APICV_INHIBIT_REASON(PHYSICAL_ID_TOO_BIG)
+ __APICV_INHIBIT_REASON(PHYSICAL_ID_TOO_BIG), \
+ __APICV_INHIBIT_REASON(PLANES)

struct kvm_possible_nx_huge_pages {
/*
diff --git a/arch/x86/kvm/svm/svm.h b/arch/x86/kvm/svm/svm.h
index 7a1bf4f5b8e0..64438317d148 100644
--- a/arch/x86/kvm/svm/svm.h
+++ b/arch/x86/kvm/svm/svm.h
@@ -925,7 +925,8 @@ extern struct kvm_x86_nested_ops svm_nested_ops;
BIT(APICV_INHIBIT_REASON_APIC_ID_MODIFIED) | \
BIT(APICV_INHIBIT_REASON_APIC_BASE_MODIFIED) | \
BIT(APICV_INHIBIT_REASON_LOGICAL_ID_ALIASED) | \
- BIT(APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG) \
+ BIT(APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG) | \
+ BIT(APICV_INHIBIT_REASON_PLANES) \
)

bool __init avic_hardware_setup(void);
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index 572921bdfb32..81a8d398068b 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -876,7 +876,8 @@ static int vt_gmem_max_mapping_level(struct kvm *kvm, kvm_pfn_t pfn,
BIT(APICV_INHIBIT_REASON_BLOCKIRQ) | \
BIT(APICV_INHIBIT_REASON_PHYSICAL_ID_ALIASED) | \
BIT(APICV_INHIBIT_REASON_APIC_ID_MODIFIED) | \
- BIT(APICV_INHIBIT_REASON_APIC_BASE_MODIFIED))
+ BIT(APICV_INHIBIT_REASON_APIC_BASE_MODIFIED) | \
+ BIT(APICV_INHIBIT_REASON_PLANES))

struct kvm_x86_ops vt_x86_ops __initdata = {
.name = KBUILD_MODNAME,
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 3e2465b2fa2b..052821799120 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -13979,6 +13979,16 @@ int kvm_arch_plane_init(struct kvm *kvm, struct kvm_plane *plane,
{
mutex_init(&plane->arch.apic_map_lock);

+ /*
+ * Cross-plane event delivery (forced VTL entry, twin kicks, NMI
+ * folding) relies on software injection via KVM_REQ_EVENT; hardware
+ * APIC virtualization on one twin would bypass the bookkeeping the
+ * other twin's plane depends on. Mirrors the reference VSM
+ * hypervisor, which force-disables APICv when VTLs are enabled.
+ */
+ if (plane_level > 0)
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_PLANES);
+
return 0;
}

--
2.50.1