Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: Sriram Nambakam <snambakam@linux.microsoft.com>
To: qemu-devel@nongnu.org
Cc: kvm@vger.kernel.org
Subject: [RFC PATCH v2 1/1] kvm: handle VM-plane configure/activate hypercalls
Date: Mon, 10 Aug 2026 18:54:27 -0700	[thread overview]
Message-ID: <20260811015427.188876-2-snambakam@linux.microsoft.com> (raw)
In-Reply-To: <20260811015427.188876-1-snambakam@linux.microsoft.com>

The guest plane bootstrap issues KVM_HC_VM_PLANES_CONFIG then
KVM_HC_VM_PLANES_ACTIVATE, which the host exits to userspace.  Handle them
here: CONFIG reads the per-plane vm_plane_config from guest memory, creates
the plane (KVM_CREATE_PLANE) and one sibling vCPU per plane-0 vCPU, and maps
the plane's RAM; ACTIVATE builds identity page tables, a minimal GDT and a
boot zero-page in that RAM and initialises each plane vCPU to enter the
loaded plane kernel in 64-bit mode.

The plane vCPUs are created and initialised on their owning plane-0 vCPU
threads (they share the plane-0 kvm_vcpu_common) and are left STOPPED.
QEMU never runs them: the secure plane boots lazily and in-kernel the first
time the normal plane issues a VBS/VTL call.

Enable userspace exit for the two plane hypercalls; KVM_HC_VBS_VTL_CALL is
handled in-kernel and is intentionally not enabled here.
---
 accel/kvm/kvm-all.c                       |  20 +
 include/standard-headers/linux/kvm_para.h |   2 +
 include/system/kvm_int.h                  |  20 +
 target/i386/kvm/kvm.c                     | 517 ++++++++++++++++++++++
 4 files changed, 559 insertions(+)

