[RFC PATCH 02/55] KVM: x86: Let userspace opt in to multiple planes with KVM_CAP_PLANES

From: David Woodhouse

Date: Sat Oct 17 2026 - 08:26:06 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

A VMM implementing a software privilege model such as Hyper-V VSM
needs more than one plane, but kvm_arch_max_planes() returns the
vendor ceiling — 1 everywhere except SEV-SNP, where VMPLs are
guest-architectural — so KVM_CREATE_PLANE rejects every id above zero.

Make KVM_CAP_PLANES a VM enable capability taking the number of planes
userspace is going to manage, up to KVM_MAX_PLANES. It must be set
before any vCPU or extra plane exists, so nothing is sized against the
old limit, and cannot be changed once set. Non-default VM types are
rejected: SEV-SNP keeps deriving its count from the supported VMPLs,
and protected-state VM types need their own audit first. A VM that
does not enable the capability keeps reporting a single plane.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
Documentation/virt/kvm/api.rst | 27 +++++++++++++++++++++
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/kvm/x86.c | 42 ++++++++++++++++++++++++++++++++-
3 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index b5f0136bb555..bc63054ce7bd 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6598,6 +6598,12 @@ KVM_S390_KEYOP_SSKE

Creates a new *plane*, i.e. a separate privilege level for the virtual machine.

+The range of valid plane ids is [0, N) where N is the value returned by
+KVM_CHECK_EXTENSION(KVM_CAP_PLANES) on the VM file descriptor. Plane 0 is
+the VM itself; for VM types where the plane count is not guest-architectural,
+ids above 0 only become valid after userspace enables KVM_CAP_PLANES with
+the number of planes it is going to manage (see section 7.47).
+
Each plane has a numeric id that is used when communicating with KVM. While
KVM is currently agnostic to whether low ids are more or less privileged, it is
expected that this will not always be the case in the future. For example KVM
@@ -8959,6 +8965,27 @@ helpful if user space wants to emulate instructions which are not
This capability can be enabled dynamically even if VCPUs were already
created and are running.

+7.47 KVM_CAP_PLANES
+-------------------
+
+:Architectures: x86
+:Target: VM
+:Parameters: args[0] contains the number of planes to make available
+
+Opts the VM in to using multiple planes, raising the limit on plane ids
+that KVM_CREATE_PLANE accepts to args[0] (between 1 and the KVM_MAX_PLANES
+value implied by KVM_CHECK_EXTENSION(KVM_CAP_PLANES) on the system fd).
+
+Planes other than plane 0 only exist when userspace manages them, so by
+default a VM reports and accepts a single plane. VM types whose plane
+count is guest-architectural (such as SEV-SNP, where it reflects the
+supported VMPLs) report that count without this capability, and enabling
+it on them fails with -EINVAL.
+
+This capability must be enabled before any vCPU or extra plane has been
+created, and the chosen count cannot be changed afterwards; otherwise
+-EEXIST is returned.
+
7.49 KVM_CAP_HCALL_FAULT_EXIT
------------------------------------

diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 0ce814bbf1ea..e4c8691dc1e1 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1442,6 +1442,7 @@ struct kvm_arch {
unsigned int indirect_shadow_pages;
u8 mmu_valid_gen;
u8 vm_type;
+ unsigned int user_max_planes;
bool has_private_mem;
bool has_protected_state;
bool has_protected_eoi;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 9a15e06fc91e..a9073436c734 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -490,11 +490,29 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_default_max_planes);

unsigned kvm_arch_max_planes(struct kvm *kvm)
{
+ unsigned int vendor_planes;
+
/* For now, planes are only supported with irqchip=split */
if (!irqchip_split(kvm))
return 1;

- return kvm_x86_call(max_planes)(kvm);
+ vendor_planes = kvm_x86_call(max_planes)(kvm);
+
+ /*
+ * When the plane count is guest-architectural (SEV-SNP VMPLs),
+ * the vendor module owns it. Otherwise planes are a software
+ * construct and only exist when userspace has opted in to the
+ * number of planes it is going to manage, via KVM_CAP_PLANES.
+ */
+ if (vendor_planes > 1)
+ return vendor_planes;
+
+ /*
+ * Lockless readers (KVM_CHECK_EXTENSION) race the KVM_ENABLE_CAP
+ * store; the value is set once, so a torn-free read is all that
+ * is needed.
+ */
+ return READ_ONCE(kvm->arch.user_max_planes) ? : 1;
}

struct kvm_plane *x86_alloc_plane(void)
@@ -6846,6 +6864,28 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
mutex_unlock(&kvm->lock);
break;
}
+ case KVM_CAP_PLANES: {
+ mutex_lock(&kvm->lock);
+ r = -EINVAL;
+ if (cap->args[0] < 1 || cap->args[0] > KVM_MAX_PLANES)
+ goto planes_unlock;
+ /*
+ * Only ordinary VMs for now. SEV-SNP VMs get their plane
+ * count from the architectural VMPL support, and VM types
+ * with protected state need per-plane audits first.
+ */
+ if (kvm->arch.vm_type != KVM_X86_DEFAULT_VM)
+ goto planes_unlock;
+ r = -EEXIST;
+ if (kvm->created_vcpus || kvm->has_planes ||
+ kvm->arch.user_max_planes)
+ goto planes_unlock;
+ WRITE_ONCE(kvm->arch.user_max_planes, cap->args[0]);
+ r = 0;
+planes_unlock:
+ mutex_unlock(&kvm->lock);
+ break;
+ }
case KVM_CAP_X2APIC_API:
r = -EINVAL;
if (cap->args[0] & ~KVM_X2APIC_API_VALID_FLAGS)
--
2.50.1