Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane
@ 2026-08-05 11:04 Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls Sriram Nambakam
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

This RFC adds the QEMU support needed to run a Virtualization-Based
Security (VBS), or Hyper-V VSM-style, secure environment using KVM VM
Planes.  It is the QEMU counterpart to the Linux VBS/VSM-on-KVM series.

The five patches in this mail series are authored by me and add:

  - userspace handlers for VM Plane and VBS VTL-call exits from KVM;
  - the VBS VTL hypercall definitions from the Linux UAPI;
  - loading and running the secure-plane kernel in-kernel; and
  - plane configuration access through the QEMU address-space API.

This series depends on the QEMU VM Planes infrastructure from Joerg
Roedel and related prerequisite work.  Those patches are not duplicated
in this mail series.  The integrated QEMU branch linked below contains
the complete tree, including Joerg's patches and the other prerequisites,
used for development and testing.

The complete implementation is available in three repositories under the
safe-tee organization:

  QEMU support and complete integrated tree:
  https://github.com/safe-tee/qemu/tree/qemu-planes-v11.1.0-rc1

  Linux and KVM support:
  https://github.com/safe-tee/linux/tree/kvm-planes-v7.2-rc5

  Build, test, and integration tooling:
  https://github.com/safe-tee/lvbs

Current build and run instructions are maintained in the lvbs repository:

  https://github.com/safe-tee/lvbs/blob/main/docs/how-to/how-to-build.md

This is prototype code and is not yet intended for production use.
Feedback on the QEMU integration, VTL-call handling, and division of
responsibility between QEMU and the in-kernel VM Plane implementation
would be appreciated.

Acknowledgments
===============

This work stands on top of, and is indebted to, several prior efforts:

  - Joerg Roedel, whose QEMU and KVM VM Planes work provides the
    infrastructure that the VBS/VSM secure plane relies on.

  - Paolo Bonzini, whose "[RFC PATCH 00/29] KVM: VM planes" introduced
    the VM Plane concept to KVM as a common in-kernel model for AMD VMPLs,
    Intel TDX partitions, Hyper-V VTLs, and Arm CCA planes.
    https://lwn.net/Articles/1016113/

  - James Bottomley and James Morris, for their ongoing VSM-on-KVM work,
    which informed the design and direction of this series.