diff --git a/accel/kvm/kvm-all.c b/accel/kvm/kvm-all.c
index 46a14ac0f4..0f42fb5b0c 100644
--- a/accel/kvm/kvm-all.c
+++ b/accel/kvm/kvm-all.c
@@ -822,6 +822,26 @@ 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(ps->vcpu_cpu_index);
+                ps->vcpu_cpu_index = 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..8810444285 100644
--- a/include/standard-headers/linux/kvm_para.h
+++ b/include/standard-headers/linux/kvm_para.h
@@ -30,6 +30,8 @@
 #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
 
 /*
  * hypercalls use architecture specific
diff --git a/include/system/kvm_int.h b/include/system/kvm_int.h
index 70b381f1ba..0ba5d8dacb 100644
--- a/include/system/kvm_int.h
+++ b/include/system/kvm_int.h
@@ -109,6 +109,22 @@ struct KVMPlane {
     bool vcpu_dirty;
 };
 
+/*
+ * Per-plane VM state managed by the LVBS VM-planes hypercall handlers.
+ * The plane fd itself is owned by the accel layer (kvm_get_plane_fd);
+ * only the plane's vCPU fds and memory description live here.
+ */
+struct kvm_vm_plane_state {
+    int *vcpu_fds;
+    unsigned int *vcpu_cpu_index;
+    unsigned int vcpu_count;
+    uint64_t load_offset;
+    uint64_t memory_size;
+    uint64_t entry_point;
+    void *host_addr;        /* host pointer to plane RAM */
+    char cmdline[512];
+};
+
 struct KVMState
 {
     AccelState parent_obj;
@@ -176,6 +192,10 @@ struct KVMState
     uint16_t xen_evtchn_max_pirq;
     char *device;
     OnOffAuto honor_guest_pat;
+    /* VM planes state (populated by the 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/target/i386/kvm/kvm.c b/target/i386/kvm/kvm.c
index 13cfa60071..357410c159 100644
--- a/target/i386/kvm/kvm.c
+++ b/target/i386/kvm/kvm.c
@@ -70,6 +70,8 @@
 #include "migration/blocker.h"
 #include "exec/memattrs.h"
 #include "exec/target_page.h"
+#include "system/address-spaces.h"
+#include "system/memory.h"
 #include "trace.h"
 
 #include CONFIG_DEVICES
@@ -3588,6 +3590,12 @@ int kvm_arch_init(MachineState *ms, KVMState *s)
         kvm_vmfd_add_change_notifier(&kvm_vmfd_change_notifier);
     }
 
+    /* Exit to userspace for VM-plane config/activate hypercalls (LVBS). */
+    if (!kvm_enable_hypercall(BIT_ULL(KVM_HC_VM_PLANES_CONFIG) |
+                              BIT_ULL(KVM_HC_VM_PLANES_ACTIVATE))) {
+        warn_report("kvm: failed to enable VM-planes 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 +6527,519 @@ static int kvm_handle_hc_map_gpa_range(X86CPU *cpu, struct kvm_run *run)
     return 0;
 }
 
+/* ========================================================================
+ * LVBS — VM planes configure/activate hypercall handlers
+ *
+ * The guest plane bootstrap (Linux init/vm_planes.c) issues
+ * KVM_HC_VM_PLANES_CONFIG then KVM_HC_VM_PLANES_ACTIVATE.  QEMU creates the
+ * plane and its per-plane-0 sibling vCPUs and initialises them to enter the
+ * loaded plane kernel in 64-bit mode.  It does NOT run the plane vCPUs: the
+ * secure plane boots lazily, in-kernel, the first time the normal plane
+ * issues a VBS/VTL call (handled by the host via KVM_HC_VBS_VTL_CALL).
+ *
+ * Guest struct vm_plane_config layout (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  kernel_format
+ *   off 28  char kernel[128]
+ *   off 156 char cmdline[512]
+ * ======================================================================== */
+
+#define VM_PLANE_CFG_STRIDE     672
+
+/*
+ * 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 all vcpu_load() the plane vCPU, which
+ * registers that shared notifier on the *calling* task.  If issued from the
+ * 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 lockup.  run_on_cpu()
+ * forces the sibling out of KVM_RUN and runs the work on its own thread.
+ */
+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];
+    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 = plane0_vcpu_count;
+        struct kvm_vm_plane_state *ps = &s->vm_planes[plane_id];
+        char cmdline_buf[512];
+        MemoryRegionSection section;
+        int plane_fd;
+        unsigned int i;
+
+        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
+         * 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 ")",
+                         plane_id, load_offset, memory_size);
+            g_free(plane0_vcpu_ids);
+            run->hypercall.ret = -EINVAL;
+            return 0;
+        }
+
+        memset(cmdline_buf, 0, 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));
+
+        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);
+        ps->vcpu_cpu_index = g_new0(unsigned int, vcpu_count);
+        for (i = 0; i < vcpu_count; i++) {
+            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;
+            }
+
+            /* 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(-cctx.fd));
+                close(plane_fd);
+                kvm_set_plane_fd(s, plane_id, -1);
+                g_free(plane0_vcpu_ids);
+                run->hypercall.ret = cctx.fd;
+                return 0;
+            }
+
+            ps->vcpu_fds[i] = cctx.fd;
+            ps->vcpu_cpu_index[i] = vcpu_id;
+        }
+
+        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 = {};
+    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;
+    }
+
+    /*
+     * 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 and KVM_SET_MP_STATE is intentionally excluded.  The
+     * kernel already establishes the correct initial MP state at create
+     * time: the plane BSP is left RUNNABLE and APs wait-for-INIT.
+     */
+    return 0;
+}
+
+/* Initialize a plane vCPU on its owning CPU's thread (see
+ * plane_vcpu_create_cb for why the shared-common vcpu_load() 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 */
+};
+
+static void plane_vcpu_init_cb(CPUState *cs, run_on_cpu_data data)
+{
+    struct plane_vcpu_init_ctx *ctx = data.host_ptr;
+
+    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)
+{
+    uint64_t gpa = run->hypercall.args[0];
+    uint64_t plane_count = run->hypercall.args[1];
+    uint64_t plane_id;
+    KVMState *s = kvm_state;
+
+    if (!gpa || !plane_count || !s->vm_planes ||
+        plane_count != s->vm_plane_count) {
+        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;
+        unsigned int i;
+
+        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;
+        }
+
+        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);
+            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 on their owning CPU threads. */
+        for (i = 0; i < ps->vcpu_count; i++) {
+            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;
+            }
+
+            /* 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();
+
+            if (ictx.ret) {
+                error_report("vm_planes: init plane %" PRIu64 " vcpu %u "
+                             "failed", plane_id, i);
+                run->hypercall.ret = ictx.ret;
+                return 0;
+            }
+        }
+#undef PLANE_HOST
+
+        /*
+         * The plane vCPU is initialized (entry point, page tables) but 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.  QEMU never runs it.
+         */
+        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;
+}
+
+/*
+ * VBS/VTL calls (KVM_HC_VBS_VTL_CALL / _RETURN) are serviced entirely
+ * in-kernel by switching to the secure plane, so they never exit to
+ * userspace here.  QEMU only handles plane configuration/activation.
+ */
 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);
 
     return -EINVAL;
 }
-- 
2.55.0


      reply	other threads:[~2026-08-11  1:54 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-11  1:54 [RFC PATCH v2 0/1] qemu: KVM VM Planes host support for VBS Sriram Nambakam
2026-08-11  1:54 ` Sriram Nambakam [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260811015427.188876-2-snambakam@linux.microsoft.com \
    --to=snambakam@linux.microsoft.com \
    --cc=kvm@vger.kernel.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox