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 v1 1/5] kvm: add userspace handlers for VM planes and VBS VTL calls
Date: Wed,  5 Aug 2026 04:04:28 -0700	[thread overview]
Message-ID: <20260805110432.25167-2-snambakam@linux.microsoft.com> (raw)
In-Reply-To: <20260805110432.25167-1-snambakam@linux.microsoft.com>

(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


  reply	other threads:[~2026-08-05 11:04 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 [this message]
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

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=20260805110432.25167-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