Sriram Nambakam (5):
  kvm: add userspace handlers for VM planes and VBS VTL calls
  vm_planes: Add VBS VTL call handling and plane memory sealing
  linux-headers: sync kvm_para.h VBS VTL hypercalls
  target/i386/kvm: run the secure plane in-kernel (Option B)
  target/i386/kvm: read plane config via address_space API

 accel/kvm/kvm-all.c                       |  18 +
 include/standard-headers/linux/kvm_para.h |   7 +-
 include/system/kvm_int.h                  |  18 +
 linux-headers/linux/kvm.h                 |  16 +
 target/i386/kvm/kvm.c                     | 561 ++++++++++++++++++++++
 5 files changed, 619 insertions(+), 1 deletion(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls
  2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
@ 2026-08-05 11:04 ` Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 2/5] vm_planes: Add VBS VTL call handling and plane memory sealing Sriram Nambakam
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

(cherry picked from commit 47d0da9ac50640f96564997edc4b79f44e70cffa)
---
 accel/kvm/kvm-all.c                       |  18 +
 include/standard-headers/linux/kvm_para.h |   3 +
 include/system/kvm_int.h                  |  17 +
 linux-headers/linux/kvm.h                 |   3 +
 target/i386/kvm/kvm.c                     | 776 ++++++++++++++++++++++
 5 files changed, 817 insertions(+)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 46a14ac0f4..80a9791078 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -822,6 +822,24 @@ void kvm_close(void)
             close(kvm_get_plane_fd(kvm_state, plane_id));
             kvm_set_plane_fd(kvm_state, plane_id, -1);
         } while (plane_id != 0);
+        if (kvm_state->vm_planes) {
+            unsigned int i, j;
+            for (i = 1; i < kvm_state->vm_plane_count; i++) {
+                struct kvm_vm_plane_state *ps = &kvm_state->vm_planes[i];
+                if (ps->vcpu_fds) {
+                    for (j = 0; j < ps->vcpu_count; j++) {
+                        if (ps->vcpu_fds[j] >= 0) {
+                            close(ps->vcpu_fds[j]);
+                        }
+                    }
+                    g_free(ps->vcpu_fds);
+                    ps->vcpu_fds = NULL;
+                }
+            }
+            g_free(kvm_state->vm_planes);
+            kvm_state->vm_planes = NULL;
+            kvm_state->vm_plane_count = 0;
+        }
         close(kvm_state->fd);
         kvm_state->fd = -1;
     }
diff --git a/include/standard-headers/linux/kvm_para.h b/include/standard-headers/linux/kvm_para.h
index 015c166302..ad19aac9d0 100644
--- a/include/standard-headers/linux/kvm_para.h
+++ b/include/standard-headers/linux/kvm_para.h
@@ -30,6 +30,9 @@
 #define KVM_HC_SEND_IPI		10
 #define KVM_HC_SCHED_YIELD		11
 #define KVM_HC_MAP_GPA_RANGE		12
+#define KVM_HC_VM_PLANES_CONFIG		13
+#define KVM_HC_VM_PLANES_ACTIVATE	14
+#define KVM_HC_VBS_VTL_CALL		15
 
 /*
  * hypercalls use architecture specific
diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h
index 70b381f1ba..e7c9dc95a2 100644
--- a/include/system/kvm_int.h
+++ b/include/system/kvm_int.h
@@ -109,6 +109,19 @@ struct KVMPlane {
     bool vcpu_dirty;
 };
 
+/* Per-plane VM state managed by LVBS hypercall handlers.
+ * The plane fd itself is owned by the accel layer and accessed via
+ * kvm_get_plane_fd(s, plane_id); only LVBS-specific state lives here. */
+struct kvm_vm_plane_state {
+    int *vcpu_fds;
+    unsigned int vcpu_count;
+    uint64_t load_offset;
+    uint64_t memory_size;
+    uint64_t entry_point;
+    void *host_addr;        /* host pointer to plane RAM (cleared after launch) */
+    char cmdline[512];
+};
+
 struct KVMState
 {
     AccelState parent_obj;
@@ -176,6 +189,10 @@ struct KVMState
     uint16_t xen_evtchn_max_pirq;
     char *device;
     OnOffAuto honor_guest_pat;
+    /* VM planes state (populated by LVBS hypercall handlers) */
+    struct kvm_vm_plane_state *vm_planes;
+    unsigned int vm_plane_count;
+    unsigned int vm_planes_max;
 };
 
 static inline void kvm_set_plane_fd(KVMState *s, unsigned plane, int fd)
diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index 8caa3eccce..d66ce6d272 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -1654,6 +1654,9 @@ struct kvm_memory_attributes {
 
 #define KVM_MEMORY_ATTRIBUTE_PRIVATE           (1ULL << 3)
 
+#define KVM_MEMORY_ATTRIBUTE_NO_WRITE          (1ULL << 4)
+#define KVM_MEMORY_ATTRIBUTE_NO_EXEC           (1ULL << 5)
+
 #define KVM_CREATE_GUEST_MEMFD	_IOWR(KVMIO,  0xd4, struct kvm_create_guest_memfd)
 #define GUEST_MEMFD_FLAG_MMAP		(1ULL << 0)
 #define GUEST_MEMFD_FLAG_INIT_SHARED	(1ULL << 1)
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 13cfa60071..9d89e5cac6 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -71,6 +71,13 @@
 #include "exec/memattrs.h"
 #include "exec/target_page.h"
 #include "trace.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
+#include "qemu/thread.h"
+#include "qemu/timer.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
 
 #include CONFIG_DEVICES
 
@@ -3588,6 +3595,13 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
         kvm_vmfd_add_change_notifier(&kvm_vmfd_change_notifier);
     }
 
+    /* Enable userspace exit for VM planes and VBS hypercalls (LVBS). */
+    if (!kvm_enable_hypercall(BIT_ULL(KVM_HC_VM_PLANES_CONFIG) |
+                              BIT_ULL(KVM_HC_VM_PLANES_ACTIVATE) |
+                              BIT_ULL(KVM_HC_VBS_VTL_CALL))) {
+        warn_report("kvm: failed to enable VM planes / VBS hypercall exit");
+    }
+
     /*
      * Most x86 CPUs in current use have self-snoop, so honoring guest PAT is
      * preferable.  As well, the bochs video driver bug which motivated making
@@ -6519,10 +6533,772 @@ static int kvm_handle_hc_map_gpa_range(X86CPU *cpu, struct kvm_run *run)
     return 0;
 }
 
+/* ========================================================================
+ * LVBS — VM planes + VBS VTL hypercall handlers
+ *
+ * Guest-side struct vm_plane_config layout (672 bytes, see Linux
+ * include/linux/vm_planes.h):
+ *   off  0  u64  load_offset
+ *   off  8  u64  memory_size
+ *   off 16  u64  entry_point
+ *   off 24  u32  vcpu_count
+ *   off 28  u32  kernel_format
+ *   off 32  char kernel[128]
+ *   off 160 char cmdline[512]
+ * ======================================================================== */
+
+#define VM_PLANE_CFG_STRIDE     672
+
+#define VBS_CALL_INIT             0x0001
+#define VBS_CALL_SHUTDOWN         0x0002
+#define VBS_CALL_PROTECT_MEMORY   0x0100
+#define VBS_CALL_SEAL_KERNEL      0x0101
+#define VBS_CALL_VALIDATE_MODULE  0x0200
+#define VBS_CALL_SET_MODULE_PERMS 0x0201
+#define VBS_CALL_UNLOAD_MODULE    0x0202
+#define VBS_CALL_ADD_KEY          0x0300
+#define VBS_CALL_REVOKE_KEY       0x0301
+#define VBS_CALL_SEND_CERTS       0x0302
+#define VBS_CALL_KEXEC_VALIDATE   0x0400
+#define VBS_CALL_KEXEC_INVALIDATE 0x0401
+
+#define CA_OFF_CALL_ID    4
+#define CA_OFF_STATUS     8
+#define CA_OFF_ARG_SIZE   12
+#define CA_OFF_RESP_SIZE  16
+#define CA_OFF_BUFFER     20
+
+struct vm_plane_boot_ctx {
+    int vcpu_fd;
+    int vcpu_mmap_size;
+    unsigned int vcpu_idx;
+    uint64_t plane_id;
+    unsigned int *halted_count;
+    QemuMutex *mutex;
+    QemuCond *cond;
+    int result;
+    int log_fd;
+    QemuMutex wake_mutex;
+    QemuCond wake_cond;
+    bool halted;
+    bool kick;
+    bool stopped;
+};
+
+static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
+{
+    uint64_t gpa = run->hypercall.args[0];
+    uint64_t plane_count = run->hypercall.args[1];
+    uint64_t plane_id;
+    unsigned int plane0_vcpu_count;
+    unsigned int *plane0_vcpu_ids;
+    CPUState *cs;
+    KVMState *s = kvm_state;
+
+    if (!gpa || !plane_count) {
+        run->hypercall.ret = -EINVAL;
+        return 0;
+    }
+
+    if (!s->vm_planes_max) {
+        int max = kvm_vm_ioctl(s, KVM_CHECK_EXTENSION, KVM_CAP_PLANES);
+        if (max <= 0) {
+            error_report("vm_planes: KVM does not support planes");
+            run->hypercall.ret = -ENOTSUP;
+            return 0;
+        }
+        s->vm_planes_max = max;
+    }
+
+    if (plane_count > s->vm_planes_max) {
+        error_report("vm_planes: requested %" PRIu64 " planes but KVM "
+                     "supports %u", plane_count, s->vm_planes_max);
+        run->hypercall.ret = -EINVAL;
+        return 0;
+    }
+
+    if (s->vm_planes) {
+        info_report("vm_planes: already configured, ignoring");
+        run->hypercall.ret = 0;
+        return 0;
+    }
+
+    s->vm_planes = g_new0(struct kvm_vm_plane_state, plane_count);
+    s->vm_plane_count = plane_count;
+
+    plane0_vcpu_count = 0;
+    CPU_FOREACH(cs) {
+        plane0_vcpu_count++;
+    }
+    if (!plane0_vcpu_count) {
+        error_report("vm_planes: no plane0 vCPUs available");
+        run->hypercall.ret = -EINVAL;
+        return 0;
+    }
+
+    plane0_vcpu_ids = g_new(unsigned int, plane0_vcpu_count);
+    plane0_vcpu_count = 0;
+    CPU_FOREACH(cs) {
+        plane0_vcpu_ids[plane0_vcpu_count++] = cs->cpu_index;
+    }
+
+    for (plane_id = 1; plane_id < plane_count; plane_id++) {
+        uint64_t plane_gpa = gpa + (plane_id * VM_PLANE_CFG_STRIDE);
+        uint64_t load_offset = 0, memory_size = 0, entry_point = 0;
+        uint32_t vcpu_count = 0;
+        struct kvm_vm_plane_state *ps = &s->vm_planes[plane_id];
+        char cmdline_buf[512];
+        MemoryRegionSection section;
+        int plane_fd;
+        unsigned int i;
+
+        cpu_physical_memory_read(plane_gpa + 0,  &load_offset, 8);
+        cpu_physical_memory_read(plane_gpa + 8,  &memory_size, 8);
+        cpu_physical_memory_read(plane_gpa + 16, &entry_point, 8);
+        cpu_physical_memory_read(plane_gpa + 24, &vcpu_count,  4);
+
+        if (!memory_size || !vcpu_count) {
+            error_report("vm_planes: plane %" PRIu64 " invalid "
+                         "(load_offset=0x%" PRIx64 " size=0x%" PRIx64
+                         " vcpus=%u)",
+                         plane_id, load_offset, memory_size, vcpu_count);
+            g_free(plane0_vcpu_ids);
+            run->hypercall.ret = -EINVAL;
+            return 0;
+        }
+
+        if (vcpu_count > plane0_vcpu_count) {
+            error_report("vm_planes: plane %" PRIu64 " requests %u vCPUs, "
+                         "but plane0 has %u", plane_id, vcpu_count,
+                         plane0_vcpu_count);
+            g_free(plane0_vcpu_ids);
+            run->hypercall.ret = -EINVAL;
+            return 0;
+        }
+
+        memset(cmdline_buf, 0, sizeof(cmdline_buf));
+        cpu_physical_memory_read(plane_gpa + 160, cmdline_buf,
+                                 sizeof(cmdline_buf));
+        cmdline_buf[sizeof(cmdline_buf) - 1] = '\0';
+        memcpy(ps->cmdline, cmdline_buf, sizeof(ps->cmdline));
+
+        plane_fd = kvm_vm_ioctl(s, KVM_CREATE_PLANE, (int)plane_id);
+        if (plane_fd < 0) {
+            error_report("vm_planes: KVM_CREATE_PLANE plane %" PRIu64
+                         " failed: %s", plane_id, strerror(errno));
+            g_free(plane0_vcpu_ids);
+            run->hypercall.ret = -errno;
+            return 0;
+        }
+        /* The kvm_vm_plane_ioctl path looks up the plane fd via
+         * kvm_get_plane_fd / kvm_set_plane_fd, so register the fd. */
+        kvm_set_plane_fd(s, plane_id, plane_fd);
+
+        section = memory_region_find(get_system_memory(),
+                                     load_offset, memory_size);
+        if (!section.mr || !memory_region_is_ram(section.mr)) {
+            error_report("vm_planes: plane %" PRIu64 " GPA 0x%" PRIx64
+                         " size 0x%" PRIx64 " is not RAM",
+                         plane_id, load_offset, memory_size);
+            if (section.mr) {
+                memory_region_unref(section.mr);
+            }
+            close(plane_fd);
+            kvm_set_plane_fd(s, plane_id, -1);
+            g_free(plane0_vcpu_ids);
+            run->hypercall.ret = -ENOMEM;
+            return 0;
+        }
+        ps->host_addr = memory_region_get_ram_ptr(section.mr) +
+                        section.offset_within_region;
+        memory_region_unref(section.mr);
+
+        ps->vcpu_fds = g_new0(int, vcpu_count);
+        for (i = 0; i < vcpu_count; i++) {
+            int vcpu_fd;
+            unsigned int vcpu_id = plane0_vcpu_ids[i];
+
+            vcpu_fd = kvm_vm_plane_ioctl(s, plane_id, KVM_CREATE_VCPU,
+                                         (void *)(uintptr_t)vcpu_id);
+            if (vcpu_fd < 0) {
+                error_report("vm_planes: KVM_CREATE_VCPU plane %" PRIu64
+                             " vcpu %u failed: %s",
+                             plane_id, vcpu_id, strerror(errno));
+                close(plane_fd);
+                kvm_set_plane_fd(s, plane_id, -1);
+                g_free(plane0_vcpu_ids);
+                run->hypercall.ret = -errno;
+                return 0;
+            }
+
+            ps->vcpu_fds[i] = vcpu_fd;
+        }
+
+        ps->vcpu_count  = vcpu_count;
+        ps->load_offset = load_offset;
+        ps->memory_size = memory_size;
+        ps->entry_point = entry_point;
+
+        info_report("vm_planes: plane %" PRIu64 " ready — GPA 0x%" PRIx64
+                    " size 0x%" PRIx64 " entry 0x%" PRIx64 " vcpus %u",
+                    plane_id, load_offset, memory_size, entry_point,
+                    vcpu_count);
+    }
+
+    g_free(plane0_vcpu_ids);
+    run->hypercall.ret = 0;
+    return 0;
+}
+
+static int kvm_init_plane_vcpu(int vcpu_fd, uint64_t entry_addr,
+                               uint64_t stack_addr, uint64_t zero_page_gpa,
+                               uint64_t page_table_gpa, uint64_t gdt_gpa,
+                               bool is_bsp)
+{
+    struct kvm_regs regs = {};
+    struct kvm_sregs sregs = {};
+    struct kvm_mp_state mp = {
+        .mp_state = is_bsp ? KVM_MP_STATE_RUNNABLE
+                           : KVM_MP_STATE_INIT_RECEIVED,
+    };
+    int ret;
+
+    sregs.cs.base = 0; sregs.cs.limit = 0xffffffff; sregs.cs.selector = 0x10;
+    sregs.cs.type = 0xb; sregs.cs.present = 1; sregs.cs.dpl = 0;
+    sregs.cs.db = 0; sregs.cs.s = 1; sregs.cs.l = 1; sregs.cs.g = 1;
+
+    sregs.ds.base = 0; sregs.ds.limit = 0xffffffff; sregs.ds.selector = 0x18;
+    sregs.ds.type = 0x3; sregs.ds.present = 1; sregs.ds.dpl = 0;
+    sregs.ds.db = 1; sregs.ds.s = 1; sregs.ds.g = 1;
+    sregs.es = sregs.ds;
+    sregs.ss = sregs.ds;
+    sregs.fs = sregs.ds; sregs.fs.selector = 0;
+    sregs.gs = sregs.fs;
+
+    sregs.gdt.base = gdt_gpa; sregs.gdt.limit = 0x2f;
+    sregs.idt.base = 0;       sregs.idt.limit = 0xffff;
+    sregs.tr.base = 0; sregs.tr.limit = 0x67; sregs.tr.selector = 0x28;
+    sregs.tr.type = 0xb; sregs.tr.present = 1; sregs.tr.dpl = 0; sregs.tr.s = 0;
+    sregs.ldt.unusable = 1;
+
+    sregs.cr3 = page_table_gpa;
+    sregs.cr4 = (1u << 5);  /* PAE */
+    sregs.cr0 = (1u << 0) | (1u << 4) | (1u << 5) | (1u << 16) | (1u << 31);
+    sregs.efer = (1u << 0) | (1u << 8) | (1u << 10) | (1u << 11);
+    sregs.apic_base = 0xfee00000 | (1u << 11);
+    if (is_bsp) {
+        sregs.apic_base |= (1u << 8);
+    }
+
+    ret = ioctl(vcpu_fd, KVM_SET_SREGS, &sregs);
+    if (ret < 0) {
+        error_report("vm_planes: KVM_SET_SREGS: %s", strerror(errno));
+        return -errno;
+    }
+
+    regs.rip = entry_addr;
+    regs.rsp = stack_addr;
+    regs.rsi = zero_page_gpa;
+    regs.rflags = 0x2;
+    ret = ioctl(vcpu_fd, KVM_SET_REGS, &regs);
+    if (ret < 0) {
+        error_report("vm_planes: KVM_SET_REGS: %s", strerror(errno));
+        return -errno;
+    }
+
+    ret = ioctl(vcpu_fd, KVM_SET_MP_STATE, &mp);
+    if (ret < 0) {
+        error_report("vm_planes: KVM_SET_MP_STATE: %s", strerror(errno));
+        return -errno;
+    }
+    return 0;
+}
+
+static void *vm_plane_vcpu_thread(void *arg)
+{
+    struct vm_plane_boot_ctx *ctx = arg;
+    struct kvm_run *kvm_run;
+    int ret;
+    bool boot_signaled = false;
+
+    kvm_run = mmap(NULL, ctx->vcpu_mmap_size, PROT_READ | PROT_WRITE,
+                   MAP_SHARED, ctx->vcpu_fd, 0);
+    if (kvm_run == MAP_FAILED) {
+        error_report("vm_planes: plane %" PRIu64 " vcpu %u: mmap failed: %s",
+                     ctx->plane_id, ctx->vcpu_idx, strerror(errno));
+        ctx->result = -errno;
+        qemu_mutex_lock(ctx->mutex);
+        (*ctx->halted_count)++;
+        qemu_cond_signal(ctx->cond);
+        qemu_mutex_unlock(ctx->mutex);
+        return NULL;
+    }
+
+    for (;;) {
+        ret = ioctl(ctx->vcpu_fd, KVM_RUN, 0);
+        if (ret < 0) {
+            if (errno == EINTR || errno == EAGAIN) {
+                continue;
+            }
+            error_report("vm_planes: plane %" PRIu64 " vcpu %u: KVM_RUN: %s",
+                         ctx->plane_id, ctx->vcpu_idx, strerror(errno));
+            ctx->result = -errno;
+            break;
+        }
+
+        switch (kvm_run->exit_reason) {
+        case KVM_EXIT_HLT:
+            if (!boot_signaled) {
+                boot_signaled = true;
+                qemu_mutex_lock(ctx->mutex);
+                (*ctx->halted_count)++;
+                qemu_cond_signal(ctx->cond);
+                qemu_mutex_unlock(ctx->mutex);
+            }
+            qemu_mutex_lock(&ctx->wake_mutex);
+            ctx->halted = true;
+            while (!ctx->kick && !ctx->stopped) {
+                qemu_cond_wait(&ctx->wake_cond, &ctx->wake_mutex);
+            }
+            ctx->halted = false;
+            ctx->kick = false;
+            if (ctx->stopped) {
+                qemu_mutex_unlock(&ctx->wake_mutex);
+                goto done;
+            }
+            qemu_mutex_unlock(&ctx->wake_mutex);
+            break;
+
+        case KVM_EXIT_IO: {
+            uint8_t *io_data = (uint8_t *)kvm_run + kvm_run->io.data_offset;
+            size_t io_size = kvm_run->io.size * kvm_run->io.count;
+            uint16_t port = kvm_run->io.port;
+
+            if (kvm_run->io.direction == KVM_EXIT_IO_OUT) {
+                if (port == 0x3f8 && ctx->log_fd >= 0) {
+                    ssize_t w = write(ctx->log_fd, io_data, io_size);
+                    (void)w;
+                }
+            } else {
+                memset(io_data, 0, io_size);
+                switch (port) {
+                case 0x3fa: memset(io_data, 0xc1, io_size); break;
+                case 0x3fb: memset(io_data, 0x03, io_size); break;
+                case 0x3fc: memset(io_data, 0x08, io_size); break;
+                case 0x3fd: memset(io_data, 0x60, io_size); break;
+                case 0x3fe: memset(io_data, 0xb0, io_size); break;
+                default: break;
+                }
+            }
+            usleep(100);
+            break;
+        }
+
+        case KVM_EXIT_MMIO:
+            usleep(100);
+            break;
+
+        case KVM_EXIT_SHUTDOWN: {
+            struct kvm_regs dbg = {};
+            ioctl(ctx->vcpu_fd, KVM_GET_REGS, &dbg);
+            error_report("vm_planes: plane %" PRIu64 " vcpu %u: shutdown "
+                         "RIP=0x%" PRIx64 " RSP=0x%" PRIx64,
+                         ctx->plane_id, ctx->vcpu_idx,
+                         (uint64_t)dbg.rip, (uint64_t)dbg.rsp);
+            ctx->result = -EFAULT;
+            goto done;
+        }
+
+        case KVM_EXIT_FAIL_ENTRY:
+            error_report("vm_planes: plane %" PRIu64 " vcpu %u: entry failure "
+                         "0x%" PRIx64, ctx->plane_id, ctx->vcpu_idx,
+                         (uint64_t)kvm_run->fail_entry.hardware_entry_failure_reason);
+            ctx->result = -EFAULT;
+            goto done;
+
+        case KVM_EXIT_INTERNAL_ERROR:
+            error_report("vm_planes: plane %" PRIu64 " vcpu %u: internal "
+                         "error %u", ctx->plane_id, ctx->vcpu_idx,
+                         kvm_run->internal.suberror);
+            ctx->result = -EFAULT;
+            goto done;
+
+        default:
+            error_report("vm_planes: plane %" PRIu64 " vcpu %u: unexpected "
+                         "exit %u", ctx->plane_id, ctx->vcpu_idx,
+                         kvm_run->exit_reason);
+            ctx->result = -EFAULT;
+            goto done;
+        }
+    }
+
+done:
+    if (ctx->log_fd >= 0 && ctx->vcpu_idx == 0) {
+        close(ctx->log_fd);
+    }
+    munmap(kvm_run, ctx->vcpu_mmap_size);
+    qemu_mutex_lock(ctx->mutex);
+    if (!boot_signaled) {
+        (*ctx->halted_count)++;
+        qemu_cond_signal(ctx->cond);
+    }
+    qemu_mutex_unlock(ctx->mutex);
+    return NULL;
+}
+
+static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
+{
+    uint64_t gpa = run->hypercall.args[0];
+    uint64_t plane_count = run->hypercall.args[1];
+    uint64_t plane_id;
+    KVMState *s = kvm_state;
+    int vcpu_mmap_size;
+
+    if (!gpa || !plane_count || !s->vm_planes ||
+        plane_count != s->vm_plane_count) {
+        run->hypercall.ret = -EINVAL;
+        return 0;
+    }
+
+    vcpu_mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0);
+    if (vcpu_mmap_size <= 0) {
+        error_report("vm_planes: KVM_GET_VCPU_MMAP_SIZE failed");
+        run->hypercall.ret = -EINVAL;
+        return 0;
+    }
+
+    for (plane_id = 1; plane_id < plane_count; plane_id++) {
+        struct kvm_vm_plane_state *ps = &s->vm_planes[plane_id];
+        uint64_t stack_addr;
+        uint64_t entry_point = 0;
+        uint64_t cmdline_gpa, zero_page_gpa;
+        uint64_t pt_base, pml4_gpa, pdpt_gpa, pd_base, gdt_gpa_val;
+        struct vm_plane_boot_ctx *ctxs;
+        QemuThread *threads;
+        QemuMutex mutex;
+        QemuCond cond;
+        unsigned int halted_count = 0;
+        unsigned int i;
+        int plane_log_fd = -1;
+
+        if (kvm_get_plane_fd(s, plane_id) < 0 || !ps->vcpu_count ||
+            !ps->host_addr) {
+            error_report("vm_planes: plane %" PRIu64 " not configured",
+                         plane_id);
+            run->hypercall.ret = -EINVAL;
+            return 0;
+        }
+
+        cpu_physical_memory_read(gpa + (plane_id * VM_PLANE_CFG_STRIDE) + 16,
+                                 &entry_point, 8);
+        if (!entry_point) {
+            error_report("vm_planes: plane %" PRIu64 " bad entry_point",
+                         plane_id);
+            run->hypercall.ret = -EIO;
+            return 0;
+        }
+        ps->entry_point = entry_point;
+
+        stack_addr     = ps->load_offset + ps->memory_size;
+        cmdline_gpa    = stack_addr - 0x1000;
+        zero_page_gpa  = stack_addr - 0x2000;
+        pt_base        = ps->load_offset + ps->memory_size - 0x10000;
+        pml4_gpa       = pt_base;
+        pdpt_gpa       = pt_base + 0x1000;
+        pd_base        = pt_base + 0x2000;
+        gdt_gpa_val    = pt_base + 0x6000;
+
+#define PLANE_HOST(g) ((uint8_t *)ps->host_addr + ((g) - ps->load_offset))
+
+        /* cmdline */
+        {
+            size_t cl = strlen(ps->cmdline) + 1;
+            memcpy(PLANE_HOST(cmdline_gpa), ps->cmdline, cl);
+        }
+
+        /* boot_params zero page */
+        {
+            uint8_t zp[4096] = {};
+            uint32_t cl_ptr = (uint32_t)(cmdline_gpa & 0xffffffff);
+            uint32_t cl_hi  = (uint32_t)(cmdline_gpa >> 32);
+            struct {
+                uint64_t addr;
+                uint64_t size;
+                uint32_t type;
+            } QEMU_PACKED e820 = {
+                ps->load_offset, ps->memory_size, 1,
+            };
+
+            zp[0x1fe] = 0x55; zp[0x1ff] = 0xAA;
+            zp[0x202] = 'H'; zp[0x203] = 'd';
+            zp[0x204] = 'r'; zp[0x205] = 'S';
+            zp[0x206] = 0x0f; zp[0x207] = 0x02;
+            zp[0x210] = 0xff;
+            memcpy(&zp[0x228], &cl_ptr, 4);
+            memcpy(&zp[0x0c8], &cl_hi, 4);
+            zp[0x1e8] = 1;
+            memcpy(&zp[0x2d0], &e820, 20);
+            memcpy(PLANE_HOST(zero_page_gpa), zp, sizeof(zp));
+        }
+
+        /* Identity-mapped page tables (PML4 → PDPT → 4×PD with 2MB pages) */
+        {
+            uint8_t page[4096];
+            uint64_t *entries;
+            int pd_idx;
+
+            memset(page, 0, sizeof(page));
+            entries = (uint64_t *)page;
+            entries[0] = pdpt_gpa | 0x3;
+            memcpy(PLANE_HOST(pml4_gpa), page, 4096);
+
+            memset(page, 0, sizeof(page));
+            entries = (uint64_t *)page;
+            for (pd_idx = 0; pd_idx < 4; pd_idx++) {
+                entries[pd_idx] = (pd_base + pd_idx * 0x1000) | 0x3;
+            }
+            memcpy(PLANE_HOST(pdpt_gpa), page, 4096);
+
+            for (pd_idx = 0; pd_idx < 4; pd_idx++) {
+                int j;
+                memset(page, 0, sizeof(page));
+                entries = (uint64_t *)page;
+                for (j = 0; j < 512; j++) {
+                    uint64_t phys = ((uint64_t)pd_idx << 30) |
+                                    ((uint64_t)j << 21);
+                    entries[j] = phys | 0x83;
+                }
+                memcpy(PLANE_HOST(pd_base + pd_idx * 0x1000), page, 4096);
+            }
+        }
+
+        /* Minimal GDT */
+        {
+            uint8_t gdt[48] = {};
+            uint64_t *gdt64 = (uint64_t *)gdt;
+
+            gdt64[0] = 0;
+            gdt64[1] = 0;
+            gdt64[2] = 0x00af9a000000ffffULL;
+            gdt64[3] = 0x00cf92000000ffffULL;
+            gdt64[4] = 0;
+            gdt64[5] = 0x0000890000000067ULL;
+            memcpy(PLANE_HOST(gdt_gpa_val), gdt, sizeof(gdt));
+        }
+
+        /* Initialize all plane vCPUs */
+        for (i = 0; i < ps->vcpu_count; i++) {
+            int ret = kvm_init_plane_vcpu(ps->vcpu_fds[i], entry_point,
+                                          stack_addr, zero_page_gpa, pml4_gpa,
+                                          gdt_gpa_val, i == 0);
+            if (ret) {
+                error_report("vm_planes: init plane %" PRIu64 " vcpu %u "
+                             "failed", plane_id, i);
+                run->hypercall.ret = ret;
+                return 0;
+            }
+        }
+#undef PLANE_HOST
+
+        /* Serial log */
+        {
+            char lp[256];
+            snprintf(lp, sizeof(lp), "/tmp/plane%" PRIu64 "-serial.log",
+                     plane_id);
+            plane_log_fd = open(lp, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+        }
+
+        /* Spawn vCPU threads */
+        qemu_mutex_init(&mutex);
+        qemu_cond_init(&cond);
+
+        ctxs = g_new0(struct vm_plane_boot_ctx, ps->vcpu_count);
+        threads = g_new0(QemuThread, ps->vcpu_count);
+
+        for (i = 0; i < ps->vcpu_count; i++) {
+            char name[48];
+
+            ctxs[i].vcpu_fd        = ps->vcpu_fds[i];
+            ctxs[i].vcpu_mmap_size = vcpu_mmap_size;
+            ctxs[i].vcpu_idx       = i;
+            ctxs[i].plane_id       = plane_id;
+            ctxs[i].halted_count   = &halted_count;
+            ctxs[i].mutex          = &mutex;
+            ctxs[i].cond           = &cond;
+            ctxs[i].result         = 0;
+            ctxs[i].log_fd         = (i == 0) ? plane_log_fd : -1;
+            qemu_mutex_init(&ctxs[i].wake_mutex);
+            qemu_cond_init(&ctxs[i].wake_cond);
+            ctxs[i].halted  = false;
+            ctxs[i].kick    = false;
+            ctxs[i].stopped = false;
+
+            snprintf(name, sizeof(name), "plane%" PRIu64 "-vcpu%u",
+                     plane_id, i);
+            qemu_thread_create(&threads[i], name, vm_plane_vcpu_thread,
+                               &ctxs[i], QEMU_THREAD_JOINABLE);
+        }
+
+        /* Wait up to 5s for plane to reach first HLT */
+        {
+            int64_t dl = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
+                         5LL * 1000000000LL;
+            qemu_mutex_lock(&mutex);
+            while (halted_count < ps->vcpu_count) {
+                int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
+                if (now >= dl) {
+                    info_report("vm_planes: plane %" PRIu64 " boot timeout "
+                                "(%u/%u halted)", plane_id, halted_count,
+                                ps->vcpu_count);
+                    break;
+                }
+                qemu_cond_timedwait(&cond, &mutex, 1000);
+            }
+            qemu_mutex_unlock(&mutex);
+        }
+
+        /* Note: threads keep running for the plane's lifetime; we
+         * intentionally leak ctxs/threads — they outlive this call. */
+
+        /* Seal plane memory via KVM_SET_MEMORY_ATTRIBUTES(NO_WRITE|NO_EXEC) */
+        {
+            struct kvm_memory_attributes ma = {
+                .address    = ps->load_offset,
+                .size       = ps->memory_size,
+                .attributes = KVM_MEMORY_ATTRIBUTE_NO_WRITE |
+                              KVM_MEMORY_ATTRIBUTE_NO_EXEC,
+                .flags      = 0,
+            };
+            int pr = kvm_vm_ioctl(s, KVM_SET_MEMORY_ATTRIBUTES, &ma);
+            if (pr < 0) {
+                warn_report("vm_planes: plane %" PRIu64 " seal failed (%d)",
+                            plane_id, pr);
+            } else {
+                info_report("vm_planes: plane %" PRIu64 " memory sealed",
+                            plane_id);
+            }
+        }
+        ps->host_addr = NULL;
+
+        info_report("vm_planes: plane %" PRIu64 " launched — entry 0x%" PRIx64
+                    " vcpus %u", plane_id, entry_point, ps->vcpu_count);
+    }
+
+    run->hypercall.ret = 0;
+    return 0;
+}
+
+static int vbs_apply_protection(KVMState *s, uint64_t gpa, uint64_t size,
+                                uint32_t perms)
+{
+    uint64_t attrs = 0;
+    struct kvm_memory_attributes ma;
+
+    if (!(perms & 2)) {
+        attrs |= KVM_MEMORY_ATTRIBUTE_NO_WRITE;
+    }
+    if (!(perms & 4)) {
+        attrs |= KVM_MEMORY_ATTRIBUTE_NO_EXEC;
+    }
+    if (!attrs) {
+        return 0;
+    }
+
+    ma.address    = gpa;
+    ma.size       = size;
+    ma.attributes = attrs;
+    ma.flags      = 0;
+    return kvm_vm_ioctl(s, KVM_SET_MEMORY_ATTRIBUTES, &ma);
+}
+
+static int32_t vbs_handle_protect_memory(KVMState *s, uint64_t ca_gpa)
+{
+    uint64_t gpa, size;
+    uint32_t perms, arg_size;
+
+    cpu_physical_memory_read(ca_gpa + CA_OFF_ARG_SIZE, &arg_size, 4);
+    if (arg_size < 24) {
+        return -22;
+    }
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 0,  &gpa,   8);
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 8,  &size,  8);
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 16, &perms, 4);
+    return vbs_apply_protection(s, gpa, size, perms);
+}
+
+static int32_t vbs_handle_seal_kernel(KVMState *s, uint64_t ca_gpa)
+{
+    uint64_t text_gpa, text_size, rodata_gpa, rodata_size;
+    uint32_t arg_size;
+    int ret;
+
+    cpu_physical_memory_read(ca_gpa + CA_OFF_ARG_SIZE, &arg_size, 4);
+    if (arg_size < 40) {
+        return -22;
+    }
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 0,  &text_gpa,    8);
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 8,  &text_size,   8);
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 16, &rodata_gpa,  8);
+    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 24, &rodata_size, 8);
+
+    ret = vbs_apply_protection(s, text_gpa, text_size, 1 | 4);
+    if (ret < 0) {
+        return ret;
+    }
+    return vbs_apply_protection(s, rodata_gpa, rodata_size, 1);
+}
+
+static int kvm_handle_hc_vbs_vtl_call(X86CPU *cpu, struct kvm_run *run)
+{
+    uint64_t ca_gpa = run->hypercall.args[0];
+    uint32_t call_id;
+    int32_t status;
+    KVMState *s = kvm_state;
+
+    cpu_physical_memory_read(ca_gpa + CA_OFF_CALL_ID, &call_id, 4);
+
+    switch (call_id) {
+    case VBS_CALL_INIT:
+    case VBS_CALL_SHUTDOWN:
+        status = 0;
+        break;
+    case VBS_CALL_PROTECT_MEMORY:
+        status = vbs_handle_protect_memory(s, ca_gpa);
+        break;
+    case VBS_CALL_SEAL_KERNEL:
+        status = vbs_handle_seal_kernel(s, ca_gpa);
+        break;
+    case VBS_CALL_VALIDATE_MODULE:
+    case VBS_CALL_SET_MODULE_PERMS:
+    case VBS_CALL_UNLOAD_MODULE:
+    case VBS_CALL_ADD_KEY:
+    case VBS_CALL_REVOKE_KEY:
+    case VBS_CALL_SEND_CERTS:
+    case VBS_CALL_KEXEC_VALIDATE:
+    case VBS_CALL_KEXEC_INVALIDATE:
+        status = 0;  /* acknowledge */
+        break;
+    default:
+        warn_report("vbs_vtl_call: unknown call_id 0x%04x", call_id);
+        status = -38;
+        break;
+    }
+
+    cpu_physical_memory_write(ca_gpa + CA_OFF_STATUS, &status, 4);
+    run->hypercall.ret = 0;
+    return 0;
+}
+
 static int kvm_handle_hypercall(X86CPU *cpu, struct kvm_run *run)
 {
     if (run->hypercall.nr == KVM_HC_MAP_GPA_RANGE)
         return kvm_handle_hc_map_gpa_range(cpu, run);
+    if (run->hypercall.nr == KVM_HC_VM_PLANES_CONFIG)
+        return kvm_handle_hc_vm_planes_config(cpu, run);
+    if (run->hypercall.nr == KVM_HC_VM_PLANES_ACTIVATE)
+        return kvm_handle_hc_vm_planes_activate(cpu, run);
+    if (run->hypercall.nr == KVM_HC_VBS_VTL_CALL)
+        return kvm_handle_hc_vbs_vtl_call(cpu, run);
 
     return -EINVAL;
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v1 2/5] vm_planes: Add VBS VTL call handling and plane memory sealing
  2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls Sriram Nambakam
@ 2026-08-05 11:04 ` Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 3/5] linux-headers: sync kvm_para.h VBS VTL hypercalls Sriram Nambakam
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

