Implement ARM64 architectural vCPU state preservation and retrieval
handlers over LUO using struct kvm_vcpu_arch_ser in
include/linux/kho/abi/kvm_arm64.h.

Signed-off-by: Pasha Tatashin <[email protected]>
---
 arch/arm64/kvm/Kconfig            |   1 +
 arch/arm64/kvm/Makefile           |   2 +
 arch/arm64/kvm/kvm_luo.c          | 201 ++++++++++++++++++++++++++++++
 include/linux/kho/abi/kvm_arm64.h |  61 +++++++++
 4 files changed, 265 insertions(+)
 create mode 100644 arch/arm64/kvm/kvm_luo.c
 create mode 100644 include/linux/kho/abi/kvm_arm64.h

diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a485..fd25b3c0d8ca 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -37,6 +37,7 @@ menuconfig KVM
        select SCHED_INFO
        select GUEST_PERF_EVENTS if PERF_EVENTS
        select KVM_GUEST_MEMFD
+       select HAVE_KVM_ARCH_VCPU_PRESERVE
        help
          Support hosting virtualized guest machines.
 
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c..d5e888d1bd50 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -47,3 +47,5 @@ $(obj)/hyp_constants.h: $(obj)/hyp-constants.s FORCE
 
 obj-kvm := $(addprefix $(obj)/, $(kvm-y))
 $(obj-kvm): $(obj)/hyp_constants.h
