[PATCH v6 14/39] KVM: arm64: gic-v5: Trap and emulate ICC_IDR0_EL1 accesses

Sascha Bischoff
2026-03-17T11:44:46Z

Unless accesses to the ICC_IDR0_EL1 are trapped by KVM, the guest
reads the same state as the host. This isn't desirable as it limits
the migratability of VMs and means that KVM can't hide hardware
features such as FEAT_GCIE_LEGACY.

Trap and emulate accesses to the register, and present KVM's chosen ID
bits and Priority bits (which is 5, as GICv5 only supports 5 bits of
priority in the CPU interface). FEAT_GCIE_LEGACY is never presented to
the guest as it is only relevant for nested guests doing mixed GICv5
and GICv3 support.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/config.c | 11 +++++++++--
arch/arm64/kvm/sys_regs.c | 23 +++++++++++++++++++++++
2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index bac5f49fdbdef..5663f25905e83 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1688,8 +1688,15 @@ 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 */
- *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
+ /*
+ * ICC_IAFFIDR_EL1 *always* needs to be trapped when running a guest.
+ *
+ * We also trap accesses to ICC_IDR0_EL1 to allow us to completely hide
+ * FEAT_GCIE_LEGACY from the guest, and to (potentially) present fewer
+ * ID bits than the host supports.
+ */
+ *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~(ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1 |
+ ICH_HFGRTR_EL2_ICC_IDRn_EL1);
}

void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index d4531457ea026..85300e76bbe46 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -681,6 +681,28 @@ static bool access_gic_dir(struct kvm_vcpu *vcpu,
return true;
}

+static bool access_gicv5_idr0(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (p->is_write)
+ return undef_access(vcpu, p, r);
+
+ /*
+ * Expose KVM's priority- and ID-bits to the guest, but not GCIE_LEGACY.
+ *
+ * Note: for GICv5 the mimic the way that the num_pri_bits and
+ * num_id_bits fields are used with GICv3:
+ * - num_pri_bits stores the actual number of priority bits, whereas the
+ * register field stores num_pri_bits - 1.
+ * - num_id_bits stores the raw field value, which is 0b0000 for 16 bits
+ * and 0b0001 for 24 bits.
+ */
+ p->regval = FIELD_PREP(ICC_IDR0_EL1_PRI_BITS, vcpu->arch.vgic_cpu.num_pri_bits - 1) |
+ FIELD_PREP(ICC_IDR0_EL1_ID_BITS, vcpu->arch.vgic_cpu.num_id_bits);
+
+ return true;
+}
+
static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
const struct sys_reg_desc *r)
{
@@ -3420,6 +3442,7 @@ static const struct sys_reg_desc sys_reg_descs[] = {
{ SYS_DESC(SYS_ICC_AP1R1_EL1), undef_access },
{ SYS_DESC(SYS_ICC_AP1R2_EL1), undef_access },
{ SYS_DESC(SYS_ICC_AP1R3_EL1), undef_access },
+ { SYS_DESC(SYS_ICC_IDR0_EL1), access_gicv5_idr0 },
{ SYS_DESC(SYS_ICC_IAFFIDR_EL1), access_gicv5_iaffid },
{ SYS_DESC(SYS_ICC_DIR_EL1), access_gic_dir },
{ SYS_DESC(SYS_ICC_RPR_EL1), undef_access },
--
2.34.1