Define struct kvm_caretaker_arch_ser (256B), ARM64 Caretaker runtime
context structures, and EL2 exception vector interfaces.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/arm64/include/asm/caretaker.h |  56 ++++++
 arch/arm64/kernel/cpu_preserve.c   | 309 +++++++++++++++++++++++++++++
 arch/arm64/kvm/caretaker.h         | 130 ++++++++++++
 include/linux/kho/abi/kvm_arm64.h  |  32 +++
 4 files changed, 527 insertions(+)
 create mode 100644 arch/arm64/include/asm/caretaker.h
 create mode 100644 arch/arm64/kvm/caretaker.h

diff --git a/arch/arm64/include/asm/caretaker.h 
b/arch/arm64/include/asm/caretaker.h
new file mode 100644
index 000000000000..7683534a28f8
--- /dev/null
+++ b/arch/arm64/include/asm/caretaker.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ */
+#ifndef __ASM_ARM64_CARETAKER_H
+#define __ASM_ARM64_CARETAKER_H
+
+#include <linux/types.h>
+#include <linux/cpu_preserve.h>
+#include <asm/arch_timer.h>
+#include <asm/cputype.h>
+#include <asm/pgtable-types.h>
+#include <asm/sysreg.h>
+
+#ifdef CONFIG_LIVEUPDATE_CPU
+extern phys_addr_t arm64_caretaker_pgd_pa;
+#else
+#define arm64_caretaker_pgd_pa 0ULL
+#endif
+
+extern char caretaker_hyp_vector[];
+
+extern bool arm64_caretaker_has_ptrauth;
+
+static inline int arm64_caretaker_get_pcpu(void)
+{
+       u64 mpidr = read_cpuid_mpidr() & MPIDR_HWID_BITMASK;
+       int cpu = arch_cpu_preserved_mpidr_to_cpu(mpidr);
+
+       if (cpu >= 0)
+               return cpu;
+       return (int)MPIDR_AFFINITY_LEVEL(mpidr, 0);
+}
+
+#ifdef CONFIG_LIVEUPDATE_CPU
+void gicv3_caretaker_clear_active_priorities(void);
+void gicv3_caretaker_enable_sgi(void);
+void gicv3_caretaker_clear_sgi(void);
+void gicv3_caretaker_kick_cpu(int cpu);
+int gicv3_caretaker_get_redist_region(int idx, phys_addr_t *pa,
+                                     unsigned long *va, size_t *size);
+#else
+static inline void gicv3_caretaker_clear_active_priorities(void) {}
+static inline void gicv3_caretaker_enable_sgi(void) {}
+static inline void gicv3_caretaker_clear_sgi(void) {}
+static inline void gicv3_caretaker_kick_cpu(int cpu) {}
+static inline int gicv3_caretaker_get_redist_region(int idx, phys_addr_t *pa,
+                                                   unsigned long *va,
+                                                   size_t *size)
+{
+       return -ENOENT;
+}
+#endif
+
+#endif /* __ASM_ARM64_CARETAKER_H */
diff --git a/arch/arm64/kernel/cpu_preserve.c b/arch/arm64/kernel/cpu_preserve.c
index 15de1ca09062..21e12ba6c788 100644
--- a/arch/arm64/kernel/cpu_preserve.c
+++ b/arch/arm64/kernel/cpu_preserve.c
@@ -7,16 +7,23 @@
  */
 #include <linux/arm-smccc.h>
 #include <linux/cpu_preserve.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
 #include <linux/irqchip/arm-gic-v3.h>
 #include <linux/kexec_handover.h>
 #include <linux/kho/abi/cpu.h>
+#include <linux/kvm_host.h>
 #include <linux/mm.h>
+#include <linux/nospec.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
 #include <linux/psci.h>
 #include <linux/sched/mm.h>
 #include <uapi/linux/psci.h>
 
 #include <asm/barrier.h>
 #include <linux/cacheflush.h>
+#include <asm/caretaker.h>
 #include <asm/cpu_ops.h>
 #include <asm/daifflags.h>
 #include <asm/kernel-pgtable.h>
@@ -27,9 +34,40 @@
 #include <asm/trans_pgd.h>
 #include <asm/virt.h>
 
