[RFC PATCH 42/46] KVM: arm64: Add Caretaker arm64 KHO ABI and runtime context headers

Pasha Tatashin pasha.tatashin at soleen.com
Mon Sep 21 14:00:50 PDT 2026


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

Signed-off-by: Pasha Tatashin <pasha.tatashin at soleen.com>
---
 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 <pasha.tatashin at soleen.com>
+ */
+#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 <pasha.tatashin at soleen.com>
+ *
+ * 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




More information about the kexec mailing list