Wire up the VBS inter-plane communication and memory protection
infrastructure on the QEMU side.

Memory sealing (plane activate path):
  After plane-1 boots and its vCPU threads are running, seal
  plane-1's memory from plane-0 access:
  1. Issue KVM_SET_PLANE_MEMORY_ATTRIBUTES to set NO_WRITE|NO_EXEC
     on plane-0's EPT for the GPA range occupied by plane-1.
  2. Clear ps->host_addr so QEMU userspace can no longer access
     the region.

VBS VTL hypercall handler:
  Handle KVM_HC_VBS_VTL_CALL (hypercall 15) exits from the guest.
  The plane-0 VBS subsystem passes the GPA of a shared calling-area
  (CAA) page; QEMU reads the call_id from the page, logs it, and
  returns -ENOSYS for now (plane-1 responder not yet implemented).
  Enable the hypercall in kvm_enable_hypercall() alongside the
  existing VM planes hypercalls.

Header sync:
  - linux-headers/linux/kvm.h: Add KVM_MEMORY_ATTRIBUTE_NO_WRITE,
    KVM_MEMORY_ATTRIBUTE_NO_EXEC, struct kvm_plane_memory_attributes,
    and KVM_SET_PLANE_MEMORY_ATTRIBUTES ioctl definition.
  - include/standard-headers/linux/kvm_para.h: Add KVM_HC_VBS_VTL_CALL.

