Implement Intel VMX Caretaker VMCS setup, exit decoding, and guest
entry/exit loop in arch/x86/kvm/vmx/caretaker.c.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/x86/kvm/vmx/caretaker.c         | 340 +++++++++++++++++++++++++++
 arch/x86/kvm/vmx/caretaker_vmenter.S |  12 +-
 arch/x86/kvm/vmx/vmx.c               |   2 +-
 arch/x86/kvm/vmx/vmx.h               |   1 +
 4 files changed, 347 insertions(+), 8 deletions(-)
 create mode 100644 arch/x86/kvm/vmx/caretaker.c

diff --git a/arch/x86/kvm/vmx/caretaker.c b/arch/x86/kvm/vmx/caretaker.c
new file mode 100644
index 000000000000..47b4743650e5
--- /dev/null
+++ b/arch/x86/kvm/vmx/caretaker.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel VMX Caretaker Standalone Execution Engine
+ *
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * Runs the Intel VMX guest execution loop in an isolated, KHO-preserved memory
+ * page that remains alive and executing across kexec relocation and
+ * kernel handover.
+ */
+
+#include <linux/cleanup.h>
+#include <linux/cpu_preserve.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/kexec.h>
+#include <linux/kexec_handover.h>
+#include <linux/kvm_host.h>
+#include <linux/objtool.h>
+#include <linux/oncore.h>
+
+#include <asm/apic.h>
+#include <asm/cpu_entry_area.h>
+#include <asm/desc.h>
+#include <asm/fixmap.h>
+#include <linux/pgtable.h>
+#include <linux/processor.h>
+#include <asm/segment.h>
+#include <asm/set_memory.h>
+#include <asm/virt.h>
+#include <asm/vmx.h>
+
+#include "../caretaker.h"
+#include "caretaker.h"
+#include "vmx.h"
+#include "vmx_ops.h"
+#include "x86.h"
+#include "x86_ops.h"
+
+static int vmx_caretaker_init_page(struct caretaker_vmx_page *cvp,
+                                  struct kvm_vcpu *vcpu);
+STACK_FRAME_NON_STANDARD(vmx_caretaker_init_page);
+
+static int vmx_caretaker_init_page(struct caretaker_vmx_page *cvp,
+                                  struct kvm_vcpu *vcpu)
+{
+       struct vcpu_vmx *vmx = to_vmx(vcpu);
+       u64 basic_msr, misc_msr;
+       int ret;
+
+       if (!vmx->vmcs01.vmcs)
+               return -EINVAL;
+
+       ret = kvm_x86_caretaker_init_common_page(&cvp->common, vcpu, 
sizeof(*cvp));
+       if (ret)
+               return ret;
+       cvp->common.abi.vmcs_pa = virt_to_phys(vmx->vmcs01.vmcs);
+       cvp->vmxon_pa = virt_to_phys(cvp->vmxon_area);
+
+       memset(cvp->vmxon_area, 0, PAGE_SIZE);
+       basic_msr = native_rdmsrq(MSR_IA32_VMX_BASIC);
+       *(u32 *)cvp->vmxon_area = vmx_basic_vmcs_revision_id(basic_msr);
+
+       if (!rdmsrq_safe(MSR_IA32_VMX_MISC, &misc_msr))
+               cvp->timer_shift = vmx_misc_preemption_timer_rate(misc_msr);
+       else
+               cvp->timer_shift = VMX_PREEMPTION_TIMER_SHIFT;
+
+       cvp->ple_supported = cpu_has_vmx_ple();
+
+       vcpu_load(vcpu);
+
+       cvp->common.kernel_gs_base = vmx->msr_guest_kernel_gs_base;
+       kvm_msr_read(vcpu, MSR_STAR, &cvp->star);
+       kvm_msr_read(vcpu, MSR_LSTAR, &cvp->lstar);
+       kvm_msr_read(vcpu, MSR_SYSCALL_MASK, &cvp->fmask);
+
+       /* Save current guest control registers from KVM */
+       cvp->common.cr0 = kvm_read_cr0(vcpu);
+       cvp->common.cr3 = kvm_read_cr3(vcpu);
+       cvp->common.cr4 = kvm_read_cr4(vcpu);
+       cvp->common.efer = vcpu->arch.efer;
+
+       cvp->common.last_exit_rip = kvm_rip_read(vcpu);
+       cvp->common.last_exit_rsp = kvm_rsp_read(vcpu);
+       cvp->common.last_exit_rflags = kvm_get_rflags(vcpu);
+
+       if (kvm_host.efer & EFER_NX)
+               cvp->common.efer |= EFER_NX;
+
+       vcpu_put(vcpu);
+
+       if (vmx->loaded_vmcs)
+               loaded_vmcs_clear(vmx->loaded_vmcs);
+
+       return 0;
+}
+
+void vmx_caretaker_init(struct kvm_vcpu *vcpu)
+{
+       struct vcpu_vmx *vmx = to_vmx(vcpu);
+       struct caretaker_vmx_page *cvp;
+       struct kvm_caretaker_arch_ser *abi;
+
+       cvp = kho_alloc_preserve(sizeof(*cvp));
+       if (IS_ERR(cvp)) {
+               pr_err("caretaker vmx: failed to allocate preserved page\n");
+               return;
+       }
+
+       if (vmx_caretaker_init_page(cvp, vcpu))
+               goto err_free;
+
+       abi = &cvp->common.abi;
+       if (vmx->vmcs01.vmcs &&
+           kvm_x86_caretaker_preserve_page(abi, 
virt_to_page(vmx->vmcs01.vmcs)))
+               goto err_free;
+       if (vmx->vmcs01.msr_bitmap &&
+           kvm_x86_caretaker_preserve_page(abi, 
virt_to_page(vmx->vmcs01.msr_bitmap)))
+               goto err_free;
+       if (vmx->pml_pg &&
+           kvm_x86_caretaker_preserve_page(abi, vmx->pml_pg))
+               goto err_free;
+       if (vmx->ve_info &&
+           kvm_x86_caretaker_preserve_page(abi, virt_to_page(vmx->ve_info)))
+               goto err_free;
+
+       return;
+
+err_free:
+       kvm_x86_caretaker_unpreserve_pages(&cvp->common.abi);
+       vcpu->caretaker.cb = NULL;
+       kho_unpreserve_free(cvp);
+}
+
+static __cpu_preserved_text void vmx_caretaker_disarm_timer(void *page)
+{
+       u32 pin = (u32)vmx_vmread(PIN_BASED_VM_EXEC_CONTROL);
+
+       if (pin & PIN_BASED_VMX_PREEMPTION_TIMER) {
+               vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL,
+                           pin & ~PIN_BASED_VMX_PREEMPTION_TIMER);
+       }
+}
+
+static inline unsigned long vmx_caretaker_read_cr0(void)
+{
+       unsigned long mask = vmx_vmread(CR0_GUEST_HOST_MASK);
+
+       return (vmx_vmread(CR0_READ_SHADOW) & mask) |
+              (vmx_vmread(GUEST_CR0) & ~mask);
+}
+
+static inline unsigned long vmx_caretaker_read_cr4(void)
+{
+       unsigned long mask = vmx_vmread(CR4_GUEST_HOST_MASK);
+
+       return (vmx_vmread(CR4_READ_SHADOW) & mask) |
+              (vmx_vmread(GUEST_CR4) & ~mask);
+}
+
+static inline u64 vmx_caretaker_read_efer(void)
+{
+       u64 efer = vmx_vmread(GUEST_IA32_EFER);
+
+       if (!efer)
+               efer = native_rdmsrq(MSR_EFER);
+       if (vmx_vmread(VM_ENTRY_CONTROLS) & VM_ENTRY_IA32E_MODE)
+               efer |= EFER_LMA | EFER_LME;
+       return efer;
+}
+
+void __cpu_preserved_text
+vmx_caretaker_decode_exit(void *page,
+                         struct kvm_caretaker_exit *exit)
+{
+       struct caretaker_vmx_page *cvp = page;
+       u32 insn_len = (u32)vmx_vmread(VM_EXIT_INSTRUCTION_LEN);
+       u16 exit_reason = (u16)vmx_vmread(VM_EXIT_REASON);
+       u64 qual = vmx_vmread(EXIT_QUALIFICATION);
+       u64 rip = vmx_vmread(GUEST_RIP);
+
+       cvp->common.last_exit_rip = rip;
+       cvp->common.last_exit_rsp = vmx_vmread(GUEST_RSP);
+       cvp->common.last_exit_rflags = vmx_vmread(GUEST_RFLAGS);
+       cvp->common.cr3 = vmx_vmread(GUEST_CR3);
+       cvp->common.cr0 = vmx_caretaker_read_cr0();
+       cvp->common.cr4 = vmx_caretaker_read_cr4();
+
+       exit->rip = rip;
+       exit->insn_len = insn_len;
+       exit->raw_reason = exit_reason;
+       exit->type = KVM_CARETAKER_EXIT_ARCH;
+
+       switch (exit_reason) {
+       case EXIT_REASON_IO_INSTRUCTION: {
+               u16 port = (u16)(qual >> VMX_IO_PORT_SHIFT);
+
+               if (port >= COM1_PORT_BASE && port <= COM1_PORT_END) {
+                       exit->type = KVM_CARETAKER_EXIT_CONSOLE;
+                       exit->mmio_io.addr = port;
+                       exit->mmio_io.is_write = !(qual & VMX_IO_DIRECTION_BIT);
+                       exit->mmio_io.size = (u8)((qual & VMX_IO_SIZE_MASK) + 
1);
+                       exit->mmio_io.is_mmio = false;
+                       exit->mmio_io.val_ptr = &cvp->common.rax;
+               }
+               break;
+       }
+       case EXIT_REASON_HLT:
+               exit->type = KVM_CARETAKER_EXIT_IDLE;
+               break;
+       case EXIT_REASON_PAUSE_INSTRUCTION:
+               exit->type = KVM_CARETAKER_EXIT_IDLE;
+               exit->insn_len = insn_len ? insn_len : PAUSE_INSN_LEN;
+               break;
+       case EXIT_REASON_CPUID:
+               exit->type = KVM_CARETAKER_EXIT_CPUID;
+               break;
+       case EXIT_REASON_VMCALL:
+               /*
+                * Do not write a return value.  The Caretaker cannot run the
+                * hypercall, and reporting success for something like
+                * KVM_HC_SEND_IPI or a PV TLB flush is worse than not
+                * answering at all.  CROSS_VCPU now stalls, so the guest
+                * stays parked on the VMCALL with RAX untouched until the
+                * incoming kernel services it.
+                */
+               exit->type = KVM_CARETAKER_EXIT_CROSS_VCPU;
+               exit->insn_len = insn_len ? insn_len : VMCALL_INSN_LEN;
+               break;
+       case EXIT_REASON_MSR_READ:
+               exit->type = KVM_CARETAKER_EXIT_MSR;
+               exit->msr.msr = (u32)cvp->common.rcx;
+               exit->msr.is_write = false;
+               break;
+       case EXIT_REASON_MSR_WRITE:
+               exit->type = KVM_CARETAKER_EXIT_MSR;
+               exit->msr.msr = (u32)cvp->common.rcx;
+               exit->msr.is_write = true;
+               break;
+       case EXIT_REASON_RDTSC:
+               exit->type = KVM_CARETAKER_EXIT_RDTSC;
+               break;
+       case EXIT_REASON_EPT_VIOLATION:
+               exit->type = KVM_CARETAKER_EXIT_UNHANDLED;
+               break;
+       case EXIT_REASON_PREEMPTION_TIMER:
+               exit->type = KVM_CARETAKER_EXIT_PREEMPT_TIMER;
+               exit->insn_len = 0;
+               vmx_caretaker_disarm_timer(cvp);
+               break;
+       case EXIT_REASON_EOI_INDUCED:
+       case EXIT_REASON_APIC_WRITE:
+       case EXIT_REASON_APIC_ACCESS:
+       case EXIT_REASON_INTERRUPT_WINDOW:
+               exit->type = KVM_CARETAKER_EXIT_CROSS_VCPU;
+               exit->insn_len = 0;
+               break;
+       case EXIT_REASON_EXTERNAL_INTERRUPT:
+       case EXIT_REASON_EXCEPTION_NMI:
+       case EXIT_REASON_INIT_SIGNAL:
+       case EXIT_REASON_SIPI_SIGNAL:
+               exit->type = KVM_CARETAKER_EXIT_PREEMPT_TIMER;
+               exit->insn_len = 0;
+               break;
+       default:
+               break;
+       }
+}
+
+void __cpu_preserved_text
+vmx_caretaker_init_host_vmcs(struct caretaker_vmx_page *cvp)
+{
+       u64 fs_base = 0, gs_base = 0;
+       unsigned long pin, cpu_ctl;
+
+       phys_addr_t host_cr3 = cvp->common.host_cr3;
+
+       /* Configure Host Controls */
+       vmx_vmwrite(HOST_CR0, read_cr0());
+       vmx_vmwrite(HOST_CR4, __read_cr4());
+       if (!host_cr3) {
+               struct cpu_preserved_stack_context *sctx = 
cpu_preserved_get_stack_context();
+
+               if (sctx && sctx->session_pgd_pa)
+                       host_cr3 = sctx->session_pgd_pa;
+               else
+                       host_cr3 = x86_caretaker_pgd_pa;
+       }
+       if (host_cr3)
+               vmx_vmwrite(HOST_CR3, host_cr3);
+       vmx_vmwrite(HOST_RSP, (unsigned 
long)&cvp->common.stack[CXP_STACK_SIZE]);
+       vmx_vmwrite(HOST_RIP, (unsigned long)&vmx_caretaker_exit_handler);
+
+       /* Configure Host Selectors */
+       vmx_vmwrite(HOST_CS_SELECTOR, __KERNEL_CS);
+       vmx_vmwrite(HOST_SS_SELECTOR, __KERNEL_DS);
+       vmx_vmwrite(HOST_DS_SELECTOR, __KERNEL_DS);
+       vmx_vmwrite(HOST_ES_SELECTOR, __KERNEL_DS);
+       vmx_vmwrite(HOST_FS_SELECTOR, 0);
+       vmx_vmwrite(HOST_GS_SELECTOR, 0);
+       vmx_vmwrite(HOST_TR_SELECTOR, GDT_ENTRY_TSS * 8);
+
+       /* Configure Host Bases */
+       fs_base = native_rdmsrq(MSR_FS_BASE);
+       gs_base = native_rdmsrq(MSR_GS_BASE);
+       vmx_vmwrite(HOST_FS_BASE, fs_base);
+       vmx_vmwrite(HOST_GS_BASE, gs_base);
+       vmx_vmwrite(HOST_TR_BASE, (unsigned long)&cvp->common.tss);
+       vmx_vmwrite(HOST_GDTR_BASE, (unsigned long)&cvp->common.gdt[0]);
+       vmx_vmwrite(HOST_IDTR_BASE, (unsigned long)&caretaker_x86_idt[0]);
+
+       /* Configure PIN and CPU execution controls */
+       pin = vmx_vmread(PIN_BASED_VM_EXEC_CONTROL);
+       pin |= (PIN_BASED_EXT_INTR_MASK | PIN_BASED_NMI_EXITING);
+       pin &= ~(PIN_BASED_VMX_PREEMPTION_TIMER | PIN_BASED_POSTED_INTR);
+       vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, pin);
+
+       cpu_ctl = vmx_vmread(CPU_BASED_VM_EXEC_CONTROL);
+       cpu_ctl &= ~(CPU_BASED_INTR_WINDOW_EXITING |
+                    CPU_BASED_NMI_WINDOW_EXITING);
+       cpu_ctl |= (CPU_BASED_HLT_EXITING |
+                   CPU_BASED_PAUSE_EXITING |
+                   CPU_BASED_MWAIT_EXITING |
+                   CPU_BASED_MONITOR_EXITING |
+                   CPU_BASED_UNCOND_IO_EXITING);
+
+       if (cvp && cvp->ple_supported &&
+           (cpu_ctl & CPU_BASED_ACTIVATE_SECONDARY_CONTROLS)) {
+               unsigned long sec_ctl = vmx_vmread(SECONDARY_VM_EXEC_CONTROL);
+
+               sec_ctl |= SECONDARY_EXEC_PAUSE_LOOP_EXITING;
+               vmx_vmwrite(SECONDARY_VM_EXEC_CONTROL, sec_ctl);
+               vmx_vmwrite(PLE_GAP, 4096);
+               vmx_vmwrite(PLE_WINDOW, 4096);
+       }
+       vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, cpu_ctl);
+}
+
diff --git a/arch/x86/kvm/vmx/caretaker_vmenter.S 
b/arch/x86/kvm/vmx/caretaker_vmenter.S
index ff135cba854d..51781e3e61e2 100644
--- a/arch/x86/kvm/vmx/caretaker_vmenter.S
+++ b/arch/x86/kvm/vmx/caretaker_vmenter.S
@@ -26,8 +26,8 @@ SYM_TYPED_FUNC_START(vmx_caretaker_enter)
        CARETAKER_PUSH_HOST_REGS
        movq %rsp, CXP_STACK_ORIG(%rdi)
 