+#define CARETAKER_RWP_TIMEOUT_COUNT    1000000
+#define CARETAKER_SGI_MASK             GENMASK(15, 0)
+#define CARETAKER_HYP_TIMER_PPI                26
+#define CARETAKER_HYP_VIRT_TIMER_PPI   30
+#define GICR_INT_PRIORITY(intid)       (GICR_IPRIORITYR0 + (intid))
+#define CARETAKER_MAX_RDIST_REGIONS    8
+
+#define MPIDR_TO_SGI_AFFINITY(cluster_id, level) \
+       (MPIDR_AFFINITY_LEVEL(cluster_id, level) \
+               << ICC_SGI1R_AFFINITY_## level ##_SHIFT)
+#define MPIDR_TO_SGI_CLUSTER_ID(mpidr) ((mpidr) & ~0xFUL)
+#define MPIDR_RS(mpidr)                        (((mpidr) & 0xf0ULL) >> 4)
+#define MPIDR_TO_SGI_RS(mpidr)         (MPIDR_RS(mpidr) << ICC_SGI1R_RS_SHIFT)
+
+struct caretaker_rdist_region {
+       phys_addr_t     pa;
+       void __iomem    *va;
+       size_t          size;
+};
+
+struct caretaker_gic_state {
+       void __iomem                    *cpu_rdist[NR_CPUS];
+       struct caretaker_rdist_region   regions[CARETAKER_MAX_RDIST_REGIONS];
+       int                             nr_regions;
+};
+
 static enum arm_smccc_conduit arm64_psci_conduit __cpu_preserved_data;
 phys_addr_t arm64_caretaker_pgd_pa __cpu_preserved_data;
 static u64 arm64_cpu_mpidr[NR_CPUS] __cpu_preserved_data;
+static struct caretaker_gic_state caretaker_gic __cpu_preserved_data;
+__cpu_preserved_data bool arm64_caretaker_has_ptrauth;
+EXPORT_SYMBOL_GPL(arm64_caretaker_has_ptrauth);
+
+static void arm64_caretaker_gic_init(void);
 
 /*
  * Signal or wake up a preserved physical CPU via SEV.
@@ -38,6 +76,7 @@ void __cpu_preserved_text arch_cpu_preserved_kick(int cpu)
 {
        dsb(ishst);
        sev();
+       gicv3_caretaker_kick_cpu(cpu);
        isb();
 }
 
@@ -61,6 +100,261 @@ int __cpu_preserved_text 
arch_cpu_preserved_mpidr_to_cpu(u64 mpidr)
 }
 EXPORT_SYMBOL_GPL(arch_cpu_preserved_mpidr_to_cpu);
 
+__cpu_preserved_text static void __iomem *gicv3_get_rdist_for_cpu(int cpu)
+{
+       if (cpu < 0) {
+               u64 mpidr = read_sysreg(mpidr_el1) & MPIDR_HWID_BITMASK;
+
+               cpu = arch_cpu_preserved_mpidr_to_cpu(mpidr);
+       }
+       if (cpu >= 0 && cpu < ARRAY_SIZE(caretaker_gic.cpu_rdist))
+               return caretaker_gic.cpu_rdist[cpu];
+       return NULL;
+}
+
+__cpu_preserved_text static inline void
+gicv3_caretaker_wait_for_rwp(void __iomem *base, u32 bit)
+{
+       int count = CARETAKER_RWP_TIMEOUT_COUNT;
+
+       while (count-- > 0) {
+               if (!(readl_relaxed(base + GICR_CTLR) & bit))
+                       return;
+               cpu_relax();
+       }
+}
+
+__cpu_preserved_text void gicv3_caretaker_clear_active_priorities(void)
+{
+       u32 ctlr = read_sysreg_s(SYS_ICC_CTLR_EL1);
+       u32 pribits = ((ctlr & ICC_CTLR_EL1_PRI_BITS_MASK) >>
+                      ICC_CTLR_EL1_PRI_BITS_SHIFT) + 1;
+
+       switch (pribits) {
+       case 8:
+       case 7:
+               write_sysreg_s(0, SYS_ICC_AP1R3_EL1);
+               write_sysreg_s(0, SYS_ICC_AP1R2_EL1);
+               fallthrough;
+       case 6:
+               write_sysreg_s(0, SYS_ICC_AP1R1_EL1);
+               fallthrough;
+       case 5:
+       case 4:
+       default:
+               write_sysreg_s(0, SYS_ICC_AP1R0_EL1);
+               break;
+       }
+       isb();
+}
+EXPORT_SYMBOL_GPL(gicv3_caretaker_clear_active_priorities);
+
+__cpu_preserved_text void gicv3_caretaker_enable_sgi(void)
+{
+       void __iomem *ptr = gicv3_get_rdist_for_cpu(-1);
+
+       if (ptr) {
+               void __iomem *rbase = ptr + SZ_64K;
+
+               writel_relaxed(~0U, rbase + GICR_IGROUPR0);
+               writel_relaxed(0, rbase + GICR_IGRPMODR0);
+               writeb_relaxed(0x00, rbase + GICR_INT_PRIORITY(0));
+               writeb_relaxed(0x00, rbase + 
GICR_INT_PRIORITY(CARETAKER_HYP_TIMER_PPI));
+               writeb_relaxed(0x00, rbase + 
GICR_INT_PRIORITY(CARETAKER_HYP_VIRT_TIMER_PPI));
+               writel_relaxed(CARETAKER_SGI_MASK |
+                              BIT(CARETAKER_HYP_TIMER_PPI) |
+                              BIT(CARETAKER_HYP_VIRT_TIMER_PPI),
+                              rbase + GICR_ISENABLER0);
+               writel_relaxed(~0U, rbase + GICR_ICACTIVER0);
+               gicv3_caretaker_wait_for_rwp(ptr, GICR_CTLR_RWP);
+       }
+
+       write_sysreg_s(0, SYS_ICC_BPR1_EL1);
+       gicv3_caretaker_clear_active_priorities();
+}
+EXPORT_SYMBOL_GPL(gicv3_caretaker_enable_sgi);
+
+__cpu_preserved_text void gicv3_caretaker_clear_sgi(void)
+{
+       void __iomem *ptr = gicv3_get_rdist_for_cpu(-1);
+
+       if (ptr) {
+               void __iomem *rbase = ptr + SZ_64K;
+
+               writel_relaxed(~0U, rbase + GICR_ICPENDR0);
+               writel_relaxed(~0U, rbase + GICR_ICACTIVER0);
+               gicv3_caretaker_wait_for_rwp(ptr, GICR_CTLR_RWP);
+       }
+}
+EXPORT_SYMBOL_GPL(gicv3_caretaker_clear_sgi);
+
+__cpu_preserved_text void gicv3_caretaker_kick_cpu(int cpu)
+{
+       u64 mpidr, cluster_id, sgi1r;
+       void __iomem *ptr;
+       u16 tlist;
+
+       if (cpu < 0 || cpu >= ARRAY_SIZE(arm64_cpu_mpidr))
+               return;
+
+       cpu = array_index_nospec(cpu, ARRAY_SIZE(arm64_cpu_mpidr));
+       mpidr = arm64_cpu_mpidr[cpu];
+       if ((mpidr & MPIDR_HWID_BITMASK) == (read_sysreg(mpidr_el1) & 
MPIDR_HWID_BITMASK))
+               return;
+
+       ptr = gicv3_get_rdist_for_cpu(cpu);
+       if (ptr) {
+               void __iomem *sgi_base = ptr + SZ_64K;
+               u32 val = readl_relaxed(ptr + GICR_WAKER);
+
+               if (val & GICR_WAKER_ProcessorSleep) {
+                       int count = CARETAKER_RWP_TIMEOUT_COUNT;
+
+                       val &= ~GICR_WAKER_ProcessorSleep;
+                       writel_relaxed(val, ptr + GICR_WAKER);
+                       while (count-- > 0) {
+                               val = readl_relaxed(ptr + GICR_WAKER);
+                               if (!(val & GICR_WAKER_ChildrenAsleep))
+                                       break;
+                               cpu_relax();
+                       }
+               }
+
+               writel_relaxed(~0U, sgi_base + GICR_IGROUPR0);
+               writel_relaxed(0, sgi_base + GICR_IGRPMODR0);
+               writel_relaxed(0, sgi_base + GICR_INT_PRIORITY(0));
+               writel_relaxed(0, sgi_base + GICR_INT_PRIORITY(4));
+               writel_relaxed(0, sgi_base + GICR_INT_PRIORITY(8));
+               writel_relaxed(0, sgi_base + GICR_INT_PRIORITY(12));
+               writel_relaxed(CARETAKER_SGI_MASK | 
BIT(CARETAKER_HYP_TIMER_PPI),
+                              sgi_base + GICR_ISENABLER0);
+               gicv3_caretaker_wait_for_rwp(ptr, GICR_CTLR_RWP);
+       }
+
+       cluster_id = MPIDR_TO_SGI_CLUSTER_ID(mpidr);
+       tlist = 1 << (mpidr & 0xf);
+
+       dsb(ishst);
+       sgi1r = (MPIDR_TO_SGI_AFFINITY(cluster_id, 3) |
+                MPIDR_TO_SGI_AFFINITY(cluster_id, 2) |
+                (0ULL << ICC_SGI1R_SGI_ID_SHIFT) |
+                MPIDR_TO_SGI_AFFINITY(cluster_id, 1) |
+                MPIDR_TO_SGI_RS(cluster_id) |
+                ((u64)tlist << ICC_SGI1R_TARGET_LIST_SHIFT));
+       write_sysreg_s(sgi1r, SYS_ICC_SGI1R_EL1);
+       isb();
+}
+EXPORT_SYMBOL_GPL(gicv3_caretaker_kick_cpu);
+
+static void arm64_add_gicr_region(phys_addr_t pa, size_t size, u64 stride)
+{
+       void __iomem *va, *ptr;
+       size_t map_size;
+       int i;
+
+       for (i = 0; i < caretaker_gic.nr_regions; i++)
+               if (caretaker_gic.regions[i].pa == pa)
+                       return;
+
+       if (caretaker_gic.nr_regions >= ARRAY_SIZE(caretaker_gic.regions))
+               return;
+
+       map_size = max_t(size_t, size, nr_cpu_ids * (stride ? : SZ_128K));
+       va = ioremap(pa, map_size);
+       if (!va)
+               return;
+
+       i = caretaker_gic.nr_regions++;
+       caretaker_gic.regions[i].pa = pa;
+       caretaker_gic.regions[i].va = va;
+       caretaker_gic.regions[i].size = map_size;
+
+       ptr = va;
+       do {
+               u64 typer = readq_relaxed(ptr + GICR_TYPER);
+               u32 aff = typer >> 32;
+               int cpu;
+               bool last = !!(typer & GICR_TYPER_LAST);
+
+               for_each_possible_cpu(cpu) {
+                       u64 mpidr = cpu_logical_map(cpu);
+                       u32 cpu_aff = (MPIDR_AFFINITY_LEVEL(mpidr, 3) << 24) |
+                                     (MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16) |
+                                     (MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8) |
+                                     MPIDR_AFFINITY_LEVEL(mpidr, 0);
+                       if (aff == cpu_aff)
+                               caretaker_gic.cpu_rdist[cpu] = ptr;
+               }
+
+               if (stride) {
+                       ptr += stride;
+               } else {
+                       ptr += SZ_64K * 2;
+                       if (typer & GICR_TYPER_VLPIS)
+                               ptr += SZ_64K * 2;
+               }
+               if (last)
+                       break;
+       } while ((ptr - va) < map_size);
+
+       cpu_preserved_clean(&caretaker_gic);
+}
+
+static void arm64_discover_gicr_res(struct resource *res)
+{
+       for (; res; res = res->sibling) {
+               if (res->name && !strcmp(res->name, "GICR"))
+                       arm64_add_gicr_region(res->start, resource_size(res), 
0);
+               if (res->child)
+                       arm64_discover_gicr_res(res->child);
+       }
+}
+
+static void arm64_caretaker_gic_init(void)
+{
+       struct device_node *node;
+
+       if (caretaker_gic.nr_regions > 0 || arch_cpu_preserved_is_active())
+               return;
+
+       node = of_find_compatible_node(NULL, NULL, "arm,gic-v3");
+       if (node) {
+               u32 nr_redist_regions = 1;
+               u64 stride = 0;
+               int i;
+
+               of_property_read_u32(node, "#redistributor-regions",
+                                    &nr_redist_regions);
+               of_property_read_u64(node, "redistributor-stride", &stride);
+               for (i = 0; i < nr_redist_regions; i++) {
+                       struct resource res;
+
+                       if (of_address_to_resource(node, 1 + i, &res) == 0)
+                               arm64_add_gicr_region(res.start,
+                                                     resource_size(&res),
+                                                     stride);
+               }
+               of_node_put(node);
+       }
+
+       if (caretaker_gic.nr_regions == 0)
+               arm64_discover_gicr_res(&iomem_resource);
+}
+
+int gicv3_caretaker_get_redist_region(int idx, phys_addr_t *pa,
+                                     unsigned long *va, size_t *size)
+{
+       arm64_caretaker_gic_init();
+       if (idx < 0 || idx >= caretaker_gic.nr_regions)
+               return -ENOENT;
+
+       *pa = caretaker_gic.regions[idx].pa;
+       *va = (unsigned long)caretaker_gic.regions[idx].va;
+       *size = caretaker_gic.regions[idx].size;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gicv3_caretaker_get_redist_region);
+
 bool __cpu_preserved_text arch_cpu_preserved_is_active(void)
 {
        struct cpu_preserved_stack_context *sctx = 
cpu_preserved_get_stack_context();
@@ -240,6 +534,7 @@ int arch_cpu_preserved_setup_buffer(struct page *text_page,
        for (i = 0; i < nr_cpu_ids; i++)
                arm64_cpu_mpidr[i] = cpu_logical_map(i);
        cpu_preserved_clean(&arm64_cpu_mpidr);
+       arm64_caretaker_gic_init();
 
        return 0;
 }
@@ -260,11 +555,18 @@ void __cpu_preserved_text 
arch_cpu_preserved_park_init(int cpu)
        local_daif_mask();
        cpu_preserved_inval(&arm64_psci_conduit);
        cpu_preserved_inval(&arm64_cpu_mpidr);
+       cpu_preserved_inval(&caretaker_gic);
        if (!pgd_pa) {
                cpu_preserved_inval(&arm64_caretaker_pgd_pa);
                pgd_pa = READ_ONCE(arm64_caretaker_pgd_pa);
        }
 
+#if IS_ENABLED(CONFIG_KVM_CARETAKER)
+       write_sysreg((unsigned long)caretaker_hyp_vector, vbar_el1);
+       write_sysreg_s((unsigned long)caretaker_hyp_vector, SYS_VBAR_EL2);
+       isb();
+#endif
+
        write_sysreg(0, ttbr0_el1);
        if (pgd_pa)
                write_sysreg(pgd_pa, ttbr1_el1);
@@ -272,6 +574,8 @@ void __cpu_preserved_text arch_cpu_preserved_park_init(int 
cpu)
        arm64_flush_host_tlb_local();
 
        write_sysreg_s(0xff, SYS_ICC_PMR_EL1);
+       isb();
+
        write_sysreg_s(1, SYS_ICC_IGRPEN1_EL1);
        isb();
 }
@@ -283,12 +587,17 @@ void arch_cpu_preserved_early_init(void)
        for (c = 0; c < ARRAY_SIZE(arm64_cpu_mpidr); c++)
                arm64_cpu_mpidr[c] = cpu_logical_map(c);
        cpu_preserved_clean(&arm64_cpu_mpidr);
+       arm64_caretaker_gic_init();
 
        cpu_preserved_inval(&arm64_psci_conduit);
        if (arm64_psci_conduit == SMCCC_CONDUIT_NONE) {
                arm64_psci_conduit = arm_smccc_1_1_get_conduit();
                cpu_preserved_clean(&arm64_psci_conduit);
        }
+
+       arm64_caretaker_has_ptrauth = IS_ENABLED(CONFIG_ARM64_PTR_AUTH) &&
+                                     system_has_full_ptr_auth();
+       cpu_preserved_clean(&arm64_caretaker_has_ptrauth);
 }
 EXPORT_SYMBOL_GPL(arch_cpu_preserved_early_init);
 
diff --git a/arch/arm64/kvm/caretaker.h b/arch/arm64/kvm/caretaker.h
new file mode 100644
index 000000000000..97271a49f5a8
--- /dev/null
+++ b/arch/arm64/kvm/caretaker.h
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * Header for ARM64 KVM Caretaker execution engine and helpers.
+ */
+#ifndef __ARCH_ARM64_KVM_CARETAKER_H
+#define __ARCH_ARM64_KVM_CARETAKER_H
+
+#define CAP_FAULT_ESR          0x00
+#define CAP_FAULT_FAR          0x08
+#define CAP_FAULT_HPFAR                0x10
+#define CAP_CTXT_OFFSET                0x20
+
+/* Special INTID range (1020-1023) reserved by GIC architecture */
+#define GIC_SPECIAL_INTID_START        1020
+
+#ifndef __ASSEMBLY__
+
+#include <linux/types.h>
+#include <linux/refcount.h>
+#include <linux/sizes.h>
+#include <linux/oncore.h>
+#include <linux/kvm_host.h>
+#include <linux/kho/abi/kvm_arm64.h>
+#include <linux/kvm_caretaker.h>
+#include <linux/irqchip/arm-gic-v3.h>
+#include <asm/esr.h>
+
+/* Default priority for software-injected SGIs */
+#define GIC_DEFAULT_SGI_PRIO           0xa0ULL
+
+/* System register opcodes for SGI generation */
+#define ESR_ELx_SYS64_ISS_SYS_ICC_SGI1R_EL1 \
+       (ESR_ELx_SYS64_ISS_SYS_VAL(3, 0, 5, 12, 11) | 
ESR_ELx_SYS64_ISS_DIR_WRITE)
+#define ESR_ELx_SYS64_ISS_SYS_ICC_ASGI1R_EL1 \
+       (ESR_ELx_SYS64_ISS_SYS_VAL(3, 0, 6, 12, 11) | 
ESR_ELx_SYS64_ISS_DIR_WRITE)
+#define ESR_ELx_SYS64_ISS_SYS_ICC_SGI0R_EL1 \
+       (ESR_ELx_SYS64_ISS_SYS_VAL(3, 0, 7, 12, 11) | 
ESR_ELx_SYS64_ISS_DIR_WRITE)
+
+/**
+ * struct caretaker_arm64_context - Preserved ARM64 vCPU architectural context
+ * @fault:            Guest fault syndrome registers from VM exits.
+ * @ctxt:             CPU registers, FP/SIMD, and system registers.
+ * @hcr_el2:          Hypervisor Configuration Register.
+ * @mdcr_el2:         Monitor Debug Configuration Register.
+ * @cflags:           vCPU execution flags.
+ * @vtcr_el2:         Stage-2 translation control register.
+ * @vttbr_el2:        Stage-2 translation table base register.
+ * @vgic_initialized: Whether VGICv3 virtual CPU interface is active.
+ * @vgic_v3:          VGICv3 virtual CPU interface registers.
+ */
+struct caretaker_arm64_context {
+       struct kvm_vcpu_fault_info fault;
+       struct kvm_cpu_context ctxt;
+
+       u64 hcr_el2;
+       u64 mdcr_el2;
+       u64 cflags;
+
+       u64 vtcr_el2;
+       u64 vttbr_el2;
+
+       bool vgic_initialized;
+       struct vgic_v3_cpu_if vgic_v3;
+
+       u64 cntv_cval_el0;
+       u64 cntv_ctl_el0;
+       u64 cntvoff_el2;
+       u16 pending_sgis;
+};
+
+struct caretaker_arm64_page;
+
+struct arm64_caretaker_ptrauth_keys {
+       u64 apia_lo, apia_hi;
+       u64 apib_lo, apib_hi;
+       u64 apda_lo, apda_hi;
+       u64 apdb_lo, apdb_hi;
+       u64 apga_lo, apga_hi;
+};
+
+/**
+ * struct caretaker_arm64_page - Old-text-private ARM64 Caretaker execution 
page
+ *
+ * Invariant: Only the embedded @abi prefix (struct kvm_caretaker_arch_ser,
+ * offset 0) is part of the KHO handover ABI and may be dereferenced
+ * by the incoming kernel.  All remaining fields are private to preserved
+ * Caretaker text executing on the isolated core.
+ */
+struct caretaker_arm64_page {
+       struct kvm_caretaker_arch_ser abi;
+       struct kvm_caretaker_vcpu vcpu;
+       struct caretaker_arm64_context ctx;
+       struct caretaker_arm64_page *next_vcpu;
+       struct kvm_vcpu_arch_ser *arch_state;
+       u64 last_ret;
+       struct arm64_caretaker_ptrauth_keys ptrauth_keys;
+};
+
+static_assert(offsetof(struct caretaker_arm64_page, abi) == 0);
+static_assert(offsetof(struct caretaker_arm64_page, abi.cb) == 0);
+
+u64 caretaker_guest_enter(struct caretaker_arm64_context *ctx);
+void kvm_arch_vcpu_luo_pre_retrieve_caretaker(struct kvm_vcpu *vcpu,
+                                             struct kvm_vcpu_ser *ser);
+void kvm_arch_vcpu_luo_attach_caretaker(struct kvm_vcpu *vcpu,
+                                       struct kvm_vcpu_ser *ser);
+void arm64_caretaker_handle_invalid(u64 elr, u64 esr, u64 far);
+
+#ifdef CONFIG_KVM_CARETAKER
+int arm64_kvm_caretaker_preserve(struct kvm_vcpu *vcpu,
+                                struct kvm_vcpu_ser *ser);
+void arm64_kvm_caretaker_unpreserve(struct kvm_vcpu_ser *ser);
+void arm64_kvm_caretaker_finish(struct kvm_vcpu_ser *ser);
+#else
+static inline int arm64_kvm_caretaker_preserve(struct kvm_vcpu *vcpu,
+                                              struct kvm_vcpu_ser *ser)
+{
+       return -EOPNOTSUPP;
+}
+
+static inline void arm64_kvm_caretaker_unpreserve(struct kvm_vcpu_ser *ser) {}
+static inline void arm64_kvm_caretaker_finish(struct kvm_vcpu_ser *ser) {}
+#endif
+
+#endif /* !__ASSEMBLY__ */
+
+#endif /* __ARCH_ARM64_KVM_CARETAKER_H */
diff --git a/include/linux/kho/abi/kvm_arm64.h 
b/include/linux/kho/abi/kvm_arm64.h
index dc121224a177..4aee91dd41b3 100644
--- a/include/linux/kho/abi/kvm_arm64.h
+++ b/include/linux/kho/abi/kvm_arm64.h
@@ -56,6 +56,38 @@ struct kvm_vcpu_arch_ser {
 static_assert(offsetof(struct kvm_vcpu_arch_ser, sysregs) % sizeof(u64) == 0,
              "sysregs must be 64-bit aligned");
 
+/**
+ * struct kvm_caretaker_arch_ser - ARM64-specific Caretaker control block ABI
+ * @cb:               Common Caretaker control block header (must be at offset 
0).
+ * @vgic_initialized: Non-zero if @vgic_* fields hold live VGICv3 CPU 
interface state.
+ * @cflags:           KVM vCPU architectural flags.
+ * @cntvoff_el2:      Guest virtual counter offset active during Caretaker 
execution.
+ * @hcr_el2:          Hypervisor Configuration Register active during 
Caretaker execution.
+ * @mdcr_el2:         Monitor Debug Configuration Register active during 
Caretaker execution.
+ * @used_lrs:         Number of active VGICv3 List Registers.
+ * @vgic_hcr:         VGICv3 Hypervisor Control Register.
+ * @vgic_vmcr:        VGICv3 Virtual Machine Control Register.
+ * @reserved:         Must be zero.
+ * @vgic_ap0r:        VGICv3 Active Priorities Group 0 Registers.
+ * @vgic_ap1r:        VGICv3 Active Priorities Group 1 Registers.
+ * @vgic_lr:          VGICv3 List Registers.
+ */
+struct kvm_caretaker_arch_ser {
+       struct kvm_caretaker_cb_ser cb;
+       u32 vgic_initialized;
+       u32 cflags;
+       u64 cntvoff_el2;
+       u64 hcr_el2;
+       u64 mdcr_el2;
+       u32 used_lrs;
+       u32 vgic_hcr;
+       u32 vgic_vmcr;
+       u32 reserved;
+       u32 vgic_ap0r[4];
+       u32 vgic_ap1r[4];
+       u64 vgic_lr[16];
+} __packed;
+
 #endif /* CONFIG_ARM64 */
 
 #endif /* _LINUX_KHO_ABI_KVM_ARM64_H */
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to