[RFC PATCH 25/55] WIP: KVM: per-plane memory overlays, richer protection-fault exits

From: David Woodhouse

Date: Sat Oct 17 2026 - 08:40:12 EST


From: David Woodhouse <dwmw@xxxxxxxxxxxx>

The pieces the VSM contract tests need:

- KVM_SET_PLANE_MEMORY_OVERLAY maps one page of userspace memory over
a GPA for a single plane. TLFS overlay pages (SynIC SIMP in
particular) show each VTL a private page while the other VTLs keep
seeing the guest RAM underneath; per-plane EPT roots make a
per-plane pfn override in the fault path sufficient. Mappings for a
plane with overlays stay 4KiB so a neighbouring fault's huge page
cannot span an overlaid gfn, and kvm_tdp_mmu_recover_huge_pages()
skips nonzero-plane roots so a later promotion cannot widen the
plane's attribute or overlay clamps back out (planes keep no lpage
tracking by design; their fault path rebuilds each mapping at the
largest level the plane allows). The overlay update runs under the
kvm_mmu_invalidate_begin()/end() protocol with the gfn registered,
as the mmu notifiers do, so a concurrent lockless fault cannot
install an SPTE for the old backing after the zap.

- SynIC message delivery reads and writes the overlay through the same
lookup, and fails with -EFAULT when a higher plane's SIMP page has
no overlay installed yet, rather than falling through to
kvm_vcpu_write_guest_page() and corrupting underlay RAM that plane 0
owns; the sender retries once the overlay appears. A non-zero
plane's SynIC exits carry the plane instead of being suppressed, so
the VMM can keep its plane-0 device state separate.

- A plane-protection fault reflected as KVM_EXIT_MEMORY_FAULT now
carries the guest-linear address and the faulting instruction's
length and bytes (via VM_EXIT_INSTRUCTION_LEN and a guest-virt
read), which the TLFS GPA-intercept message requires. LLDT joins
LGDT/LIDT in the descriptor-write divert, reporting the written
selector.

Signed-off-by: David Woodhouse <dwmw@xxxxxxxxxxxx>
Assisted-by: LLM
---
Documentation/virt/kvm/api.rst | 62 ++++++++
arch/x86/kvm/hyperv.c | 82 +++++++---
arch/x86/kvm/mmu/mmu.c | 29 +++-
arch/x86/kvm/mmu/tdp_mmu.c | 23 ++-
arch/x86/kvm/vmx/vmx.c | 51 ++++++-
arch/x86/kvm/x86.c | 14 ++
include/linux/kvm_host.h | 17 +++
include/uapi/linux/kvm.h | 17 +++
virt/kvm/kvm_main.c | 271 +++++++++++++++++++++++++++++++++
9 files changed, 541 insertions(+), 25 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 3ef57b3b050b..215547802377 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -6657,6 +6657,38 @@ stop policy.
The run state is distinct from KVM_GET/SET_MP_STATE, which describes
the architectural state of a vCPU within its plane.

+4.149 KVM_SET_PLANE_MEMORY_OVERLAY
+----------------------------------
+
+:Capability: KVM_CAP_PLANE_MEMORY_OVERLAYS
+:Architectures: x86
+:Type: vm ioctl
+:Parameters: struct kvm_plane_memory_overlay (in)
+:Returns: 0 on success, <0 on error
+
+::
+
+ struct kvm_plane_memory_overlay {
+ __u32 plane;
+ __u32 flags;
+ __u64 gfn;
+ __u64 uaddr; /* page-aligned; 0 removes the overlay */
+ };
+
+Installs, replaces or removes a memory overlay for a single guest page
+frame on one plane. While an overlay is installed, translations of
+'gfn' for vCPUs of 'plane' resolve to the (pinned) page at 'uaddr' in
+the calling process instead of to the memslot backing; other planes are
+unaffected. Hyper-V VSM uses this for per-VTL overlay pages such as
+the SynIC message and event-flags pages.
+
+'flags' must be zero and 'uaddr' must be page-aligned. At most
+KVM_CHECK_EXTENSION(KVM_CAP_PLANE_MEMORY_OVERLAYS) distinct gfns can
+carry an overlay per plane at any one time; further installs fail with
+-ENOSPC until an overlay is removed. An overlay cannot be installed
+over a private or guest_memfd-only gfn (-EINVAL). Passing 'uaddr' == 0
+removes the overlay for 'gfn', if any.
+
5. The kvm_run structure
========================