+
+kvm-y += kvm_luo.o
diff --git a/arch/arm64/kvm/kvm_luo.c b/arch/arm64/kvm/kvm_luo.c
new file mode 100644
index 000000000000..59c56836cda8
--- /dev/null
+++ b/arch/arm64/kvm/kvm_luo.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ *
+ * ARM64 KVM LUO preservation and retrieval handlers.
+ */
+
+#include <linux/kexec_handover.h>
+#include <linux/kho/abi/kvm_arm64.h>
+#include <linux/kvm_host.h>
+#include <linux/sched.h>
+
+#include <asm/kvm_emulate.h>
+#include <asm/kvm_mmu.h>
+
+#include <kvm/arm_arch_timer.h>
+#include <kvm/arm_vgic.h>
+
+#include "sys_regs.h"
+#include "vgic/vgic.h"
+
+int kvm_arch_vm_luo_preserve(struct kvm *kvm, struct kvm_luo_ser *ser)
+{
+       ser->type = kvm_phys_shift(&kvm->arch.mmu);
+       if (kvm_vm_is_protected(kvm))
+               ser->type |= KVM_VM_TYPE_ARM_PROTECTED;
+
+       return 0;
+}
+
+int kvm_arch_vm_luo_retrieve(struct kvm *kvm, struct kvm_luo_ser *ser)
+{
+       return 0;
+}
+
+void kvm_arch_vm_luo_unpreserve(struct kvm *kvm, struct kvm_luo_ser *ser)
+{
+}
+
+void kvm_arch_vm_luo_finish(struct kvm_luo_ser *ser)
+{
+}
+
+static void kvm_arm_luo_get_regs(struct kvm_vcpu *vcpu, struct kvm_regs *regs)
+{
+       regs->regs = vcpu->arch.ctxt.regs;
+       regs->sp_el1 = ctxt_sys_reg(&vcpu->arch.ctxt, SP_EL1);
+       regs->elr_el1 = ctxt_sys_reg(&vcpu->arch.ctxt, ELR_EL1);
+       regs->spsr[KVM_SPSR_EL1] = ctxt_sys_reg(&vcpu->arch.ctxt, SPSR_EL1);
+       regs->spsr[KVM_SPSR_ABT] = vcpu->arch.ctxt.spsr_abt;
+       regs->spsr[KVM_SPSR_UND] = vcpu->arch.ctxt.spsr_und;
+       regs->spsr[KVM_SPSR_IRQ] = vcpu->arch.ctxt.spsr_irq;
+       regs->spsr[KVM_SPSR_FIQ] = vcpu->arch.ctxt.spsr_fiq;
+       regs->fp_regs = vcpu->arch.ctxt.fp_regs;
+}
+
+static void kvm_arm_luo_set_regs(struct kvm_vcpu *vcpu, const struct kvm_regs 
*regs)
+{
+       vcpu->arch.ctxt.regs = regs->regs;
+       ctxt_sys_reg(&vcpu->arch.ctxt, SP_EL1) = regs->sp_el1;
+       ctxt_sys_reg(&vcpu->arch.ctxt, ELR_EL1) = regs->elr_el1;
+       ctxt_sys_reg(&vcpu->arch.ctxt, SPSR_EL1) = regs->spsr[KVM_SPSR_EL1];
+       vcpu->arch.ctxt.spsr_abt = regs->spsr[KVM_SPSR_ABT];
+       vcpu->arch.ctxt.spsr_und = regs->spsr[KVM_SPSR_UND];
+       vcpu->arch.ctxt.spsr_irq = regs->spsr[KVM_SPSR_IRQ];
+       vcpu->arch.ctxt.spsr_fiq = regs->spsr[KVM_SPSR_FIQ];
+       vcpu->arch.ctxt.fp_regs = regs->fp_regs;
+}
+
+int kvm_arch_vcpu_luo_preserve(struct kvm_vcpu *vcpu, struct kvm_vcpu_ser *ser)
+{
+       struct kvm_vcpu_arch_ser *state;
+       int num_sysregs;
+       u64 *indices;
+       size_t size;
+       int i;
+
+       num_sysregs = kvm_arm_get_sys_reg_indices(vcpu, NULL);
+       indices = kmalloc_array(num_sysregs, sizeof(u64), GFP_KERNEL);
+       if (!indices)
+               return -ENOMEM;
+       num_sysregs = kvm_arm_get_sys_reg_indices(vcpu, indices);
+
+       size = struct_size(state, sysregs, num_sysregs);
+       state = kho_alloc_preserve(size);
+       if (IS_ERR(state)) {
+               kfree(indices);
+               return PTR_ERR(state);
+       }
+
+       /* Core register state (uAPI struct kvm_regs) */
+       kvm_arm_luo_get_regs(vcpu, &state->regs);
+
+       /* Multiprocessor execution state (uAPI struct kvm_mp_state) */
+       kvm_arch_vcpu_ioctl_get_mpstate(vcpu, &state->mp_state);
+       state->pad = 0;
+
+       /* Exception / SError injection events (uAPI struct kvm_vcpu_events) */
+       __kvm_arm_vcpu_get_events(vcpu, &state->events);
+
+       /* CPU target and feature configuration (uAPI struct kvm_vcpu_init) */
+       state->init.target = KVM_ARM_TARGET_GENERIC_V8;
+       bitmap_to_arr32(state->init.features, vcpu->kvm->arch.vcpu_features,
+                       KVM_VCPU_MAX_FEATURES);
+
+       /* System registers (uAPI struct kvm_one_reg array) */
+       state->num_sysregs = 0;
+       state->reserved = 0;
+       for (i = 0; i < num_sysregs; i++) {
+               u64 val;
+
+               if (kvm_arm_sys_reg_read(vcpu, indices[i], &val) == 0) {
+                       state->sysregs[state->num_sysregs].id = indices[i];
+                       state->sysregs[state->num_sysregs].addr = val;
+                       state->num_sysregs++;
+               }
+       }
+       kfree(indices);
+
+       KHOSER_STORE_PTR(ser->arch_state, state);
+       return 0;
+}
+
+int kvm_arch_vcpu_luo_retrieve(struct kvm_vcpu *vcpu, struct kvm_vcpu_ser *ser)
+{
+       struct kvm_vcpu_arch_ser *state;
+       int i;
+
+       if (!ser->arch_state.phys)
+               return 0;
+
+       state = KHOSER_LOAD_PTR(ser->arch_state);
+
+       /* Restore vCPU features */
+       bitmap_from_arr32(vcpu->kvm->arch.vcpu_features, state->init.features,
+                         KVM_VCPU_MAX_FEATURES);
+       set_bit(KVM_ARCH_FLAG_VCPU_FEATURES_CONFIGURED, &vcpu->kvm->arch.flags);
+       kvm_reset_vcpu(vcpu);
+       vcpu_set_flag(vcpu, VCPU_INITIALIZED);
+
+       vcpu_reset_hcr(vcpu);
+
+       /* Restore core registers */
+       kvm_arm_luo_set_regs(vcpu, &state->regs);
+
+       if (irqchip_in_kernel(vcpu->kvm) &&
+           vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+               vgic_v3_reset(vcpu);
+       }
+
+       /* Restore system registers */
+       for (i = 0; i < state->num_sysregs; i++)
+               kvm_arm_sys_reg_write(vcpu, state->sysregs[i].id, 
state->sysregs[i].addr);
+
+       /* Restore multiprocessor execution state */
+       kvm_arch_vcpu_ioctl_set_mpstate(vcpu, &state->mp_state);
+
+       /* Restore exception / SError injection events */
+       __kvm_arm_vcpu_set_events(vcpu, &state->events);
+
+       if (irqchip_in_kernel(vcpu->kvm)) {
+               struct vgic_irq *irq;
+               unsigned long flags;
+
+               vcpu->kvm->arch.vgic.enabled = true;
+
+               irq = vgic_get_vcpu_irq(vcpu, timer_irq(vcpu_vtimer(vcpu)));
+               if (irq) {
+                       raw_spin_lock_irqsave(&irq->irq_lock, flags);
+                       irq->enabled = true;
+                       raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+                       vgic_put_irq(vcpu->kvm, irq);
+               }
+               irq = vgic_get_vcpu_irq(vcpu, timer_irq(vcpu_ptimer(vcpu)));
+               if (irq) {
+                       raw_spin_lock_irqsave(&irq->irq_lock, flags);
+                       irq->enabled = true;
+                       raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+                       vgic_put_irq(vcpu->kvm, irq);
+               }
+       }
+
+       return 0;
+}
+
+void kvm_arch_vcpu_luo_unpreserve(struct kvm_vcpu_ser *ser)
+{
+       if (ser->arch_state.phys) {
+               kho_unpreserve_free(phys_to_virt(ser->arch_state.phys));
+               ser->arch_state.phys = 0;
+       }
+}
+
+void kvm_arch_vcpu_luo_finish(struct kvm_vcpu_ser *ser)
+{
+       if (ser->arch_state.phys) {
+               kho_restore_free(phys_to_virt(ser->arch_state.phys));
+               ser->arch_state.phys = 0;
+       }
+}
diff --git a/include/linux/kho/abi/kvm_arm64.h 
b/include/linux/kho/abi/kvm_arm64.h
new file mode 100644
index 000000000000..dc121224a177
--- /dev/null
+++ b/include/linux/kho/abi/kvm_arm64.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2026, Google LLC.
+ * Pasha Tatashin <[email protected]>
+ */
+#ifndef _LINUX_KHO_ABI_KVM_ARM64_H
+#define _LINUX_KHO_ABI_KVM_ARM64_H
+
+#ifdef CONFIG_ARM64
+
+#include <linux/build_bug.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+#include <linux/kho/abi/kvm.h>
+#include <uapi/linux/kvm.h>
+#include <uapi/asm/kvm.h>
+
+/**
+ * DOC: arm64 KVM vCPU Live Update ABI
+ *
+ * arm64 KVM uses the ABI defined below for preserving architectural vCPU state
+ * across a kexec reboot using LUO.
+ *
+ * The state is serialized into a packed structure `struct kvm_vcpu_arch_ser`
+ * which is handed over to the next kernel via KHO.
+ *
+ * The core register structure (struct kvm_regs) is a uAPI contract.
+ *
+ * This interface is a contract. Any modification to the structure layout
+ * constitutes a breaking change. Such changes require incrementing the version
+ * number in the KVM_VCPU_LUO_FH_COMPATIBLE string.
+ */
+
+/**
+ * struct kvm_vcpu_arch_ser - Preserved arm64 architectural vCPU state in RAM.
+ * @regs:        Core general-purpose and floating-point registers (uAPI 
struct kvm_regs).
+ * @mp_state:    Multiprocessor execution state (uAPI struct kvm_mp_state).
+ * @pad:         Padding to maintain 64-bit alignment after mp_state.
+ * @events:      Exception and SError injection state (uAPI struct 
kvm_vcpu_events).
+ * @init:        Target CPU and vCPU feature bitmap (uAPI struct 
kvm_vcpu_init).
+ * @num_sysregs: Number of serialized system registers in sysregs array.
+ * @reserved:    Reserved padding for 64-bit alignment.
+ * @sysregs:     Guest architectural system registers (uAPI struct kvm_one_reg 
array).
+ */
+struct kvm_vcpu_arch_ser {
+       struct kvm_regs regs;
+       struct kvm_mp_state mp_state;
+       u32 pad;
+       struct kvm_vcpu_events events;
+       struct kvm_vcpu_init init;
+       u32 num_sysregs;
+       u32 reserved;
+       struct kvm_one_reg sysregs[];
+} __packed;
+
+static_assert(offsetof(struct kvm_vcpu_arch_ser, sysregs) % sizeof(u64) == 0,
+             "sysregs must be 64-bit aligned");
+
+#endif /* CONFIG_ARM64 */
+
+#endif /* _LINUX_KHO_ABI_KVM_ARM64_H */
-- 
2.55.0.1082.g2b9226bbc0-goog


Reply via email to