]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
authorSascha Bischoff <Sascha.Bischoff@arm.com>
Thu, 19 Mar 2026 15:53:52 +0000 (15:53 +0000)
committerMarc Zyngier <maz@kernel.org>
Thu, 19 Mar 2026 18:21:28 +0000 (18:21 +0000)
Introduce the following hyp functions to save/restore GICv5 state:

* __vgic_v5_save_apr()
* __vgic_v5_restore_vmcr_apr()
* __vgic_v5_save_ppi_state() - no hypercall required
* __vgic_v5_restore_ppi_state() - no hypercall required
* __vgic_v5_save_state() - no hypercall required
* __vgic_v5_restore_state() - no hypercall required

Note that the functions tagged as not requiring hypercalls are always
called directly from the same context. They are either called via the
vgic_save_state()/vgic_restore_state() path when running with VHE, or
via __hyp_vgic_save_state()/__hyp_vgic_restore_state() otherwise. This
mimics how vgic_v3_save_state()/vgic_v3_restore_state() are
implemented.

Overall, the state of the following registers is saved/restored:

* ICC_ICSR_EL1
* ICH_APR_EL2
* ICH_PPI_ACTIVERx_EL2
* ICH_PPI_DVIRx_EL2
* ICH_PPI_ENABLERx_EL2
* ICH_PPI_PENDRx_EL2
* ICH_PPI_PRIORITYRx_EL2
* ICH_VMCR_EL2

All of these are saved/restored to/from the KVM vgic_v5 CPUIF shadow
state, with the exception of the PPI active, pending, and enable
state. The pending state is saved and restored from kvm_host_data as
any changes here need to be tracked and propagated back to the
vgic_irq shadow structures (coming in a future commit). Therefore, an
entry and an exit copy is required. The active and enable state is
restored from the vgic_v5 CPUIF, but is saved to kvm_host_data. Again,
this needs to by synced back into the shadow data structures.

The ICSR must be save/restored as this register is shared between host
and guest. Therefore, to avoid leaking host state to the guest, this
must be saved and restored. Moreover, as this can by used by the host
at any time, it must be save/restored eagerly. Note: the host state is
not preserved as the host should only use this register when
preemption is disabled.

As with GICv3, the VMCR is eagerly saved as this is required when
checking if interrupts can be injected or not, and therefore impacts
things such as WFI.

As part of restoring the ICH_VMCR_EL2 and ICH_APR_EL2, GICv3-compat
mode is also disabled by setting the ICH_VCTLR_EL2.V3 bit to 0. The
correspoinding GICv3-compat mode enable is part of the VMCR & APR
restore for a GICv3 guest as it only takes effect when actually
running a guest.

Co-authored-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Timothy Hayes <timothy.hayes@arm.com>
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Link: https://patch.msgid.link/20260319154937.3619520-17-sascha.bischoff@arm.com
Signed-off-by: Marc Zyngier <maz@kernel.org>
arch/arm64/include/asm/kvm_asm.h
arch/arm64/include/asm/kvm_host.h
arch/arm64/include/asm/kvm_hyp.h
arch/arm64/kvm/hyp/nvhe/Makefile
arch/arm64/kvm/hyp/nvhe/hyp-main.c
arch/arm64/kvm/hyp/vgic-v5-sr.c [new file with mode: 0644]
arch/arm64/kvm/hyp/vhe/Makefile
include/kvm/arm_vgic.h

index a1ad12c72ebf14f81379aa0a0ac8727ec28a2982..44e4696ca113ea0d4f8e268a64cc46f3386b7731 100644 (file)
@@ -89,6 +89,8 @@ enum __kvm_host_smccc_func {
        __KVM_HOST_SMCCC_FUNC___pkvm_vcpu_load,
        __KVM_HOST_SMCCC_FUNC___pkvm_vcpu_put,
        __KVM_HOST_SMCCC_FUNC___pkvm_tlb_flush_vmid,
+       __KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
+       __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
 };
 
 #define DECLARE_KVM_VHE_SYM(sym)       extern char sym[]