@@ -7387,9 +7419,15 @@ spec refer, https://github.com/riscv/riscv-sbi-doc.
#define KVM_MEMORY_EXIT_FLAG_WRITE (1ULL << 1)
#define KVM_MEMORY_EXIT_FLAG_EXEC (1ULL << 2)
#define KVM_MEMORY_EXIT_FLAG_PRIVATE (1ULL << 3)
+ #define KVM_MEMORY_EXIT_FLAG_GVA_VALID (1ULL << 4)
__u64 flags;
__u64 gpa;
__u64 size;
+ /* set on plane-protection faults when available */
+ __u64 gva;
+ __u32 insn_len;
+ __u8 insn_bytes[16];
+ __u32 pad;
} memory_fault;

KVM_EXIT_MEMORY_FAULT indicates the vCPU has encountered a memory fault that
@@ -7402,6 +7440,14 @@ describes properties of the faulting access that are likely pertinent:
- KVM_MEMORY_EXIT_FLAG_PRIVATE - When set, indicates the memory fault occurred
on a private memory access. When clear, indicates the fault occurred on a
shared access.
+ - KVM_MEMORY_EXIT_FLAG_GVA_VALID - When set, 'gva' holds the faulting guest
+ virtual address.
+
+The 'gva', 'insn_len' and 'insn_bytes' fields exist only on kernels
+which advertise KVM_CAP_PLANE_MEMORY_OVERLAYS (see section 8.48). On
+plane memory-protection faults, 'insn_len' and 'insn_bytes' carry a
+best-effort copy of the faulting instruction when KVM could fetch it;
+an 'insn_len' of 0 means no instruction bytes are available.

Note! KVM_EXIT_MEMORY_FAULT is unique among all KVM exit reasons in that it
accompanies a return code of '-1', not '0'! errno will always be set to EFAULT
@@ -9676,6 +9722,22 @@ take care to differentiate between these cases.
The presence of this capability indicates that the nested KVM guest can
start in ESA mode.

+8.48 KVM_CAP_PLANE_MEMORY_OVERLAYS
+----------------------------------
+
+:Architectures: x86
+
+A positive return from KVM_CHECK_EXTENSION(KVM_CAP_PLANE_MEMORY_OVERLAYS)
+announces two things:
+
+ - The KVM_SET_PLANE_MEMORY_OVERLAY vm ioctl (section 4.149). The
+ returned value is the number of gfns which can carry an overlay
+ simultaneously on each plane.
+
+ - The extended KVM_EXIT_MEMORY_FAULT layout: 'memory_fault' carries
+ the additional 'gva', 'insn_len' and 'insn_bytes' fields, with
+ KVM_MEMORY_EXIT_FLAG_GVA_VALID qualifying 'gva' (see section 5).
+
9. Known KVM API problems
=========================

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 4551e011bf56..d4e44ae3f6e8 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -247,16 +247,8 @@ static void synic_exit(struct kvm_vcpu_hv_synic *synic, u32 msr)
struct kvm_vcpu *vcpu = hv_synic_to_vcpu(synic);
struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu);

- /*
- * Userspace's SynIC device models plane 0 only; a higher plane's
- * exit would be mistaken for a plane-0 page move and relocate the
- * overlay the VMM keeps for SIMP/SIEFP. Higher-plane SynICs are
- * handled fully in-kernel.
- */
- if (vcpu->plane_level)
- return;
-
hv_vcpu->exit.type = KVM_EXIT_HYPERV_SYNIC;
+ hv_vcpu->exit.plane = vcpu->plane_level;
hv_vcpu->exit.u.synic.msr = msr;
hv_vcpu->exit.u.synic.control = synic->control;
hv_vcpu->exit.u.synic.evt_page = synic->evt_page;
@@ -358,6 +350,7 @@ static void syndbg_exit(struct kvm_vcpu *vcpu, u32 msr)
struct kvm_vcpu_hv *hv_vcpu = to_hv_vcpu(vcpu);

hv_vcpu->exit.type = KVM_EXIT_HYPERV_SYNDBG;
+ hv_vcpu->exit.plane = vcpu->plane_level;
hv_vcpu->exit.u.syndbg.msr = msr;
hv_vcpu->exit.u.syndbg.control = syndbg->control.control;
hv_vcpu->exit.u.syndbg.send_page = syndbg->control.send_page;
@@ -783,6 +776,51 @@ static int stimer_get_count(struct kvm_vcpu_hv_stimer *stimer, u64 *pcount)
return 0;
}