(cherry picked from commit 414016c40ec8226b0f5d4cbd5eed0589b7c1689e)
---
 linux-headers/linux/kvm.h | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index d66ce6d272..9dbd3824e9 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -1653,6 +1653,19 @@ struct kvm_memory_attributes {
 };
 
 #define KVM_MEMORY_ATTRIBUTE_PRIVATE           (1ULL << 3)
+#define KVM_MEMORY_ATTRIBUTE_NO_WRITE          (1ULL << 4)
+#define KVM_MEMORY_ATTRIBUTE_NO_EXEC           (1ULL << 5)
+
+/* Set memory attributes on a specific plane's address space. */
+struct kvm_plane_memory_attributes {
+	__u32 plane;
+	__u32 flags;
+	__u64 address;
+	__u64 size;
+	__u64 attributes;
+};
+
+#define KVM_SET_PLANE_MEMORY_ATTRIBUTES	_IOW(KVMIO, 0xd6, struct kvm_plane_memory_attributes)
 
 #define KVM_MEMORY_ATTRIBUTE_NO_WRITE          (1ULL << 4)
 #define KVM_MEMORY_ATTRIBUTE_NO_EXEC           (1ULL << 5)
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v1 3/5] linux-headers: sync kvm_para.h VBS VTL hypercalls
  2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 2/5] vm_planes: Add VBS VTL call handling and plane memory sealing Sriram Nambakam