index 64a1ee6c442f0ef279de43315a786de61179ee3d..c4a172b7020636c2dc5c37d9b7859741a1de237a 100644 (file)
@@ -800,6 +800,22 @@ struct kvm_host_data {
 
        /* Last vgic_irq part of the AP list recorded in an LR */
        struct vgic_irq *last_lr_irq;
+
+       /* PPI state tracking for GICv5-based guests */
+       struct {
+               /*
+                * For tracking the PPI pending state, we need both the entry
+                * state and exit state to correctly detect edges as it is
+                * possible that an interrupt has been injected in software in
+                * the interim.
+                */
+               DECLARE_BITMAP(pendr_entry, VGIC_V5_NR_PRIVATE_IRQS);
+               DECLARE_BITMAP(pendr_exit, VGIC_V5_NR_PRIVATE_IRQS);
+
+               /* The saved state of the regs when leaving the guest */
+               DECLARE_BITMAP(activer_exit, VGIC_V5_NR_PRIVATE_IRQS);
+               DECLARE_BITMAP(enabler_exit, VGIC_V5_NR_PRIVATE_IRQS);
+       } vgic_v5_ppi_state;
 };
 
 struct kvm_host_psci_config {
index 76ce2b94bd97e3dfb90ac770583d814ff8125497..2d8dfd534bd9dc6682f88930daacf791ffc978bf 100644 (file)
@@ -87,6 +87,15 @@ void __vgic_v3_save_aprs(struct vgic_v3_cpu_if *cpu_if);
 void __vgic_v3_restore_vmcr_aprs(struct vgic_v3_cpu_if *cpu_if);
 int __vgic_v3_perform_cpuif_access(struct kvm_vcpu *vcpu);
 
+/* GICv5 */
+void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_restore_vmcr_apr(struct vgic_v5_cpu_if *cpu_if);
+/* No hypercalls for the following */
+void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_restore_ppi_state(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_save_state(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if);
+
 #ifdef __KVM_NVHE_HYPERVISOR__
 void __timer_enable_traps(struct kvm_vcpu *vcpu);
 void __timer_disable_traps(struct kvm_vcpu *vcpu);
index a244ec25f8c5bd0a744f7791102265323ecc681c..84a3bf96def6bc494c66912d558ddbe0f5acc366 100644 (file)
@@ -26,7 +26,7 @@ hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o
         hyp-main.o hyp-smp.o psci-relay.o early_alloc.o page_alloc.o \
         cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o
 hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
-        ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o
+        ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o ../vgic-v5-sr.o
 hyp-obj-y += ../../../kernel/smccc-call.o
 hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
 hyp-obj-y += $(lib-objs)
index e7790097db93a02bbd8101babf71348acee8448b..007fc993f2319f4e1544952649a567af6457d9a0 100644 (file)
@@ -589,6 +589,20 @@ static void handle___pkvm_teardown_vm(struct kvm_cpu_context *host_ctxt)
        cpu_reg(host_ctxt, 1) = __pkvm_teardown_vm(handle);
 }
 
+static void handle___vgic_v5_save_apr(struct kvm_cpu_context *host_ctxt)
+{
+       DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+       __vgic_v5_save_apr(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
+{
+       DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+       __vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
+}
+
 typedef void (*hcall_t)(struct kvm_cpu_context *);
 
 #define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
@@ -630,6 +644,8 @@ static const hcall_t host_hcall[] = {
        HANDLE_FUNC(__pkvm_vcpu_load),
        HANDLE_FUNC(__pkvm_vcpu_put),
        HANDLE_FUNC(__pkvm_tlb_flush_vmid),
+       HANDLE_FUNC(__vgic_v5_save_apr),
+       HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
 };
 
 static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
new file mode 100644 (file)
index 0000000..f34ea21
--- /dev/null
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, 2026 - Arm Ltd
+ */
+
+#include <linux/irqchip/arm-gic-v5.h>
+
+#include <asm/kvm_hyp.h>
+
+void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if)
+{
+       cpu_if->vgic_apr = read_sysreg_s(SYS_ICH_APR_EL2);
+}
+
+static void  __vgic_v5_compat_mode_disable(void)
+{
+       sysreg_clear_set_s(SYS_ICH_VCTLR_EL2, ICH_VCTLR_EL2_V3, 0);
+       isb();
+}
+
+void __vgic_v5_restore_vmcr_apr(struct vgic_v5_cpu_if *cpu_if)
+{
+       __vgic_v5_compat_mode_disable();
+
+       write_sysreg_s(cpu_if->vgic_vmcr, SYS_ICH_VMCR_EL2);
+       write_sysreg_s(cpu_if->vgic_apr, SYS_ICH_APR_EL2);
+}
+
+void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if)
+{
+       /*
+        * The following code assumes that the bitmap storage that we have for
+        * PPIs is either 64 (architected PPIs, only) or 128 bits (architected &
+        * impdef PPIs).
+        */
+       BUILD_BUG_ON(VGIC_V5_NR_PRIVATE_IRQS % 64);
+
+       bitmap_write(host_data_ptr(vgic_v5_ppi_state)->activer_exit,
+                    read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2), 0, 64);
+       bitmap_write(host_data_ptr(vgic_v5_ppi_state)->enabler_exit,
+                    read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2), 0, 64);
+       bitmap_write(host_data_ptr(vgic_v5_ppi_state)->pendr_exit,
+                    read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2), 0, 64);
+
+       cpu_if->vgic_ppi_priorityr[0] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR0_EL2);
+       cpu_if->vgic_ppi_priorityr[1] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR1_EL2);
+       cpu_if->vgic_ppi_priorityr[2] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR2_EL2);
+       cpu_if->vgic_ppi_priorityr[3] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR3_EL2);
+       cpu_if->vgic_ppi_priorityr[4] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR4_EL2);
+       cpu_if->vgic_ppi_priorityr[5] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR5_EL2);
+       cpu_if->vgic_ppi_priorityr[6] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR6_EL2);
+       cpu_if->vgic_ppi_priorityr[7] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR7_EL2);
+
+       if (VGIC_V5_NR_PRIVATE_IRQS == 128) {
+               bitmap_write(host_data_ptr(vgic_v5_ppi_state)->activer_exit,
+                            read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2), 64, 64);
+               bitmap_write(host_data_ptr(vgic_v5_ppi_state)->enabler_exit,
+                            read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2), 64, 64);
+               bitmap_write(host_data_ptr(vgic_v5_ppi_state)->pendr_exit,
+                            read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2), 64, 64);
+
+               cpu_if->vgic_ppi_priorityr[8] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR8_EL2);
+               cpu_if->vgic_ppi_priorityr[9] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR9_EL2);
+               cpu_if->vgic_ppi_priorityr[10] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR10_EL2);
+               cpu_if->vgic_ppi_priorityr[11] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR11_EL2);
+               cpu_if->vgic_ppi_priorityr[12] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR12_EL2);
+               cpu_if->vgic_ppi_priorityr[13] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR13_EL2);
+               cpu_if->vgic_ppi_priorityr[14] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR14_EL2);
+               cpu_if->vgic_ppi_priorityr[15] = read_sysreg_s(SYS_ICH_PPI_PRIORITYR15_EL2);
+       }
+
+       /* Now that we are done, disable DVI */
+       write_sysreg_s(0, SYS_ICH_PPI_DVIR0_EL2);
+       write_sysreg_s(0, SYS_ICH_PPI_DVIR1_EL2);
+}
+
+void __vgic_v5_restore_ppi_state(struct vgic_v5_cpu_if *cpu_if)
+{
+       DECLARE_BITMAP(pendr, VGIC_V5_NR_PRIVATE_IRQS);
+
+       /* We assume 64 or 128 PPIs - see above comment */
+       BUILD_BUG_ON(VGIC_V5_NR_PRIVATE_IRQS % 64);
+
+       /* Enable DVI so that the guest's interrupt config takes over */
+       write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_dvir, 0, 64),
+                      SYS_ICH_PPI_DVIR0_EL2);
+
+       write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_activer, 0, 64),
+                      SYS_ICH_PPI_ACTIVER0_EL2);
+       write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_enabler, 0, 64),
+                      SYS_ICH_PPI_ENABLER0_EL2);
+
+       /* Update the pending state of the NON-DVI'd PPIs, only */
+       bitmap_andnot(pendr, host_data_ptr(vgic_v5_ppi_state)->pendr_entry,
+                     cpu_if->vgic_ppi_dvir, VGIC_V5_NR_PRIVATE_IRQS);
+       write_sysreg_s(bitmap_read(pendr, 0, 64), SYS_ICH_PPI_PENDR0_EL2);
+
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[0],
+                      SYS_ICH_PPI_PRIORITYR0_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[1],
+                      SYS_ICH_PPI_PRIORITYR1_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[2],
+                      SYS_ICH_PPI_PRIORITYR2_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[3],
+                      SYS_ICH_PPI_PRIORITYR3_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[4],
+                      SYS_ICH_PPI_PRIORITYR4_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[5],
+                      SYS_ICH_PPI_PRIORITYR5_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[6],
+                      SYS_ICH_PPI_PRIORITYR6_EL2);
+       write_sysreg_s(cpu_if->vgic_ppi_priorityr[7],
+                      SYS_ICH_PPI_PRIORITYR7_EL2);
+
+       if (VGIC_V5_NR_PRIVATE_IRQS == 128) {
+               /* Enable DVI so that the guest's interrupt config takes over */
+               write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_dvir, 64, 64),
+                              SYS_ICH_PPI_DVIR1_EL2);
+
+               write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_activer, 64, 64),
+                              SYS_ICH_PPI_ACTIVER1_EL2);
+               write_sysreg_s(bitmap_read(cpu_if->vgic_ppi_enabler, 64, 64),
+                              SYS_ICH_PPI_ENABLER1_EL2);
+               write_sysreg_s(bitmap_read(pendr, 64, 64),
+                              SYS_ICH_PPI_PENDR1_EL2);
+
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[8],
+                              SYS_ICH_PPI_PRIORITYR8_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[9],
+                              SYS_ICH_PPI_PRIORITYR9_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[10],
+                              SYS_ICH_PPI_PRIORITYR10_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[11],
+                              SYS_ICH_PPI_PRIORITYR11_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[12],
+                              SYS_ICH_PPI_PRIORITYR12_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[13],
+                              SYS_ICH_PPI_PRIORITYR13_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[14],
+                              SYS_ICH_PPI_PRIORITYR14_EL2);
+               write_sysreg_s(cpu_if->vgic_ppi_priorityr[15],
+                              SYS_ICH_PPI_PRIORITYR15_EL2);
+       } else {
+               write_sysreg_s(0, SYS_ICH_PPI_DVIR1_EL2);
+
+               write_sysreg_s(0, SYS_ICH_PPI_ACTIVER1_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_ENABLER1_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PENDR1_EL2);
+
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR8_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR9_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR10_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR11_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR12_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR13_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR14_EL2);
+               write_sysreg_s(0, SYS_ICH_PPI_PRIORITYR15_EL2);
+       }
+}
+
+void __vgic_v5_save_state(struct vgic_v5_cpu_if *cpu_if)
+{
+       cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2);
+       cpu_if->vgic_icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
+}
+
+void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if)
+{
+       write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
+}
index afc4aed9231ac0518b84131ec0bae4289eefbd1b..9695328bbd96ec0cd989ee2b903fd37cbc19041c 100644 (file)
@@ -10,4 +10,4 @@ CFLAGS_switch.o += -Wno-override-init
 
 obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o
 obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