+/*
+ * SIMP accessors: a plane overlay page, when installed by userspace,
+ * carries the live message page for this plane; the memslot holds only
+ * the underlay the other planes see.
+ */
+static int synic_msg_page_read(struct kvm_vcpu *vcpu, gfn_t gfn, void *data,
+ int offset, int len)
+{
+ unsigned long uaddr = kvm_plane_overlay_uaddr(vcpu->plane, gfn);
+
+ if (uaddr)
+ return copy_from_user(data, (void __user *)(uaddr + offset),
+ len) ? -EFAULT : 0;
+ /*
+ * Mirror the write side: without an overlay a higher plane's
+ * message page is backed by underlay memory that plane 0 controls.
+ * Deciding deliverability from plane-0-writable bytes would let a
+ * lower VTL plant a non-HVMSG_NONE type and silently swallow a
+ * no_retry (periodic stimer) message; fail so the delivery is
+ * retried once the overlay appears.
+ */
+ if (vcpu->plane_level)
+ return -EFAULT;
+ return kvm_vcpu_read_guest_page(vcpu, gfn, data, offset, len);
+}
+
+static int synic_msg_page_write(struct kvm_vcpu *vcpu, gfn_t gfn,
+ const void *data, int offset, int len)
+{
+ unsigned long uaddr = kvm_plane_overlay_uaddr(vcpu->plane, gfn);
+
+ if (uaddr)
+ return copy_to_user((void __user *)(uaddr + offset), data,
+ len) ? -EFAULT : 0;
+ /*
+ * Never write a higher plane's messages into guest RAM: without an
+ * overlay installed that memory is the underlay every other plane
+ * sees, and silently corrupting plane-0-owned pages is far worse
+ * than retrying the message once the overlay appears.
+ */
+ if (vcpu->plane_level)
+ return -EFAULT;
+ return kvm_vcpu_write_guest_page(vcpu, gfn, data, offset, len);
+}
+
static int synic_deliver_msg(struct kvm_vcpu_hv_synic *synic, u32 sint,
struct hv_message *src_msg, bool no_retry)
{
@@ -803,10 +841,10 @@ static int synic_deliver_msg(struct kvm_vcpu_hv_synic *synic, u32 sint,
* is only called in vcpu context so the entire update is atomic from
* guest POV and thus the exact order here doesn't matter.
*/
- r = kvm_vcpu_read_guest_page(vcpu, msg_page_gfn, &hv_hdr.message_type,
- msg_off + offsetof(struct hv_message,
- header.message_type),
- sizeof(hv_hdr.message_type));
+ r = synic_msg_page_read(vcpu, msg_page_gfn, &hv_hdr.message_type,
+ msg_off + offsetof(struct hv_message,
+ header.message_type),
+ sizeof(hv_hdr.message_type));
if (r < 0)
return r;

@@ -815,20 +853,20 @@ static int synic_deliver_msg(struct kvm_vcpu_hv_synic *synic, u32 sint,
return 0;

hv_hdr.message_flags.msg_pending = 1;
- r = kvm_vcpu_write_guest_page(vcpu, msg_page_gfn,
- &hv_hdr.message_flags,
- msg_off +
- offsetof(struct hv_message,
- header.message_flags),
- sizeof(hv_hdr.message_flags));
+ r = synic_msg_page_write(vcpu, msg_page_gfn,
+ &hv_hdr.message_flags,
+ msg_off +
+ offsetof(struct hv_message,
+ header.message_flags),
+ sizeof(hv_hdr.message_flags));
if (r < 0)
return r;
return -EAGAIN;
}

- r = kvm_vcpu_write_guest_page(vcpu, msg_page_gfn, src_msg, msg_off,
- sizeof(src_msg->header) +
- src_msg->header.payload_size);
+ r = synic_msg_page_write(vcpu, msg_page_gfn, src_msg, msg_off,
+ sizeof(src_msg->header) +
+ src_msg->header.payload_size);
if (r < 0)
return r;

diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index cdebb5c324fe..4b11631d9e99 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4620,7 +4620,13 @@ static int kvm_mmu_faultin_pfn_gmem(struct kvm_vcpu *vcpu,
if (fault->slot->flags & KVM_MEM_READONLY)
fault->host_access &= ~ACC_WRITE_MASK;

- fault->max_level = kvm_max_level_for_order(max_order);
+ /*
+ * Only ever narrow max_level: the overlay check in
+ * __kvm_mmu_faultin_pfn() may already have clamped it to 4KiB, and
+ * a huge gmem mapping must not span an overlaid gfn.
+ */
+ fault->max_level = min(fault->max_level,
+ kvm_max_level_for_order(max_order));
return RET_PF_CONTINUE;
}

@@ -4682,8 +4688,29 @@ static int __kvm_mmu_faultin_pfn(struct kvm_vcpu *vcpu,
struct kvm_page_fault *fault)
{
unsigned int foll = fault->write ? FOLL_WRITE : 0;
+ struct page *overlay;
bool writable;

+ /*
+ * A plane overlay page (VSM SynIC SIMP/SIEFP etc.) replaces the
+ * memslot translation for this plane only. The page is pinned
+ * for the overlay's lifetime, so no refcounting here.
+ */
+ /*
+ * Keep this plane's mappings at 4KiB while it has overlays: a huge
+ * mapping established by a neighbouring gfn's fault would span the
+ * overlaid gfn and bypass the override below.
+ */
+ if (READ_ONCE(vcpu->plane->nr_overlays)) {
+ fault->max_level = PG_LEVEL_4K;
+
+ overlay = kvm_plane_overlay_page(vcpu->plane, fault->gfn);
+ if (overlay) {
+ fault->pfn = page_to_pfn(overlay);
+ return RET_PF_CONTINUE;
+ }
+ }
+
if (fault->is_private || kvm_memslot_is_gmem_only(fault->slot))
return kvm_mmu_faultin_pfn_gmem(vcpu, fault);

diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
index 1e56a4784160..d5d0b2bc547c 100644
--- a/arch/x86/kvm/mmu/tdp_mmu.c
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
@@ -1871,8 +1871,29 @@ void kvm_tdp_mmu_recover_huge_pages(struct kvm *kvm,
struct kvm_mmu_page *root;

lockdep_assert_held_read(&kvm->mmu_lock);
- for_each_valid_tdp_mmu_root_yield_safe(kvm, root, slot->as_id)
+ for_each_valid_tdp_mmu_root_yield_safe(kvm, root, slot->as_id) {
+ /*
+ * kvm_mmu_max_mapping_level() consults only plane-0 state
+ * (lpage_info and plane-0 private attributes). Nonzero
+ * planes carry per-plane memory attributes and memory
+ * overlays whose fault-time 4KiB clamps a huge SPTE would
+ * undo, extending one page's protections or overlay
+ * backing over gfns the plane maps differently. Leave
+ * those roots alone; their fault path rebuilds mappings at
+ * the largest level the plane allows.
+ *
+ * Plane 0 can carry overlays too (kvm->planes[0] always
+ * exists and KVM_SET_PLANE_MEMORY_OVERLAY accepts plane 0),
+ * and the same reasoning applies: a recovered huge SPTE
+ * would either span an overlaid gfn with the memslot
+ * backing, or huge-align the pinned overlay page's pfn and
+ * map ~2MiB of unrelated host memory around it.
+ */
+ if (root->role.plane ||
+ READ_ONCE(kvm->planes[root->role.plane]->nr_overlays))
+ continue;
recover_huge_pages_range(kvm, root, slot);
+ }
}

/*
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index e1c5f1608b09..248d799b46e9 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -70,6 +70,7 @@
#include "vmcs12.h"
#include "vmx.h"
#include "x86.h"
+#include "kvm_emulate.h"
#include "x86_ops.h"
#include "smm.h"
#include "vmx_onhyperv.h"
@@ -5949,6 +5950,7 @@ static int handle_ept_violation(struct kvm_vcpu *vcpu)
{
unsigned long exit_qualification = vmx_get_exit_qual(vcpu);
gpa_t gpa;
+ int ret;

/*
* EPT violation happened while executing iret from NMI,
@@ -5975,7 +5977,54 @@ static int handle_ept_violation(struct kvm_vcpu *vcpu)
if (unlikely(allow_smaller_maxphyaddr && !kvm_vcpu_is_legal_gpa(vcpu, gpa)))
return kvm_emulate_instruction(vcpu, 0);

- return __vmx_handle_ept_violation(vcpu, gpa, exit_qualification);
+ ret = __vmx_handle_ept_violation(vcpu, gpa, exit_qualification);
+
+ /*
+ * A plane-protection fault reflected to userspace becomes a TLFS
+ * GPA intercept message, which carries the guest-linear address
+ * and the faulting instruction's bytes. Harvest them while the
+ * exit state is still at hand.
+ */
+ if (ret == -EFAULT &&
+ vcpu->run->exit_reason == KVM_EXIT_MEMORY_FAULT &&
+ (vcpu->run->memory_fault.flags & (KVM_MEMORY_EXIT_FLAG_READ |
+ KVM_MEMORY_EXIT_FLAG_WRITE |
+ KVM_MEMORY_EXIT_FLAG_EXEC))) {
+ unsigned int len;
+
+ if (exit_qualification & EPT_VIOLATION_GVA_IS_VALID) {
+ vcpu->run->memory_fault.gva =
+ vmcs_readl(GUEST_LINEAR_ADDRESS);
+ vcpu->run->memory_fault.flags |=
+ KVM_MEMORY_EXIT_FLAG_GVA_VALID;
+ }
+ /*
+ * Architecturally undefined for EPT violations, but the
+ * hardware provides the faulting (for a fetch: the
+ * fetching) instruction's length here, and the TLFS
+ * message carries exactly that.
+ */
+ len = vmcs_read32(VM_EXIT_INSTRUCTION_LEN);
+ len = min_t(unsigned int, len,
+ sizeof(vcpu->run->memory_fault.insn_bytes));
+ vcpu->run->memory_fault.insn_len = len;
+ if (!(exit_qualification & EPT_VIOLATION_ACC_INSTR)) {
+ struct x86_exception e;
+
+ /*
+ * kvm_read_guest_virt() takes a linear address: RIP
+ * is CS-relative, and the two coincide only when
+ * CS.base is zero (64-bit mode, or a flat 32-bit
+ * code segment).
+ */
+ if (kvm_read_guest_virt(vcpu, kvm_get_linear_rip(vcpu),
+ vcpu->run->memory_fault.insn_bytes,
+ len, &e))
+ memset(vcpu->run->memory_fault.insn_bytes, 0,
+ sizeof(vcpu->run->memory_fault.insn_bytes));
+ }
+ }
+ return ret;
}

static int handle_ept_misconfig(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b12ec7ee100..10e14ab3e2ca 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -8858,8 +8858,10 @@ static bool hv_divert_dt_write(struct x86_emulate_ctxt *ctxt, u32 hv_reg,

#define HV_CR_INTERCEPT_GDTR_WRITE BIT_ULL(15)
#define HV_CR_INTERCEPT_IDTR_WRITE BIT_ULL(16)
+#define HV_CR_INTERCEPT_LDTR_WRITE BIT_ULL(17)
#define HV_X64_REGISTER_IDTR_ID 0x00070000
#define HV_X64_REGISTER_GDTR_ID 0x00070001
+#define HV_X64_REGISTER_LDTR_ID 0x00060006

static void emulator_set_gdt(struct x86_emulate_ctxt *ctxt, struct desc_ptr *dt)
{
@@ -8947,6 +8949,18 @@ static void emulator_set_segment(struct x86_emulate_ctxt *ctxt, u16 selector,
var.unusable = !var.present;
var.padding = 0;

+ if (seg == VCPU_SREG_LDTR) {
+ struct desc_ptr sel_as_dt = {
+ .address = selector,
+ .size = 0,
+ };
+
+ /* The intercept message carries the written selector. */
+ if (hv_divert_dt_write(ctxt, HV_X64_REGISTER_LDTR_ID,
+ &sel_as_dt, HV_CR_INTERCEPT_LDTR_WRITE))
+ return;
+ }
+
kvm_set_segment(vcpu, &var, seg);
return;
}
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index a76615f2c3dc..cb8c4798df12 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -927,9 +927,26 @@ struct kvm_plane {
struct xarray mem_attrs;
#endif

+ /*
+ * VSM overlay pages: per-plane gfn -> pinned user page overriding
+ * the memslot translation for this plane only. Written under
+ * kvm->slots_lock; readers tolerate a transient stale view, the
+ * post-update zap makes the EPT coherent.
+ */
+#define KVM_PLANE_MAX_OVERLAYS 64
+ struct kvm_plane_overlay {
+ gfn_t gfn;
+ unsigned long uaddr;
+ struct page *page;
+ } overlays[KVM_PLANE_MAX_OVERLAYS];
+ int nr_overlays;
+
struct kvm_arch_plane arch;
};

+struct page *kvm_plane_overlay_page(struct kvm_plane *plane, gfn_t gfn);
+unsigned long kvm_plane_overlay_uaddr(struct kvm_plane *plane, gfn_t gfn);
+
struct kvm {
#ifdef KVM_HAVE_MMU_RWLOCK
rwlock_t mmu_lock;
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 9e453f215e68..843cef8f4cbf 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -480,9 +480,15 @@ struct kvm_run {
#define KVM_MEMORY_EXIT_FLAG_WRITE (1ULL << 1)
#define KVM_MEMORY_EXIT_FLAG_EXEC (1ULL << 2)
#define KVM_MEMORY_EXIT_FLAG_PRIVATE (1ULL << 3)
+#define KVM_MEMORY_EXIT_FLAG_GVA_VALID (1ULL << 4)
__u64 flags;
__u64 gpa;
__u64 size;
+ /* set on plane-protection faults when available */
+ __u64 gva;
+ __u32 insn_len;
+ __u8 insn_bytes[16];
+ __u32 pad;
} memory_fault;
/* KVM_EXIT_TDX */
struct {
@@ -1038,6 +1044,7 @@ struct kvm_enable_cap {
#define KVM_CAP_HCALL_FAULT_EXIT 252
#define KVM_CAP_HYPERV_VTL_SWITCH 253
#define KVM_CAP_HYPERV_REG_INTERCEPT 254
+#define KVM_CAP_PLANE_MEMORY_OVERLAYS 257

struct kvm_irq_routing_irqchip {
__u32 irqchip;
@@ -1394,6 +1401,16 @@ struct kvm_s390_keyop {
#define KVM_CREATE_PLANE _IO(KVMIO, 0xe4)
#define KVM_GET_PLANE_RUN_STATE _IOR(KVMIO, 0xe5, __u32)
#define KVM_SET_PLANE_RUN_STATE _IOW(KVMIO, 0xe6, __u32)
+
+/* Map one page of userspace memory over a GPA for a single plane (VSM
+ * overlay pages: SynIC SIMP/SIEFP, hypercall page, VP assist page). */
+struct kvm_plane_memory_overlay {
+ __u32 plane;
+ __u32 flags;
+ __u64 gfn;
+ __u64 uaddr; /* page-aligned; 0 removes the overlay */
+};
+#define KVM_SET_PLANE_MEMORY_OVERLAY _IOW(KVMIO, 0xe7, struct kvm_plane_memory_overlay)
#define KVM_PLANE_RUN_STATE_STOPPED 0
#define KVM_PLANE_RUN_STATE_RUNNABLE 1
#define KVM_PLANE_RUN_STATE_KERNEL_STOPPED 2
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 4197496f7176..5856672bbf6d 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1251,6 +1251,14 @@ static struct kvm_plane *kvm_create_plane(struct kvm *kvm, unsigned plane_level)

static void kvm_destroy_one_plane(struct kvm_plane *plane)
{
+ int i;
+
+ for (i = 0; i < plane->nr_overlays; i++)
+ if (plane->overlays[i].page)
+ unpin_user_pages_dirty_lock(&plane->overlays[i].page,
+ 1, true);
+ plane->nr_overlays = 0;
+
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
xa_destroy(&plane->mem_attrs);
#endif
@@ -5629,6 +5637,8 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
* VM actually has.
*/
return KVM_MAX_PLANES;
+ case KVM_CAP_PLANE_MEMORY_OVERLAYS:
+ return KVM_PLANE_MAX_OVERLAYS;
default:
break;
}
@@ -5896,6 +5906,258 @@ do { \
sizeof_field(struct kvm_userspace_memory_region2, field)); \
} while (0)

+struct page *kvm_plane_overlay_page(struct kvm_plane *plane, gfn_t gfn)
+{
+ int i, n = READ_ONCE(plane->nr_overlays);
+
+ /* pairs with the smp_wmb() publishing a new entry */
+ smp_rmb();
+ for (i = 0; i < n; i++) {
+ if (READ_ONCE(plane->overlays[i].gfn) != gfn)
+ continue;
+ /* pairs with the smp_store_release() updating an entry */
+ return smp_load_acquire(&plane->overlays[i].page);
+ }
+ return NULL;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_plane_overlay_page);
+
+unsigned long kvm_plane_overlay_uaddr(struct kvm_plane *plane, gfn_t gfn)
+{
+ int i, n = READ_ONCE(plane->nr_overlays);
+
+ smp_rmb();
+ for (i = 0; i < n; i++) {
+ if (READ_ONCE(plane->overlays[i].gfn) != gfn)
+ continue;
+ /*
+ * The acquire pairs with the smp_store_release() updating
+ * an entry: a reader which observes the new page also
+ * observes the uaddr written before it.
+ */
+ if (smp_load_acquire(&plane->overlays[i].page))
+ return READ_ONCE(plane->overlays[i].uaddr);
+ return 0;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_plane_overlay_uaddr);
+
+/*
+ * The invalidation window registered around an overlay update must
+ * cover every gfn whose in-flight fault could install a huge leaf
+ * spanning the overlaid gfn, i.e. the naturally-aligned region of the
+ * widest stage-2 huge mapping any plane-capable architecture supports
+ * (a 1GiB page on x86). A narrower window lets a racing fault on a
+ * neighbouring gfn, which sampled nr_overlays and mmu_invalidate_seq
+ * before the update, pass mmu_invalidate_retry_gfn() and install a
+ * huge SPTE over the overlay after the zap.
+ */
+#define KVM_PLANE_OVERLAY_INVAL_PAGES (SZ_1G >> PAGE_SHIFT)
+
+static void kvm_plane_overlay_invalidate_add(struct kvm *kvm, gfn_t gfn)
+{
+ gfn_t start = ALIGN_DOWN(gfn, KVM_PLANE_OVERLAY_INVAL_PAGES);
+
+ kvm_mmu_invalidate_range_add(kvm, start,
+ start + KVM_PLANE_OVERLAY_INVAL_PAGES);
+}
+
+/*
+ * Drop every translation of @gfn, in all address spaces and all planes'
+ * roots. A huge leaf spanning the gfn intersects the single-page range
+ * and is zapped with it. Hardware TLBs are not flushed here.
+ */
+static void kvm_plane_overlay_unmap(struct kvm *kvm, gfn_t gfn)
+{
+#ifdef CONFIG_KVM_GENERIC_MMU_NOTIFIER
+ struct kvm_memslot_iter iter;
+ struct kvm_memslots *slots;
+ int i;
+
+ KVM_MMU_LOCK(kvm);
+ for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
+ slots = __kvm_memslots(kvm, i);
+
+ kvm_for_each_memslot_in_gfn_range(&iter, slots, gfn, gfn + 1) {
+ struct kvm_gfn_range range = {
+ .slot = iter.slot,
+ .start = gfn,
+ .end = gfn + 1,
+ .attr_filter = KVM_FILTER_PRIVATE |
+ KVM_FILTER_SHARED,
+ .may_block = true,
+ };
+
+ kvm_unmap_gfn_range(kvm, &range);
+ }
+ }
+ KVM_MMU_UNLOCK(kvm);
+#else
+ kvm_arch_flush_shadow_all(kvm);
+#endif
+}
+
+static int kvm_vm_ioctl_set_plane_overlay(struct kvm *kvm,
+ struct kvm_plane_memory_overlay *ov)
+{
+ struct kvm_plane_overlay *e = NULL;
+ struct page *page = NULL, *old_page = NULL;
+ struct kvm_plane *plane;
+ bool moved = false;
+ gfn_t moved_gfn = 0;
+ int i, r;
+
+ if (ov->flags || ov->plane >= KVM_MAX_PLANES)
+ return -EINVAL;
+ if (ov->uaddr & ~PAGE_MASK)
+ return -EINVAL;
+ plane = kvm->planes[ov->plane];
+ if (!plane)
+ return -EINVAL;
+
+ if (ov->uaddr) {
+ r = pin_user_pages_fast(ov->uaddr, 1,
+ FOLL_WRITE | FOLL_LONGTERM, &page);
+ if (r != 1)
+ return r < 0 ? r : -EFAULT;
+ }
+
+ mutex_lock(&kvm->slots_lock);
+ for (i = 0; i < plane->nr_overlays; i++) {
+ if (plane->overlays[i].gfn == ov->gfn) {
+ e = &plane->overlays[i];
+ break;
+ }
+ }
+
+ /*
+ * Removing an overlay which was never installed changes nothing;
+ * don't zap and flush for it.
+ */
+ if (!ov->uaddr && !e) {
+ mutex_unlock(&kvm->slots_lock);
+ return 0;
+ }
+
+ /*
+ * guest_memfd-backed and private gfns have their own backing
+ * contract which an ordinary pinned user page cannot substitute
+ * for, and kvm_mmu_faultin_pfn_gmem() does not consult overlays.
+ * Both the private attribute and the memslot flags are stable
+ * under slots_lock.
+ */
+ if (ov->uaddr) {
+ struct kvm_memory_slot *slot = gfn_to_memslot(kvm, ov->gfn);
+
+ if ((slot && kvm_memslot_is_gmem_only(slot)) ||
+ kvm_mem_is_private(kvm, ov->gfn)) {
+ mutex_unlock(&kvm->slots_lock);
+ unpin_user_page(page);
+ return -EINVAL;
+ }
+ }
+
+ if (ov->uaddr) {
+ if (!e) {
+ if (plane->nr_overlays >= KVM_PLANE_MAX_OVERLAYS) {
+ mutex_unlock(&kvm->slots_lock);
+ unpin_user_page(page);
+ return -ENOSPC;
+ }
+ e = &plane->overlays[plane->nr_overlays];
+ e->gfn = ov->gfn;
+ e->uaddr = ov->uaddr;
+ e->page = page;
+ /*
+ * Publish the fully-initialised entry before
+ * raising the count; pairs with the smp_rmb() in
+ * the lockless readers.
+ */
+ smp_wmb();
+ WRITE_ONCE(plane->nr_overlays, plane->nr_overlays + 1);
+ } else {
+ old_page = e->page;
+ WRITE_ONCE(e->uaddr, ov->uaddr);
+ /*
+ * Order the uaddr store before the page store; a
+ * reader which acquires the new page must see the
+ * uaddr it belongs to.
+ */
+ smp_store_release(&e->page, page);
+ }
+ } else if (e) {
+ struct kvm_plane_overlay *tail =
+ &plane->overlays[plane->nr_overlays - 1];
+
+ old_page = e->page;
+ WRITE_ONCE(e->page, NULL);
+ WRITE_ONCE(e->uaddr, 0);
+
+ /*
+ * Reclaim the slot: move the tail entry into the vacated
+ * one and shrink the array. A lockless reader scanning
+ * with the old count can miss the moved entry entirely
+ * (it may already have passed the vacated slot), so the
+ * moved gfn is added to the invalidation window below and
+ * any such in-flight fault retries. The cleared tail is
+ * harmless to a stale-count reader: the live copy sits at
+ * a lower index and is found first.
+ */
+ if (e != tail) {
+ moved = true;
+ moved_gfn = tail->gfn;
+ WRITE_ONCE(e->gfn, tail->gfn);
+ WRITE_ONCE(e->uaddr, tail->uaddr);
+ /* pairs with smp_load_acquire() in the readers */
+ smp_store_release(&e->page, tail->page);
+ }
+ WRITE_ONCE(plane->nr_overlays, plane->nr_overlays - 1);
+ WRITE_ONCE(tail->page, NULL);
+ WRITE_ONCE(tail->uaddr, 0);
+ WRITE_ONCE(tail->gfn, 0);
+ }
+ /*
+ * Bump the invalidation sequence across the update and zap: a
+ * concurrent fault resolves the overlay pfn locklessly before
+ * taking mmu_lock, and without a sequence change
+ * is_page_fault_stale() would let it install an SPTE for the OLD
+ * backing after the zap, mapping a page this function is about
+ * to unpin.
+ */
+ KVM_MMU_LOCK(kvm);
+ kvm_mmu_invalidate_begin(kvm);
+ kvm_plane_overlay_invalidate_add(kvm, ov->gfn);
+ if (moved)
+ kvm_plane_overlay_invalidate_add(kvm, moved_gfn);
+ KVM_MMU_UNLOCK(kvm);
+
+ mutex_unlock(&kvm->slots_lock);
+
+ /*
+ * Drop any translation mapping the old or new backing. The unmap
+ * removes the SPTEs but does not flush hardware TLBs: a vCPU that
+ * mapped the gfn before the update still holds a translation to
+ * the old backing in its TLB (tagged by the live ASID on SVM,
+ * which nothing else invalidates). Flush before the old page is
+ * unpinned: KVM_REQ_TLB_FLUSH is a per-plane request, so the
+ * fan-out in kvm_make_vcpu_request() reaches every plane twin and
+ * each flushes its own hardware context.
+ */
+ kvm_plane_overlay_unmap(kvm, ov->gfn);
+ if (moved)
+ kvm_plane_overlay_unmap(kvm, moved_gfn);
+ kvm_flush_remote_tlbs(kvm);
+
+ KVM_MMU_LOCK(kvm);
+ kvm_mmu_invalidate_end(kvm);
+ KVM_MMU_UNLOCK(kvm);
+
+ if (old_page)
+ unpin_user_pages_dirty_lock(&old_page, 1, true);
+ return 0;
+}
+
static long kvm_vm_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
@@ -5906,6 +6168,15 @@ static long kvm_vm_ioctl(struct file *filp,
if (kvm->mm != current->mm || kvm->vm_dead)
return -EIO;
switch (ioctl) {
+ case KVM_SET_PLANE_MEMORY_OVERLAY: {
+ struct kvm_plane_memory_overlay ov;
+
+ r = -EFAULT;
+ if (copy_from_user(&ov, argp, sizeof(ov)))
+ goto out;
+ r = kvm_vm_ioctl_set_plane_overlay(kvm, &ov);
+ break;
+ }
case KVM_CREATE_PLANE:
r = kvm_vm_ioctl_create_plane(kvm, arg);
break;
--
2.50.1