Implement AMD SVM Caretaker VMCB preservation, exit decoding, and
detach-time serialization in arch/x86/kvm/svm/caretaker.c.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/x86/kvm/Makefile                |  23 +-
 arch/x86/kvm/svm/caretaker.c         | 467 +++++++++++++++++++++++++++
 arch/x86/kvm/svm/caretaker_vmenter.S |   4 +-
 arch/x86/kvm/svm/svm.c               |  11 +-
 4 files changed, 500 insertions(+), 5 deletions(-)
 create mode 100644 arch/x86/kvm/svm/caretaker.c

diff --git a/arch/x86/kvm/Makefile b/arch/x86/kvm/Makefile
index b550a3fd0708..9213d274817e 100644
--- a/arch/x86/kvm/Makefile
+++ b/arch/x86/kvm/Makefile
@@ -26,6 +26,7 @@ kvm-intel-$(CONFIG_KVM_HYPERV)        += vmx/hyperv.o 
vmx/hyperv_evmcs.o
 kvm-intel-$(CONFIG_KVM_INTEL_TDX)      += vmx/tdx.o
 
 kvm-amd-y              += svm/svm.o svm/vmenter.o svm/pmu.o svm/nested.o 
svm/avic.o
+kvm-amd-$(CONFIG_KVM_CARETAKER) += svm/caretaker.o svm/caretaker_vmenter.o
 
 kvm-amd-$(CONFIG_KVM_AMD_SEV)  += svm/sev.o
 kvm-amd-$(CONFIG_KVM_HYPERV)   += svm/hyperv.o
@@ -43,6 +44,26 @@ obj-$(CONFIG_KVM_AMD)        += kvm-amd.o
 AFLAGS_svm/vmenter.o    := -iquote $(obj)
 $(obj)/svm/vmenter.o: $(obj)/kvm-asm-offsets.h
 
+AFLAGS_svm/caretaker_vmenter.o := -iquote $(obj)
+$(obj)/svm/caretaker_vmenter.o: $(obj)/kvm-asm-offsets.h
+
+KASAN_SANITIZE_caretaker.o := n
+KCSAN_SANITIZE_caretaker.o := n
+UBSAN_SANITIZE_caretaker.o := n
+KCOV_INSTRUMENT_caretaker.o := n
+KASAN_SANITIZE_svm/caretaker.o := n
+KCSAN_SANITIZE_svm/caretaker.o := n
+UBSAN_SANITIZE_svm/caretaker.o := n
+KCOV_INSTRUMENT_svm/caretaker.o := n
+KASAN_SANITIZE_vmx/caretaker.o := n
+KCSAN_SANITIZE_vmx/caretaker.o := n
+UBSAN_SANITIZE_vmx/caretaker.o := n
+KCOV_INSTRUMENT_vmx/caretaker.o := n
+CFLAGS_REMOVE_caretaker.o = $(CC_FLAGS_FTRACE)
+CFLAGS_caretaker.o += -fno-stack-protector $(call 
cc-option,-ftrivial-auto-var-init=uninitialized) $(call 
cc-option,-fno-jump-tables)
+CFLAGS_REMOVE_svm/caretaker.o = $(CC_FLAGS_FTRACE)
+CFLAGS_svm/caretaker.o := -fno-stack-protector $(call 
cc-option,-ftrivial-auto-var-init=uninitialized) $(call 
cc-option,-fno-jump-tables)
+
 AFLAGS_vmx/vmenter.o    := -iquote $(obj)
 $(obj)/vmx/vmenter.o: $(obj)/kvm-asm-offsets.h
 
@@ -50,7 +71,7 @@ AFLAGS_vmx/caretaker_vmenter.o := -iquote $(obj)
 $(obj)/vmx/caretaker_vmenter.o: $(obj)/kvm-asm-offsets.h
 
 CFLAGS_REMOVE_vmx/caretaker.o = $(CC_FLAGS_FTRACE)
-CFLAGS_vmx/caretaker.o := -fno-stack-protector
+CFLAGS_vmx/caretaker.o := -fno-stack-protector $(call 
cc-option,-ftrivial-auto-var-init=uninitialized) $(call 
cc-option,-fno-jump-tables)
 
 
 $(obj)/kvm-asm-offsets.h: $(obj)/kvm-asm-offsets.s FORCE
diff --git a/arch/x86/kvm/svm/caretaker.c b/arch/x86/kvm/svm/caretaker.c
new file mode 100644
index 000000000000..cb30e336f014
--- /dev/null
+++ b/arch/x86/kvm/svm/caretaker.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AMD SVM Caretaker Standalone Execution Engine
+ *
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * Runs the AMD SVM guest VMRUN loop in an isolated, KHO-preserved memory
+ * page that remains alive and executing across kexec relocation and
+ * kernel handover.
+ */
+
+#include <linux/cpu_preserve.h>
+#include <linux/delay.h>
+#include <linux/kernel.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 <linux/pgtable.h>
+#include <linux/processor.h>
+#include <asm/set_memory.h>
+#include <asm/svm.h>
+#include <asm/tlbflush.h>
+
+#include "../caretaker.h"
+#include "caretaker.h"
+#include "mmu.h"
+#include "svm.h"
+#include "svm_ops.h"
+
+static int svm_caretaker_init_page(struct caretaker_svm_page *csp, struct 
kvm_vcpu *vcpu);
+STACK_FRAME_NON_STANDARD(svm_caretaker_init_page);
+
+static int svm_caretaker_init_page(struct caretaker_svm_page *csp, struct 
kvm_vcpu *vcpu)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+       int ret;
+
+       if (is_guest_mode(vcpu) || is_sev_guest(vcpu) ||
+           kvm_vcpu_apicv_activated(vcpu))
+               return -EOPNOTSUPP;
+
+       ret = kvm_x86_caretaker_init_common_page(&csp->common, vcpu, 
sizeof(*csp));
+       if (ret)
+               return ret;
+
+       if (svm->vmcb01.ptr)
+               csp->vmcb = *svm->vmcb01.ptr;
+       csp->vmcb_pa = virt_to_phys(&csp->vmcb);
+       csp->hsave_pa = virt_to_phys(csp->hsave_area);
+
+       if (svm->msrpm)
+               memcpy(csp->msrpm, svm->msrpm, MSRPM_SIZE);
+       else
+               memset(csp->msrpm, 0xff, MSRPM_SIZE);
+       memset(csp->iopm, 0xff, IOPM_SIZE);
+       csp->vmcb.control.msrpm_base_pa = virt_to_phys(csp->msrpm);
+       csp->vmcb.control.iopm_base_pa = virt_to_phys(csp->iopm);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_MSR_PROT);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_IOIO_PROT);
+
+       /* Enable HLT/CPUID/VMMCALL intercepts handled by standalone loop */
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_HLT);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_PAUSE);
+       csp->vmcb.control.pause_filter_count = 4096;
+       csp->vmcb.control.pause_filter_thresh = 128;
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_RDTSC);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_VMMCALL);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_CPUID);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_MONITOR);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_MWAIT);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_NMI);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_INTR);
+       vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_INIT);
+
+       /*
+        * Clear interrupt-window intercepts and virtual IRQ injection state
+        * left behind in vmcb01 if vcpu_enter_guest() was aborted while
+        * KVM had an active interrupt window open (matching VMX clearing
+        * CPU_BASED_INTR_WINDOW_EXITING | CPU_BASED_NMI_WINDOW_EXITING).
+        */
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_VINTR);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_IRET);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_STGI);
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_CR8_WRITE);
+
+       /* Clear VMCB clean bits and flush TLB for execution on Caretaker CPU */
+       csp->vmcb.control.clean = 0;
+       csp->vmcb.control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID;
+       csp->vmcb.control.int_ctl &= ~V_IRQ_INJECTION_BITS_MASK;
+       csp->vmcb.control.int_ctl |= V_INTR_MASKING_MASK;
+       if (csp->vmcb.control.int_ctl & V_GIF_ENABLE_MASK)
+               csp->vmcb.control.int_ctl |= V_GIF_MASK;
+       csp->vmcb.control.int_vector = 0;
+       csp->vmcb.control.int_state = 0;
+       csp->vmcb.control.event_inj = 0;
+       csp->vmcb.control.exit_int_info = 0;
+       if (csp->vmcb.control.asid == 0)
+               csp->vmcb.control.asid = 1;
+       csp->vmcb.save.rflags = kvm_get_rflags(vcpu);
+       csp->vmcb.save.rax = csp->common.rax;
+       csp->vmcb.save.rsp = kvm_rsp_read(vcpu);
+       csp->vmcb.save.rip = kvm_rip_read(vcpu);
+       csp->common.cr3 = csp->vmcb.save.cr3;
+       csp->common.cr0 = csp->vmcb.save.cr0;
+       csp->common.cr4 = csp->vmcb.save.cr4;
+       csp->common.efer = csp->vmcb.save.efer;
+       csp->common.last_exit_rsp = csp->vmcb.save.rsp;
+       csp->common.last_exit_rip = csp->vmcb.save.rip;
+       csp->common.last_exit_rflags = csp->vmcb.save.rflags;
+
+       return 0;
+}
+
+void svm_caretaker_init(struct kvm_vcpu *vcpu)
+{
+       struct caretaker_svm_page *csp;
+
+       csp = kho_alloc_preserve(sizeof(*csp));
+       if (IS_ERR(csp)) {
+               pr_err("caretaker svm: failed to allocate preserved page\n");
+               return;
+       }
+
+       if (svm_caretaker_init_page(csp, vcpu)) {
+               kvm_x86_caretaker_unpreserve_pages(&csp->common.abi);
+               vcpu->caretaker.cb = NULL;
+               kho_unpreserve_free(csp);
+       }
+}
+
+static __cpu_preserved_text void
+svm_caretaker_seg_to_kvm(struct kvm_segment *var, const struct vmcb_seg *s, 
int seg)
+{
+       var->base = s->base;
+       var->limit = s->limit;
+       var->selector = s->selector;
+       var->type = s->attrib & SVM_SELECTOR_TYPE_MASK;
+       var->s = (s->attrib >> SVM_SELECTOR_S_SHIFT) & 1;
+       var->dpl = (s->attrib >> SVM_SELECTOR_DPL_SHIFT) & 3;
+       var->present = (s->attrib >> SVM_SELECTOR_P_SHIFT) & 1;
+       var->avl = (s->attrib >> SVM_SELECTOR_AVL_SHIFT) & 1;
+       var->l = (s->attrib >> SVM_SELECTOR_L_SHIFT) & 1;
+       var->db = (s->attrib >> SVM_SELECTOR_DB_SHIFT) & 1;
+       var->g = s->limit > 0xfffff;
+       var->unusable = !var->present;
+       if (seg == VCPU_SREG_TR)
+               var->type |= 0x2;
+}
+
+static __cpu_preserved_text void
+svm_caretaker_detach_serialize(void *page, struct kvm_vcpu_arch_ser *state)
+{
+       struct caretaker_svm_page *csp = page;
+       struct vmcb_save_area *save = &csp->vmcb.save;
+
+       csp->common.cr0 = save->cr0;
+       csp->common.cr3 = save->cr3;
+       csp->common.cr4 = save->cr4;
+       csp->common.efer = save->efer;
+       csp->common.last_exit_rip = save->rip;
+       csp->common.last_exit_rsp = save->rsp;
+       csp->common.last_exit_rflags = save->rflags;
+
+       kvm_x86_caretaker_detach_serialize_common(&csp->common, state);
+
+       state->sregs.cr2 = save->cr2;
+       svm_caretaker_seg_to_kvm(&state->sregs.cs, &save->cs, VCPU_SREG_CS);
+       svm_caretaker_seg_to_kvm(&state->sregs.ds, &save->ds, VCPU_SREG_DS);
+       svm_caretaker_seg_to_kvm(&state->sregs.es, &save->es, VCPU_SREG_ES);
+       svm_caretaker_seg_to_kvm(&state->sregs.fs, &save->fs, VCPU_SREG_FS);
+       svm_caretaker_seg_to_kvm(&state->sregs.gs, &save->gs, VCPU_SREG_GS);
+       svm_caretaker_seg_to_kvm(&state->sregs.ss, &save->ss, VCPU_SREG_SS);
+       svm_caretaker_seg_to_kvm(&state->sregs.tr, &save->tr, VCPU_SREG_TR);
+       svm_caretaker_seg_to_kvm(&state->sregs.ldt, &save->ldtr, 
VCPU_SREG_LDTR);
+       state->sregs.gdt.base = save->gdtr.base;
+       state->sregs.gdt.limit = save->gdtr.limit;
+       state->sregs.idt.base = save->idtr.base;
+       state->sregs.idt.limit = save->idtr.limit;
+
+       kvm_x86_caretaker_update_msr(state, MSR_STAR, save->star);
+       kvm_x86_caretaker_update_msr(state, MSR_LSTAR, save->lstar);
+       kvm_x86_caretaker_update_msr(state, MSR_CSTAR, save->cstar);
+       kvm_x86_caretaker_update_msr(state, MSR_SYSCALL_MASK, save->sfmask);
+       kvm_x86_caretaker_update_msr(state, MSR_KERNEL_GS_BASE, 
save->kernel_gs_base);
+       kvm_x86_caretaker_update_msr(state, MSR_IA32_SYSENTER_CS, 
save->sysenter_cs);
+       kvm_x86_caretaker_update_msr(state, MSR_IA32_SYSENTER_ESP, 
save->sysenter_esp);
+       kvm_x86_caretaker_update_msr(state, MSR_IA32_SYSENTER_EIP, 
save->sysenter_eip);
+}
+
+static void svm_caretaker_sync_vcpu(struct kvm_vcpu *vcpu, void *vcpu_data)
+{
+       struct vcpu_svm *svm = to_svm(vcpu);
+       struct svm_cpu_data *sd = per_cpu_ptr(&svm_data, 
raw_smp_processor_id());
+
+       if (sd && sd->save_area_pa)
+               wrmsrq(MSR_VM_HSAVE_PA, sd->save_area_pa);
+
+       kvm_x86_caretaker_sync_vcpu_common(vcpu);
+
+       if (svm->vmcb) {
+               if (!svm->vmcb->control.next_rip && svm->vmcb->control.insn_len)
+                       svm->vmcb->control.next_rip = kvm_rip_read(vcpu) +
+                                                     
svm->vmcb->control.insn_len;
+               if (!svm->vmcb->control.next_rip) {
+                       switch (svm->vmcb->control.exit_code) {
+                       case SVM_EXIT_CPUID:
+                       case SVM_EXIT_MSR:
+                       case SVM_EXIT_PAUSE:
+                               svm->vmcb->control.next_rip = 
kvm_rip_read(vcpu) + 2;
+                               break;
+                       case SVM_EXIT_HLT:
+                               svm->vmcb->control.next_rip = 
kvm_rip_read(vcpu) + 1;
+                               break;
+                       case SVM_EXIT_VMMCALL:
+                       case SVM_EXIT_XSETBV:
+                       case SVM_EXIT_INVLPGA:
+                               svm->vmcb->control.next_rip = 
kvm_rip_read(vcpu) + 3;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+
+               svm->vmcb->control.clean = 0;
+               svm->vmcb->control.tlb_ctl = TLB_CONTROL_FLUSH_ALL_ASID;
+               svm->vmcb->control.int_ctl &= ~V_IRQ_INJECTION_BITS_MASK;
+               svm->vmcb->control.int_ctl |= V_INTR_MASKING_MASK;
+               if (vgif)
+                       svm_set_gif(svm, true);
+               svm_clr_intercept(svm, INTERCEPT_VINTR);
+               svm->vmcb->control.int_state = 0;
+               svm->nmi_masked = false;
+               svm->awaiting_iret_completion = false;
+               svm_set_intercept(svm, INTERCEPT_INTR);
+               svm_set_intercept(svm, INTERCEPT_NMI);
+               svm_set_intercept(svm, INTERCEPT_INIT);
+               svm_clr_intercept(svm, INTERCEPT_RDTSC);
+               svm_clr_intercept(svm, INTERCEPT_PAUSE);
+               svm_recalc_intercepts(vcpu);
+               svm->vmcb->control.event_inj = 0;
+               svm->vmcb->control.exit_int_info = 0;
+       }
+}
+
+void __cpu_preserved_text
+svm_caretaker_decode_exit(void *page,
+                         struct kvm_caretaker_exit *exit)
+{
+       struct caretaker_svm_page *csp = page;
+       u64 exit_code = csp->vmcb.control.exit_code;
+       u64 info1 = csp->vmcb.control.exit_info_1;
+       u64 info2 = csp->vmcb.control.exit_info_2;
+       struct vmcb *vmcb = &csp->vmcb;
+       u64 rip = vmcb->save.rip;
+       u32 insn_len;
+
+       if (vmcb->control.next_rip && vmcb->control.next_rip > rip)
+               insn_len = vmcb->control.next_rip - rip;
+       else
+               insn_len = vmcb->control.insn_len;
+
+       csp->common.last_exit_rip = rip;
+       csp->common.last_exit_rsp = vmcb->save.rsp;
+       csp->common.last_exit_rflags = vmcb->save.rflags;
+       csp->common.cr3 = vmcb->save.cr3;
+       csp->common.cr0 = vmcb->save.cr0;
+       csp->common.cr4 = vmcb->save.cr4;
+       csp->common.efer = vmcb->save.efer;
+       vmcb->control.exit_int_info = 0;
+
+       exit->rip = rip;
+       exit->insn_len = insn_len;
+       exit->raw_reason = exit_code;
+       exit->type = KVM_CARETAKER_EXIT_ARCH;
+
+       switch (exit_code) {
+       case SVM_EXIT_IOIO: {
+               u16 port = (u16)(info1 >> 16);
+
+               if (!exit->insn_len && info2 > rip)
+                       exit->insn_len = (u32)(info2 - rip);
+
+               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 = !(info1 & SVM_IOIO_TYPE_MASK);
+                       exit->mmio_io.size = (u8)((info1 & SVM_IOIO_SIZE_MASK) 
>>
+                                                 SVM_IOIO_SIZE_SHIFT);
+                       exit->mmio_io.is_mmio = false;
+                       exit->mmio_io.val_ptr = &csp->common.rax;
+               }
+               break;
+       }
+       case SVM_EXIT_HLT:
+               exit->type = KVM_CARETAKER_EXIT_IDLE;
+               if (!exit->insn_len)
+                       exit->insn_len = 1;
+               break;
+       case SVM_EXIT_PAUSE:
+               exit->type = KVM_CARETAKER_EXIT_IDLE;
+               if (!exit->insn_len)
+                       exit->insn_len = 2;
+               break;
+       case SVM_EXIT_CPUID:
+               exit->type = KVM_CARETAKER_EXIT_CPUID;
+               if (!exit->insn_len)
+                       exit->insn_len = 2;
+               break;
+       case SVM_EXIT_MSR:
+               exit->type = KVM_CARETAKER_EXIT_MSR;
+               exit->msr.msr = (u32)csp->common.rcx;
+               exit->msr.is_write = (info1 != 0);
+               if (!exit->insn_len)
+                       exit->insn_len = 2;
+               break;
+       case SVM_EXIT_INVD:
+       case SVM_EXIT_WBINVD:
+               exit->type = KVM_CARETAKER_EXIT_INSN_STEP;
+               if (!exit->insn_len)
+                       exit->insn_len = 2;
+               break;
+       case SVM_EXIT_NPF:
+               exit->type = KVM_CARETAKER_EXIT_UNHANDLED;
+               break;
+       case SVM_EXIT_INTR:
+               kvm_x86_caretaker_disarm_timer();
+               asm volatile("sti\n\tnop\n\tpause\n\tcli" : : : "memory");
+               exit->type = KVM_CARETAKER_EXIT_PREEMPT_TIMER;
+               exit->insn_len = 0;
+               break;
+       case SVM_EXIT_NMI:
+       case SVM_EXIT_INIT:
+               exit->type = KVM_CARETAKER_EXIT_PREEMPT_TIMER;
+               exit->insn_len = 0;
+               break;
+       case SVM_EXIT_VINTR:
+       case SVM_EXIT_WRITE_CR8:
+               exit->type = KVM_CARETAKER_EXIT_CROSS_VCPU;
+               exit->insn_len = 0;
+               break;
+       case SVM_EXIT_VMMCALL:
+               exit->type = KVM_CARETAKER_EXIT_CROSS_VCPU;
+               if (!exit->insn_len)
+                       exit->insn_len = 3;
+               break;
+       case SVM_EXIT_RDTSC:
+               exit->type = KVM_CARETAKER_EXIT_RDTSC;
+               if (!exit->insn_len)
+                       exit->insn_len = 2;
+               break;
+       default:
+               /*
+                * Everything not decoded above keeps KVM_CARETAKER_EXIT_ARCH
+                * and stalls in kvm_x86_caretaker_handle_exit().
+                *
+                * XSETBV and RDTSCP used to be decoded here and are
+                * deliberately not any more.  INSN_STEP means "the instruction
+                * had no architectural effect worth emulating", which holds
+                * for INVD and WBINVD on a coherent, guest_memfd-backed VM but
+                * not for these: stepping XSETBV leaves XCR0 unchanged while
+                * the guest believes it changed.  RDTSCP is the same story via
+                * KVM_CARETAKER_EXIT_RDTSC, which writes only RAX and RDX and
+                * would leave a stale RCX instead of IA32_TSC_AUX.
+                */
+               break;
+       }
+}
+
+static __cpu_preserved_text void
+svm_caretaker_advance_rip(void *page, u64 rip)
+{
+       struct caretaker_svm_page *csp = page;
+
+       csp->vmcb.save.rax = csp->common.rax;
+       csp->vmcb.save.rip = rip;
+       csp->common.last_exit_rip = rip;
+       csp->common.last_exit_rsp = csp->vmcb.save.rsp;
+}
+
+static __cpu_preserved_text void
+svm_caretaker_arm_timer(void *page, u64 deadline_ticks)
+{
+       struct caretaker_svm_page *csp = page;
+
+       if (deadline_ticks) {
+               vmcb_set_intercept(&csp->vmcb.control, INTERCEPT_INTR);
+               kvm_x86_caretaker_arm_timer(deadline_ticks);
+       } else {
+               vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_INTR);
+       }
+}
+
+static __cpu_preserved_text void
+svm_caretaker_disarm_timer(void *page)
+{
+       struct caretaker_svm_page *csp = page;
+
+       kvm_x86_caretaker_disarm_timer();
+       vmcb_clr_intercept(&csp->vmcb.control, INTERCEPT_INTR);
+       asm volatile("sti\n\tnop\n\tpause\n\tcli" : : : "memory");
+}
+
+static __cpu_preserved_text void
+svm_caretaker_pre_enter(void *page)
+{
+       struct caretaker_svm_page *csp = page;
+       u64 efer;
+
+       csp->orig_hsave_pa = native_rdmsrq(MSR_VM_HSAVE_PA);
+
+       /* Ensure EFER_SVME is enabled and HSAVE is configured */
+       efer = native_rdmsrq(MSR_EFER);
+       csp->orig_efer = efer;
+       native_wrmsrq(MSR_EFER, efer | EFER_SVME);
+       native_wrmsrq(MSR_VM_HSAVE_PA, csp->hsave_pa);
+}
+
+static __cpu_preserved_text void
+svm_caretaker_post_exit(void *page)
+{
+       struct caretaker_svm_page *csp = page;
+
+       native_wrmsrq(MSR_VM_HSAVE_PA, csp->orig_hsave_pa);
+       asm volatile("1: stgi\n\t"
+                    "2:\n\t"
+                    _ASM_EXTABLE(1b, 2b)
+                    : : : "memory");
+       if (!(csp->orig_efer & EFER_SVME))
+               native_wrmsrq(MSR_EFER, native_rdmsrq(MSR_EFER) & ~EFER_SVME);
+}
+
+static const struct kvm_x86_caretaker_runtime_ops svm_caretaker_runtime_ops 
__cpu_preserved_data = {
+       .detach_serialize = svm_caretaker_detach_serialize,
+       .common = {
+               .enter_guest = svm_caretaker_enter,
+               .decode_exit = svm_caretaker_decode_exit,
+               .handle_arch_exit = kvm_x86_caretaker_handle_exit,
+               .advance_rip = svm_caretaker_advance_rip,
+               .arm_timer = svm_caretaker_arm_timer,
+               .disarm_timer = svm_caretaker_disarm_timer,
+               .pre_run = svm_caretaker_pre_enter,
+               .post_run = svm_caretaker_post_exit,
+       },
+};
+
+static const struct kvm_x86_caretaker_ops svm_caretaker_ops = {
+       .name = "svm",
+       .init = svm_caretaker_init,
+       .sync_vcpu = svm_caretaker_sync_vcpu,
+       .runtime = &svm_caretaker_runtime_ops,
+};
+
+void svm_caretaker_register(void)
+{
+       kvm_x86_caretaker_register_ops(&svm_caretaker_ops);
+}
+
+void svm_caretaker_unregister(void)
+{
+       kvm_x86_caretaker_unregister_ops(&svm_caretaker_ops);
+}
diff --git a/arch/x86/kvm/svm/caretaker_vmenter.S 
b/arch/x86/kvm/svm/caretaker_vmenter.S
index e0276a34403e..10303268844e 100644
--- a/arch/x86/kvm/svm/caretaker_vmenter.S
+++ b/arch/x86/kvm/svm/caretaker_vmenter.S
@@ -27,7 +27,7 @@ SYM_TYPED_FUNC_START(svm_caretaker_enter)
        sti
 
        /* 0. Save host VMCB state (FS, GS, TR, LDTR) to host save area */
-       movq CXP_HSAVE_PA(%rdi), %rax
+       movq CSP_HSAVE_PA_OFFSET(%rdi), %rax
        testq %rax, %rax
        jz 10f
        vmsave %rax
@@ -69,7 +69,7 @@ SYM_TYPED_FUNC_START(svm_caretaker_enter)
        movq %rbx, CXP_REG_RAX(%rdi)
 
        /* 5a. Restore host VMCB state (FS, GS, TR, LDTR) from HSAVE */
-       movq CXP_HSAVE_PA(%rdi), %rax
+       movq CSP_HSAVE_PA_OFFSET(%rdi), %rax
        testq %rax, %rax
        jz 40f
        vmload %rax
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 7d59d301e1e5..ca6b0d1e80fe 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -54,6 +54,7 @@
 #include "svm.h"
 #include "svm_ops.h"
 
+#include "caretaker.h"
 #include "hyperv.h"
 #include "kvm_onhyperv.h"
 #include "svm_onhyperv.h"
@@ -973,6 +974,8 @@ static void svm_hardware_unsetup(void)
 {
        int cpu;
 
+       svm_caretaker_unregister();
+
        avic_hardware_unsetup();
 
        sev_hardware_unsetup();
@@ -1117,7 +1120,7 @@ static void svm_recalc_instruction_intercepts(struct 
kvm_vcpu *vcpu)
                svm_clr_intercept(svm, INTERCEPT_RDPMC);
 }
 
-static void svm_recalc_intercepts(struct kvm_vcpu *vcpu)
+void svm_recalc_intercepts(struct kvm_vcpu *vcpu)
 {
        svm_recalc_instruction_intercepts(vcpu);
        svm_recalc_msr_intercepts(vcpu);
@@ -4344,6 +4347,7 @@ static void svm_complete_interrupts(struct kvm_vcpu *vcpu)
        bool nmi_l1_to_l2 = svm->nmi_l1_to_l2;
        bool soft_int_injected = svm->soft_int_injected;
 
+       svm->vmcb->control.exit_int_info = 0;
        svm->nmi_l1_to_l2 = false;
        svm->soft_int_injected = false;
 
@@ -4396,7 +4400,8 @@ static void svm_complete_interrupts(struct kvm_vcpu *vcpu)
                break;
        }
        case SVM_EXITINTINFO_TYPE_INTR:
-               kvm_queue_interrupt(vcpu, vector, false);
+               if (kvm_get_rflags(vcpu) & X86_EFLAGS_IF)
+                       kvm_queue_interrupt(vcpu, vector, false);
                break;
        case SVM_EXITINTINFO_TYPE_SOFT:
                kvm_queue_interrupt(vcpu, vector, true);
@@ -5762,6 +5767,8 @@ static __init int svm_hardware_setup(void)
                        goto err;
        }
 
+       svm_caretaker_register();
+
        return 0;
 
 err:
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to