-        ../fpsimd.o ../hyp-entry.o ../exception.o
+        ../fpsimd.o ../hyp-entry.o ../exception.o ../vgic-v5-sr.o
index 24969fa8d02d6410c9cf5e2b894a29ad9fb00a1f..07e394690dccb03a2921c66ed8ed44e3b28477f8 100644 (file)
@@ -428,6 +428,27 @@ struct vgic_v3_cpu_if {
        unsigned int used_lrs;
 };
 
+struct vgic_v5_cpu_if {
+       u64     vgic_apr;
+       u64     vgic_vmcr;
+
+       /* PPI register state */
+       DECLARE_BITMAP(vgic_ppi_dvir, VGIC_V5_NR_PRIVATE_IRQS);
+       DECLARE_BITMAP(vgic_ppi_activer, VGIC_V5_NR_PRIVATE_IRQS);
+       DECLARE_BITMAP(vgic_ppi_enabler, VGIC_V5_NR_PRIVATE_IRQS);
+       /* We have one byte (of which 5 bits are used) per PPI for priority */
+       u64     vgic_ppi_priorityr[VGIC_V5_NR_PRIVATE_IRQS / 8];
+
+       /*
+        * The ICSR is re-used across host and guest, and hence it needs to be
+        * saved/restored. Only one copy is required as the host should block
+        * preemption between executing GIC CDRCFG and acccessing the
+        * ICC_ICSR_EL1. A guest, of course, can never guarantee this, and hence
+        * it is the hyp's responsibility to keep the state constistent.
+        */
+       u64     vgic_icsr;
+};
+
 /* What PPI capabilities does a GICv5 host have */
 struct vgic_v5_ppi_caps {
        DECLARE_BITMAP(impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
@@ -438,6 +459,7 @@ struct vgic_cpu {
        union {
                struct vgic_v2_cpu_if   vgic_v2;
                struct vgic_v3_cpu_if   vgic_v3;
+               struct vgic_v5_cpu_if   vgic_v5;
        };
 
        struct vgic_irq *private_irqs;