-       /* Switch to private preserved stack */
-       movq CXP_STACK_TOP(%rdi), %rsp
+       /* Switch to private preserved stack at top of caretaker_x86_page */
+       leaq PAGE_SIZE(%rdi), %rsp
 
        /* Activate VMCS on this pCPU */
        vmptrld CXP_VMCS_PA(%rdi)
@@ -60,11 +60,10 @@ SYM_TYPED_FUNC_START(vmx_caretaker_enter)
        vmresume
        vmlaunch
 
-       /* If both fail, record error and return */
+       /* If both fail, return error in %rax */
        popq %rdi
        mov $VMCS_VM_INSTRUCTION_ERROR, %eax
        vmread %rax, %rax
-       movq %rax, CXP_LAST_EXIT_CODE(%rdi)
        btsq $31, %rax
        jmp .Lvmx_ret
 SYM_FUNC_END(vmx_caretaker_enter)
@@ -73,15 +72,14 @@ SYM_FUNC_START(vmx_caretaker_exit_handler)
        ENDBR
        /*
         * Hardware jumps here on VM-Exit with:
-        * RSP = HOST_RSP (CXP_STACK_TOP)
+        * RSP = HOST_RSP (top of 4 KB struct caretaker_x86_page)
         * CR3 = HOST_CR3
         */
        pushq %rdi
 