@ 2026-08-05 11:04 ` Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 4/5] target/i386/kvm: run the secure plane in-kernel (Option B) Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 5/5] target/i386/kvm: read plane config via address_space API Sriram Nambakam
  4 siblings, 0 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

Import the two new VBS secure-plane hypercall numbers from the kernel
UAPI: KVM_HC_VBS_VTL_RETURN (16) and KVM_HC_VBS_SET_MEM_ATTRS (17).

Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
(cherry picked from commit 92f71c68c2eb42588694ac23fb4a6a8ef8dfa12b)
---
 include/standard-headers/linux/kvm_para.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/standard-headers/linux/kvm_para.h b/include/standard-headers/linux/kvm_para.h
index ad19aac9d0..def451c43c 100644
--- a/include/standard-headers/linux/kvm_para.h
+++ b/include/standard-headers/linux/kvm_para.h
@@ -33,6 +33,8 @@
 #define KVM_HC_VM_PLANES_CONFIG		13
 #define KVM_HC_VM_PLANES_ACTIVATE	14
 #define KVM_HC_VBS_VTL_CALL		15
+#define KVM_HC_VBS_VTL_RETURN		16
+#define KVM_HC_VBS_SET_MEM_ATTRS	17
 
 /*
  * hypercalls use architecture specific
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v1 4/5] target/i386/kvm: run the secure plane in-kernel (Option B)
  2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
                   ` (2 preceding siblings ...)
  2026-08-05 11:04 ` [RFC PATCH v1 3/5] linux-headers: sync kvm_para.h VBS VTL hypercalls Sriram Nambakam
@ 2026-08-05 11:04 ` Sriram Nambakam
  2026-08-05 11:04 ` [RFC PATCH v1 5/5] target/i386/kvm: read plane config via address_space API Sriram Nambakam
  4 siblings, 0 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

Move secure-plane execution into the kernel and out of QEMU. Plane vCPUs
are now created and initialised on their owning plane-0 CPU thread via
run_on_cpu() (plane_vcpu_create_cb / plane_vcpu_init_cb), and the
activate path leaves the secure vCPU stopped: it boots lazily inside KVM
on the first KVM_HC_VBS_VTL_CALL, which KVM now services itself.

Drop the userspace secure-world emulation that this replaces: the
dedicated vm_plane_vcpu_thread (with its 8250 emulation and per-plane
serial capture), vbs_apply_protection, vbs_handle_protect_memory,
vbs_handle_seal_kernel and the kvm_handle_hc_vbs_vtl_call dispatch.
Track each plane vCPU's owning CPU index (vcpu_cpu_index) so the
run_on_cpu callbacks target the right thread.

Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
(cherry picked from commit cbecc9eda1514d200fefe2bb89f330be4f1ab1be)
---
 include/system/kvm_int.h |   1 +
 target/i386/kvm/kvm.c    | 536 ++++++++++++---------------------------
 2 files changed, 157 insertions(+), 380 deletions(-)

diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h
index e7c9dc95a2..cde0a148e1 100644
--- a/include/system/kvm_int.h
+++ b/include/system/kvm_int.h
@@ -114,6 +114,7 @@ struct KVMPlane {
  * kvm_get_plane_fd(s, plane_id); only LVBS-specific state lives here. */
 struct kvm_vm_plane_state {
     int *vcpu_fds;
+    unsigned int *vcpu_cpu_index;  /* owning plane-0 CPU index per plane vCPU */
     unsigned int vcpu_count;
     uint64_t load_offset;
     uint64_t memory_size;
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 9d89e5cac6..5ac7962e61 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -6541,10 +6541,9 @@ static int kvm_handle_hc_map_gpa_range(X86CPU *cpu, struct kvm_run *run)
  *   off  0  u64  load_offset
  *   off  8  u64  memory_size
  *   off 16  u64  entry_point
- *   off 24  u32  vcpu_count
- *   off 28  u32  kernel_format
- *   off 32  char kernel[128]
- *   off 160 char cmdline[512]
+ *   off 24  u32  kernel_format
+ *   off 28  char kernel[128]
+ *   off 156 char cmdline[512]
  * ======================================================================== */
 
 #define VM_PLANE_CFG_STRIDE     672
@@ -6568,23 +6567,37 @@ static int kvm_handle_hc_map_gpa_range(X86CPU *cpu, struct kvm_run *run)
 #define CA_OFF_RESP_SIZE  16
 #define CA_OFF_BUFFER     20
 
-struct vm_plane_boot_ctx {
-    int vcpu_fd;
-    int vcpu_mmap_size;
-    unsigned int vcpu_idx;
-    uint64_t plane_id;
-    unsigned int *halted_count;
-    QemuMutex *mutex;
-    QemuCond *cond;
-    int result;
-    int log_fd;
-    QemuMutex wake_mutex;
-    QemuCond wake_cond;
-    bool halted;
-    bool kick;
-    bool stopped;
+/*
+ * Plane>0 vCPU creation and initialization must run on the OWNING plane-0
+ * vCPU's thread.  A plane>0 vCPU shares its plane-0 sibling's
+ * struct kvm_vcpu_common, including the single embedded preempt_notifier.
+ * KVM_CREATE_VCPU and KVM_SET_{S,}REGS / KVM_SET_MP_STATE all vcpu_load()
+ * the plane vCPU, which registers that shared notifier on the *calling*
+ * task.  If issued from the config/activate-hypercall vCPU's thread while a
+ * sibling is concurrently in KVM_RUN on another host CPU, the same
+ * hlist_node would be linked onto two tasks' preempt-notifier lists ->
+ * list corruption -> host hard lockup.  run_on_cpu() forces the sibling out
+ * of KVM_RUN (KVM_RUN returns, vcpu_put() unregisters the notifier) and
+ * runs the work on that sibling's own thread, so the notifier is owned by
+ * exactly one task.  For the current vCPU, run_on_cpu() runs inline.
+ */
+struct plane_vcpu_create_ctx {
+    KVMState *s;
+    unsigned int plane_id;
+    unsigned int vcpu_id;
+    int fd;                 /* out: vcpu fd, or -errno on failure */
 };
 
+static void plane_vcpu_create_cb(CPUState *cs, run_on_cpu_data data)
+{
+    struct plane_vcpu_create_ctx *ctx = data.host_ptr;
+    int fd;
+
+    fd = kvm_vm_plane_ioctl(ctx->s, ctx->plane_id, KVM_CREATE_VCPU,
+                            (void *)(uintptr_t)ctx->vcpu_id);
+    ctx->fd = (fd < 0) ? -errno : fd;
+}
+
 static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
 {
     uint64_t gpa = run->hypercall.args[0];
@@ -6645,7 +6658,7 @@ static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
     for (plane_id = 1; plane_id < plane_count; plane_id++) {
         uint64_t plane_gpa = gpa + (plane_id * VM_PLANE_CFG_STRIDE);
         uint64_t load_offset = 0, memory_size = 0, entry_point = 0;
-        uint32_t vcpu_count = 0;
+        uint32_t vcpu_count = plane0_vcpu_count;
         struct kvm_vm_plane_state *ps = &s->vm_planes[plane_id];
         char cmdline_buf[512];
         MemoryRegionSection section;
@@ -6655,29 +6668,24 @@ static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
         cpu_physical_memory_read(plane_gpa + 0,  &load_offset, 8);
         cpu_physical_memory_read(plane_gpa + 8,  &memory_size, 8);
         cpu_physical_memory_read(plane_gpa + 16, &entry_point, 8);
-        cpu_physical_memory_read(plane_gpa + 24, &vcpu_count,  4);
 
-        if (!memory_size || !vcpu_count) {
+        /*
+         * joergroedel plane model: a plane has exactly one vCPU per
+         * plane-0 vCPU — each is the sibling of a plane-0 vCPU sharing the
+         * same logical CPU.  The guest does not configure a count; QEMU
+         * mirrors the plane-0 vCPU set.
+         */
+        if (!memory_size) {
             error_report("vm_planes: plane %" PRIu64 " invalid "
-                         "(load_offset=0x%" PRIx64 " size=0x%" PRIx64
-                         " vcpus=%u)",
-                         plane_id, load_offset, memory_size, vcpu_count);
-            g_free(plane0_vcpu_ids);
-            run->hypercall.ret = -EINVAL;
-            return 0;
-        }
-
-        if (vcpu_count > plane0_vcpu_count) {
-            error_report("vm_planes: plane %" PRIu64 " requests %u vCPUs, "
-                         "but plane0 has %u", plane_id, vcpu_count,
-                         plane0_vcpu_count);
+                         "(load_offset=0x%" PRIx64 " size=0x%" PRIx64 ")",
+                         plane_id, load_offset, memory_size);
             g_free(plane0_vcpu_ids);
             run->hypercall.ret = -EINVAL;
             return 0;
         }
 
         memset(cmdline_buf, 0, sizeof(cmdline_buf));
-        cpu_physical_memory_read(plane_gpa + 160, cmdline_buf,
+        cpu_physical_memory_read(plane_gpa + 156, cmdline_buf,
                                  sizeof(cmdline_buf));
         cmdline_buf[sizeof(cmdline_buf) - 1] = '\0';
         memcpy(ps->cmdline, cmdline_buf, sizeof(ps->cmdline));
@@ -6714,24 +6722,46 @@ static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
         memory_region_unref(section.mr);
 
         ps->vcpu_fds = g_new0(int, vcpu_count);
+        ps->vcpu_cpu_index = g_new0(unsigned int, vcpu_count);
         for (i = 0; i < vcpu_count; i++) {
-            int vcpu_fd;
             unsigned int vcpu_id = plane0_vcpu_ids[i];
+            CPUState *target = qemu_get_cpu(vcpu_id);
+            struct plane_vcpu_create_ctx cctx = {
+                .s = s,
+                .plane_id = plane_id,
+                .vcpu_id = vcpu_id,
+                .fd = -EINVAL,
+            };
+
+            if (!target) {
+                error_report("vm_planes: plane %" PRIu64 " no CPU for id=%u",
+                             plane_id, vcpu_id);
+                close(plane_fd);
+                kvm_set_plane_fd(s, plane_id, -1);
+                g_free(plane0_vcpu_ids);
+                run->hypercall.ret = -EINVAL;
+                return 0;
+            }
 
-            vcpu_fd = kvm_vm_plane_ioctl(s, plane_id, KVM_CREATE_VCPU,
-                                         (void *)(uintptr_t)vcpu_id);
-            if (vcpu_fd < 0) {
+            /* Create on the owning CPU's thread; see plane_vcpu_create_cb. */
+            bql_lock();
+            run_on_cpu(target, plane_vcpu_create_cb,
+                       RUN_ON_CPU_HOST_PTR(&cctx));
+            bql_unlock();
+
+            if (cctx.fd < 0) {
                 error_report("vm_planes: KVM_CREATE_VCPU plane %" PRIu64
                              " vcpu %u failed: %s",
-                             plane_id, vcpu_id, strerror(errno));
+                             plane_id, vcpu_id, strerror(-cctx.fd));
                 close(plane_fd);
                 kvm_set_plane_fd(s, plane_id, -1);
                 g_free(plane0_vcpu_ids);
-                run->hypercall.ret = -errno;
+                run->hypercall.ret = cctx.fd;
                 return 0;
             }
 
-            ps->vcpu_fds[i] = vcpu_fd;
+            ps->vcpu_fds[i] = cctx.fd;
+            ps->vcpu_cpu_index[i] = vcpu_id;
         }
 
         ps->vcpu_count  = vcpu_count;
@@ -6757,10 +6787,6 @@ static int kvm_init_plane_vcpu(int vcpu_fd, uint64_t entry_addr,
 {
     struct kvm_regs regs = {};
     struct kvm_sregs sregs = {};
-    struct kvm_mp_state mp = {
-        .mp_state = is_bsp ? KVM_MP_STATE_RUNNABLE
-                           : KVM_MP_STATE_INIT_RECEIVED,
-    };
     int ret;
 
     sregs.cs.base = 0; sregs.cs.limit = 0xffffffff; sregs.cs.selector = 0x10;
@@ -6806,144 +6832,42 @@ static int kvm_init_plane_vcpu(int vcpu_fd, uint64_t entry_addr,
         return -errno;
     }
 
-    ret = ioctl(vcpu_fd, KVM_SET_MP_STATE, &mp);
-    if (ret < 0) {
-        error_report("vm_planes: KVM_SET_MP_STATE: %s", strerror(errno));
-        return -errno;
-    }
+    /*
+     * Do NOT issue KVM_SET_MP_STATE on a plane (plane_level > 0) vCPU fd:
+     * the VM-planes kernel only whitelists a subset of vCPU ioctls for
+     * plane vCPUs (KVM_SET_*REGS/SREGS/FPU/LAPIC/...), and KVM_SET_MP_STATE
+     * is intentionally excluded -> it returns -EINVAL.  The kernel already
+     * establishes the correct initial MP state at create time: the plane
+     * BSP (vcpu_id == bsp_vcpu_id) is left RUNNABLE, and APs are left
+     * UNINITIALIZED (wait-for-INIT), which is the correct power-on state.
+     * The plane guest's own SMP bringup INIT/SIPIs its APs via the
+     * in-kernel LAPIC, so no userspace MP-state poke is needed.
+     */
     return 0;
 }
 
-static void *vm_plane_vcpu_thread(void *arg)
-{
-    struct vm_plane_boot_ctx *ctx = arg;
-    struct kvm_run *kvm_run;
-    int ret;
-    bool boot_signaled = false;
-
-    kvm_run = mmap(NULL, ctx->vcpu_mmap_size, PROT_READ | PROT_WRITE,
-                   MAP_SHARED, ctx->vcpu_fd, 0);
-    if (kvm_run == MAP_FAILED) {
-        error_report("vm_planes: plane %" PRIu64 " vcpu %u: mmap failed: %s",
-                     ctx->plane_id, ctx->vcpu_idx, strerror(errno));
-        ctx->result = -errno;
-        qemu_mutex_lock(ctx->mutex);
-        (*ctx->halted_count)++;
-        qemu_cond_signal(ctx->cond);
-        qemu_mutex_unlock(ctx->mutex);
-        return NULL;
-    }
-
-    for (;;) {
-        ret = ioctl(ctx->vcpu_fd, KVM_RUN, 0);
-        if (ret < 0) {
-            if (errno == EINTR || errno == EAGAIN) {
-                continue;
-            }
-            error_report("vm_planes: plane %" PRIu64 " vcpu %u: KVM_RUN: %s",
-                         ctx->plane_id, ctx->vcpu_idx, strerror(errno));
-            ctx->result = -errno;
-            break;
-        }
-
-        switch (kvm_run->exit_reason) {
-        case KVM_EXIT_HLT:
-            if (!boot_signaled) {
-                boot_signaled = true;
-                qemu_mutex_lock(ctx->mutex);
-                (*ctx->halted_count)++;
-                qemu_cond_signal(ctx->cond);
-                qemu_mutex_unlock(ctx->mutex);
-            }
-            qemu_mutex_lock(&ctx->wake_mutex);
-            ctx->halted = true;
-            while (!ctx->kick && !ctx->stopped) {
-                qemu_cond_wait(&ctx->wake_cond, &ctx->wake_mutex);
-            }
-            ctx->halted = false;
-            ctx->kick = false;
-            if (ctx->stopped) {
-                qemu_mutex_unlock(&ctx->wake_mutex);
-                goto done;
-            }
-            qemu_mutex_unlock(&ctx->wake_mutex);
-            break;
-
-        case KVM_EXIT_IO: {
-            uint8_t *io_data = (uint8_t *)kvm_run + kvm_run->io.data_offset;
-            size_t io_size = kvm_run->io.size * kvm_run->io.count;
-            uint16_t port = kvm_run->io.port;
-
-            if (kvm_run->io.direction == KVM_EXIT_IO_OUT) {
-                if (port == 0x3f8 && ctx->log_fd >= 0) {
-                    ssize_t w = write(ctx->log_fd, io_data, io_size);
-                    (void)w;
-                }
-            } else {
-                memset(io_data, 0, io_size);
-                switch (port) {
-                case 0x3fa: memset(io_data, 0xc1, io_size); break;
-                case 0x3fb: memset(io_data, 0x03, io_size); break;
-                case 0x3fc: memset(io_data, 0x08, io_size); break;
-                case 0x3fd: memset(io_data, 0x60, io_size); break;
-                case 0x3fe: memset(io_data, 0xb0, io_size); break;
-                default: break;
-                }
-            }
-            usleep(100);
-            break;
-        }
-
-        case KVM_EXIT_MMIO:
-            usleep(100);
-            break;
-
-        case KVM_EXIT_SHUTDOWN: {
-            struct kvm_regs dbg = {};
-            ioctl(ctx->vcpu_fd, KVM_GET_REGS, &dbg);
-            error_report("vm_planes: plane %" PRIu64 " vcpu %u: shutdown "
-                         "RIP=0x%" PRIx64 " RSP=0x%" PRIx64,
-                         ctx->plane_id, ctx->vcpu_idx,
-                         (uint64_t)dbg.rip, (uint64_t)dbg.rsp);
-            ctx->result = -EFAULT;
-            goto done;
-        }
-
-        case KVM_EXIT_FAIL_ENTRY:
-            error_report("vm_planes: plane %" PRIu64 " vcpu %u: entry failure "
-                         "0x%" PRIx64, ctx->plane_id, ctx->vcpu_idx,
-                         (uint64_t)kvm_run->fail_entry.hardware_entry_failure_reason);
-            ctx->result = -EFAULT;
-            goto done;
-
-        case KVM_EXIT_INTERNAL_ERROR:
-            error_report("vm_planes: plane %" PRIu64 " vcpu %u: internal "
-                         "error %u", ctx->plane_id, ctx->vcpu_idx,
-                         kvm_run->internal.suberror);
-            ctx->result = -EFAULT;
-            goto done;
+/* Initialize a plane vCPU on its owning CPU's thread (see
+ * plane_vcpu_create_cb for why KVM_SET_*REGS / KVM_SET_MP_STATE, which
+ * vcpu_load() the shared common, must not race the running sibling). */
+struct plane_vcpu_init_ctx {
+    int vcpu_fd;
+    uint64_t entry_addr;
+    uint64_t stack_addr;
+    uint64_t zero_page_gpa;
+    uint64_t page_table_gpa;
+    uint64_t gdt_gpa;
+    bool is_bsp;
+    int ret;                /* out: 0 on success, -errno on failure */
+};
 
-        default:
-            error_report("vm_planes: plane %" PRIu64 " vcpu %u: unexpected "
-                         "exit %u", ctx->plane_id, ctx->vcpu_idx,
-                         kvm_run->exit_reason);
-            ctx->result = -EFAULT;
-            goto done;
-        }
-    }
+static void plane_vcpu_init_cb(CPUState *cs, run_on_cpu_data data)
+{
+    struct plane_vcpu_init_ctx *ctx = data.host_ptr;
 
-done:
-    if (ctx->log_fd >= 0 && ctx->vcpu_idx == 0) {
-        close(ctx->log_fd);
-    }
-    munmap(kvm_run, ctx->vcpu_mmap_size);
-    qemu_mutex_lock(ctx->mutex);
-    if (!boot_signaled) {
-        (*ctx->halted_count)++;
-        qemu_cond_signal(ctx->cond);
-    }
-    qemu_mutex_unlock(ctx->mutex);
-    return NULL;
+    ctx->ret = kvm_init_plane_vcpu(ctx->vcpu_fd, ctx->entry_addr,
+                                   ctx->stack_addr, ctx->zero_page_gpa,
+                                   ctx->page_table_gpa, ctx->gdt_gpa,
+                                   ctx->is_bsp);
 }
 
 static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
@@ -6952,7 +6876,6 @@ static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
     uint64_t plane_count = run->hypercall.args[1];
     uint64_t plane_id;
     KVMState *s = kvm_state;
-    int vcpu_mmap_size;
 
     if (!gpa || !plane_count || !s->vm_planes ||
         plane_count != s->vm_plane_count) {
@@ -6960,26 +6883,13 @@ static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
         return 0;
     }
 
-    vcpu_mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0);
-    if (vcpu_mmap_size <= 0) {
-        error_report("vm_planes: KVM_GET_VCPU_MMAP_SIZE failed");
-        run->hypercall.ret = -EINVAL;
-        return 0;
-    }
-
     for (plane_id = 1; plane_id < plane_count; plane_id++) {
         struct kvm_vm_plane_state *ps = &s->vm_planes[plane_id];
         uint64_t stack_addr;
         uint64_t entry_point = 0;
         uint64_t cmdline_gpa, zero_page_gpa;
         uint64_t pt_base, pml4_gpa, pdpt_gpa, pd_base, gdt_gpa_val;
-        struct vm_plane_boot_ctx *ctxs;
-        QemuThread *threads;
-        QemuMutex mutex;
-        QemuCond cond;
-        unsigned int halted_count = 0;
         unsigned int i;
-        int plane_log_fd = -1;
 
         if (kvm_get_plane_fd(s, plane_id) < 0 || !ps->vcpu_count ||
             !ps->host_addr) {
@@ -7086,98 +6996,60 @@ static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
             memcpy(PLANE_HOST(gdt_gpa_val), gdt, sizeof(gdt));
         }
 
-        /* Initialize all plane vCPUs */
+        /* Initialize all plane vCPUs on their owning CPU threads. */
         for (i = 0; i < ps->vcpu_count; i++) {
-            int ret = kvm_init_plane_vcpu(ps->vcpu_fds[i], entry_point,
-                                          stack_addr, zero_page_gpa, pml4_gpa,
-                                          gdt_gpa_val, i == 0);
-            if (ret) {
-                error_report("vm_planes: init plane %" PRIu64 " vcpu %u "
-                             "failed", plane_id, i);
-                run->hypercall.ret = ret;
+            CPUState *target = qemu_get_cpu(ps->vcpu_cpu_index[i]);
+            struct plane_vcpu_init_ctx ictx = {
+                .vcpu_fd = ps->vcpu_fds[i],
+                .entry_addr = entry_point,
+                .stack_addr = stack_addr,
+                .zero_page_gpa = zero_page_gpa,
+                .page_table_gpa = pml4_gpa,
+                .gdt_gpa = gdt_gpa_val,
+                .is_bsp = (i == 0),
+                .ret = -EINVAL,
+            };
+
+            if (!target) {
+                error_report("vm_planes: plane %" PRIu64 " no CPU for vcpu %u",
+                             plane_id, i);
+                run->hypercall.ret = -EINVAL;
                 return 0;
             }
-        }
-#undef PLANE_HOST
-
-        /* Serial log */
-        {
-            char lp[256];
-            snprintf(lp, sizeof(lp), "/tmp/plane%" PRIu64 "-serial.log",
-                     plane_id);
-            plane_log_fd = open(lp, O_CREAT | O_WRONLY | O_TRUNC, 0644);
-        }
 
-        /* Spawn vCPU threads */
-        qemu_mutex_init(&mutex);
-        qemu_cond_init(&cond);
-
-        ctxs = g_new0(struct vm_plane_boot_ctx, ps->vcpu_count);
-        threads = g_new0(QemuThread, ps->vcpu_count);
+            /* See plane_vcpu_init_cb: must run on the sibling's own thread. */
+            bql_lock();
+            run_on_cpu(target, plane_vcpu_init_cb,
+                       RUN_ON_CPU_HOST_PTR(&ictx));
+            bql_unlock();
 
-        for (i = 0; i < ps->vcpu_count; i++) {
-            char name[48];
-
-            ctxs[i].vcpu_fd        = ps->vcpu_fds[i];
-            ctxs[i].vcpu_mmap_size = vcpu_mmap_size;
-            ctxs[i].vcpu_idx       = i;
-            ctxs[i].plane_id       = plane_id;
-            ctxs[i].halted_count   = &halted_count;
-            ctxs[i].mutex          = &mutex;
-            ctxs[i].cond           = &cond;
-            ctxs[i].result         = 0;
-            ctxs[i].log_fd         = (i == 0) ? plane_log_fd : -1;
-            qemu_mutex_init(&ctxs[i].wake_mutex);
-            qemu_cond_init(&ctxs[i].wake_cond);
-            ctxs[i].halted  = false;
-            ctxs[i].kick    = false;
-            ctxs[i].stopped = false;
-
-            snprintf(name, sizeof(name), "plane%" PRIu64 "-vcpu%u",
-                     plane_id, i);
-            qemu_thread_create(&threads[i], name, vm_plane_vcpu_thread,
-                               &ctxs[i], QEMU_THREAD_JOINABLE);
-        }
-
-        /* Wait up to 5s for plane to reach first HLT */
-        {
-            int64_t dl = qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
-                         5LL * 1000000000LL;
-            qemu_mutex_lock(&mutex);
-            while (halted_count < ps->vcpu_count) {
-                int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
-                if (now >= dl) {
-                    info_report("vm_planes: plane %" PRIu64 " boot timeout "
-                                "(%u/%u halted)", plane_id, halted_count,
-                                ps->vcpu_count);
-                    break;
-                }
-                qemu_cond_timedwait(&cond, &mutex, 1000);
+            if (ictx.ret) {
+                error_report("vm_planes: init plane %" PRIu64 " vcpu %u "
+                             "failed", plane_id, i);
+                run->hypercall.ret = ictx.ret;
+                return 0;
             }
-            qemu_mutex_unlock(&mutex);
         }
+#undef PLANE_HOST
 
-        /* Note: threads keep running for the plane's lifetime; we
-         * intentionally leak ctxs/threads — they outlive this call. */
-
-        /* Seal plane memory via KVM_SET_MEMORY_ATTRIBUTES(NO_WRITE|NO_EXEC) */
-        {
-            struct kvm_memory_attributes ma = {
-                .address    = ps->load_offset,
-                .size       = ps->memory_size,
-                .attributes = KVM_MEMORY_ATTRIBUTE_NO_WRITE |
-                              KVM_MEMORY_ATTRIBUTE_NO_EXEC,
-                .flags      = 0,
-            };
-            int pr = kvm_vm_ioctl(s, KVM_SET_MEMORY_ATTRIBUTES, &ma);
-            if (pr < 0) {
-                warn_report("vm_planes: plane %" PRIu64 " seal failed (%d)",
-                            plane_id, pr);
-            } else {
-                info_report("vm_planes: plane %" PRIu64 " memory sealed",
-                            plane_id);
-            }
-        }
+        /*
+         * Full Option B: do NOT run the secure plane from a dedicated
+         * userspace thread.  The plane's vCPU has been initialized (entry
+         * point, page tables, MP state) but is left STOPPED.  It boots
+         * lazily and in-kernel the first time the normal plane issues a
+         * VBS/VTL call: KVM switches to the secure plane within the normal
+         * plane's KVM_RUN, the secure kernel boots to its dispatch loop and
+         * parks itself via KVM_HC_VBS_VTL_RETURN.  This removes the racy
+         * per-plane thread that concurrently drove the shared vcpu->run
+         * page and caused host lockups.
+         *
+         * NB: the secure plane's own memory is intentionally NOT sealed
+         * here.  Because memory attributes are currently applied to every
+         * plane's EPT alike, sealing the secure plane's region NO_EXEC
+         * would prevent the secure kernel from executing its own dispatch
+         * loop.  Protecting the secure plane's memory from the normal plane
+         * requires plane-aware EPT attributes and is left as a follow-up.
+         */
         ps->host_addr = NULL;
 
         info_report("vm_planes: plane %" PRIu64 " launched — entry 0x%" PRIx64
@@ -7188,107 +7060,13 @@ static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
     return 0;
 }
 
-static int vbs_apply_protection(KVMState *s, uint64_t gpa, uint64_t size,
-                                uint32_t perms)
-{
-    uint64_t attrs = 0;
-    struct kvm_memory_attributes ma;
-
-    if (!(perms & 2)) {
-        attrs |= KVM_MEMORY_ATTRIBUTE_NO_WRITE;
-    }
-    if (!(perms & 4)) {
-        attrs |= KVM_MEMORY_ATTRIBUTE_NO_EXEC;
-    }
-    if (!attrs) {
-        return 0;
-    }
-
-    ma.address    = gpa;
-    ma.size       = size;
-    ma.attributes = attrs;
-    ma.flags      = 0;
-    return kvm_vm_ioctl(s, KVM_SET_MEMORY_ATTRIBUTES, &ma);
-}
-
-static int32_t vbs_handle_protect_memory(KVMState *s, uint64_t ca_gpa)
-{
-    uint64_t gpa, size;
-    uint32_t perms, arg_size;
-
-    cpu_physical_memory_read(ca_gpa + CA_OFF_ARG_SIZE, &arg_size, 4);
-    if (arg_size < 24) {
-        return -22;
-    }
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 0,  &gpa,   8);
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 8,  &size,  8);
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 16, &perms, 4);
-    return vbs_apply_protection(s, gpa, size, perms);
-}
-
-static int32_t vbs_handle_seal_kernel(KVMState *s, uint64_t ca_gpa)
-{
-    uint64_t text_gpa, text_size, rodata_gpa, rodata_size;
-    uint32_t arg_size;
-    int ret;
-
-    cpu_physical_memory_read(ca_gpa + CA_OFF_ARG_SIZE, &arg_size, 4);
-    if (arg_size < 40) {
-        return -22;
-    }
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 0,  &text_gpa,    8);
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 8,  &text_size,   8);
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 16, &rodata_gpa,  8);
-    cpu_physical_memory_read(ca_gpa + CA_OFF_BUFFER + 24, &rodata_size, 8);
-
-    ret = vbs_apply_protection(s, text_gpa, text_size, 1 | 4);
-    if (ret < 0) {
-        return ret;
-    }
-    return vbs_apply_protection(s, rodata_gpa, rodata_size, 1);
-}
-
-static int kvm_handle_hc_vbs_vtl_call(X86CPU *cpu, struct kvm_run *run)
-{
-    uint64_t ca_gpa = run->hypercall.args[0];
-    uint32_t call_id;
-    int32_t status;
-    KVMState *s = kvm_state;
-
-    cpu_physical_memory_read(ca_gpa + CA_OFF_CALL_ID, &call_id, 4);
-
-    switch (call_id) {
-    case VBS_CALL_INIT:
-    case VBS_CALL_SHUTDOWN:
-        status = 0;
-        break;
-    case VBS_CALL_PROTECT_MEMORY:
-        status = vbs_handle_protect_memory(s, ca_gpa);
-        break;
-    case VBS_CALL_SEAL_KERNEL:
-        status = vbs_handle_seal_kernel(s, ca_gpa);
-        break;
-    case VBS_CALL_VALIDATE_MODULE:
-    case VBS_CALL_SET_MODULE_PERMS:
-    case VBS_CALL_UNLOAD_MODULE:
-    case VBS_CALL_ADD_KEY:
-    case VBS_CALL_REVOKE_KEY:
-    case VBS_CALL_SEND_CERTS:
-    case VBS_CALL_KEXEC_VALIDATE:
-    case VBS_CALL_KEXEC_INVALIDATE:
-        status = 0;  /* acknowledge */
-        break;
-    default:
-        warn_report("vbs_vtl_call: unknown call_id 0x%04x", call_id);
-        status = -38;
-        break;
-    }
-
-    cpu_physical_memory_write(ca_gpa + CA_OFF_STATUS, &status, 4);
-    run->hypercall.ret = 0;
-    return 0;
-}
-
+/*
+ * Full Option B: VBS/VTL calls (KVM_HC_VBS_VTL_CALL / _RETURN) are now
+ * serviced entirely in-kernel by switching to the secure plane, which runs
+ * a real in-guest dispatcher.  QEMU no longer emulates the secure world, so
+ * those hypercalls never exit to userspace here.  Only the plane
+ * configuration/activation hypercalls are handled by QEMU.
+ */
 static int kvm_handle_hypercall(X86CPU *cpu, struct kvm_run *run)
 {
     if (run->hypercall.nr == KVM_HC_MAP_GPA_RANGE)
@@ -7297,8 +7075,6 @@ static int kvm_handle_hypercall(X86CPU *cpu, struct kvm_run *run)
         return kvm_handle_hc_vm_planes_config(cpu, run);
     if (run->hypercall.nr == KVM_HC_VM_PLANES_ACTIVATE)
         return kvm_handle_hc_vm_planes_activate(cpu, run);
-    if (run->hypercall.nr == KVM_HC_VBS_VTL_CALL)
-        return kvm_handle_hc_vbs_vtl_call(cpu, run);
 
     return -EINVAL;
 }
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH v1 5/5] target/i386/kvm: read plane config via address_space API
  2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
                   ` (3 preceding siblings ...)
  2026-08-05 11:04 ` [RFC PATCH v1 4/5] target/i386/kvm: run the secure plane in-kernel (Option B) Sriram Nambakam
@ 2026-08-05 11:04 ` Sriram Nambakam
  4 siblings, 0 replies; 6+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:04 UTC (permalink / raw)
  To: qemu-devel; +Cc: kvm

