[PATCH v3 22/36] KVM: arm64: gic-v5: Trap and mask guest ICC_PPI_ENABLERx_EL1 writes

Sascha Bischoff Sascha.Bischoff at arm.com
Fri Jan 9 09:04:46 PST 2026


A guest should not be able to detect if a PPI that is not exposed to
the guest is implemented or not. Avoid the guest enabling any PPIs
that are not implemented as far as the guest is concerned by trapping
and masking writes to the two ICC_PPI_ENABLERx_EL1 regisers.

When a guest writes these registers, the write is masked with the set
of PPIs actually exposed to the guest, and the state is written back
to KVM's shadow state. As there is now no way for the guest to change
the PPI enable state without it being trapped, saving of the PPI
Enable state is dropped from guest exit.

Reads for the above registers are not masked. When the guest is
running and reads from the above registers, it is presented with what
KVM provides in the ICH_PPI_ENABLERx_EL2 registers, which is the
masked version of what the guest last wrote.

Signed-off-by: Sascha Bischoff <sascha.bischoff at arm.com>
---
 arch/arm64/include/asm/kvm_host.h |  1 -
 arch/arm64/kvm/config.c           | 15 +++++++++++--
 arch/arm64/kvm/hyp/vgic-v5-sr.c   |  3 ---
 arch/arm64/kvm/sys_regs.c         | 35 +++++++++++++++++++++++++++++++
 4 files changed, 48 insertions(+), 6 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index b49820d05e6c5..0c7ac0f0a1823 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -788,7 +788,6 @@ struct kvm_host_data {
 
 		/* The saved state of the regs when leaving the guest */
 		u64 activer_exit[2];
-		u64 enabler_exit[2];
 	} vgic_v5_ppi_state;
 };
 
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 79e8d6e3b5f8e..a82a72973378f 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1586,10 +1586,21 @@ static void __compute_ich_hfgrtr(struct kvm_vcpu *vcpu)
 {
 	__compute_fgt(vcpu, ICH_HFGRTR_EL2);
 
-	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest */
+	/* ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest. */
 	*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
 }
 
+static void __compute_ich_hfgwtr(struct kvm_vcpu *vcpu)
+{
+	__compute_fgt(vcpu, ICH_HFGWTR_EL2);
+
+	/*
+	 * We present a different subset of PPIs the guest from what
+	 * exist in real hardware. We only trap writes, not reads.
+	 */
+	*vcpu_fgt(vcpu, ICH_HFGWTR_EL2) &= ~(ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1);
+}
+
 void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 {
 	if (!cpus_have_final_cap(ARM64_HAS_FGT))
@@ -1612,7 +1623,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
 
 	if (cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF)) {
 		__compute_ich_hfgrtr(vcpu);
-		__compute_fgt(vcpu, ICH_HFGWTR_EL2);
+		__compute_ich_hfgwtr(vcpu);
 		__compute_fgt(vcpu, ICH_HFGITR_EL2);
 	}
 }
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 47c71c53fcb10..4d20b90031711 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -31,9 +31,6 @@ void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if)
 	host_data_ptr(vgic_v5_ppi_state)->activer_exit[0] = read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
 	host_data_ptr(vgic_v5_ppi_state)->activer_exit[1] = read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);
 
-	host_data_ptr(vgic_v5_ppi_state)->enabler_exit[0] = read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
-	host_data_ptr(vgic_v5_ppi_state)->enabler_exit[1] = read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
-
 	host_data_ptr(vgic_v5_ppi_state)->pendr_exit[0] = read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
 	host_data_ptr(vgic_v5_ppi_state)->pendr_exit[1] = read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);
 
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index eecfb5f2db79e..89bc2f6ea68d1 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -696,6 +696,39 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 	return true;
 }
 
+static bool access_gicv5_ppi_enabler(struct kvm_vcpu *vcpu,
+				     struct sys_reg_params *p,
+				     const struct sys_reg_desc *r)
+{
+	struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+	u64 masked_write;
+
+	/* We never expect to get here with a read! */
+	if (WARN_ON_ONCE(!p->is_write))
+		return undef_access(vcpu, p, r);
+
+	masked_write = p->regval & vcpu->kvm->arch.vgic.gicv5_vm.vgic_ppi_mask[p->Op2 % 2];
+	cpu_if->vgic_ppi_enabler[p->Op2 % 2] = masked_write;
+
+	/* Sync the change in enable states to the vgic_irqs */
+	for (int i = 0; i < 64; i++) {
+		struct vgic_irq *irq;
+		u32 intid;
+
+		intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+		intid |= FIELD_PREP(GICV5_HWIRQ_ID, (p->Op2 % 2) * 64 + i);
+
+		irq = vgic_get_vcpu_irq(vcpu, intid);
+
+		scoped_guard(raw_spinlock_irqsave, &irq->irq_lock)
+			irq->enabled = !!(cpu_if->vgic_ppi_enabler[p->Op2 % 2] & BIT(i));
+
+		vgic_put_irq(vcpu->kvm, irq);
+	}
+
+	return true;
+}
+
 static bool trap_raz_wi(struct kvm_vcpu *vcpu,
 			struct sys_reg_params *p,
 			const struct sys_reg_desc *r)
@@ -3430,6 +3463,8 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
+	{ SYS_DESC(SYS_ICC_PPI_ENABLER0_EL1), access_gicv5_ppi_enabler },
+	{ SYS_DESC(SYS_ICC_PPI_ENABLER1_EL1), access_gicv5_ppi_enabler },
 	{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
 	{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
 	{ SYS_DESC(SYS_ICC_SGI1R_EL1), access_gic_sgi },
-- 
2.34.1



More information about the linux-arm-kernel mailing list