]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
KVM: arm64: nv: Plumb handling of GICv3 EL2 accesses
authorMarc Zyngier <maz@kernel.org>
Tue, 25 Feb 2025 17:29:20 +0000 (17:29 +0000)
committerOliver Upton <oliver.upton@linux.dev>
Mon, 3 Mar 2025 22:55:10 +0000 (14:55 -0800)
Wire the handling of all GICv3 EL2 registers, and provide emulation
for all the non memory-backed registers (ICC_SRE_EL2, ICH_VTR_EL2,
ICH_MISR_EL2, ICH_ELRSR_EL2, and ICH_EISR_EL2).

Signed-off-by: Marc Zyngier <maz@kernel.org>
Link: https://lore.kernel.org/r/20250225172930.1850838-7-maz@kernel.org
Signed-off-by: Oliver Upton <oliver.upton@linux.dev>
arch/arm64/kvm/Makefile
arch/arm64/kvm/sys_regs.c
arch/arm64/kvm/vgic/vgic-v3-nested.c [new file with mode: 0644]
include/kvm/arm_vgic.h

index 3cf7adb2b5038c6b0e0e3676e6069d236dc9c9f9..209bc76263f100b053113e88d12801ead9d060f0 100644 (file)
@@ -23,7 +23,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
         vgic/vgic-v3.o vgic/vgic-v4.o \
         vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
         vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
-        vgic/vgic-its.o vgic/vgic-debug.o
+        vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o
 
 kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o
 kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o
index 82430c1e1dd02b1ac24fd2ddcd05a91272997fdb..84248fabd418ca84494848ccb19db65093e580d2 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/mm.h>
 #include <linux/printk.h>
 #include <linux/uaccess.h>
+#include <linux/irqchip/arm-gic-v3.h>
 
 #include <asm/arm_pmuv3.h>
 #include <asm/cacheflush.h>
@@ -531,7 +532,13 @@ static bool access_gic_sre(struct kvm_vcpu *vcpu,
        if (p->is_write)
                return ignore_write(vcpu, p);
 
-       p->regval = vcpu->arch.vgic_cpu.vgic_v3.vgic_sre;
+       if (p->Op1 == 4) {      /* ICC_SRE_EL2 */
+               p->regval = (ICC_SRE_EL2_ENABLE | ICC_SRE_EL2_SRE |
+                            ICC_SRE_EL1_DIB | ICC_SRE_EL1_DFB);
+       } else {                /* ICC_SRE_EL1 */
+               p->regval = vcpu->arch.vgic_cpu.vgic_v3.vgic_sre;
+       }
+
        return true;
 }
 
@@ -2426,6 +2433,59 @@ static bool access_zcr_el2(struct kvm_vcpu *vcpu,
        vq = SYS_FIELD_GET(ZCR_ELx, LEN, p->regval) + 1;
        vq = min(vq, vcpu_sve_max_vq(vcpu));
        vcpu_write_sys_reg(vcpu, vq - 1, ZCR_EL2);
+
+       return true;
+}
+
+static bool access_gic_vtr(struct kvm_vcpu *vcpu,
+                          struct sys_reg_params *p,
+                          const struct sys_reg_desc *r)
+{
+       if (p->is_write)
+               return write_to_read_only(vcpu, p, r);
+
+       p->regval = kvm_vgic_global_state.ich_vtr_el2;
+       p->regval &= ~(ICH_VTR_EL2_DVIM         |
+                      ICH_VTR_EL2_A3V          |
+                      ICH_VTR_EL2_IDbits);
+       p->regval |= ICH_VTR_EL2_nV4;
+
+       return true;
+}
+
+static bool access_gic_misr(struct kvm_vcpu *vcpu,
+                           struct sys_reg_params *p,
+                           const struct sys_reg_desc *r)
+{
+       if (p->is_write)
+               return write_to_read_only(vcpu, p, r);
+
+       p->regval = vgic_v3_get_misr(vcpu);
+
+       return true;
+}
+
+static bool access_gic_eisr(struct kvm_vcpu *vcpu,
+                           struct sys_reg_params *p,
+                           const struct sys_reg_desc *r)
+{
+       if (p->is_write)
+               return write_to_read_only(vcpu, p, r);
+
+       p->regval = vgic_v3_get_eisr(vcpu);
+
+       return true;
+}
+
+static bool access_gic_elrsr(struct kvm_vcpu *vcpu,
+                            struct sys_reg_params *p,
+                            const struct sys_reg_desc *r)
+{
+       if (p->is_write)
+               return write_to_read_only(vcpu, p, r);
+
+       p->regval = vgic_v3_get_elrsr(vcpu);
+
        return true;
 }
 
@@ -3102,7 +3162,40 @@ static const struct sys_reg_desc sys_reg_descs[] = {
        EL2_REG(RVBAR_EL2, access_rw, reset_val, 0),
        { SYS_DESC(SYS_RMR_EL2), undef_access },
 
+       EL2_REG_VNCR(ICH_AP0R0_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP0R1_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP0R2_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP0R3_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP1R0_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP1R1_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP1R2_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_AP1R3_EL2, reset_val, 0),
+
+       { SYS_DESC(SYS_ICC_SRE_EL2), access_gic_sre },
+
        EL2_REG_VNCR(ICH_HCR_EL2, reset_val, 0),
+       { SYS_DESC(SYS_ICH_VTR_EL2), access_gic_vtr },
+       { SYS_DESC(SYS_ICH_MISR_EL2), access_gic_misr },
+       { SYS_DESC(SYS_ICH_EISR_EL2), access_gic_eisr },
+       { SYS_DESC(SYS_ICH_ELRSR_EL2), access_gic_elrsr },
+       EL2_REG_VNCR(ICH_VMCR_EL2, reset_val, 0),
+
+       EL2_REG_VNCR(ICH_LR0_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR1_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR2_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR3_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR4_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR5_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR6_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR7_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR8_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR9_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR10_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR11_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR12_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR13_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR14_EL2, reset_val, 0),
+       EL2_REG_VNCR(ICH_LR15_EL2, reset_val, 0),
 
        EL2_REG(CONTEXTIDR_EL2, access_rw, reset_val, 0),
        EL2_REG(TPIDR_EL2, access_rw, reset_val, 0),
diff --git a/arch/arm64/kvm/vgic/vgic-v3-nested.c b/arch/arm64/kvm/vgic/vgic-v3-nested.c
new file mode 100644 (file)
index 0000000..48bfd2f
--- /dev/null
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/cpu.h>
+#include <linux/kvm.h>
+#include <linux/kvm_host.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <kvm/arm_vgic.h>
+
+#include <asm/kvm_arm.h>
+#include <asm/kvm_emulate.h>
+#include <asm/kvm_nested.h>
+
+#include "vgic.h"
+
+#define ICH_LRN(n)     (ICH_LR0_EL2 + (n))
+
+struct mi_state {
+       u16     eisr;
+       u16     elrsr;
+       bool    pend;
+};
+
+/*
+ * Nesting GICv3 support
+ *
+ * System register emulation:
+ *
+ * We get two classes of registers:
+ *
+ * - those backed by memory (LRs, APRs, HCR, VMCR): L1 can freely access
+ *   them, and L0 doesn't see a thing.
+ *
+ * - those that always trap (ELRSR, EISR, MISR): these are status registers
+ *   that are built on the fly based on the in-memory state.
+ *
+ * Only L1 can access the ICH_*_EL2 registers. A non-NV L2 obviously cannot,
+ * and a NV L2 would either access the VNCR page provided by L1 (memory
+ * based registers), or see the access redirected to L1 (registers that
+ * trap) thanks to NV being set by L1.
+ */
+
+static bool lr_triggers_eoi(u64 lr)
+{
+       return !(lr & (ICH_LR_STATE | ICH_LR_HW)) && (lr & ICH_LR_EOI);
+}
+
+static void vgic_compute_mi_state(struct kvm_vcpu *vcpu, struct mi_state *mi_state)
+{
+       u16 eisr = 0, elrsr = 0;
+       bool pend = false;
+
+       for (int i = 0; i < kvm_vgic_global_state.nr_lr; i++) {
+               u64 lr = __vcpu_sys_reg(vcpu, ICH_LRN(i));
+
+               if (lr_triggers_eoi(lr))
+                       eisr |= BIT(i);
+               if (!(lr & ICH_LR_STATE))
+                       elrsr |= BIT(i);
+               pend |= (lr & ICH_LR_PENDING_BIT);
+       }
+
+       mi_state->eisr  = eisr;
+       mi_state->elrsr = elrsr;
+       mi_state->pend  = pend;
+}
+
+u16 vgic_v3_get_eisr(struct kvm_vcpu *vcpu)
+{
+       struct mi_state mi_state;
+
+       vgic_compute_mi_state(vcpu, &mi_state);
+       return mi_state.eisr;
+}
+
+u16 vgic_v3_get_elrsr(struct kvm_vcpu *vcpu)
+{
+       struct mi_state mi_state;
+
+       vgic_compute_mi_state(vcpu, &mi_state);
+       return mi_state.elrsr;
+}
+
+u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu)
+{
+       struct mi_state mi_state;
+       u64 reg = 0, hcr, vmcr;
+
+       hcr = __vcpu_sys_reg(vcpu, ICH_HCR_EL2);
+       vmcr = __vcpu_sys_reg(vcpu, ICH_VMCR_EL2);
+
+       vgic_compute_mi_state(vcpu, &mi_state);
+
+       if (mi_state.eisr)
+               reg |= ICH_MISR_EL2_EOI;
+
+       if (__vcpu_sys_reg(vcpu, ICH_HCR_EL2) & ICH_HCR_EL2_UIE) {
+               int used_lrs = kvm_vgic_global_state.nr_lr;
+
+               used_lrs -= hweight16(mi_state.elrsr);
+               reg |= (used_lrs <= 1) ? ICH_MISR_EL2_U : 0;
+       }
+
+       if ((hcr & ICH_HCR_EL2_LRENPIE) && FIELD_GET(ICH_HCR_EL2_EOIcount_MASK, hcr))
+               reg |= ICH_MISR_EL2_LRENP;
+
+       if ((hcr & ICH_HCR_EL2_NPIE) && !mi_state.pend)
+               reg |= ICH_MISR_EL2_NP;
+
+       if ((hcr & ICH_HCR_EL2_VGrp0EIE) && (vmcr & ICH_VMCR_ENG0_MASK))
+               reg |= ICH_MISR_EL2_VGrp0E;
+
+       if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_ENG0_MASK))
+               reg |= ICH_MISR_EL2_VGrp0D;
+
+       if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_ENG1_MASK))
+               reg |= ICH_MISR_EL2_VGrp1E;
+
+       if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_ENG1_MASK))
+               reg |= ICH_MISR_EL2_VGrp1D;
+
+       return reg;
+}
index 3a8ccfda34d29571d2d6f110f6bf7c002c996297..5017fcc71e6040248e0342210e3f14fded7e107f 100644 (file)
@@ -389,6 +389,10 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu);
 void kvm_vgic_load(struct kvm_vcpu *vcpu);
 void kvm_vgic_put(struct kvm_vcpu *vcpu);
 
+u16 vgic_v3_get_eisr(struct kvm_vcpu *vcpu);
+u16 vgic_v3_get_elrsr(struct kvm_vcpu *vcpu);
+u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu);
+
 #define irqchip_in_kernel(k)   (!!((k)->arch.vgic.in_kernel))
 #define vgic_initialized(k)    ((k)->arch.vgic.initialized)
 #define vgic_ready(k)          ((k)->arch.vgic.ready)