* [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 +
| 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)
--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, ®s);
+ 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)
---
| 13 +++++++++++++
1 file changed, 13 insertions(+)
--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