From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BF336432E64 for ; Wed, 5 Aug 2026 11:04:35 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785927878; cv=none; b=CmrGXKjUglLwXObMvvbsVcb6sMomNl2Wsta6Wo4Tz+TxAB2vSDpHtjxMS1DjteqEsonng/lPgDlBTBW+w3LjHISjs3Qhea+71fzGF6PDZdvrHDKB90Y2vQZ/JN2X47zrDckcNnGXPS2sM9S8APG/kOrYqmf0upZkzHyp5ifT1gY= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785927878; c=relaxed/simple; bh=7XOJz2TmgKdvT6R+LFWA1JGkiy7cc2t4R3Uiz5WQrHw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=k48d8+UXhfwCRv/9gVJyDJEi/6rJ9exUgvutnwM6VJ//5IoVD2NPhUNfKH203lNo+Ox+204SxhkricaK7iQdPbqqW6lPKEltNopUTEK7JgE+BszvZTJUGZq7HjpUgKaM64+ykDmyUM7BoM4FuGCrOfOXTFtvM3UB/agnbZieUiI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=jeJe8BEZ; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="jeJe8BEZ" Received: from fedora.hsd1.wa.comcast.net (unknown [52.148.140.42]) by linux.microsoft.com (Postfix) with ESMTPSA id 8681D20B716A; Wed, 5 Aug 2026 04:04:13 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 8681D20B716A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1785927853; bh=cKpLSPc5Ft5FlEL2TPYsuyNO1mO7zlGBjKjNjZCumb0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jeJe8BEZS6SaED7DkDupduivx9FuLihWlULQySkpXZdklJW1abcHTC3S57j4UNwDV X8nGxMlcTK+8ySS9WnKbVSuZ5Xj8LVBfBlRhjoQgnu4IGJ2wYl9U+rViLbgRuyUhEf HXZEnN/Q0znekiHMYYzYeBASwBuyJ6l9Y/xT+M/Q= From: Sriram Nambakam 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 Message-ID: <20260805110432.25167-2-snambakam@linux.microsoft.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260805110432.25167-1-snambakam@linux.microsoft.com> References: <20260805110432.25167-1-snambakam@linux.microsoft.com> Precedence: bulk X-Mailing-List: kvm@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (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 +#include +#include #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