Match the vm-planes-merged integration result: read the guest
vm_plane_config/calling-area through address_space_read() (with the
address-spaces/memory/thread includes) instead of the
cpu_physical_memory_read() helpers, and pick up the kvm_para.h
whitespace from the header re-sync. This delta originated in the
integration branch's merge-commit conflict resolution.
---
 include/standard-headers/linux/kvm_para.h |  2 +-
 target/i386/kvm/kvm.c                     | 23 ++++++++++++++++-------
 2 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/include/standard-headers/linux/kvm_para.h b/include/standard-headers/linux/kvm_para.h
index def451c43c..3094d70c8d 100644
--- a/include/standard-headers/linux/kvm_para.h
+++ b/include/standard-headers/linux/kvm_para.h
@@ -27,7 +27,7 @@
 #define KVM_HC_MIPS_EXIT_VM		7
 #define KVM_HC_MIPS_CONSOLE_OUTPUT	8
 #define KVM_HC_CLOCK_PAIRING		9
-#define KVM_HC_SEND_IPI		10
+#define KVM_HC_SEND_IPI			10
 #define KVM_HC_SCHED_YIELD		11
 #define KVM_HC_MAP_GPA_RANGE		12
 #define KVM_HC_VM_PLANES_CONFIG		13
diff --git a/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 5ac7962e61..b8a68a1c56 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -21,6 +21,7 @@
 #include <sys/utsname.h>
 #include <sys/syscall.h>
 #include <sys/resource.h>