-       /* Recompute CVP pointer from RSP: CVP = (RSP & PAGE_MASK) - 
CXP_STACK_OFFSET */
+       /* Recover CVP base from RSP: stack lives in the upper half of page 0 */
        movq %rsp, %rdi
        andq $PAGE_MASK, %rdi
-       subq $CXP_STACK_OFFSET, %rdi
 
        /* Save guest GPRs into CVP */
        CARETAKER_SAVE_GPRS %rdi
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 612ab07d4100..48b6e69f99bf 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -839,7 +839,7 @@ static void __loaded_vmcs_clear(void *arg)
        loaded_vmcs->launched = 0;
 }
 
-static void loaded_vmcs_clear(struct loaded_vmcs *loaded_vmcs)
+void loaded_vmcs_clear(struct loaded_vmcs *loaded_vmcs)
 {
        int cpu = loaded_vmcs->cpu;
 
diff --git a/arch/x86/kvm/vmx/vmx.h b/arch/x86/kvm/vmx/vmx.h
index dc8517f15bc4..5cb64ae4c8ee 100644
--- a/arch/x86/kvm/vmx/vmx.h
+++ b/arch/x86/kvm/vmx/vmx.h
@@ -342,6 +342,7 @@ static __always_inline u32 vmx_get_intr_info(struct 
kvm_vcpu *vcpu)
 }
 
 void vmx_vcpu_load_vmcs(struct kvm_vcpu *vcpu, int cpu);
+void loaded_vmcs_clear(struct loaded_vmcs *loaded_vmcs);
 int allocate_vpid(void);
 void free_vpid(int vpid);
 void vmx_set_constant_host_state(struct vcpu_vmx *vmx);
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to