Add struct kvm_caretaker_arch_ser, x86 Caretaker runtime context headers,
and generated assembly offsets for Caretaker guest entry/exit.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/x86/kvm/caretaker.h        | 230 ++++++++++++++++++++++++++++++++
 arch/x86/kvm/caretaker_asm.h    | 117 ++++++++++++++++
 arch/x86/kvm/kvm-asm-offsets.c  |  32 +++++
 include/linux/kho/abi/kvm_x86.h |  22 +++
 4 files changed, 401 insertions(+)
 create mode 100644 arch/x86/kvm/caretaker.h
 create mode 100644 arch/x86/kvm/caretaker_asm.h

diff --git a/arch/x86/kvm/caretaker.h b/arch/x86/kvm/caretaker.h
new file mode 100644
index 000000000000..9dd8815d414b
--- /dev/null
+++ b/arch/x86/kvm/caretaker.h
@@ -0,0 +1,230 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * Header for x86 KVM Caretaker common C execution engine and helpers.
+ */
+#ifndef __ARCH_X86_KVM_CARETAKER_H
+#define __ARCH_X86_KVM_CARETAKER_H
+
+/*
+ * Size of the caretaker's standalone VMX exit and IST stack in the upper half
+ * of struct caretaker_x86_page ([2048..4096)).
+ */
+#define CXP_STACK_SIZE         2048
+
+/* COM1 serial port range intercepted by Caretaker */
+#define COM1_PORT_BASE         0x3f8
+#define COM1_PORT_END          0x3ff
+
+/* Number of x2APIC MSRs (0x800 - 0x83f) */
+#define X2APIC_MSR_COUNT       0x40
+
+/* Caretaker APIC version: 6 LVT entries (max index 5), version 0x14 */
+#define CARETAKER_APIC_LVR     ((5 << 16) | 0x14)
+
+#ifndef __ASSEMBLY__
+
+#include <linux/types.h>
+#include <linux/oncore.h>
+#include <linux/kho/abi/kvm_x86.h>
+#include <linux/kvm_caretaker.h>
+
+/* Architecture-specific VM exit types for x86 */
+#define KVM_CARETAKER_EXIT_CPUID \
+       ((enum kvm_caretaker_exit_type)(KVM_CARETAKER_EXIT_ARCH + 1))
+#define KVM_CARETAKER_EXIT_MSR \
+       ((enum kvm_caretaker_exit_type)(KVM_CARETAKER_EXIT_ARCH + 2))
+#define KVM_CARETAKER_EXIT_RDTSC \
+       ((enum kvm_caretaker_exit_type)(KVM_CARETAKER_EXIT_ARCH + 3))
+
+/* 8250 UART register state for guest early printk emulation */
+struct caretaker_uart {
+       u8 lcr;
+       u8 ier;
+       u8 mcr;
+       u8 scr;
+       u8 dll;
+       u8 dlm;
+};
+
+#include <linux/processor.h>
+#include <asm/desc.h>
+#include <asm/page.h>
+
+static inline void caretaker_set_tss_desc(struct desc_struct *gdt,
+                                         unsigned long addr, unsigned int size)
+{
+       struct ldttss_desc *desc = (struct ldttss_desc *)&gdt[GDT_ENTRY_TSS];
+
+       memset(desc, 0, sizeof(*desc));
+       desc->limit0 = (u16)size;
+       desc->base0 = (u16)addr;
+       desc->base1 = (addr >> 16) & 0xFF;
+       desc->type = DESC_TSS;
+       desc->p = 1;
+       desc->limit1 = (size >> 16) & 0xF;
+       desc->base2 = (addr >> 24) & 0xFF;
+       desc->base3 = (u32)(addr >> 32);
+}
+
+struct kvm_vcpu;
+struct kvm_caretaker_exit;
+
+/**
+ * struct caretaker_x86_page - Vendor-common x86 Caretaker runtime page (4 KB)
+ * @abi:              Cross-kexec KHO ABI header (must remain at offset 0).
+ * @vcpu:             Common Caretaker vCPU execution engine descriptor.
+ * @arch_state:       Pointer to KHO-preserved vCPU architectural state buffer.
+ * @stack_orig:       Saved caller %rsp across vmx_caretaker_enter() guest 
entry.
+ * @rax:              Guest RAX switched in caretaker_vmenter.S.
+ * @rbx:              Guest RBX switched in caretaker_vmenter.S.
+ * @rcx:              Guest RCX switched in caretaker_vmenter.S.
+ * @rdx:              Guest RDX switched in caretaker_vmenter.S.
+ * @rsi:              Guest RSI switched in caretaker_vmenter.S.
+ * @rdi:              Guest RDI switched in caretaker_vmenter.S.
+ * @rbp:              Guest RBP switched in caretaker_vmenter.S.
+ * @r8:               Guest R8 switched in caretaker_vmenter.S.
+ * @r9:               Guest R9 switched in caretaker_vmenter.S.
+ * @r10:              Guest R10 switched in caretaker_vmenter.S.
+ * @r11:              Guest R11 switched in caretaker_vmenter.S.
+ * @r12:              Guest R12 switched in caretaker_vmenter.S.
+ * @r13:              Guest R13 switched in caretaker_vmenter.S.
+ * @r14:              Guest R14 switched in caretaker_vmenter.S.
+ * @r15:              Guest R15 switched in caretaker_vmenter.S.
+ * @last_exit_rip:    Guest RIP captured on VM exit and updated on instruction 
skip.
+ * @last_exit_rsp:    Guest RSP captured on VM exit.
+ * @last_exit_rflags: Guest RFLAGS captured on VM exit.
+ * @host_cr3:         Isolated Caretaker page table root PA loaded during run.
+ * @cr3:              Guest CR3 shadow value captured on VM exit.
+ * @cr0:              Guest CR0 shadow value captured on VM exit.
+ * @cr4:              Guest CR4 shadow value captured on VM exit.
+ * @efer:             Guest EFER shadow value captured on VM exit.
+ * @kernel_gs_base:   Guest MSR_KERNEL_GS_BASE switched across VMX entry/exit.
+ * @uart:             Emulated 8250 UART register state for early guest 
console.
+ * @save_guest_fpu:   True if guest FPU state can be captured via XSAVE at 
detach.
+ * @gdt:              Per-vCPU preserved GDT containing the active Caretaker 
TSS.
+ * @tss:              Per-vCPU hardware TSS whose SP0/IST point to @stack.
+ * @stack:            2 KB standalone host stack occupying the upper half of 
the
+ *                    page ([2048..4096)).  VMX sets HOST_RSP to the top of 
this
+ *                    page so vmx_caretaker_exit_handler can recover the base
+ *                    address of struct caretaker_x86_page via (%rsp & 
PAGE_MASK).
+ *
+ * Cross-kexec invariant: Only @abi (and @arch_state) may be dereferenced by 
the
+ * incoming kernel.  All remaining fields are private to the preserved 
Caretaker
+ * text executing on the isolated physical CPU during the kexec handover 
window.
+ */
+struct caretaker_x86_page {
+       /* KHO ABI prefix (offset 0) and common scheduler descriptor */
+       struct kvm_caretaker_arch_ser abi;
+       struct kvm_caretaker_vcpu vcpu;
+
+       struct kvm_vcpu_arch_ser *arch_state;
+       u64 stack_orig;
+
+       /* Guest GPRs switched in caretaker_vmenter.S */
+       u64 rax, rbx, rcx, rdx, rsi, rdi, rbp;
+       u64 r8, r9, r10, r11, r12, r13, r14, r15;
+
+       /* Guest instruction/stack pointers and control registers at VM exit */
+       u64 last_exit_rip;
+       u64 last_exit_rsp;
+       u64 last_exit_rflags;
+       u64 host_cr3;
+       u64 cr3;
+       u64 cr0;
+       u64 cr4;
+       u64 efer;
+       u64 kernel_gs_base;
+
+       /* Emulated UART and FPU capability state */
+       struct caretaker_uart uart;
+       bool save_guest_fpu;
+
+       /* Isolated host descriptors loaded while Caretaker owns the pCPU */
+       struct desc_struct gdt[GDT_ENTRIES] __aligned(16);
+       struct x86_hw_tss tss __aligned(16);
+
+       /* Upper half of Page 0 (2 KB): standalone VMX exit and IST stack */
+       u8 stack[CXP_STACK_SIZE] __aligned(CXP_STACK_SIZE);
+} __aligned(PAGE_SIZE);
+
+static_assert(offsetof(struct caretaker_x86_page, abi) == 0);
+static_assert(offsetof(struct caretaker_x86_page, abi.cb) == 0);
+static_assert(offsetof(struct caretaker_x86_page, stack) == CXP_STACK_SIZE);
+static_assert(sizeof(struct caretaker_x86_page) == PAGE_SIZE);
+
+int kvm_x86_caretaker_preserve_page(struct kvm_caretaker_arch_ser *abi,
+                                   struct page *page);
+void kvm_x86_caretaker_unpreserve_pages(struct kvm_caretaker_arch_ser *abi);
+
+/* Shared page table, IDT, and GPR helpers */
+extern gate_desc caretaker_x86_idt[IDT_ENTRIES];
+#ifdef CONFIG_LIVEUPDATE_CPU
+extern phys_addr_t x86_caretaker_pgd_pa;
+#else
+#define x86_caretaker_pgd_pa 0ULL
+#endif
+
+int kvm_x86_caretaker_init_common_page(struct caretaker_x86_page *cxp,
+                                      struct kvm_vcpu *vcpu,
+                                      size_t full_page_size);
+
+void kvm_x86_caretaker_sync_vcpu_common(struct kvm_vcpu *vcpu);
+__caretaker_text void
+kvm_x86_caretaker_detach_serialize_common(struct caretaker_x86_page *cxp,
+                                         struct kvm_vcpu_arch_ser *state);
+__caretaker_text void
+kvm_x86_caretaker_update_msr(struct kvm_vcpu_arch_ser *state,
+                            u32 msr, u64 val);
+__caretaker_text bool
+kvm_x86_caretaker_handle_exit(void *data, struct kvm_caretaker_exit *exit);
+
+void x86_preserved_iret_stub(void);
+void x86_preserved_iret_err_stub(void);
+void x86_preserved_apic_eoi_stub(void);
+__caretaker_text void kvm_x86_caretaker_arm_timer(u64 deadline_ticks);
+__caretaker_text void kvm_x86_caretaker_disarm_timer(void);
+
+/**
+ * struct kvm_x86_caretaker_runtime_ops - Preserved runtime vectors for x86 
Caretaker
+ * @detach_serialize: Serialize live vendor guest state into struct 
kvm_vcpu_arch_ser.
+ * @common:           Common Caretaker operations table (enter_guest, 
decode_exit, etc.).
+ */
+struct kvm_x86_caretaker_runtime_ops {
+       void (*detach_serialize)(void *page, struct kvm_vcpu_arch_ser *state);
+       struct kvm_caretaker_ops common;
+};
+
+/**
+ * struct kvm_x86_caretaker_ops - Vendor virtualization vectors for Caretaker
+ * @name:      Vendor name identifier ("vmx" or "svm").
+ * @init:      Initialize vendor-specific Caretaker page and hardware state 
for vCPU.
+ * @sync_vcpu: Synchronize preserved hardware state back into @vcpu during 
attach.
+ * @runtime:   Preserved runtime operations table placed in 
__cpu_preserved_data.
+ */
+struct kvm_x86_caretaker_ops {
+       const char *name;
+       void (*init)(struct kvm_vcpu *vcpu);
+       void (*sync_vcpu)(struct kvm_vcpu *vcpu, void *vcpu_data);
+       const struct kvm_x86_caretaker_runtime_ops *runtime;
+};
+
+void kvm_x86_caretaker_register_ops(const struct kvm_x86_caretaker_ops *ops);
+void kvm_x86_caretaker_unregister_ops(const struct kvm_x86_caretaker_ops *ops);
+
+struct kvm_vcpu_ser;
+
+#ifdef CONFIG_KVM_CARETAKER
+void kvm_arch_vcpu_caretaker_init(struct kvm_vcpu *vcpu);
+void kvm_arch_vcpu_caretaker_unpreserve(struct kvm_vcpu_ser *ser);
+void kvm_arch_vcpu_caretaker_finish(struct kvm_vcpu_ser *ser);
+#else
+static inline void kvm_arch_vcpu_caretaker_init(struct kvm_vcpu *vcpu) {}
+static inline void kvm_arch_vcpu_caretaker_unpreserve(struct kvm_vcpu_ser 
*ser) {}
+static inline void kvm_arch_vcpu_caretaker_finish(struct kvm_vcpu_ser *ser) {}
+#endif
+#endif /* !__ASSEMBLY__ */
+
+#endif /* __ARCH_X86_KVM_CARETAKER_H */
diff --git a/arch/x86/kvm/caretaker_asm.h b/arch/x86/kvm/caretaker_asm.h
new file mode 100644
index 000000000000..8bae9bf07e16
--- /dev/null
+++ b/arch/x86/kvm/caretaker_asm.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * Assembly macros for x86 Caretaker host context switch and guest GPRs.
+ */
+#ifndef __ARCH_X86_KVM_CARETAKER_ASM_H
+#define __ARCH_X86_KVM_CARETAKER_ASM_H
+
+#include <linux/objtool.h>
+#include <asm/segment.h>
+#include "caretaker.h"
+#include "kvm-asm-offsets.h"
+
+/* Save host callee-saved registers to caller stack */
+.macro CARETAKER_PUSH_HOST_REGS
+       pushq %rbp
+       movq %rsp, %rbp
+       pushq %rbx
+       pushq %r12
+       pushq %r13
+       pushq %r14
+       pushq %r15
+.endm
+
+/* Restore host callee-saved registers from caller stack */
+.macro CARETAKER_POP_HOST_REGS
+       popq %r15
+       popq %r14
+       popq %r13
+       popq %r12
+       popq %rbx
+       popq %rbp
+.endm
+
+/* Restore guest GPRs (RBX..R15) from preserved page structure */
+.macro CARETAKER_RESTORE_GPRS_NO_RAX base:req
+       movq CXP_REG_RBX(\base), %rbx
+       movq CXP_REG_RCX(\base), %rcx
+       movq CXP_REG_RDX(\base), %rdx
+       movq CXP_REG_RSI(\base), %rsi
+       movq CXP_REG_RBP(\base), %rbp
+       movq CXP_REG_R8(\base),  %r8
+       movq CXP_REG_R9(\base),  %r9
+       movq CXP_REG_R10(\base), %r10
+       movq CXP_REG_R11(\base), %r11
+       movq CXP_REG_R12(\base), %r12
+       movq CXP_REG_R13(\base), %r13
+       movq CXP_REG_R14(\base), %r14
+       movq CXP_REG_R15(\base), %r15
+.endm
+
+/* Restore guest GPRs including RAX from preserved page structure */
+.macro CARETAKER_RESTORE_GPRS base:req
+       movq CXP_REG_RAX(\base), %rax
+       CARETAKER_RESTORE_GPRS_NO_RAX \base
+.endm
+
+/* Save guest GPRs (RBX..R15) to preserved page structure */
+.macro CARETAKER_SAVE_GPRS_NO_RAX base:req
+       movq %rbx, CXP_REG_RBX(\base)
+       movq %rcx, CXP_REG_RCX(\base)
+       movq %rdx, CXP_REG_RDX(\base)
+       movq %rsi, CXP_REG_RSI(\base)
+       movq %rbp, CXP_REG_RBP(\base)
+       movq %r8,  CXP_REG_R8(\base)
+       movq %r9,  CXP_REG_R9(\base)
+       movq %r10, CXP_REG_R10(\base)
+       movq %r11, CXP_REG_R11(\base)
+       movq %r12, CXP_REG_R12(\base)
+       movq %r13, CXP_REG_R13(\base)
+       movq %r14, CXP_REG_R14(\base)
+       movq %r15, CXP_REG_R15(\base)
+.endm
+
+/* Save guest GPRs including RAX to preserved page structure */
+.macro CARETAKER_SAVE_GPRS base:req
+       movq %rax, CXP_REG_RAX(\base)
+       CARETAKER_SAVE_GPRS_NO_RAX \base
+.endm
+
+/*
+ * Clear CPU microarchitectural buffers (MDS/TAA/MMIO Stale Data/RFDS) via VERW
+ * before entering guest mode, using a selector stored in .data.cpu_preserved.
+ */
+.macro CARETAKER_CLEAR_CPU_BUFFERS
+       .pushsection .data.cpu_preserved, "aw"
+       .align 2
+.Lcaretaker_verw_sel\@:
+       .word __KERNEL_DS
+       .popsection
+       verw .Lcaretaker_verw_sel\@(%rip)
+.endm
+
+/*
+ * Stuff the 32-entry hardware Return Stack Buffer (RSB) on VM-exit without
+ * referencing external thunks or unmapped per-CPU call-depth variables.
+ */
+.macro CARETAKER_FILL_RETURN_BUFFER reg:req
+       mov     $(RSB_CLEAR_LOOPS / 2), \reg
+.Lrsb_loop\@:
+       ANNOTATE_INTRA_FUNCTION_CALL
+       call    .Lrsb_1\@
+       int3
+.Lrsb_1\@:
+       ANNOTATE_INTRA_FUNCTION_CALL
+       call    .Lrsb_2\@
+       int3
+.Lrsb_2\@:
+       add     $16, %rsp
+       dec     \reg
+       jnz     .Lrsb_loop\@
+       lfence
+.endm
+
+#endif /* __ARCH_X86_KVM_CARETAKER_ASM_H */
diff --git a/arch/x86/kvm/kvm-asm-offsets.c b/arch/x86/kvm/kvm-asm-offsets.c
index 36ac61724dd7..e743ddae255b 100644
--- a/arch/x86/kvm/kvm-asm-offsets.c
+++ b/arch/x86/kvm/kvm-asm-offsets.c
@@ -9,6 +9,9 @@
 #include <linux/kbuild.h>
 #include "vmx/vmx.h"
 #include "svm/svm.h"
+#ifdef CONFIG_KVM_CARETAKER
+#include "caretaker.h"
+#endif
 
 static void __used common(void)
 {
@@ -27,4 +30,33 @@ static void __used common(void)
                OFFSET(VMX_vcpu_arch_regs, vcpu_vmx, vcpu.arch.regs);
                OFFSET(VMX_spec_ctrl, vcpu_vmx, spec_ctrl);
        }
+
+#ifdef CONFIG_KVM_CARETAKER
+       /*
+        * Offsets into the caretaker page used by the on-core guest entry and
+        * exit paths in {vmx,svm}/caretaker_vmenter.S.  These used to be a
+        * hand-maintained table of literals in caretaker.h.
+        */
+       BLANK();
+       OFFSET(CXP_VMCS_PA, caretaker_x86_page, abi.vmcs_pa);
+       OFFSET(CXP_STACK_ORIG, caretaker_x86_page, stack_orig);
+       OFFSET(CXP_KERNEL_GS_BASE, caretaker_x86_page, kernel_gs_base);
+
+       BLANK();
+       OFFSET(CXP_REG_RAX, caretaker_x86_page, rax);
+       OFFSET(CXP_REG_RBX, caretaker_x86_page, rbx);
+       OFFSET(CXP_REG_RCX, caretaker_x86_page, rcx);
+       OFFSET(CXP_REG_RDX, caretaker_x86_page, rdx);
+       OFFSET(CXP_REG_RSI, caretaker_x86_page, rsi);
+       OFFSET(CXP_REG_RDI, caretaker_x86_page, rdi);
+       OFFSET(CXP_REG_RBP, caretaker_x86_page, rbp);
+       OFFSET(CXP_REG_R8, caretaker_x86_page, r8);
+       OFFSET(CXP_REG_R9, caretaker_x86_page, r9);
+       OFFSET(CXP_REG_R10, caretaker_x86_page, r10);
+       OFFSET(CXP_REG_R11, caretaker_x86_page, r11);
+       OFFSET(CXP_REG_R12, caretaker_x86_page, r12);
+       OFFSET(CXP_REG_R13, caretaker_x86_page, r13);
+       OFFSET(CXP_REG_R14, caretaker_x86_page, r14);
+       OFFSET(CXP_REG_R15, caretaker_x86_page, r15);
+#endif
 }
diff --git a/include/linux/kho/abi/kvm_x86.h b/include/linux/kho/abi/kvm_x86.h
index 316250482db5..72038ca65b0f 100644
--- a/include/linux/kho/abi/kvm_x86.h
+++ b/include/linux/kho/abi/kvm_x86.h
@@ -79,6 +79,28 @@ static_assert(offsetof(struct kvm_vcpu_arch_ser, msrs) % 
sizeof(u64) == 0,
 static_assert(offsetof(struct kvm_vcpu_arch_ser, xsave) % 64 == 0,
              "xsave must be 64-byte aligned to be XSAVE-able in place");
 
+#define KVM_X86_CARETAKER_MAX_PAGES    5
+
+/**
+ * struct kvm_caretaker_arch_ser - x86-specific Caretaker control block ABI
+ * @cb:                 Common Caretaker control block header (must be at 
offset 0).
+ * @apic_id:            Hardware APIC ID of the preserved core, used by the 
incoming
+ *                      kernel to deliver the attach-signal IPI.
+ * @nr_preserved_pages: Number of valid physical addresses in 
@preserved_pages_pa.
+ * @vmcs_pa:            Physical address of the VMCS the Caretaker ran the 
vCPU on
+ *                      (Intel VMX), used by the incoming kernel to VMCLEAR and
+ *                      flush the VMCS after the vCPU stops.
+ * @preserved_pages_pa: Physical addresses of per-vCPU hardware control pages
+ *                      preserved via kho_preserve_pages().
+ */
+struct kvm_caretaker_arch_ser {
+       struct kvm_caretaker_cb_ser cb;
+       u32 apic_id;
+       u32 nr_preserved_pages;
+       u64 vmcs_pa;
+       u64 preserved_pages_pa[KVM_X86_CARETAKER_MAX_PAGES];
+} __packed;
+
 #endif /* CONFIG_X86_64 */
 
 #endif /* _LINUX_KHO_ABI_KVM_X86_H */
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to