+#include <sys/mman.h>
 
 #include <linux/kvm.h>
 #include <linux/kvm_para.h>
@@ -36,6 +37,8 @@
 #include "system/kvm_int.h"
 #include "system/runstate.h"
 #include "system/ramblock.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
 #include "kvm_i386.h"
 #include "../confidential-guest.h"
 #include "sev.h"
@@ -47,6 +50,7 @@
 #include "gdbstub/enums.h"
 #include "qemu/host-utils.h"
 #include "qemu/main-loop.h"
+#include "qemu/thread.h"
 #include "qemu/ratelimit.h"
 #include "qemu/config-file.h"
 #include "qemu/error-report.h"
@@ -6665,9 +6669,12 @@ static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
         int plane_fd;
         unsigned int i;
 
-        cpu_physical_memory_read(plane_gpa + 0,  &load_offset, 8);
-        cpu_physical_memory_read(plane_gpa + 8,  &memory_size, 8);
-        cpu_physical_memory_read(plane_gpa + 16, &entry_point, 8);
+        address_space_read(&address_space_memory, plane_gpa + 0,
+                           MEMTXATTRS_UNSPECIFIED, &load_offset, 8);
+        address_space_read(&address_space_memory, plane_gpa + 8,
+                           MEMTXATTRS_UNSPECIFIED, &memory_size, 8);
+        address_space_read(&address_space_memory, plane_gpa + 16,
+                           MEMTXATTRS_UNSPECIFIED, &entry_point, 8);
 
         /*
          * joergroedel plane model: a plane has exactly one vCPU per
@@ -6685,8 +6692,9 @@ static int kvm_handle_hc_vm_planes_config(X86CPU *cpu, struct kvm_run *run)
         }
 
         memset(cmdline_buf, 0, sizeof(cmdline_buf));
-        cpu_physical_memory_read(plane_gpa + 156, cmdline_buf,
-                                 sizeof(cmdline_buf));
+        address_space_read(&address_space_memory, plane_gpa + 156,
+                           MEMTXATTRS_UNSPECIFIED, cmdline_buf,
+                           sizeof(cmdline_buf));
         cmdline_buf[sizeof(cmdline_buf) - 1] = '\0';
         memcpy(ps->cmdline, cmdline_buf, sizeof(ps->cmdline));
 
@@ -6899,8 +6907,9 @@ static int kvm_handle_hc_vm_planes_activate(X86CPU *cpu, struct kvm_run *run)
             return 0;
         }
 
-        cpu_physical_memory_read(gpa + (plane_id * VM_PLANE_CFG_STRIDE) + 16,
-                                 &entry_point, 8);
+        address_space_read(&address_space_memory,
+                           gpa + (plane_id * VM_PLANE_CFG_STRIDE) + 16,
+                           MEMTXATTRS_UNSPECIFIED, &entry_point, 8);
         if (!entry_point) {
             error_report("vm_planes: plane %" PRIu64 " bad entry_point",
                          plane_id);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-05 11:04 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 11:04 [RFC PATCH v1 0/5] VBS/VSM-on-KVM: QEMU support for the secure VM plane Sriram Nambakam
2026-08-05 11:04 ` [RFC PATCH v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls Sriram Nambakam
2026-08-05 11:04 ` [RFC PATCH v1 2/5] vm_planes: Add VBS VTL call handling and plane memory sealing Sriram Nambakam
2026-08-05 11:04 ` [RFC PATCH v1 3/5] linux-headers: sync kvm_para.h VBS VTL hypercalls Sriram Nambakam
2026-08-05 11:04 ` [RFC PATCH v1 4/5] target/i386/kvm: run the secure plane in-kernel (Option B) Sriram Nambakam
2026-08-05 11:04 ` [RFC PATCH v1 5/5] target/i386/kvm: read plane config via address_space API Sriram Nambakam

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox