* [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-15 11:52 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 01/32] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
` (30 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes, Sascha Bischoff
From: Sascha Bischoff <Sascha.Bischoff@arm.com>
The VGIC-v3 code relied on hand-written definitions for the
ICH_VMCR_EL2 register. This register, and the associated fields, is
now generated as part of the sysreg framework. Move to using the
generated definitions instead of the hand-written ones.
There are no functional changes as part of this change.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/sysreg.h | 21 ---------
arch/arm64/kvm/hyp/vgic-v3-sr.c | 64 ++++++++++++----------------
arch/arm64/kvm/vgic/vgic-v3-nested.c | 8 ++--
arch/arm64/kvm/vgic/vgic-v3.c | 48 ++++++++++-----------
4 files changed, 54 insertions(+), 87 deletions(-)
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 9df51accbb025..b3b8b8cd7bf1e 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -560,7 +560,6 @@
#define SYS_ICC_SRE_EL2 sys_reg(3, 4, 12, 9, 5)
#define SYS_ICH_EISR_EL2 sys_reg(3, 4, 12, 11, 3)
#define SYS_ICH_ELRSR_EL2 sys_reg(3, 4, 12, 11, 5)
-#define SYS_ICH_VMCR_EL2 sys_reg(3, 4, 12, 11, 7)
#define __SYS__LR0_EL2(x) sys_reg(3, 4, 12, 12, x)
#define SYS_ICH_LR0_EL2 __SYS__LR0_EL2(0)
@@ -988,26 +987,6 @@
#define ICH_LR_PRIORITY_SHIFT 48
#define ICH_LR_PRIORITY_MASK (0xffULL << ICH_LR_PRIORITY_SHIFT)
-/* ICH_VMCR_EL2 bit definitions */
-#define ICH_VMCR_ACK_CTL_SHIFT 2
-#define ICH_VMCR_ACK_CTL_MASK (1 << ICH_VMCR_ACK_CTL_SHIFT)
-#define ICH_VMCR_FIQ_EN_SHIFT 3
-#define ICH_VMCR_FIQ_EN_MASK (1 << ICH_VMCR_FIQ_EN_SHIFT)
-#define ICH_VMCR_CBPR_SHIFT 4
-#define ICH_VMCR_CBPR_MASK (1 << ICH_VMCR_CBPR_SHIFT)
-#define ICH_VMCR_EOIM_SHIFT 9
-#define ICH_VMCR_EOIM_MASK (1 << ICH_VMCR_EOIM_SHIFT)
-#define ICH_VMCR_BPR1_SHIFT 18
-#define ICH_VMCR_BPR1_MASK (7 << ICH_VMCR_BPR1_SHIFT)
-#define ICH_VMCR_BPR0_SHIFT 21
-#define ICH_VMCR_BPR0_MASK (7 << ICH_VMCR_BPR0_SHIFT)
-#define ICH_VMCR_PMR_SHIFT 24
-#define ICH_VMCR_PMR_MASK (0xffUL << ICH_VMCR_PMR_SHIFT)
-#define ICH_VMCR_ENG0_SHIFT 0
-#define ICH_VMCR_ENG0_MASK (1 << ICH_VMCR_ENG0_SHIFT)
-#define ICH_VMCR_ENG1_SHIFT 1
-#define ICH_VMCR_ENG1_MASK (1 << ICH_VMCR_ENG1_SHIFT)
-
/*
* Permission Indirection Extension (PIE) permission encodings.
* Encodings with the _O suffix, have overlays applied (Permission Overlay Extension).
diff --git a/arch/arm64/kvm/hyp/vgic-v3-sr.c b/arch/arm64/kvm/hyp/vgic-v3-sr.c
index 0b670a033fd87..24a2074f3a8cf 100644
--- a/arch/arm64/kvm/hyp/vgic-v3-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v3-sr.c
@@ -569,11 +569,11 @@ static int __vgic_v3_highest_priority_lr(struct kvm_vcpu *vcpu, u32 vmcr,
continue;
/* Group-0 interrupt, but Group-0 disabled? */
- if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG0_MASK))
+ if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG0_MASK))
continue;
/* Group-1 interrupt, but Group-1 disabled? */
- if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG1_MASK))
+ if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG1_MASK))
continue;
/* Not the highest priority? */
@@ -646,19 +646,19 @@ static int __vgic_v3_get_highest_active_priority(void)
static unsigned int __vgic_v3_get_bpr0(u32 vmcr)
{
- return (vmcr & ICH_VMCR_BPR0_MASK) >> ICH_VMCR_BPR0_SHIFT;
+ return FIELD_GET(ICH_VMCR_EL2_VBPR0, vmcr);
}
static unsigned int __vgic_v3_get_bpr1(u32 vmcr)
{
unsigned int bpr;
- if (vmcr & ICH_VMCR_CBPR_MASK) {
+ if (vmcr & ICH_VMCR_EL2_VCBPR_MASK) {
bpr = __vgic_v3_get_bpr0(vmcr);
if (bpr < 7)
bpr++;
} else {
- bpr = (vmcr & ICH_VMCR_BPR1_MASK) >> ICH_VMCR_BPR1_SHIFT;
+ bpr = FIELD_GET(ICH_VMCR_EL2_VBPR1, vmcr);
}
return bpr;
@@ -758,7 +758,7 @@ static void __vgic_v3_read_iar(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
if (grp != !!(lr_val & ICH_LR_GROUP))
goto spurious;
- pmr = (vmcr & ICH_VMCR_PMR_MASK) >> ICH_VMCR_PMR_SHIFT;
+ pmr = FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr);
lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
if (pmr <= lr_prio)
goto spurious;
@@ -806,7 +806,7 @@ static int ___vgic_v3_write_dir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
int lr;
/* EOImode == 0, nothing to be done here */
- if (!(vmcr & ICH_VMCR_EOIM_MASK))
+ if (!FIELD_GET(ICH_VMCR_EL2_VEOIM_MASK, vmcr))
return 1;
/* No deactivate to be performed on an LPI */
@@ -849,7 +849,7 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
}
/* EOImode == 1 and not an LPI, nothing to be done here */
- if ((vmcr & ICH_VMCR_EOIM_MASK) && !(vid >= VGIC_MIN_LPI))
+ if (FIELD_GET(ICH_VMCR_EL2_VEOIM_MASK, vmcr) && !(vid >= VGIC_MIN_LPI))
return;
lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
@@ -865,12 +865,12 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
{
- vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
+ vcpu_set_reg(vcpu, rt, !!FIELD_GET(ICH_VMCR_EL2_VENG0_MASK, vmcr));
}
static void __vgic_v3_read_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
{
- vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG1_MASK));
+ vcpu_set_reg(vcpu, rt, !!FIELD_GET(ICH_VMCR_EL2_VENG1_MASK, vmcr));
}
static void __vgic_v3_write_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
@@ -878,9 +878,9 @@ static void __vgic_v3_write_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
u64 val = vcpu_get_reg(vcpu, rt);
if (val & 1)
- vmcr |= ICH_VMCR_ENG0_MASK;
+ vmcr |= ICH_VMCR_EL2_VENG0_MASK;
else
- vmcr &= ~ICH_VMCR_ENG0_MASK;
+ vmcr &= ~ICH_VMCR_EL2_VENG0_MASK;
__vgic_v3_write_vmcr(vmcr);
}
@@ -890,9 +890,9 @@ static void __vgic_v3_write_igrpen1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
u64 val = vcpu_get_reg(vcpu, rt);
if (val & 1)
- vmcr |= ICH_VMCR_ENG1_MASK;
+ vmcr |= ICH_VMCR_EL2_VENG1_MASK;
else
- vmcr &= ~ICH_VMCR_ENG1_MASK;
+ vmcr &= ~ICH_VMCR_EL2_VENG1_MASK;
__vgic_v3_write_vmcr(vmcr);
}
@@ -916,10 +916,8 @@ static void __vgic_v3_write_bpr0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
if (val < bpr_min)
val = bpr_min;
- val <<= ICH_VMCR_BPR0_SHIFT;
- val &= ICH_VMCR_BPR0_MASK;
- vmcr &= ~ICH_VMCR_BPR0_MASK;
- vmcr |= val;
+ vmcr &= ~ICH_VMCR_EL2_VBPR0_MASK;
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, val);
__vgic_v3_write_vmcr(vmcr);
}
@@ -929,17 +927,15 @@ static void __vgic_v3_write_bpr1(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
u64 val = vcpu_get_reg(vcpu, rt);
u8 bpr_min = __vgic_v3_bpr_min();
- if (vmcr & ICH_VMCR_CBPR_MASK)
+ if (FIELD_GET(ICH_VMCR_EL2_VCBPR_MASK, val))
return;
/* Enforce BPR limiting */
if (val < bpr_min)
val = bpr_min;
- val <<= ICH_VMCR_BPR1_SHIFT;
- val &= ICH_VMCR_BPR1_MASK;
- vmcr &= ~ICH_VMCR_BPR1_MASK;
- vmcr |= val;
+ vmcr &= ~ICH_VMCR_EL2_VBPR1_MASK;
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, val);
__vgic_v3_write_vmcr(vmcr);
}
@@ -1029,19 +1025,15 @@ static void __vgic_v3_read_hppir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
static void __vgic_v3_read_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
{
- vmcr &= ICH_VMCR_PMR_MASK;
- vmcr >>= ICH_VMCR_PMR_SHIFT;
- vcpu_set_reg(vcpu, rt, vmcr);
+ vcpu_set_reg(vcpu, rt, FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr));
}
static void __vgic_v3_write_pmr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
{
u32 val = vcpu_get_reg(vcpu, rt);
- val <<= ICH_VMCR_PMR_SHIFT;
- val &= ICH_VMCR_PMR_MASK;
- vmcr &= ~ICH_VMCR_PMR_MASK;
- vmcr |= val;
+ vmcr &= ~ICH_VMCR_EL2_VPMR_MASK;
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, val);
write_gicreg(vmcr, ICH_VMCR_EL2);
}
@@ -1064,9 +1056,9 @@ static void __vgic_v3_read_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
/* A3V */
val |= ((vtr >> 21) & 1) << ICC_CTLR_EL1_A3V_SHIFT;
/* EOImode */
- val |= ((vmcr & ICH_VMCR_EOIM_MASK) >> ICH_VMCR_EOIM_SHIFT) << ICC_CTLR_EL1_EOImode_SHIFT;
+ val |= FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr) << ICC_CTLR_EL1_EOImode_SHIFT;
/* CBPR */
- val |= (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
+ val |= FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
vcpu_set_reg(vcpu, rt, val);
}
@@ -1076,14 +1068,14 @@ static void __vgic_v3_write_ctlr(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
u32 val = vcpu_get_reg(vcpu, rt);
if (val & ICC_CTLR_EL1_CBPR_MASK)
- vmcr |= ICH_VMCR_CBPR_MASK;
+ vmcr |= ICH_VMCR_EL2_VCBPR_MASK;
else
- vmcr &= ~ICH_VMCR_CBPR_MASK;
+ vmcr &= ~ICH_VMCR_EL2_VCBPR_MASK;
if (val & ICC_CTLR_EL1_EOImode_MASK)
- vmcr |= ICH_VMCR_EOIM_MASK;
+ vmcr |= ICH_VMCR_EL2_VEOIM_MASK;
else
- vmcr &= ~ICH_VMCR_EOIM_MASK;
+ vmcr &= ~ICH_VMCR_EL2_VEOIM_MASK;
write_gicreg(vmcr, ICH_VMCR_EL2);
}
diff --git a/arch/arm64/kvm/vgic/vgic-v3-nested.c b/arch/arm64/kvm/vgic/vgic-v3-nested.c
index 61b44f3f2bf14..c9e35ec671173 100644
--- a/arch/arm64/kvm/vgic/vgic-v3-nested.c
+++ b/arch/arm64/kvm/vgic/vgic-v3-nested.c
@@ -202,16 +202,16 @@ u64 vgic_v3_get_misr(struct kvm_vcpu *vcpu)
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))
+ if ((hcr & ICH_HCR_EL2_VGrp0EIE) && (vmcr & ICH_VMCR_EL2_VENG0_MASK))
reg |= ICH_MISR_EL2_VGrp0E;
- if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_ENG0_MASK))
+ if ((hcr & ICH_HCR_EL2_VGrp0DIE) && !(vmcr & ICH_VMCR_EL2_VENG0_MASK))
reg |= ICH_MISR_EL2_VGrp0D;
- if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_ENG1_MASK))
+ if ((hcr & ICH_HCR_EL2_VGrp1EIE) && (vmcr & ICH_VMCR_EL2_VENG1_MASK))
reg |= ICH_MISR_EL2_VGrp1E;
- if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_ENG1_MASK))
+ if ((hcr & ICH_HCR_EL2_VGrp1DIE) && !(vmcr & ICH_VMCR_EL2_VENG1_MASK))
reg |= ICH_MISR_EL2_VGrp1D;
return reg;
diff --git a/arch/arm64/kvm/vgic/vgic-v3.c b/arch/arm64/kvm/vgic/vgic-v3.c
index 1d6dd1b545bdd..2afc041672311 100644
--- a/arch/arm64/kvm/vgic/vgic-v3.c
+++ b/arch/arm64/kvm/vgic/vgic-v3.c
@@ -41,9 +41,9 @@ void vgic_v3_configure_hcr(struct kvm_vcpu *vcpu,
if (!als->nr_sgi)
cpuif->vgic_hcr |= ICH_HCR_EL2_vSGIEOICount;
- cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_ENG0_MASK) ?
+ cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_EL2_VENG0_MASK) ?
ICH_HCR_EL2_VGrp0DIE : ICH_HCR_EL2_VGrp0EIE;
- cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_ENG1_MASK) ?
+ cpuif->vgic_hcr |= (cpuif->vgic_vmcr & ICH_VMCR_EL2_VENG1_MASK) ?
ICH_HCR_EL2_VGrp1DIE : ICH_HCR_EL2_VGrp1EIE;
/*
@@ -215,7 +215,7 @@ void vgic_v3_deactivate(struct kvm_vcpu *vcpu, u64 val)
* We only deal with DIR when EOIMode==1, and only for SGI,
* PPI or SPI.
*/
- if (!(cpuif->vgic_vmcr & ICH_VMCR_EOIM_MASK) ||
+ if (!(cpuif->vgic_vmcr & ICH_VMCR_EL2_VEOIM_MASK) ||
val >= vcpu->kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)
return;
@@ -408,25 +408,23 @@ void vgic_v3_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
u32 vmcr;
if (model == KVM_DEV_TYPE_ARM_VGIC_V2) {
- vmcr = (vmcrp->ackctl << ICH_VMCR_ACK_CTL_SHIFT) &
- ICH_VMCR_ACK_CTL_MASK;
- vmcr |= (vmcrp->fiqen << ICH_VMCR_FIQ_EN_SHIFT) &
- ICH_VMCR_FIQ_EN_MASK;
+ vmcr = FIELD_PREP(ICH_VMCR_EL2_VAckCtl, vmcrp->ackctl);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VFIQEn, vmcrp->fiqen);
} else {
/*
* When emulating GICv3 on GICv3 with SRE=1 on the
* VFIQEn bit is RES1 and the VAckCtl bit is RES0.
*/
- vmcr = ICH_VMCR_FIQ_EN_MASK;
+ vmcr = ICH_VMCR_EL2_VFIQEn_MASK;
}
- vmcr |= (vmcrp->cbpr << ICH_VMCR_CBPR_SHIFT) & ICH_VMCR_CBPR_MASK;
- vmcr |= (vmcrp->eoim << ICH_VMCR_EOIM_SHIFT) & ICH_VMCR_EOIM_MASK;
- vmcr |= (vmcrp->abpr << ICH_VMCR_BPR1_SHIFT) & ICH_VMCR_BPR1_MASK;
- vmcr |= (vmcrp->bpr << ICH_VMCR_BPR0_SHIFT) & ICH_VMCR_BPR0_MASK;
- vmcr |= (vmcrp->pmr << ICH_VMCR_PMR_SHIFT) & ICH_VMCR_PMR_MASK;
- vmcr |= (vmcrp->grpen0 << ICH_VMCR_ENG0_SHIFT) & ICH_VMCR_ENG0_MASK;
- vmcr |= (vmcrp->grpen1 << ICH_VMCR_ENG1_SHIFT) & ICH_VMCR_ENG1_MASK;
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VCBPR, vmcrp->cbpr);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VEOIM, vmcrp->eoim);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR1, vmcrp->abpr);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VBPR0, vmcrp->bpr);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VPMR, vmcrp->pmr);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VENG0, vmcrp->grpen0);
+ vmcr |= FIELD_PREP(ICH_VMCR_EL2_VENG1, vmcrp->grpen1);
cpu_if->vgic_vmcr = vmcr;
}
@@ -440,10 +438,8 @@ void vgic_v3_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
vmcr = cpu_if->vgic_vmcr;
if (model == KVM_DEV_TYPE_ARM_VGIC_V2) {
- vmcrp->ackctl = (vmcr & ICH_VMCR_ACK_CTL_MASK) >>
- ICH_VMCR_ACK_CTL_SHIFT;
- vmcrp->fiqen = (vmcr & ICH_VMCR_FIQ_EN_MASK) >>
- ICH_VMCR_FIQ_EN_SHIFT;
+ vmcrp->ackctl = FIELD_GET(ICH_VMCR_EL2_VAckCtl, vmcr);
+ vmcrp->fiqen = FIELD_GET(ICH_VMCR_EL2_VFIQEn, vmcr);
} else {
/*
* When emulating GICv3 on GICv3 with SRE=1 on the
@@ -453,13 +449,13 @@ void vgic_v3_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
vmcrp->ackctl = 0;
}
- vmcrp->cbpr = (vmcr & ICH_VMCR_CBPR_MASK) >> ICH_VMCR_CBPR_SHIFT;
- vmcrp->eoim = (vmcr & ICH_VMCR_EOIM_MASK) >> ICH_VMCR_EOIM_SHIFT;
- vmcrp->abpr = (vmcr & ICH_VMCR_BPR1_MASK) >> ICH_VMCR_BPR1_SHIFT;
- vmcrp->bpr = (vmcr & ICH_VMCR_BPR0_MASK) >> ICH_VMCR_BPR0_SHIFT;
- vmcrp->pmr = (vmcr & ICH_VMCR_PMR_MASK) >> ICH_VMCR_PMR_SHIFT;
- vmcrp->grpen0 = (vmcr & ICH_VMCR_ENG0_MASK) >> ICH_VMCR_ENG0_SHIFT;
- vmcrp->grpen1 = (vmcr & ICH_VMCR_ENG1_MASK) >> ICH_VMCR_ENG1_SHIFT;
+ vmcrp->cbpr = FIELD_GET(ICH_VMCR_EL2_VCBPR, vmcr);
+ vmcrp->eoim = FIELD_GET(ICH_VMCR_EL2_VEOIM, vmcr);
+ vmcrp->abpr = FIELD_GET(ICH_VMCR_EL2_VBPR1, vmcr);
+ vmcrp->bpr = FIELD_GET(ICH_VMCR_EL2_VBPR0, vmcr);
+ vmcrp->pmr = FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr);
+ vmcrp->grpen0 = FIELD_GET(ICH_VMCR_EL2_VENG0, vmcr);
+ vmcrp->grpen1 = FIELD_GET(ICH_VMCR_EL2_VENG1, vmcr);
}
#define INITIAL_PENDBASER_VALUE \
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
2025-12-12 15:22 ` [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
@ 2025-12-15 11:52 ` Marc Zyngier
2025-12-15 14:15 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-15 11:52 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:35 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> From: Sascha Bischoff <Sascha.Bischoff@arm.com>
>
> The VGIC-v3 code relied on hand-written definitions for the
> ICH_VMCR_EL2 register. This register, and the associated fields, is
> now generated as part of the sysreg framework. Move to using the
> generated definitions instead of the hand-written ones.
>
> There are no functional changes as part of this change.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/include/asm/sysreg.h | 21 ---------
> arch/arm64/kvm/hyp/vgic-v3-sr.c | 64 ++++++++++++----------------
> arch/arm64/kvm/vgic/vgic-v3-nested.c | 8 ++--
> arch/arm64/kvm/vgic/vgic-v3.c | 48 ++++++++++-----------
> 4 files changed, 54 insertions(+), 87 deletions(-)
>
> diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
> index 9df51accbb025..b3b8b8cd7bf1e 100644
> --- a/arch/arm64/include/asm/sysreg.h
> +++ b/arch/arm64/include/asm/sysreg.h
> @@ -560,7 +560,6 @@
> #define SYS_ICC_SRE_EL2 sys_reg(3, 4, 12, 9, 5)
> #define SYS_ICH_EISR_EL2 sys_reg(3, 4, 12, 11, 3)
> #define SYS_ICH_ELRSR_EL2 sys_reg(3, 4, 12, 11, 5)
> -#define SYS_ICH_VMCR_EL2 sys_reg(3, 4, 12, 11, 7)
>
> #define __SYS__LR0_EL2(x) sys_reg(3, 4, 12, 12, x)
> #define SYS_ICH_LR0_EL2 __SYS__LR0_EL2(0)
> @@ -988,26 +987,6 @@
> #define ICH_LR_PRIORITY_SHIFT 48
> #define ICH_LR_PRIORITY_MASK (0xffULL << ICH_LR_PRIORITY_SHIFT)
>
> -/* ICH_VMCR_EL2 bit definitions */
> -#define ICH_VMCR_ACK_CTL_SHIFT 2
> -#define ICH_VMCR_ACK_CTL_MASK (1 << ICH_VMCR_ACK_CTL_SHIFT)
> -#define ICH_VMCR_FIQ_EN_SHIFT 3
> -#define ICH_VMCR_FIQ_EN_MASK (1 << ICH_VMCR_FIQ_EN_SHIFT)
> -#define ICH_VMCR_CBPR_SHIFT 4
> -#define ICH_VMCR_CBPR_MASK (1 << ICH_VMCR_CBPR_SHIFT)
> -#define ICH_VMCR_EOIM_SHIFT 9
> -#define ICH_VMCR_EOIM_MASK (1 << ICH_VMCR_EOIM_SHIFT)
> -#define ICH_VMCR_BPR1_SHIFT 18
> -#define ICH_VMCR_BPR1_MASK (7 << ICH_VMCR_BPR1_SHIFT)
> -#define ICH_VMCR_BPR0_SHIFT 21
> -#define ICH_VMCR_BPR0_MASK (7 << ICH_VMCR_BPR0_SHIFT)
> -#define ICH_VMCR_PMR_SHIFT 24
> -#define ICH_VMCR_PMR_MASK (0xffUL << ICH_VMCR_PMR_SHIFT)
> -#define ICH_VMCR_ENG0_SHIFT 0
> -#define ICH_VMCR_ENG0_MASK (1 << ICH_VMCR_ENG0_SHIFT)
> -#define ICH_VMCR_ENG1_SHIFT 1
> -#define ICH_VMCR_ENG1_MASK (1 << ICH_VMCR_ENG1_SHIFT)
> -
> /*
> * Permission Indirection Extension (PIE) permission encodings.
> * Encodings with the _O suffix, have overlays applied (Permission Overlay Extension).
> diff --git a/arch/arm64/kvm/hyp/vgic-v3-sr.c b/arch/arm64/kvm/hyp/vgic-v3-sr.c
> index 0b670a033fd87..24a2074f3a8cf 100644
> --- a/arch/arm64/kvm/hyp/vgic-v3-sr.c
> +++ b/arch/arm64/kvm/hyp/vgic-v3-sr.c
> @@ -569,11 +569,11 @@ static int __vgic_v3_highest_priority_lr(struct kvm_vcpu *vcpu, u32 vmcr,
> continue;
>
> /* Group-0 interrupt, but Group-0 disabled? */
> - if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG0_MASK))
> + if (!(val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG0_MASK))
> continue;
>
> /* Group-1 interrupt, but Group-1 disabled? */
> - if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_ENG1_MASK))
> + if ((val & ICH_LR_GROUP) && !(vmcr & ICH_VMCR_EL2_VENG1_MASK))
> continue;
>
> /* Not the highest priority? */
> @@ -646,19 +646,19 @@ static int __vgic_v3_get_highest_active_priority(void)
>
> static unsigned int __vgic_v3_get_bpr0(u32 vmcr)
> {
> - return (vmcr & ICH_VMCR_BPR0_MASK) >> ICH_VMCR_BPR0_SHIFT;
> + return FIELD_GET(ICH_VMCR_EL2_VBPR0, vmcr);
> }
>
> static unsigned int __vgic_v3_get_bpr1(u32 vmcr)
> {
> unsigned int bpr;
>
> - if (vmcr & ICH_VMCR_CBPR_MASK) {
> + if (vmcr & ICH_VMCR_EL2_VCBPR_MASK) {
> bpr = __vgic_v3_get_bpr0(vmcr);
> if (bpr < 7)
> bpr++;
> } else {
> - bpr = (vmcr & ICH_VMCR_BPR1_MASK) >> ICH_VMCR_BPR1_SHIFT;
> + bpr = FIELD_GET(ICH_VMCR_EL2_VBPR1, vmcr);
> }
>
> return bpr;
> @@ -758,7 +758,7 @@ static void __vgic_v3_read_iar(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> if (grp != !!(lr_val & ICH_LR_GROUP))
> goto spurious;
>
> - pmr = (vmcr & ICH_VMCR_PMR_MASK) >> ICH_VMCR_PMR_SHIFT;
> + pmr = FIELD_GET(ICH_VMCR_EL2_VPMR, vmcr);
> lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
> if (pmr <= lr_prio)
> goto spurious;
> @@ -806,7 +806,7 @@ static int ___vgic_v3_write_dir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> int lr;
>
> /* EOImode == 0, nothing to be done here */
> - if (!(vmcr & ICH_VMCR_EOIM_MASK))
> + if (!FIELD_GET(ICH_VMCR_EL2_VEOIM_MASK, vmcr))
nit: FIELD_GET() doesn't bring anything here. Similar comment applies
to most 'if (val & MASK)' constructs that get changed here.
> return 1;
>
> /* No deactivate to be performed on an LPI */
> @@ -849,7 +849,7 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> }
>
> /* EOImode == 1 and not an LPI, nothing to be done here */
> - if ((vmcr & ICH_VMCR_EOIM_MASK) && !(vid >= VGIC_MIN_LPI))
> + if (FIELD_GET(ICH_VMCR_EL2_VEOIM_MASK, vmcr) && !(vid >= VGIC_MIN_LPI))
> return;
>
> lr_prio = (lr_val & ICH_LR_PRIORITY_MASK) >> ICH_LR_PRIORITY_SHIFT;
> @@ -865,12 +865,12 @@ static void __vgic_v3_write_eoir(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
>
> static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32 vmcr, int rt)
> {
> - vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
> + vcpu_set_reg(vcpu, rt, !!FIELD_GET(ICH_VMCR_EL2_VENG0_MASK, vmcr));
Here, !! is actually really superfluous and makes it harder to
understand what is being done. Similar thing for IRGPEN1.
Apart from these two points, this looks OK to me.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2
2025-12-15 11:52 ` Marc Zyngier
@ 2025-12-15 14:15 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-15 14:15 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 11:52 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:35 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > From: Sascha Bischoff <Sascha.Bischoff@arm.com>
> >
> > /* EOImode == 0, nothing to be done here */
> > - if (!(vmcr & ICH_VMCR_EOIM_MASK))
> > + if (!FIELD_GET(ICH_VMCR_EL2_VEOIM_MASK, vmcr))
>
> nit: FIELD_GET() doesn't bring anything here. Similar comment applies
> to most 'if (val & MASK)' constructs that get changed here.
I've reverted all bare instances of `val & MASK` (without shifts) to
not use FIELD_GET(). Anything with an additional shift I've left as a
FIELD_GET().
> > static void __vgic_v3_read_igrpen0(struct kvm_vcpu *vcpu, u32
> > vmcr, int rt)
> > {
> > - vcpu_set_reg(vcpu, rt, !!(vmcr & ICH_VMCR_ENG0_MASK));
> > + vcpu_set_reg(vcpu, rt,
> > !!FIELD_GET(ICH_VMCR_EL2_VENG0_MASK, vmcr));
>
> Here, !! is actually really superfluous and makes it harder to
> understand what is being done. Similar thing for IRGPEN1.
Dropped those !! as part of this change.
> Apart from these two points, this looks OK to me.
>
> Thanks,
>
> M.
Thanks,
Sascha
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 01/32] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
2025-12-12 15:22 ` [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 03/32] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
` (29 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
From: Marc Zyngier <maz@kernel.org>
None of the registers we manage in the feature dependency infrastructure
so far has any RES1 bit. This is about to change, as VTCR_EL2 has
its bit 31 being RES1.
In order to not fail the consistency checks by not describing a bit,
add RES1 bits to the set of immutable bits. This requires some extra
surgery for the FGT handling, as we now need to track RES1 bits there
as well.
There are no RES1 FGT bits *yet*. Watch this space.
Signed-off-by: Marc Zyngier <maz@kernel.org>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/config.c | 25 +++++++-------
arch/arm64/kvm/emulate-nested.c | 55 +++++++++++++++++--------------
3 files changed, 45 insertions(+), 36 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index ac7f970c78830..b552a1e03848c 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -638,6 +638,7 @@ struct fgt_masks {
u64 mask;
u64 nmask;
u64 res0;
+ u64 res1;
};
extern struct fgt_masks hfgrtr_masks;
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 24bb3f36e9d59..3845b188551b6 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -16,14 +16,14 @@
*/
struct reg_bits_to_feat_map {
union {
- u64 bits;
- u64 *res0p;
+ u64 bits;
+ struct fgt_masks *masks;
};
#define NEVER_FGU BIT(0) /* Can trap, but never UNDEF */
#define CALL_FUNC BIT(1) /* Needs to evaluate tons of crap */
#define FIXED_VALUE BIT(2) /* RAZ/WI or RAO/WI in KVM */
-#define RES0_POINTER BIT(3) /* Pointer to RES0 value instead of bits */
+#define MASKS_POINTER BIT(3) /* Pointer to fgt_masks struct instead of bits */
unsigned long flags;
@@ -92,8 +92,8 @@ struct reg_feat_map_desc {
#define NEEDS_FEAT_FIXED(m, ...) \
__NEEDS_FEAT_FLAG(m, FIXED_VALUE, bits, __VA_ARGS__, 0)
-#define NEEDS_FEAT_RES0(p, ...) \
- __NEEDS_FEAT_FLAG(p, RES0_POINTER, res0p, __VA_ARGS__)
+#define NEEDS_FEAT_MASKS(p, ...) \
+ __NEEDS_FEAT_FLAG(p, MASKS_POINTER, masks, __VA_ARGS__)
/*
* Declare the dependency between a set of bits and a set of features,
@@ -109,19 +109,20 @@ struct reg_feat_map_desc {
#define DECLARE_FEAT_MAP(n, r, m, f) \
struct reg_feat_map_desc n = { \
.name = #r, \
- .feat_map = NEEDS_FEAT(~r##_RES0, f), \
+ .feat_map = NEEDS_FEAT(~(r##_RES0 | \
+ r##_RES1), f), \
.bit_feat_map = m, \
.bit_feat_map_sz = ARRAY_SIZE(m), \
}
/*
* Specialised version of the above for FGT registers that have their
- * RES0 masks described as struct fgt_masks.
+ * RESx masks described as struct fgt_masks.
*/
#define DECLARE_FEAT_MAP_FGT(n, msk, m, f) \
struct reg_feat_map_desc n = { \
.name = #msk, \
- .feat_map = NEEDS_FEAT_RES0(&msk.res0, f),\
+ .feat_map = NEEDS_FEAT_MASKS(&msk, f), \
.bit_feat_map = m, \
.bit_feat_map_sz = ARRAY_SIZE(m), \
}
@@ -1168,21 +1169,21 @@ static const DECLARE_FEAT_MAP(mdcr_el2_desc, MDCR_EL2,
mdcr_el2_feat_map, FEAT_AA64EL2);
static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
- int map_size, u64 res0, const char *str)
+ int map_size, u64 resx, const char *str)
{
u64 mask = 0;
for (int i = 0; i < map_size; i++)
mask |= map[i].bits;
- if (mask != ~res0)
+ if (mask != ~resx)
kvm_err("Undefined %s behaviour, bits %016llx\n",
- str, mask ^ ~res0);
+ str, mask ^ ~resx);
}
static u64 reg_feat_map_bits(const struct reg_bits_to_feat_map *map)
{
- return map->flags & RES0_POINTER ? ~(*map->res0p) : map->bits;
+ return map->flags & MASKS_POINTER ? (map->masks->mask | map->masks->nmask) : map->bits;
}
static void __init check_reg_desc(const struct reg_feat_map_desc *r)
diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
index 834f13fb1fb7d..75d49f83342a5 100644
--- a/arch/arm64/kvm/emulate-nested.c
+++ b/arch/arm64/kvm/emulate-nested.c
@@ -2105,23 +2105,24 @@ static u32 encoding_next(u32 encoding)
}
#define FGT_MASKS(__n, __m) \
- struct fgt_masks __n = { .str = #__m, .res0 = __m, }
-
-FGT_MASKS(hfgrtr_masks, HFGRTR_EL2_RES0);
-FGT_MASKS(hfgwtr_masks, HFGWTR_EL2_RES0);
-FGT_MASKS(hfgitr_masks, HFGITR_EL2_RES0);
-FGT_MASKS(hdfgrtr_masks, HDFGRTR_EL2_RES0);
-FGT_MASKS(hdfgwtr_masks, HDFGWTR_EL2_RES0);
-FGT_MASKS(hafgrtr_masks, HAFGRTR_EL2_RES0);
-FGT_MASKS(hfgrtr2_masks, HFGRTR2_EL2_RES0);
-FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2_RES0);
-FGT_MASKS(hfgitr2_masks, HFGITR2_EL2_RES0);
-FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2_RES0);
-FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2_RES0);
+ struct fgt_masks __n = { .str = #__m, .res0 = __m ## _RES0, .res1 = __m ## _RES1 }
+
+FGT_MASKS(hfgrtr_masks, HFGRTR_EL2);
+FGT_MASKS(hfgwtr_masks, HFGWTR_EL2);
+FGT_MASKS(hfgitr_masks, HFGITR_EL2);
+FGT_MASKS(hdfgrtr_masks, HDFGRTR_EL2);
+FGT_MASKS(hdfgwtr_masks, HDFGWTR_EL2);
+FGT_MASKS(hafgrtr_masks, HAFGRTR_EL2);
+FGT_MASKS(hfgrtr2_masks, HFGRTR2_EL2);
+FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2);
+FGT_MASKS(hfgitr2_masks, HFGITR2_EL2);
+FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2);
+FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2);
static __init bool aggregate_fgt(union trap_config tc)
{
struct fgt_masks *rmasks, *wmasks;
+ u64 rresx, wresx;
switch (tc.fgt) {
case HFGRTR_GROUP:
@@ -2154,24 +2155,27 @@ static __init bool aggregate_fgt(union trap_config tc)
break;
}
+ rresx = rmasks->res0 | rmasks->res1;
+ if (wmasks)
+ wresx = wmasks->res0 | wmasks->res1;
+
/*
* A bit can be reserved in either the R or W register, but
* not both.
*/
- if ((BIT(tc.bit) & rmasks->res0) &&
- (!wmasks || (BIT(tc.bit) & wmasks->res0)))
+ if ((BIT(tc.bit) & rresx) && (!wmasks || (BIT(tc.bit) & wresx)))
return false;
if (tc.pol)
- rmasks->mask |= BIT(tc.bit) & ~rmasks->res0;
+ rmasks->mask |= BIT(tc.bit) & ~rresx;
else
- rmasks->nmask |= BIT(tc.bit) & ~rmasks->res0;
+ rmasks->nmask |= BIT(tc.bit) & ~rresx;
if (wmasks) {
if (tc.pol)
- wmasks->mask |= BIT(tc.bit) & ~wmasks->res0;
+ wmasks->mask |= BIT(tc.bit) & ~wresx;
else
- wmasks->nmask |= BIT(tc.bit) & ~wmasks->res0;
+ wmasks->nmask |= BIT(tc.bit) & ~wresx;
}
return true;
@@ -2180,7 +2184,6 @@ static __init bool aggregate_fgt(union trap_config tc)
static __init int check_fgt_masks(struct fgt_masks *masks)
{
unsigned long duplicate = masks->mask & masks->nmask;
- u64 res0 = masks->res0;
int ret = 0;
if (duplicate) {
@@ -2194,10 +2197,14 @@ static __init int check_fgt_masks(struct fgt_masks *masks)
ret = -EINVAL;
}
- masks->res0 = ~(masks->mask | masks->nmask);
- if (masks->res0 != res0)
- kvm_info("Implicit %s = %016llx, expecting %016llx\n",
- masks->str, masks->res0, res0);
+ if ((masks->res0 | masks->res1 | masks->mask | masks->nmask) != GENMASK(63, 0) ||
+ (masks->res0 & masks->res1) || (masks->res0 & masks->mask) ||
+ (masks->res0 & masks->nmask) || (masks->res1 & masks->mask) ||
+ (masks->res1 & masks->nmask) || (masks->mask & masks->nmask)) {
+ kvm_info("Inconsistent masks for %s (%016llx, %016llx, %016llx, %016llx)\n",
+ masks->str, masks->res0, masks->res1, masks->mask, masks->nmask);
+ masks->res0 = ~(masks->res1 | masks->mask | masks->nmask);
+ }
return ret;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 03/32] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
2025-12-12 15:22 ` [PATCH 02/32] KVM: arm64: gic-v3: Switch vGIC-v3 to use generated ICH_VMCR_EL2 Sascha Bischoff
2025-12-12 15:22 ` [PATCH 01/32] KVM: arm64: Account for RES1 bits in DECLARE_FEAT_MAP() and co Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 04/32] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
` (28 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
The GICv5 architecture is dropping the ICC_HAPR_EL1 and ICV_HAPR_EL1
system registers. These registers were never added to the sysregs, but
the traps for them were.
Drop the trap bit from the ICH_HFGRTR_EL2 and make it Res1 as per the
upcoming GICv5 spec change. Additionally, update the EL2 setup code to
not attempt to set that bit.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/el2_setup.h | 1 -
arch/arm64/tools/sysreg | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index cacd20df1786e..07c12f4a69b41 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -225,7 +225,6 @@
ICH_HFGRTR_EL2_ICC_ICSR_EL1 | \
ICH_HFGRTR_EL2_ICC_PCR_EL1 | \
ICH_HFGRTR_EL2_ICC_HPPIR_EL1 | \
- ICH_HFGRTR_EL2_ICC_HAPR_EL1 | \
ICH_HFGRTR_EL2_ICC_CR0_EL1 | \
ICH_HFGRTR_EL2_ICC_IDRn_EL1 | \
ICH_HFGRTR_EL2_ICC_APR_EL1)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 8921b51866d64..dab5bfe8c9686 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -4579,7 +4579,7 @@ Field 7 ICC_IAFFIDR_EL1
Field 6 ICC_ICSR_EL1
Field 5 ICC_PCR_EL1
Field 4 ICC_HPPIR_EL1
-Field 3 ICC_HAPR_EL1
+Res1 3
Field 2 ICC_CR0_EL1
Field 1 ICC_IDRn_EL1
Field 0 ICC_APR_EL1
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 04/32] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (2 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 03/32] arm64/sysreg: Drop ICH_HFGRTR_EL2.ICC_HAPR_EL1 and make RES1 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 05/32] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
` (27 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Add the GICv5 system registers required to support native GICv5 guests
with KVM. Many of the GICv5 sysregs have already been added as part of
the host GICv5 driver, keeping this set relatively small. The
registers added in this change complete the set by adding those
required by KVM either directly (ICH_) or indirectly (FGTs for the
ICC_ sysregs).
The following system registers and their fields are added:
ICC_APR_EL1
ICC_HPPIR_EL1
ICC_IAFFIDR_EL1
ICH_APR_EL2
ICH_CONTEXTR_EL2
ICH_PPI_ACTIVER<n>_EL2
ICH_PPI_DVI<n>_EL2
ICH_PPI_ENABLER<n>_EL2
ICH_PPI_PENDR<n>_EL2
ICH_PPI_PRIORITYR<n>_EL2
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/tools/sysreg | 480 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 480 insertions(+)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index dab5bfe8c9686..2f44a568ebf4e 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3248,6 +3248,14 @@ UnsignedEnum 3:0 ID_BITS
EndEnum
EndSysreg
+Sysreg ICC_HPPIR_EL1 3 0 12 10 3
+Res0 63:33
+Field 32 HPPIV
+Field 31:29 TYPE
+Res0 28:24
+Field 23:0 ID
+EndSysreg
+
Sysreg ICC_ICSR_EL1 3 0 12 10 4
Res0 63:48
Field 47:32 IAFFID
@@ -3262,6 +3270,11 @@ Field 1 Enabled
Field 0 F
EndSysreg
+Sysreg ICC_IAFFIDR_EL1 3 0 12 10 5
+Res0 63:16
+Field 15:0 IAFFID
+EndSysreg
+
SysregFields ICC_PPI_ENABLERx_EL1
Field 63 EN63
Field 62 EN62
@@ -3668,6 +3681,42 @@ Res0 14:12
Field 11:0 AFFINITY
EndSysreg
+Sysreg ICC_APR_EL1 3 1 12 0 0
+Res0 63:32
+Field 31 P31
+Field 30 P30
+Field 29 P29
+Field 28 P28
+Field 27 P27
+Field 26 P26
+Field 25 P25
+Field 24 P24
+Field 23 P23
+Field 22 P22
+Field 21 P21
+Field 20 P20
+Field 19 P19
+Field 18 P18
+Field 17 P17
+Field 16 P16
+Field 15 P15
+Field 14 P14
+Field 13 P13
+Field 12 P12
+Field 11 P11
+Field 10 P10
+Field 9 P9
+Field 8 P8
+Field 7 P7
+Field 6 P6
+Field 5 P5
+Field 4 P4
+Field 3 P3
+Field 2 P2
+Field 1 P1
+Field 0 P0
+EndSysreg
+
Sysreg ICC_CR0_EL1 3 1 12 0 1
Res0 63:39
Field 38 PID
@@ -4567,6 +4616,42 @@ Field 31:16 PhyPARTID29
Field 15:0 PhyPARTID28
EndSysreg
+Sysreg ICH_APR_EL2 3 4 12 8 4
+Res0 63:32
+Field 31 P31
+Field 30 P30
+Field 29 P29
+Field 28 P28
+Field 27 P27
+Field 26 P26
+Field 25 P25
+Field 24 P24
+Field 23 P23
+Field 22 P22
+Field 21 P21
+Field 20 P20
+Field 19 P19
+Field 18 P18
+Field 17 P17
+Field 16 P16
+Field 15 P15
+Field 14 P14
+Field 13 P13
+Field 12 P12
+Field 11 P11
+Field 10 P10
+Field 9 P9
+Field 8 P8
+Field 7 P7
+Field 6 P6
+Field 5 P5
+Field 4 P4
+Field 3 P3
+Field 2 P2
+Field 1 P1
+Field 0 P0
+EndSysreg
+
Sysreg ICH_HFGRTR_EL2 3 4 12 9 4
Res0 63:21
Field 20 ICC_PPI_ACTIVERn_EL1
@@ -4615,6 +4700,306 @@ Field 1 GICCDDIS
Field 0 GICCDEN
EndSysreg
+SysregFields ICH_PPI_DVIRx_EL2
+Field 63 DVI63
+Field 62 DVI62
+Field 61 DVI61
+Field 60 DVI60
+Field 59 DVI59
+Field 58 DVI58
+Field 57 DVI57
+Field 56 DVI56
+Field 55 DVI55
+Field 54 DVI54
+Field 53 DVI53
+Field 52 DVI52
+Field 51 DVI51
+Field 50 DVI50
+Field 49 DVI49
+Field 48 DVI48
+Field 47 DVI47
+Field 46 DVI46
+Field 45 DVI45
+Field 44 DVI44
+Field 43 DVI43
+Field 42 DVI42
+Field 41 DVI41
+Field 40 DVI40
+Field 39 DVI39
+Field 38 DVI38
+Field 37 DVI37
+Field 36 DVI36
+Field 35 DVI35
+Field 34 DVI34
+Field 33 DVI33
+Field 32 DVI32
+Field 31 DVI31
+Field 30 DVI30
+Field 29 DVI29
+Field 28 DVI28
+Field 27 DVI27
+Field 26 DVI26
+Field 25 DVI25
+Field 24 DVI24
+Field 23 DVI23
+Field 22 DVI22
+Field 21 DVI21
+Field 20 DVI20
+Field 19 DVI19
+Field 18 DVI18
+Field 17 DVI17
+Field 16 DVI16
+Field 15 DVI15
+Field 14 DVI14
+Field 13 DVI13
+Field 12 DVI12
+Field 11 DVI11
+Field 10 DVI10
+Field 9 DVI9
+Field 8 DVI8
+Field 7 DVI7
+Field 6 DVI6
+Field 5 DVI5
+Field 4 DVI4
+Field 3 DVI3
+Field 2 DVI2
+Field 1 DVI1
+Field 0 DVI0
+EndSysregFields
+
+Sysreg ICH_PPI_DVIR0_EL2 3 4 12 10 0
+Fields ICH_PPI_DVIx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_DVIR1_EL2 3 4 12 10 1
+Fields ICH_PPI_DVIx_EL2
+EndSysreg
+
+SysregFields ICH_PPI_ENABLERx_EL2
+Field 63 EN63
+Field 62 EN62
+Field 61 EN61
+Field 60 EN60
+Field 59 EN59
+Field 58 EN58
+Field 57 EN57
+Field 56 EN56
+Field 55 EN55
+Field 54 EN54
+Field 53 EN53
+Field 52 EN52
+Field 51 EN51
+Field 50 EN50
+Field 49 EN49
+Field 48 EN48
+Field 47 EN47
+Field 46 EN46
+Field 45 EN45
+Field 44 EN44
+Field 43 EN43
+Field 42 EN42
+Field 41 EN41
+Field 40 EN40
+Field 39 EN39
+Field 38 EN38
+Field 37 EN37
+Field 36 EN36
+Field 35 EN35
+Field 34 EN34
+Field 33 EN33
+Field 32 EN32
+Field 31 EN31
+Field 30 EN30
+Field 29 EN29
+Field 28 EN28
+Field 27 EN27
+Field 26 EN26
+Field 25 EN25
+Field 24 EN24
+Field 23 EN23
+Field 22 EN22
+Field 21 EN21
+Field 20 EN20
+Field 19 EN19
+Field 18 EN18
+Field 17 EN17
+Field 16 EN16
+Field 15 EN15
+Field 14 EN14
+Field 13 EN13
+Field 12 EN12
+Field 11 EN11
+Field 10 EN10
+Field 9 EN9
+Field 8 EN8
+Field 7 EN7
+Field 6 EN6
+Field 5 EN5
+Field 4 EN4
+Field 3 EN3
+Field 2 EN2
+Field 1 EN1
+Field 0 EN0
+EndSysregFields
+
+Sysreg ICH_PPI_ENABLER0_EL2 3 4 12 10 2
+Fields ICH_PPI_ENABLERx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_ENABLER1_EL2 3 4 12 10 3
+Fields ICH_PPI_ENABLERx_EL2
+EndSysreg
+
+SysregFields ICH_PPI_PENDRx_EL2
+Field 63 PEND63
+Field 62 PEND62
+Field 61 PEND61
+Field 60 PEND60
+Field 59 PEND59
+Field 58 PEND58
+Field 57 PEND57
+Field 56 PEND56
+Field 55 PEND55
+Field 54 PEND54
+Field 53 PEND53
+Field 52 PEND52
+Field 51 PEND51
+Field 50 PEND50
+Field 49 PEND49
+Field 48 PEND48
+Field 47 PEND47
+Field 46 PEND46
+Field 45 PEND45
+Field 44 PEND44
+Field 43 PEND43
+Field 42 PEND42
+Field 41 PEND41
+Field 40 PEND40
+Field 39 PEND39
+Field 38 PEND38
+Field 37 PEND37
+Field 36 PEND36
+Field 35 PEND35
+Field 34 PEND34
+Field 33 PEND33
+Field 32 PEND32
+Field 31 PEND31
+Field 30 PEND30
+Field 29 PEND29
+Field 28 PEND28
+Field 27 PEND27
+Field 26 PEND26
+Field 25 PEND25
+Field 24 PEND24
+Field 23 PEND23
+Field 22 PEND22
+Field 21 PEND21
+Field 20 PEND20
+Field 19 PEND19
+Field 18 PEND18
+Field 17 PEND17
+Field 16 PEND16
+Field 15 PEND15
+Field 14 PEND14
+Field 13 PEND13
+Field 12 PEND12
+Field 11 PEND11
+Field 10 PEND10
+Field 9 PEND9
+Field 8 PEND8
+Field 7 PEND7
+Field 6 PEND6
+Field 5 PEND5
+Field 4 PEND4
+Field 3 PEND3
+Field 2 PEND2
+Field 1 PEND1
+Field 0 PEND0
+EndSysregFields
+
+Sysreg ICH_PPI_PENDR0_EL2 3 4 12 10 4
+Fields ICH_PPI_PENDRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PENDR1_EL2 3 4 12 10 5
+Fields ICH_PPI_PENDRx_EL2
+EndSysreg
+
+SysregFields ICH_PPI_ACTIVERx_EL2
+Field 63 ACTIVE63
+Field 62 ACTIVE62
+Field 61 ACTIVE61
+Field 60 ACTIVE60
+Field 59 ACTIVE59
+Field 58 ACTIVE58
+Field 57 ACTIVE57
+Field 56 ACTIVE56
+Field 55 ACTIVE55
+Field 54 ACTIVE54
+Field 53 ACTIVE53
+Field 52 ACTIVE52
+Field 51 ACTIVE51
+Field 50 ACTIVE50
+Field 49 ACTIVE49
+Field 48 ACTIVE48
+Field 47 ACTIVE47
+Field 46 ACTIVE46
+Field 45 ACTIVE45
+Field 44 ACTIVE44
+Field 43 ACTIVE43
+Field 42 ACTIVE42
+Field 41 ACTIVE41
+Field 40 ACTIVE40
+Field 39 ACTIVE39
+Field 38 ACTIVE38
+Field 37 ACTIVE37
+Field 36 ACTIVE36
+Field 35 ACTIVE35
+Field 34 ACTIVE34
+Field 33 ACTIVE33
+Field 32 ACTIVE32
+Field 31 ACTIVE31
+Field 30 ACTIVE30
+Field 29 ACTIVE29
+Field 28 ACTIVE28
+Field 27 ACTIVE27
+Field 26 ACTIVE26
+Field 25 ACTIVE25
+Field 24 ACTIVE24
+Field 23 ACTIVE23
+Field 22 ACTIVE22
+Field 21 ACTIVE21
+Field 20 ACTIVE20
+Field 19 ACTIVE19
+Field 18 ACTIVE18
+Field 17 ACTIVE17
+Field 16 ACTIVE16
+Field 15 ACTIVE15
+Field 14 ACTIVE14
+Field 13 ACTIVE13
+Field 12 ACTIVE12
+Field 11 ACTIVE11
+Field 10 ACTIVE10
+Field 9 ACTIVE9
+Field 8 ACTIVE8
+Field 7 ACTIVE7
+Field 6 ACTIVE6
+Field 5 ACTIVE5
+Field 4 ACTIVE4
+Field 3 ACTIVE3
+Field 2 ACTIVE2
+Field 1 ACTIVE1
+Field 0 ACTIVE0
+EndSysregFields
+
+Sysreg ICH_PPI_ACTIVER0_EL2 3 4 12 10 6
+Fields ICH_PPI_ACTIVERx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_ACTIVER1_EL2 3 4 12 10 7
+Fields ICH_PPI_ACTIVERx_EL2
+EndSysreg
+
Sysreg ICH_HCR_EL2 3 4 12 11 0
Res0 63:32
Field 31:27 EOIcount
@@ -4669,6 +5054,18 @@ Field 1 V3
Field 0 En
EndSysreg
+Sysreg ICH_CONTEXTR_EL2 3 4 12 11 6
+Field 63 V
+Field 62 F
+Field 61 IRICHPPIDIS
+Field 60 DB
+Field 59:55 DBPM
+Res0 54:48
+Field 47:32 VPE
+Res0 31:16
+Field 15:0 VM
+EndSysreg
+
Sysreg ICH_VMCR_EL2 3 4 12 11 7
Prefix FEAT_GCIE
Res0 63:32
@@ -4690,6 +5087,89 @@ Field 1 VENG1
Field 0 VENG0
EndSysreg
+SysregFields ICH_PPI_PRIORITYRx_EL2
+Res0 63:61
+Field 60:56 Priority7
+Res0 55:53
+Field 52:48 Priority6
+Res0 47:45
+Field 44:40 Priority5
+Res0 39:37
+Field 36:32 Priority4
+Res0 31:29
+Field 28:24 Priority3
+Res0 23:21
+Field 20:16 Priority2
+Res0 15:13
+Field 12:8 Priority1
+Res0 7:5
+Field 4:0 Priority0
+EndSysregFields
+
+Sysreg ICH_PPI_PRIORITYR0_EL2 3 4 12 14 0
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR1_EL2 3 4 12 14 1
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR2_EL2 3 4 12 14 2
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR3_EL2 3 4 12 14 3
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR4_EL2 3 4 12 14 4
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR5_EL2 3 4 12 14 5
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR6_EL2 3 4 12 14 6
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR7_EL2 3 4 12 14 7
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR8_EL2 3 4 12 15 0
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR9_EL2 3 4 12 15 1
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR10_EL2 3 4 12 15 2
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR11_EL2 3 4 12 15 3
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR12_EL2 3 4 12 15 4
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR13_EL2 3 4 12 15 5
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR14_EL2 3 4 12 15 6
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
+Sysreg ICH_PPI_PRIORITYR15_EL2 3 4 12 15 7
+Fields ICH_PPI_PRIORITYRx_EL2
+EndSysreg
+
Sysreg CONTEXTIDR_EL2 3 4 13 0 1
Fields CONTEXTIDR_ELx
EndSysreg
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 05/32] arm64/sysreg: Add GICR CDNMIA encoding
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (3 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 04/32] arm64/sysreg: Add remaining GICv5 ICC_ & ICH_ sysregs for KVM support Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 08/32] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
` (26 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
The encoding for the GICR CDNMIA system instruction is thus far unused
(and shall remain unused for the time being). However, in order to
plumb the FGTs into KVM correctly, KVM needs to be made aware of the
encoding of this system instruction.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/sysreg.h | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index b3b8b8cd7bf1e..e99acb6dbd5d8 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1059,6 +1059,7 @@
#define GICV5_OP_GIC_CDPRI sys_insn(1, 0, 12, 1, 2)
#define GICV5_OP_GIC_CDRCFG sys_insn(1, 0, 12, 1, 5)
#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
+#define GICV5_OP_GICR_CDNMIA sys_insn(1, 0, 12, 3, 1)
/* Definitions for GIC CDAFF */
#define GICV5_GIC_CDAFF_IAFFID_MASK GENMASK_ULL(47, 32)
@@ -1105,6 +1106,12 @@
#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
+/* Definitions for GICR CDNMIA */
+#define GICV5_GIC_CDNMIA_VALID_MASK BIT_ULL(32)
+#define GICV5_GICR_CDNMIA_VALID(r) FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
+#define GICV5_GIC_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+
#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 08/32] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (4 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 05/32] arm64/sysreg: Add GICR CDNMIA encoding Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 06/32] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers Sascha Bischoff
` (25 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Set the guest's view of the GCIE field to IMP when running a GICv5 VM,
NI otherwise. Reject any writes to the register that try to do
anything but set GCIE to IMP when running a GICv5 VM.
As part of this change, we also introduce vgic_is_v5(kvm), in order to
check if the guest is a GICv5-native VM. We're also required to extend
vgic_is_v3_compat to check for the actual vgic_model. This has one
potential issue - if any of the vgic_is_v* checks are used prior to
setting the vgic_model (that is, before kvm_vgic_create) then
vgic_model will be set to 0, which can result in a false-positive.
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>
---
arch/arm64/kvm/sys_regs.c | 39 ++++++++++++++++++++++++++++++--------
arch/arm64/kvm/vgic/vgic.h | 15 ++++++++++++++-
2 files changed, 45 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index c8fd7c6a12a13..a065f8939bc8f 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1758,6 +1758,7 @@ static u8 pmuver_to_perfmon(u8 pmuver)
static u64 sanitise_id_aa64pfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val);
+static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val);
static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
/* Read a sanitised cpufeature ID register by sys_reg_desc */
@@ -1783,10 +1784,7 @@ static u64 __kvm_read_sanitised_id_reg(const struct kvm_vcpu *vcpu,
val = sanitise_id_aa64pfr1_el1(vcpu, val);
break;
case SYS_ID_AA64PFR2_EL1:
- val &= ID_AA64PFR2_EL1_FPMR |
- (kvm_has_mte(vcpu->kvm) ?
- ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY :
- 0);
+ val = sanitise_id_aa64pfr2_el1(vcpu, val);
break;
case SYS_ID_AA64ISAR1_EL1:
if (!vcpu_has_ptrauth(vcpu))
@@ -2024,6 +2022,20 @@ static u64 sanitise_id_aa64pfr1_el1(const struct kvm_vcpu *vcpu, u64 val)
return val;
}
+static u64 sanitise_id_aa64pfr2_el1(const struct kvm_vcpu *vcpu, u64 val)
+{
+ val &= ID_AA64PFR2_EL1_FPMR |
+ (kvm_has_mte(vcpu->kvm) ?
+ ID_AA64PFR2_EL1_MTEFAR | ID_AA64PFR2_EL1_MTESTOREONLY : 0);
+
+ if (vgic_is_v5(vcpu->kvm)) {
+ val &= ~ID_AA64PFR2_EL1_GCIE_MASK;
+ val |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
+ }
+
+ return val;
+}
+
static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
{
val = ID_REG_LIMIT_FIELD_ENUM(val, ID_AA64DFR0_EL1, DebugVer, V8P8);
@@ -2221,6 +2233,16 @@ static int set_id_aa64pfr1_el1(struct kvm_vcpu *vcpu,
return set_id_reg(vcpu, rd, user_val);
}
+static int set_id_aa64pfr2_el1(struct kvm_vcpu *vcpu,
+ const struct sys_reg_desc *rd, u64 user_val)
+{
+ if (vgic_is_v5(vcpu->kvm) &&
+ FIELD_GET(ID_AA64PFR2_EL1_GCIE_MASK, user_val) != ID_AA64PFR2_EL1_GCIE_IMP)
+ return -EINVAL;
+
+ return set_id_reg(vcpu, rd, user_val);
+}
+
/*
* Allow userspace to de-feature a stage-2 translation granule but prevent it
* from claiming the impossible.
@@ -3202,10 +3224,11 @@ static const struct sys_reg_desc sys_reg_descs[] = {
ID_AA64PFR1_EL1_RES0 |
ID_AA64PFR1_EL1_MPAM_frac |
ID_AA64PFR1_EL1_MTE)),
- ID_WRITABLE(ID_AA64PFR2_EL1,
- ID_AA64PFR2_EL1_FPMR |
- ID_AA64PFR2_EL1_MTEFAR |
- ID_AA64PFR2_EL1_MTESTOREONLY),
+ ID_FILTERED(ID_AA64PFR2_EL1, id_aa64pfr2_el1,
+ ~(ID_AA64PFR2_EL1_FPMR |
+ ID_AA64PFR2_EL1_MTEFAR |
+ ID_AA64PFR2_EL1_MTESTOREONLY |
+ ID_AA64PFR2_EL1_GCIE)),
ID_UNALLOCATED(4,3),
ID_WRITABLE(ID_AA64ZFR0_EL1, ~ID_AA64ZFR0_EL1_RES0),
ID_HIDDEN(ID_AA64SMFR0_EL1),
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 5f0fc96b4dc29..bf5bae023751b 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -455,8 +455,16 @@ void vgic_v3_nested_update_mi(struct kvm_vcpu *vcpu);
static inline bool vgic_is_v3_compat(struct kvm *kvm)
{
+ /*
+ * We need to be careful here. This could be called early,
+ * which means that there is no vgic_model set. For the time
+ * being, fall back to assuming that we're trying run a legacy
+ * VM in that case, which keeps existing software happy. Long
+ * term, this will need to be revisited a little.
+ */
return cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF) &&
- kvm_vgic_global_state.has_gcie_v3_compat;
+ kvm_vgic_global_state.has_gcie_v3_compat &&
+ kvm->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5;
}
static inline bool vgic_is_v3(struct kvm *kvm)
@@ -464,6 +472,11 @@ static inline bool vgic_is_v3(struct kvm *kvm)
return kvm_vgic_global_state.type == VGIC_V3 || vgic_is_v3_compat(kvm);
}
+static inline bool vgic_is_v5(struct kvm *kvm)
+{
+ return kvm_vgic_global_state.type == VGIC_V5 && !vgic_is_v3_compat(kvm);
+}
+
int vgic_its_debug_init(struct kvm_device *dev);
void vgic_its_debug_destroy(struct kvm_device *dev);
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 06/32] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (5 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 08/32] KVM: arm64: gic-v5: Sanitize ID_AA64PFR2_EL1.GCIE Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
` (24 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This is the base GICv5 device which is to be used with the
KVM_CREATE_DEVICE ioctl to create a GICv5-based vgic.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/uapi/linux/kvm.h | 2 ++
tools/include/uapi/linux/kvm.h | 2 ++
2 files changed, 4 insertions(+)
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index dddb781b0507d..f7dabbf17e1a7 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1209,6 +1209,8 @@ enum kvm_device_type {
#define KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_EIOINTC
KVM_DEV_TYPE_LOONGARCH_PCHPIC,
#define KVM_DEV_TYPE_LOONGARCH_PCHPIC KVM_DEV_TYPE_LOONGARCH_PCHPIC
+ KVM_DEV_TYPE_ARM_VGIC_V5,
+#define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5
KVM_DEV_TYPE_MAX,
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 52f6000ab0208..8303124973e2a 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -1198,6 +1198,8 @@ enum kvm_device_type {
#define KVM_DEV_TYPE_LOONGARCH_EIOINTC KVM_DEV_TYPE_LOONGARCH_EIOINTC
KVM_DEV_TYPE_LOONGARCH_PCHPIC,
#define KVM_DEV_TYPE_LOONGARCH_PCHPIC KVM_DEV_TYPE_LOONGARCH_PCHPIC
+ KVM_DEV_TYPE_ARM_VGIC_V5,
+#define KVM_DEV_TYPE_ARM_VGIC_V5 KVM_DEV_TYPE_ARM_VGIC_V5
KVM_DEV_TYPE_MAX,
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (6 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 06/32] KVM: arm64: gic-v5: Add ARM_VGIC_V5 device to KVM headers Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-15 13:32 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load Sascha Bischoff
` (23 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
GICv5 has moved from using interrupt ranges for different interrupt
types to using some of the upper bits of the interrupt ID to denote
the interrupt type. This is not compatible with older GICs (which rely
on ranges of interrupts to determine the type), and hence a set of
helpers is introduced. These helpers take a struct kvm*, and use the
vgic model to determine how to interpret the interrupt ID.
Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
helper is introduced to determine if an interrupt is private - SGIs
and PPIs for older GICs, and PPIs only for GICv5.
The helpers are plumbed into the core vgic code, as well as the Arch
Timer and PMU code.
There should be no functional changes as part of this change.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/arch_timer.c | 2 +-
arch/arm64/kvm/pmu-emul.c | 6 +++---
arch/arm64/kvm/vgic/vgic-kvm-device.c | 2 +-
arch/arm64/kvm/vgic/vgic.c | 14 +++++++-------
include/kvm/arm_vgic.h | 27 +++++++++++++++++++++++----
5 files changed, 35 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 99a07972068d1..6f033f6644219 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -1598,7 +1598,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
if (get_user(irq, uaddr))
return -EFAULT;
- if (!(irq_is_ppi(irq)))
+ if (!(irq_is_ppi(vcpu->kvm, irq)))
return -EINVAL;
mutex_lock(&vcpu->kvm->arch.config_lock);
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index b03dbda7f1ab9..0baf8e0fe23bd 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -939,7 +939,7 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu)
* number against the dimensions of the vgic and make sure
* it's valid.
*/
- if (!irq_is_ppi(irq) && !vgic_valid_spi(vcpu->kvm, irq))
+ if (!irq_is_ppi(vcpu->kvm, irq) && !vgic_valid_spi(vcpu->kvm, irq))
return -EINVAL;
} else if (kvm_arm_pmu_irq_initialized(vcpu)) {
return -EINVAL;
@@ -991,7 +991,7 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
if (!kvm_arm_pmu_irq_initialized(vcpu))
continue;
- if (irq_is_ppi(irq)) {
+ if (irq_is_ppi(kvm, irq)) {
if (vcpu->arch.pmu.irq_num != irq)
return false;
} else {
@@ -1142,7 +1142,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
return -EFAULT;
/* The PMU overflow interrupt can be a PPI or a valid SPI. */
- if (!(irq_is_ppi(irq) || irq_is_spi(irq)))
+ if (!(irq_is_ppi(vcpu->kvm, irq) || irq_is_spi(vcpu->kvm, irq)))
return -EINVAL;
if (!pmu_irq_is_valid(kvm, irq))
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 3d1a776b716d7..b12ba99a423e5 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -639,7 +639,7 @@ static int vgic_v3_set_attr(struct kvm_device *dev,
if (vgic_initialized(dev->kvm))
return -EBUSY;
- if (!irq_is_ppi(val))
+ if (!irq_is_ppi(dev->kvm, val))
return -EINVAL;
dev->kvm->arch.vgic.mi_intid = val;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 430aa98888fda..2c0e8803342e2 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -94,7 +94,7 @@ struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
}
/* LPIs */
- if (intid >= VGIC_MIN_LPI)
+ if (irq_is_lpi(kvm, intid))
return vgic_get_lpi(kvm, intid);
return NULL;
@@ -123,7 +123,7 @@ static void vgic_release_lpi_locked(struct vgic_dist *dist, struct vgic_irq *irq
static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
{
- if (irq->intid < VGIC_MIN_LPI)
+ if (!irq_is_lpi(kvm, irq->intid))
return false;
return refcount_dec_and_test(&irq->refcount);
@@ -148,7 +148,7 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
* Acquire/release it early on lockdep kernels to make locking issues
* in rare release paths a bit more obvious.
*/
- if (IS_ENABLED(CONFIG_LOCKDEP) && irq->intid >= VGIC_MIN_LPI) {
+ if (IS_ENABLED(CONFIG_LOCKDEP) && irq_is_lpi(kvm, irq->intid)) {
guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock);
}
@@ -186,7 +186,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
raw_spin_lock_irqsave(&vgic_cpu->ap_list_lock, flags);
list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
- if (irq->intid >= VGIC_MIN_LPI) {
+ if (irq_is_lpi(vcpu->kvm, irq->intid)) {
raw_spin_lock(&irq->irq_lock);
list_del(&irq->ap_list);
irq->vcpu = NULL;
@@ -521,12 +521,12 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
if (ret)
return ret;
- if (!vcpu && intid < VGIC_NR_PRIVATE_IRQS)
+ if (!vcpu && irq_is_private(kvm, intid))
return -EINVAL;
trace_vgic_update_irq_pending(vcpu ? vcpu->vcpu_idx : 0, intid, level);
- if (intid < VGIC_NR_PRIVATE_IRQS)
+ if (irq_is_private(kvm, intid))
irq = vgic_get_vcpu_irq(vcpu, intid);
else
irq = vgic_get_irq(kvm, intid);
@@ -685,7 +685,7 @@ int kvm_vgic_set_owner(struct kvm_vcpu *vcpu, unsigned int intid, void *owner)
return -EAGAIN;
/* SGIs and LPIs cannot be wired up to any device */
- if (!irq_is_ppi(intid) && !vgic_valid_spi(vcpu->kvm, intid))
+ if (!irq_is_ppi(vcpu->kvm, intid) && !vgic_valid_spi(vcpu->kvm, intid))
return -EINVAL;
irq = vgic_get_vcpu_irq(vcpu, intid);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index b261fb3968d03..be1f45a494f78 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -19,6 +19,7 @@
#include <linux/jump_label.h>
#include <linux/irqchip/arm-gic-v4.h>
+#include <linux/irqchip/arm-gic-v5.h>
#define VGIC_V3_MAX_CPUS 512
#define VGIC_V2_MAX_CPUS 8
@@ -31,9 +32,22 @@
#define VGIC_MIN_LPI 8192
#define KVM_IRQCHIP_NUM_PINS (1020 - 32)
-#define irq_is_ppi(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
-#define irq_is_spi(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
- (irq) <= VGIC_MAX_SPI)
+#define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
+#define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
+ (irq) <= VGIC_MAX_SPI)
+#define irq_is_lpi_legacy(irq) ((irq) > VGIC_MAX_SPI)
+
+#define irq_is_ppi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_PPI)
+#define irq_is_spi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_SPI)
+#define irq_is_lpi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_LPI)
+
+#define gic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+
+#define irq_is_ppi(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : irq_is_ppi_legacy(i))
+#define irq_is_spi(k, i) (gic_is_v5(k) ? irq_is_spi_v5(i) : irq_is_spi_legacy(i))
+#define irq_is_lpi(k, i) (gic_is_v5(k) ? irq_is_lpi_v5(i) : irq_is_lpi_legacy(i))
+
+#define irq_is_private(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : i < VGIC_NR_PRIVATE_IRQS)
enum vgic_type {
VGIC_V2, /* Good ol' GICv2 */
@@ -418,8 +432,13 @@ 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_valid_spi(k, i) (((i) >= VGIC_NR_PRIVATE_IRQS) && \
+#define vgic_ready(k) ((k)->arch.vgic.ready)
+#define vgic_valid_spi_legacy(k, i) (((i) >= VGIC_NR_PRIVATE_IRQS) && \
((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
+#define vgic_valid_spi_v5(k, i) (irq_is_spi(k, i) && \
+ (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
+#define vgic_valid_spi(k, i) (((k)->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5) ? \
+ vgic_valid_spi_legacy(k, i) : vgic_valid_spi_v5(k, i))
bool kvm_vcpu_has_pending_irqs(struct kvm_vcpu *vcpu);
void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu);
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers
2025-12-12 15:22 ` [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
@ 2025-12-15 13:32 ` Marc Zyngier
2025-12-15 16:01 ` Sascha Bischoff
2025-12-15 16:05 ` Marc Zyngier
0 siblings, 2 replies; 70+ messages in thread
From: Marc Zyngier @ 2025-12-15 13:32 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:37 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> GICv5 has moved from using interrupt ranges for different interrupt
> types to using some of the upper bits of the interrupt ID to denote
> the interrupt type. This is not compatible with older GICs (which rely
> on ranges of interrupts to determine the type), and hence a set of
> helpers is introduced. These helpers take a struct kvm*, and use the
> vgic model to determine how to interpret the interrupt ID.
>
> Helpers are introduced for PPIs, SPIs, and LPIs. Additionally, a
> helper is introduced to determine if an interrupt is private - SGIs
> and PPIs for older GICs, and PPIs only for GICv5.
>
> The helpers are plumbed into the core vgic code, as well as the Arch
> Timer and PMU code.
>
> There should be no functional changes as part of this change.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/arch_timer.c | 2 +-
> arch/arm64/kvm/pmu-emul.c | 6 +++---
> arch/arm64/kvm/vgic/vgic-kvm-device.c | 2 +-
> arch/arm64/kvm/vgic/vgic.c | 14 +++++++-------
> include/kvm/arm_vgic.h | 27 +++++++++++++++++++++++----
> 5 files changed, 35 insertions(+), 16 deletions(-)
>
> diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
> index 99a07972068d1..6f033f6644219 100644
> --- a/arch/arm64/kvm/arch_timer.c
> +++ b/arch/arm64/kvm/arch_timer.c
> @@ -1598,7 +1598,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
> if (get_user(irq, uaddr))
> return -EFAULT;
>
> - if (!(irq_is_ppi(irq)))
> + if (!(irq_is_ppi(vcpu->kvm, irq)))
nit: From a high-level perspective, I'd find it mentally more
satisfying to pass a vcpu to the macro rather than a vm pointer when
dealing with PPIs. It would also keep the dereference hidden away. But
maybe i just need to go with the flow here.
> return -EINVAL;
>
> mutex_lock(&vcpu->kvm->arch.config_lock);
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index b03dbda7f1ab9..0baf8e0fe23bd 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -939,7 +939,7 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu)
> * number against the dimensions of the vgic and make sure
> * it's valid.
> */
> - if (!irq_is_ppi(irq) && !vgic_valid_spi(vcpu->kvm, irq))
> + if (!irq_is_ppi(vcpu->kvm, irq) && !vgic_valid_spi(vcpu->kvm, irq))
> return -EINVAL;
> } else if (kvm_arm_pmu_irq_initialized(vcpu)) {
> return -EINVAL;
> @@ -991,7 +991,7 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
> if (!kvm_arm_pmu_irq_initialized(vcpu))
> continue;
>
> - if (irq_is_ppi(irq)) {
> + if (irq_is_ppi(kvm, irq)) {
> if (vcpu->arch.pmu.irq_num != irq)
> return false;
> } else {
> @@ -1142,7 +1142,7 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
> return -EFAULT;
>
> /* The PMU overflow interrupt can be a PPI or a valid SPI. */
> - if (!(irq_is_ppi(irq) || irq_is_spi(irq)))
> + if (!(irq_is_ppi(vcpu->kvm, irq) || irq_is_spi(vcpu->kvm, irq)))
> return -EINVAL;
>
> if (!pmu_irq_is_valid(kvm, irq))
> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> index 3d1a776b716d7..b12ba99a423e5 100644
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> @@ -639,7 +639,7 @@ static int vgic_v3_set_attr(struct kvm_device *dev,
> if (vgic_initialized(dev->kvm))
> return -EBUSY;
>
> - if (!irq_is_ppi(val))
> + if (!irq_is_ppi(dev->kvm, val))
> return -EINVAL;
>
> dev->kvm->arch.vgic.mi_intid = val;
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 430aa98888fda..2c0e8803342e2 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -94,7 +94,7 @@ struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
> }
>
> /* LPIs */
> - if (intid >= VGIC_MIN_LPI)
> + if (irq_is_lpi(kvm, intid))
> return vgic_get_lpi(kvm, intid);
>
> return NULL;
> @@ -123,7 +123,7 @@ static void vgic_release_lpi_locked(struct vgic_dist *dist, struct vgic_irq *irq
>
> static __must_check bool __vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
> {
> - if (irq->intid < VGIC_MIN_LPI)
> + if (!irq_is_lpi(kvm, irq->intid))
> return false;
>
> return refcount_dec_and_test(&irq->refcount);
> @@ -148,7 +148,7 @@ void vgic_put_irq(struct kvm *kvm, struct vgic_irq *irq)
> * Acquire/release it early on lockdep kernels to make locking issues
> * in rare release paths a bit more obvious.
> */
> - if (IS_ENABLED(CONFIG_LOCKDEP) && irq->intid >= VGIC_MIN_LPI) {
> + if (IS_ENABLED(CONFIG_LOCKDEP) && irq_is_lpi(kvm, irq->intid)) {
> guard(spinlock_irqsave)(&dist->lpi_xa.xa_lock);
> }
>
> @@ -186,7 +186,7 @@ void vgic_flush_pending_lpis(struct kvm_vcpu *vcpu)
> raw_spin_lock_irqsave(&vgic_cpu->ap_list_lock, flags);
>
> list_for_each_entry_safe(irq, tmp, &vgic_cpu->ap_list_head, ap_list) {
> - if (irq->intid >= VGIC_MIN_LPI) {
> + if (irq_is_lpi(vcpu->kvm, irq->intid)) {
> raw_spin_lock(&irq->irq_lock);
> list_del(&irq->ap_list);
> irq->vcpu = NULL;
> @@ -521,12 +521,12 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
> if (ret)
> return ret;
>
> - if (!vcpu && intid < VGIC_NR_PRIVATE_IRQS)
> + if (!vcpu && irq_is_private(kvm, intid))
> return -EINVAL;
>
> trace_vgic_update_irq_pending(vcpu ? vcpu->vcpu_idx : 0, intid, level);
>
> - if (intid < VGIC_NR_PRIVATE_IRQS)
> + if (irq_is_private(kvm, intid))
> irq = vgic_get_vcpu_irq(vcpu, intid);
> else
> irq = vgic_get_irq(kvm, intid);
> @@ -685,7 +685,7 @@ int kvm_vgic_set_owner(struct kvm_vcpu *vcpu, unsigned int intid, void *owner)
> return -EAGAIN;
>
> /* SGIs and LPIs cannot be wired up to any device */
> - if (!irq_is_ppi(intid) && !vgic_valid_spi(vcpu->kvm, intid))
> + if (!irq_is_ppi(vcpu->kvm, intid) && !vgic_valid_spi(vcpu->kvm, intid))
> return -EINVAL;
>
> irq = vgic_get_vcpu_irq(vcpu, intid);
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index b261fb3968d03..be1f45a494f78 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -19,6 +19,7 @@
> #include <linux/jump_label.h>
>
> #include <linux/irqchip/arm-gic-v4.h>
> +#include <linux/irqchip/arm-gic-v5.h>
>
> #define VGIC_V3_MAX_CPUS 512
> #define VGIC_V2_MAX_CPUS 8
> @@ -31,9 +32,22 @@
> #define VGIC_MIN_LPI 8192
> #define KVM_IRQCHIP_NUM_PINS (1020 - 32)
>
> -#define irq_is_ppi(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
> -#define irq_is_spi(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> - (irq) <= VGIC_MAX_SPI)
> +#define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
> +#define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> + (irq) <= VGIC_MAX_SPI)
> +#define irq_is_lpi_legacy(irq) ((irq) > VGIC_MAX_SPI)
This last line is wrong. v3 LPIs start at 8192, while VGIC_MAX_SPI is
1019. Also, "legacy" is remarkably ambiguous. v2 is legacy for v3, v3
for v4... You see where this is going.
I'd rather you have something that denotes the non-GICv5-ness of the
implementation. irq_is_nv5_ppi()?
> +
> +#define irq_is_ppi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_PPI)
> +#define irq_is_spi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_SPI)
> +#define irq_is_lpi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_LPI)
> +
> +#define gic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> +
> +#define irq_is_ppi(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : irq_is_ppi_legacy(i))
> +#define irq_is_spi(k, i) (gic_is_v5(k) ? irq_is_spi_v5(i) : irq_is_spi_legacy(i))
> +#define irq_is_lpi(k, i) (gic_is_v5(k) ? irq_is_lpi_v5(i) : irq_is_lpi_legacy(i))
> +
> +#define irq_is_private(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : i < VGIC_NR_PRIVATE_IRQS)
>
> enum vgic_type {
> VGIC_V2, /* Good ol' GICv2 */
> @@ -418,8 +432,13 @@ 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_valid_spi(k, i) (((i) >= VGIC_NR_PRIVATE_IRQS) && \
> +#define vgic_ready(k) ((k)->arch.vgic.ready)
What is this for? Nothing seem to be using it yet. How different is it
from the 'initialized' field?
> +#define vgic_valid_spi_legacy(k, i) (((i) >= VGIC_NR_PRIVATE_IRQS) && \
> ((i) < (k)->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS))
> +#define vgic_valid_spi_v5(k, i) (irq_is_spi(k, i) && \
> + (FIELD_GET(GICV5_HWIRQ_ID, i) < (k)->arch.vgic.nr_spis))
> +#define vgic_valid_spi(k, i) (((k)->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5) ? \
> + vgic_valid_spi_legacy(k, i) : vgic_valid_spi_v5(k, i))
>
This macro has its v5/nv5 statements in the opposite order of all the
others. Some consistency would be welcome.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers
2025-12-15 13:32 ` Marc Zyngier
@ 2025-12-15 16:01 ` Sascha Bischoff
2025-12-15 16:05 ` Marc Zyngier
1 sibling, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-15 16:01 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 13:32 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:37 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > - if (!(irq_is_ppi(irq)))
> > + if (!(irq_is_ppi(vcpu->kvm, irq)))
>
> nit: From a high-level perspective, I'd find it mentally more
> satisfying to pass a vcpu to the macro rather than a vm pointer when
> dealing with PPIs. It would also keep the dereference hidden away.
> But
> maybe i just need to go with the flow here.
No, I think you're right. It better aligns with the semantics
underlying private IRQs, and does make the code a bit neater. If we're
ever generically checking (e.g., not using the _v5 variant) and don't
have a vcpu pointer in that context then something is likely awry.
It does make irq_is_private(k, i) a bit strange though as that is used
for things like
if (!vcpu && irq_is_private(kvm, intid))
in later commits. That needs to take struct kvm if we want to do the
same sort of check. Else, we might as well not have it.
> > +#define irq_is_lpi_legacy(irq) ((irq) > VGIC_MAX_SPI)
>
> This last line is wrong. v3 LPIs start at 8192, while VGIC_MAX_SPI is
> 1019. Also, "legacy" is remarkably ambiguous. v2 is legacy for v3, v3
> for v4... You see where this is going.
>
> I'd rather you have something that denotes the non-GICv5-ness of the
> implementation. irq_is_nv5_ppi()?
You are absolutely correct; that last line is wrong. Fixed, thanks!
And yeah, naming is hard. From my (unique) point of view, everything
that isn't GICv5 is legacy, but that's not exactly helpful for anyone
else.
IMO, nv5 is a bit too close to NV & NV2. I'd prefer something more like
irq_is_non_v5_ppi for the naming, but definitely don't heel strongly.
Happy for suggestions here, but for the time being I've locally changed
to using nv5 in the place of legacy.
> > #define irqchip_in_kernel(k) (!!((k)->arch.vgic.in_kernel))
> > #define vgic_initialized(k) ((k)->arch.vgic.initialized)
> > -#define vgic_valid_spi(k, i) (((i) >= VGIC_NR_PRIVATE_IRQS) &&
> > \
> > +#define vgic_ready(k) ((k)->arch.vgic.ready)
>
> What is this for? Nothing seem to be using it yet. How different is
> it
> from the 'initialized' field?
This is for nothing, and wasn't meant to be here at all. Seems it crept
in from the prototyping, and slipped through the cracks when preparing
this series. Have dropped it. Apologies!
> > +#define vgic_valid_spi_legacy(k, i) (((i) >=
> > VGIC_NR_PRIVATE_IRQS) && \
> > ((i) < (k)->arch.vgic.nr_spis +
> > VGIC_NR_PRIVATE_IRQS))
> > +#define vgic_valid_spi_v5(k, i) (irq_is_spi(k, i) && \
> > + (FIELD_GET(GICV5_HWIRQ_ID, i) <
> > (k)->arch.vgic.nr_spis))
> > +#define vgic_valid_spi(k, i) (((k)->arch.vgic.vgic_model !=
> > KVM_DEV_TYPE_ARM_VGIC_V5) ? \
> > + vgic_valid_spi_legacy(k, i) :
> > vgic_valid_spi_v5(k, i))
> >
>
> This macro has its v5/nv5 statements in the opposite order of all the
> others. Some consistency would be welcome.
Have re-ordered to match the order above.
>
> Thanks,
>
> M.
>
Thanks,
Sascha
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers
2025-12-15 13:32 ` Marc Zyngier
2025-12-15 16:01 ` Sascha Bischoff
@ 2025-12-15 16:05 ` Marc Zyngier
2025-12-16 8:57 ` Sascha Bischoff
1 sibling, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-15 16:05 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Mon, 15 Dec 2025 13:32:04 +0000,
Marc Zyngier <maz@kernel.org> wrote:
>
> I'd rather you have something that denotes the non-GICv5-ness of the
> implementation. irq_is_nv5_ppi()?
Actually, this is just as bad. I spent the past 30 minutes hacking on
this, and came up with this hack (which compiles, but probably doesn't
run). It is significantly more code, but I like that it treats all GIC
implementations more or less the same way.
It also clears the who [v]gic_is_v5() situation that made little
sense.
Let me know what you think.
M.
diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index b0a5a6c6bf8da..c908d5ac4d678 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -62,11 +62,6 @@ static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
.queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
};
-static bool vgic_is_v5(struct kvm_vcpu *vcpu)
-{
- return vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5;
-}
-
static int nr_timers(struct kvm_vcpu *vcpu)
{
if (!vcpu_has_nv(vcpu))
@@ -708,7 +703,7 @@ static void kvm_timer_vcpu_load_gic(struct arch_timer_context *ctx)
phys_active |= ctx->irq.level;
- if (!vgic_is_v5(vcpu))
+ if (!vgic_is_v5(vcpu->kvm))
set_timer_irq_phys_active(ctx, phys_active);
else
set_timer_irq_phys_masked(ctx, true);
@@ -760,7 +755,7 @@ static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
if (!irqchip_in_kernel(vcpu->kvm))
return;
- ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
+ ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
&arch_timer_irq_ops;
/*
@@ -905,7 +900,7 @@ void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)
if (static_branch_likely(&has_gic_active_state)) {
/* We don't do NV on GICv5, yet */
- if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu))
+ if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu->kvm))
kvm_timer_vcpu_load_nested_switch(vcpu, &map);
kvm_timer_vcpu_load_gic(map.direct_vtimer);
@@ -977,7 +972,7 @@ void kvm_timer_vcpu_put(struct kvm_vcpu *vcpu)
kvm_timer_blocking(vcpu);
/* Unmask again on GICV5 */
- if (vgic_is_v5(vcpu)) {
+ if (vgic_is_v5(vcpu->kvm)) {
set_timer_irq_phys_masked(map.direct_vtimer, false);
if (map.direct_ptimer)
@@ -1623,7 +1618,7 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
return -EINVAL;
}
- ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
+ ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
&arch_timer_irq_ops;
get_timer_map(vcpu, &map);
@@ -1700,7 +1695,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
* The PPIs for the Arch Timers arch architecturally defined for
* GICv5. Reject anything that changes them from the specified value.
*/
- if (vgic_is_v5(vcpu) && vcpu->kvm->arch.timer_data.ppi[idx] != irq) {
+ if (vgic_is_v5(vcpu->kvm) && vcpu->kvm->arch.timer_data.ppi[idx] != irq) {
ret = -EINVAL;
goto out;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d74cc3543b9a4..19d8bf90f8f6c 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -225,7 +225,7 @@ bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
lockdep_assert_held(&irq->irq_lock);
- if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
+ if (WARN_ON_ONCE(!__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
return false;
vcpu = irq->target_vcpu;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 62d7d4c5650e4..6d8e4cd661734 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -109,10 +109,10 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
if (WARN_ON(!vcpu))
return NULL;
- if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ if (vgic_is_v5(vcpu->kvm)) {
u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
- if (irq_is_ppi_v5(intid)) {
+ if (__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
return &vcpu->arch.vgic_cpu.private_irqs[int_num];
}
@@ -600,15 +600,13 @@ static int kvm_vgic_map_irq(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
irq->hwintid = data->hwirq;
irq->ops = ops;
- if (vgic_is_v5(vcpu->kvm)) {
- /* Nothing for us to do */
- if (!irq_is_ppi_v5(irq->intid))
- return 0;
+ if (vgic_is_v5(vcpu->kvm) &&
+ !__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid))
+ return 0;
- if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
- if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
- irq->directly_injected = true;
- }
+ if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
+ if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
+ irq->directly_injected = true;
}
return 0;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 91969b3b80d04..d8a947a7eb941 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -500,11 +500,6 @@ static inline bool vgic_is_v3(struct kvm *kvm)
return kvm_vgic_global_state.type == VGIC_V3 || vgic_is_v3_compat(kvm);
}
-static inline bool vgic_is_v5(struct kvm *kvm)
-{
- return kvm_vgic_global_state.type == VGIC_V5 && !vgic_is_v3_compat(kvm);
-}
-
bool system_supports_direct_sgis(void);
bool vgic_supports_direct_msis(struct kvm *kvm);
bool vgic_supports_direct_sgis(struct kvm *kvm);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 6863e19d6eeb7..ae2897c539af7 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -36,22 +36,78 @@
/* GICv5 constants */
#define VGIC_V5_NR_PRIVATE_IRQS 128
-#define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
-#define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
- (irq) <= VGIC_MAX_SPI)
-#define irq_is_lpi_legacy(irq) ((irq) > VGIC_MAX_SPI)
-
-#define irq_is_ppi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_PPI)
-#define irq_is_spi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_SPI)
-#define irq_is_lpi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) == GICV5_HWIRQ_TYPE_LPI)
-
-#define gic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
-
-#define irq_is_ppi(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : irq_is_ppi_legacy(i))
-#define irq_is_spi(k, i) (gic_is_v5(k) ? irq_is_spi_v5(i) : irq_is_spi_legacy(i))
-#define irq_is_lpi(k, i) (gic_is_v5(k) ? irq_is_lpi_v5(i) : irq_is_lpi_legacy(i))
-
-#define irq_is_private(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : i < VGIC_NR_PRIVATE_IRQS)
+#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
+
+#define __irq_is_sgi(t, i) \
+ ({ \
+ bool __ret; \
+ \
+ switch (t) { \
+ case KVM_DEV_TYPE_ARM_VGIC_V5: \
+ __ret = false; \
+ break; \
+ default: \
+ __ret = (i) < VGIC_NR_SGIS; \
+ } \
+ \
+ __ret; \
+ })
+
+#define __irq_is_ppi(t, i) \
+ ({ \
+ bool __ret; \
+ \
+ switch (t) { \
+ case KVM_DEV_TYPE_ARM_VGIC_V5: \
+ __ret = is_v5_type(GICV5_HWIRQ_TYPE_PPI, (i)); \
+ break; \
+ default: \
+ __ret = (i) >= VGIC_NR_SGIS; \
+ __ret &= (i) < VGIC_NR_PRIVATE_IRQS; \
+ } \
+ \
+ __ret; \
+ })
+
+#define __irq_is_spi(t, i) \
+ ({ \
+ bool __ret; \
+ \
+ switch (t) { \
+ case KVM_DEV_TYPE_ARM_VGIC_V5: \
+ __ret = is_v5_type(GICV5_HWIRQ_TYPE_SPI, (i)); \
+ break; \
+ default: \
+ __ret = (i) <= VGIC_MAX_SPI; \
+ __ret &= (i) >= VGIC_NR_PRIVATE_IRQS; \
+ } \
+ \
+ __ret; \
+ })
+
+#define __irq_is_lpi(t, i) \
+ ({ \
+ bool __ret; \
+ \
+ switch (t) { \
+ case KVM_DEV_TYPE_ARM_VGIC_V5: \
+ __ret = is_v5_type(GICV5_HWIRQ_TYPE_LPI, (i)); \
+ break; \
+ default: \
+ __ret = (i) >= 8192; \
+ } \
+ \
+ __ret; \
+ })
+
+#define irq_is_sgi(k, i) __irq_is_sgi((k)->arch.vgic.vgic_model, i)
+#define irq_is_ppi(k, i) __irq_is_ppi((k)->arch.vgic.vgic_model, i)
+#define irq_is_spi(k, i) __irq_is_spi((k)->arch.vgic.vgic_model, i)
+#define irq_is_lpi(k, i) __irq_is_lpi((k)->arch.vgic.vgic_model, i)
+
+#define irq_is_private(k, i) (irq_is_ppi(k, i) || irq_is_sgi(k, i))
+
+#define vgic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
enum vgic_type {
VGIC_V2, /* Good ol' GICv2 */
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers
2025-12-15 16:05 ` Marc Zyngier
@ 2025-12-16 8:57 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-16 8:57 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 16:05 +0000, Marc Zyngier wrote:
> On Mon, 15 Dec 2025 13:32:04 +0000,
> Marc Zyngier <maz@kernel.org> wrote:
> >
> > I'd rather you have something that denotes the non-GICv5-ness of
> > the
> > implementation. irq_is_nv5_ppi()?
>
> Actually, this is just as bad. I spent the past 30 minutes hacking on
> this, and came up with this hack (which compiles, but probably
> doesn't
> run). It is significantly more code, but I like that it treats all
> GIC
> implementations more or less the same way.
>
> It also clears the who [v]gic_is_v5() situation that made little
> sense.
>
> Let me know what you think.
>
> M.
Thanks, Marc!
I do think that this is better overall. It removes the naming issues as
you say, and makes the whole situation more readable (and adds in SGI
support in those checks).
It does mean that we're still passing a non-vcpu pointer for the
private irqs (PPIs, SGIs) but else it gets quite inconsistent. Going
through the code we do actually have one place where we check for a PPI
without having a vcpu - setting the GICv3 maint IRQ. I guess that it is
worth paying the price of having vcpu->kvm in a few places for the sake
of consistency.
Will work these changes into the patch series (including cleaning up
the gic type checks).
Thanks,
Sascha
>
> diff --git a/arch/arm64/kvm/arch_timer.c
> b/arch/arm64/kvm/arch_timer.c
> index b0a5a6c6bf8da..c908d5ac4d678 100644
> --- a/arch/arm64/kvm/arch_timer.c
> +++ b/arch/arm64/kvm/arch_timer.c
> @@ -62,11 +62,6 @@ static struct irq_ops arch_timer_irq_ops_vgic_v5 =
> {
> .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> };
>
> -static bool vgic_is_v5(struct kvm_vcpu *vcpu)
> -{
> - return vcpu->kvm->arch.vgic.vgic_model ==
> KVM_DEV_TYPE_ARM_VGIC_V5;
> -}
> -
> static int nr_timers(struct kvm_vcpu *vcpu)
> {
> if (!vcpu_has_nv(vcpu))
> @@ -708,7 +703,7 @@ static void kvm_timer_vcpu_load_gic(struct
> arch_timer_context *ctx)
>
> phys_active |= ctx->irq.level;
>
> - if (!vgic_is_v5(vcpu))
> + if (!vgic_is_v5(vcpu->kvm))
> set_timer_irq_phys_active(ctx, phys_active);
> else
> set_timer_irq_phys_masked(ctx, true);
> @@ -760,7 +755,7 @@ static void
> kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
> if (!irqchip_in_kernel(vcpu->kvm))
> return;
>
> - ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
> + ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
> &arch_timer_irq_ops;
>
> /*
> @@ -905,7 +900,7 @@ void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)
>
> if (static_branch_likely(&has_gic_active_state)) {
> /* We don't do NV on GICv5, yet */
> - if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu))
> + if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu->kvm))
> kvm_timer_vcpu_load_nested_switch(vcpu,
> &map);
>
> kvm_timer_vcpu_load_gic(map.direct_vtimer);
> @@ -977,7 +972,7 @@ void kvm_timer_vcpu_put(struct kvm_vcpu *vcpu)
> kvm_timer_blocking(vcpu);
>
> /* Unmask again on GICV5 */
> - if (vgic_is_v5(vcpu)) {
> + if (vgic_is_v5(vcpu->kvm)) {
> set_timer_irq_phys_masked(map.direct_vtimer, false);
>
> if (map.direct_ptimer)
> @@ -1623,7 +1618,7 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
> return -EINVAL;
> }
>
> - ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
> + ops = vgic_is_v5(vcpu->kvm) ? &arch_timer_irq_ops_vgic_v5 :
> &arch_timer_irq_ops;
>
> get_timer_map(vcpu, &map);
> @@ -1700,7 +1695,7 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu
> *vcpu, struct kvm_device_attr *attr)
> * The PPIs for the Arch Timers arch architecturally defined
> for
> * GICv5. Reject anything that changes them from the
> specified value.
> */
> - if (vgic_is_v5(vcpu) && vcpu->kvm->arch.timer_data.ppi[idx]
> != irq) {
> + if (vgic_is_v5(vcpu->kvm) && vcpu->kvm-
> >arch.timer_data.ppi[idx] != irq) {
> ret = -EINVAL;
> goto out;
> }
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> b/arch/arm64/kvm/vgic/vgic-v5.c
> index d74cc3543b9a4..19d8bf90f8f6c 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -225,7 +225,7 @@ bool vgic_v5_ppi_queue_irq_unlock(struct kvm
> *kvm, struct vgic_irq *irq,
>
> lockdep_assert_held(&irq->irq_lock);
>
> - if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
> + if (WARN_ON_ONCE(!__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5,
> irq->intid)))
> return false;
>
> vcpu = irq->target_vcpu;
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 62d7d4c5650e4..6d8e4cd661734 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -109,10 +109,10 @@ struct vgic_irq *vgic_get_vcpu_irq(struct
> kvm_vcpu *vcpu, u32 intid)
> if (WARN_ON(!vcpu))
> return NULL;
>
> - if (vcpu->kvm->arch.vgic.vgic_model ==
> KVM_DEV_TYPE_ARM_VGIC_V5) {
> + if (vgic_is_v5(vcpu->kvm)) {
> u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
>
> - if (irq_is_ppi_v5(intid)) {
> + if (__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, intid)) {
> int_num = array_index_nospec(int_num,
> VGIC_V5_NR_PRIVATE_IRQS);
> return &vcpu-
> >arch.vgic_cpu.private_irqs[int_num];
> }
> @@ -600,15 +600,13 @@ static int kvm_vgic_map_irq(struct kvm_vcpu
> *vcpu, struct vgic_irq *irq,
> irq->hwintid = data->hwirq;
> irq->ops = ops;
>
> - if (vgic_is_v5(vcpu->kvm)) {
> - /* Nothing for us to do */
> - if (!irq_is_ppi_v5(irq->intid))
> - return 0;
> + if (vgic_is_v5(vcpu->kvm) &&
> + !__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid))
> + return 0;
>
> - if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq-
> >hwintid) {
> - if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid,
> true))
> - irq->directly_injected = true;
> - }
> + if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
> + if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
> + irq->directly_injected = true;
> }
>
> return 0;
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 91969b3b80d04..d8a947a7eb941 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -500,11 +500,6 @@ static inline bool vgic_is_v3(struct kvm *kvm)
> return kvm_vgic_global_state.type == VGIC_V3 ||
> vgic_is_v3_compat(kvm);
> }
>
> -static inline bool vgic_is_v5(struct kvm *kvm)
> -{
> - return kvm_vgic_global_state.type == VGIC_V5 &&
> !vgic_is_v3_compat(kvm);
> -}
> -
> bool system_supports_direct_sgis(void);
> bool vgic_supports_direct_msis(struct kvm *kvm);
> bool vgic_supports_direct_sgis(struct kvm *kvm);
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 6863e19d6eeb7..ae2897c539af7 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -36,22 +36,78 @@
> /* GICv5 constants */
> #define VGIC_V5_NR_PRIVATE_IRQS 128
>
> -#define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) <
> VGIC_NR_PRIVATE_IRQS)
> -#define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> - (irq) <= VGIC_MAX_SPI)
> -#define irq_is_lpi_legacy(irq) ((irq) > VGIC_MAX_SPI)
> -
> -#define irq_is_ppi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) ==
> GICV5_HWIRQ_TYPE_PPI)
> -#define irq_is_spi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) ==
> GICV5_HWIRQ_TYPE_SPI)
> -#define irq_is_lpi_v5(irq) (FIELD_GET(GICV5_HWIRQ_TYPE, irq) ==
> GICV5_HWIRQ_TYPE_LPI)
> -
> -#define gic_is_v5(k) ((k)->arch.vgic.vgic_model ==
> KVM_DEV_TYPE_ARM_VGIC_V5)
> -
> -#define irq_is_ppi(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) :
> irq_is_ppi_legacy(i))
> -#define irq_is_spi(k, i) (gic_is_v5(k) ? irq_is_spi_v5(i) :
> irq_is_spi_legacy(i))
> -#define irq_is_lpi(k, i) (gic_is_v5(k) ? irq_is_lpi_v5(i) :
> irq_is_lpi_legacy(i))
> -
> -#define irq_is_private(k, i) (gic_is_v5(k) ? irq_is_ppi_v5(i) : i <
> VGIC_NR_PRIVATE_IRQS)
> +#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) ==
> (t))
> +
> +#define __irq_is_sgi(t,
> i) \
> + ({
> \
> + bool
> __ret; \
> +
> \
> + switch (t)
> { \
> + case
> KVM_DEV_TYPE_ARM_VGIC_V5: \
> + __ret =
> false; \
> + break;
> \
> + default:
> \
> + __ret = (i) <
> VGIC_NR_SGIS; \
> + }
> \
> +
> \
> + __ret;
> \
> + })
> +
> +#define __irq_is_ppi(t,
> i) \
> + ({
> \
> + bool
> __ret; \
> +
> \
> + switch (t)
> { \
> + case
> KVM_DEV_TYPE_ARM_VGIC_V5: \
> + __ret = is_v5_type(GICV5_HWIRQ_TYPE_PPI,
> (i)); \
> + break;
> \
> + default:
> \
> + __ret = (i) >=
> VGIC_NR_SGIS; \
> + __ret &= (i) <
> VGIC_NR_PRIVATE_IRQS; \
> + }
> \
> +
> \
> + __ret;
> \
> + })
> +
> +#define __irq_is_spi(t,
> i) \
> + ({
> \
> + bool
> __ret; \
> +
> \
> + switch (t)
> { \
> + case
> KVM_DEV_TYPE_ARM_VGIC_V5: \
> + __ret = is_v5_type(GICV5_HWIRQ_TYPE_SPI,
> (i)); \
> + break;
> \
> + default:
> \
> + __ret = (i) <=
> VGIC_MAX_SPI; \
> + __ret &= (i) >=
> VGIC_NR_PRIVATE_IRQS; \
> + }
> \
> +
> \
> + __ret;
> \
> + })
> +
> +#define __irq_is_lpi(t,
> i) \
> + ({
> \
> + bool
> __ret; \
> +
> \
> + switch (t)
> { \
> + case
> KVM_DEV_TYPE_ARM_VGIC_V5: \
> + __ret = is_v5_type(GICV5_HWIRQ_TYPE_LPI,
> (i)); \
> + break;
> \
> + default:
> \
> + __ret = (i) >=
> 8192; \
> + }
> \
> +
> \
> + __ret;
> \
> + })
> +
> +#define irq_is_sgi(k, i) __irq_is_sgi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_ppi(k, i) __irq_is_ppi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_spi(k, i) __irq_is_spi((k)->arch.vgic.vgic_model, i)
> +#define irq_is_lpi(k, i) __irq_is_lpi((k)->arch.vgic.vgic_model, i)
> +
> +#define irq_is_private(k, i) (irq_is_ppi(k, i) || irq_is_sgi(k, i))
> +
> +#define vgic_is_v5(k) ((k)->arch.vgic.vgic_model ==
> KVM_DEV_TYPE_ARM_VGIC_V5)
>
> enum vgic_type {
> VGIC_V2, /* Good ol' GICv2 */
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (7 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 07/32] KVM: arm64: gic: Introduce interrupt type helpers Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 16:24 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses Sascha Bischoff
` (22 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Extend the existing FGT infrastructure to calculate and activate any
required GICv5 traps (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2, ICH_HFGITR_EL2)
before entering the guest, and restore the original ICH_HFGxTR_EL2
contents on the return path. This ensures that the host and guest
behaviour remains independent.
As of this change, none of the GICv5 instructions or register accesses
are being trapped, but this will change in subsequent commits as some
GICv5 system registers must always be trapped (ICC_IAFFIDR_EL1,
ICH_PPI_HMRx_EL1).
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_host.h | 19 +++++
arch/arm64/include/asm/vncr_mapping.h | 3 +
arch/arm64/kvm/arm.c | 3 +
arch/arm64/kvm/config.c | 96 ++++++++++++++++++++++++-
arch/arm64/kvm/emulate-nested.c | 68 ++++++++++++++++++
arch/arm64/kvm/hyp/include/hyp/switch.h | 27 +++++++
arch/arm64/kvm/hyp/nvhe/switch.c | 3 +
arch/arm64/kvm/sys_regs.c | 2 +
8 files changed, 219 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index b552a1e03848c..0e535ef50c231 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -287,6 +287,9 @@ enum fgt_group_id {
HDFGRTR2_GROUP,
HDFGWTR2_GROUP = HDFGRTR2_GROUP,
HFGITR2_GROUP,
+ ICH_HFGRTR_GROUP,
+ ICH_HFGWTR_GROUP = ICH_HFGRTR_GROUP,
+ ICH_HFGITR_GROUP,
/* Must be last */
__NR_FGT_GROUP_IDS__
@@ -623,6 +626,10 @@ enum vcpu_sysreg {
VNCR(ICH_HCR_EL2),
VNCR(ICH_VMCR_EL2),
+ VNCR(ICH_HFGRTR_EL2),
+ VNCR(ICH_HFGWTR_EL2),
+ VNCR(ICH_HFGITR_EL2),
+
NR_SYS_REGS /* Nothing after this line! */
};
@@ -652,6 +659,9 @@ extern struct fgt_masks hfgwtr2_masks;
extern struct fgt_masks hfgitr2_masks;
extern struct fgt_masks hdfgrtr2_masks;
extern struct fgt_masks hdfgwtr2_masks;
+extern struct fgt_masks ich_hfgrtr_masks;
+extern struct fgt_masks ich_hfgwtr_masks;
+extern struct fgt_masks ich_hfgitr_masks;
extern struct fgt_masks kvm_nvhe_sym(hfgrtr_masks);
extern struct fgt_masks kvm_nvhe_sym(hfgwtr_masks);
@@ -664,6 +674,9 @@ extern struct fgt_masks kvm_nvhe_sym(hfgwtr2_masks);
extern struct fgt_masks kvm_nvhe_sym(hfgitr2_masks);
extern struct fgt_masks kvm_nvhe_sym(hdfgrtr2_masks);
extern struct fgt_masks kvm_nvhe_sym(hdfgwtr2_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgrtr_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgwtr_masks);
+extern struct fgt_masks kvm_nvhe_sym(ich_hfgitr_masks);
struct kvm_cpu_context {
struct user_pt_regs regs; /* sp = sp_el0 */
@@ -1632,6 +1645,11 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg
case HDFGRTR2_EL2:
case HDFGWTR2_EL2:
return HDFGRTR2_GROUP;
+ case ICH_HFGRTR_EL2:
+ case ICH_HFGWTR_EL2:
+ return ICH_HFGRTR_GROUP;
+ case ICH_HFGITR_EL2:
+ return ICH_HFGITR_GROUP;
default:
BUILD_BUG_ON(1);
}
@@ -1646,6 +1664,7 @@ static __always_inline enum fgt_group_id __fgt_reg_to_group_id(enum vcpu_sysreg
case HDFGWTR_EL2: \
case HFGWTR2_EL2: \
case HDFGWTR2_EL2: \
+ case ICH_HFGWTR_EL2: \
p = &(vcpu)->arch.fgt[id].w; \
break; \
default: \
diff --git a/arch/arm64/include/asm/vncr_mapping.h b/arch/arm64/include/asm/vncr_mapping.h
index c2485a862e690..14366d35ce82f 100644
--- a/arch/arm64/include/asm/vncr_mapping.h
+++ b/arch/arm64/include/asm/vncr_mapping.h
@@ -108,5 +108,8 @@
#define VNCR_MPAMVPM5_EL2 0x968
#define VNCR_MPAMVPM6_EL2 0x970
#define VNCR_MPAMVPM7_EL2 0x978
+#define VNCR_ICH_HFGITR_EL2 0xB10
+#define VNCR_ICH_HFGRTR_EL2 0xB18
+#define VNCR_ICH_HFGWTR_EL2 0xB20
#endif /* __ARM64_VNCR_MAPPING_H__ */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 4f80da0c0d1de..b7cf9d86aabb7 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2474,6 +2474,9 @@ static void kvm_hyp_init_symbols(void)
kvm_nvhe_sym(hfgitr2_masks) = hfgitr2_masks;
kvm_nvhe_sym(hdfgrtr2_masks)= hdfgrtr2_masks;
kvm_nvhe_sym(hdfgwtr2_masks)= hdfgwtr2_masks;
+ kvm_nvhe_sym(ich_hfgrtr_masks) = ich_hfgrtr_masks;
+ kvm_nvhe_sym(ich_hfgwtr_masks) = ich_hfgwtr_masks;
+ kvm_nvhe_sym(ich_hfgitr_masks) = ich_hfgitr_masks;
/*
* Flush entire BSS since part of its data containing init symbols is read
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 3845b188551b6..57ef67f718113 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -219,6 +219,7 @@ struct reg_feat_map_desc {
#define FEAT_FGT2 ID_AA64MMFR0_EL1, FGT, FGT2
#define FEAT_MTPMU ID_AA64DFR0_EL1, MTPMU, IMP
#define FEAT_HCX ID_AA64MMFR1_EL1, HCX, IMP
+#define FEAT_GCIE ID_AA64PFR2_EL1, GCIE, IMP
static bool not_feat_aa64el3(struct kvm *kvm)
{
@@ -1168,6 +1169,58 @@ static const struct reg_bits_to_feat_map mdcr_el2_feat_map[] = {
static const DECLARE_FEAT_MAP(mdcr_el2_desc, MDCR_EL2,
mdcr_el2_feat_map, FEAT_AA64EL2);
+static const struct reg_bits_to_feat_map ich_hfgrtr_feat_map[] = {
+ NEEDS_FEAT(ICH_HFGRTR_EL2_ICC_APR_EL1 |
+ ICH_HFGRTR_EL2_ICC_IDRn_EL1 |
+ ICH_HFGRTR_EL2_ICC_CR0_EL1 |
+ ICH_HFGRTR_EL2_ICC_HPPIR_EL1 |
+ ICH_HFGRTR_EL2_ICC_PCR_EL1 |
+ ICH_HFGRTR_EL2_ICC_ICSR_EL1 |
+ ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1 |
+ ICH_HFGRTR_EL2_ICC_PPI_HMRn_EL1 |
+ ICH_HFGRTR_EL2_ICC_PPI_ENABLERn_EL1 |
+ ICH_HFGRTR_EL2_ICC_PPI_PENDRn_EL1 |
+ ICH_HFGRTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+ ICH_HFGRTR_EL2_ICC_PPI_ACTIVERn_EL1,
+ FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgrtr_desc, ich_hfgrtr_masks,
+ ich_hfgrtr_feat_map, FEAT_GCIE);
+
+static const struct reg_bits_to_feat_map ich_hfgwtr_feat_map[] = {
+ NEEDS_FEAT(ICH_HFGWTR_EL2_ICC_APR_EL1 |
+ ICH_HFGWTR_EL2_ICC_CR0_EL1 |
+ ICH_HFGWTR_EL2_ICC_PCR_EL1 |
+ ICH_HFGWTR_EL2_ICC_ICSR_EL1 |
+ ICH_HFGWTR_EL2_ICC_PPI_ENABLERn_EL1 |
+ ICH_HFGWTR_EL2_ICC_PPI_PENDRn_EL1 |
+ ICH_HFGWTR_EL2_ICC_PPI_PRIORITYRn_EL1 |
+ ICH_HFGWTR_EL2_ICC_PPI_ACTIVERn_EL1,
+ FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgwtr_desc, ich_hfgwtr_masks,
+ ich_hfgwtr_feat_map, FEAT_GCIE);
+
+static const struct reg_bits_to_feat_map ich_hfgitr_feat_map[] = {
+ NEEDS_FEAT(ICH_HFGITR_EL2_GICCDEN |
+ ICH_HFGITR_EL2_GICCDDIS |
+ ICH_HFGITR_EL2_GICCDPRI |
+ ICH_HFGITR_EL2_GICCDAFF |
+ ICH_HFGITR_EL2_GICCDPEND |
+ ICH_HFGITR_EL2_GICCDRCFG |
+ ICH_HFGITR_EL2_GICCDHM |
+ ICH_HFGITR_EL2_GICCDEOI |
+ ICH_HFGITR_EL2_GICCDDI |
+ ICH_HFGITR_EL2_GICRCDIA |
+ ICH_HFGITR_EL2_GICRCDNMIA,
+ FEAT_GCIE),
+};
+
+static const DECLARE_FEAT_MAP_FGT(ich_hfgitr_desc, ich_hfgitr_masks,
+ ich_hfgitr_feat_map, FEAT_GCIE);
+
static void __init check_feat_map(const struct reg_bits_to_feat_map *map,
int map_size, u64 resx, const char *str)
{
@@ -1211,6 +1264,9 @@ void __init check_feature_map(void)
check_reg_desc(&tcr2_el2_desc);
check_reg_desc(&sctlr_el1_desc);
check_reg_desc(&mdcr_el2_desc);
+ check_reg_desc(&ich_hfgrtr_desc);
+ check_reg_desc(&ich_hfgwtr_desc);
+ check_reg_desc(&ich_hfgitr_desc);
}
static bool idreg_feat_match(struct kvm *kvm, const struct reg_bits_to_feat_map *map)
@@ -1342,6 +1398,16 @@ void compute_fgu(struct kvm *kvm, enum fgt_group_id fgt)
val |= compute_reg_res0_bits(kvm, &hdfgwtr2_desc,
0, NEVER_FGU);
break;
+ case ICH_HFGRTR_GROUP:
+ val |= compute_reg_res0_bits(kvm, &ich_hfgrtr_desc,
+ 0, NEVER_FGU);
+ val |= compute_reg_res0_bits(kvm, &ich_hfgwtr_desc,
+ 0, NEVER_FGU);
+ break;
+ case ICH_HFGITR_GROUP:
+ val |= compute_reg_res0_bits(kvm, &ich_hfgitr_desc,
+ 0, NEVER_FGU);
+ break;
default:
BUG();
}
@@ -1425,6 +1491,18 @@ void get_reg_fixed_bits(struct kvm *kvm, enum vcpu_sysreg reg, u64 *res0, u64 *r
*res0 = compute_reg_res0_bits(kvm, &mdcr_el2_desc, 0, 0);
*res1 = MDCR_EL2_RES1;
break;
+ case ICH_HFGRTR_EL2:
+ *res0 = compute_reg_res0_bits(kvm, &ich_hfgrtr_desc, 0, 0);
+ *res1 = ICH_HFGRTR_EL2_RES1;
+ break;
+ case ICH_HFGWTR_EL2:
+ *res0 = compute_reg_res0_bits(kvm, &ich_hfgwtr_desc, 0, 0);
+ *res1 = ICH_HFGWTR_EL2_RES1;
+ break;
+ case ICH_HFGITR_EL2:
+ *res0 = compute_reg_res0_bits(kvm, &ich_hfgitr_desc, 0, 0);
+ *res1 = ICH_HFGITR_EL2_RES1;
+ break;
default:
WARN_ON_ONCE(1);
*res0 = *res1 = 0;
@@ -1457,6 +1535,12 @@ static __always_inline struct fgt_masks *__fgt_reg_to_masks(enum vcpu_sysreg reg
return &hdfgrtr2_masks;
case HDFGWTR2_EL2:
return &hdfgwtr2_masks;
+ case ICH_HFGRTR_EL2:
+ return &ich_hfgrtr_masks;
+ case ICH_HFGWTR_EL2:
+ return &ich_hfgwtr_masks;
+ case ICH_HFGITR_EL2:
+ return &ich_hfgitr_masks;
default:
BUILD_BUG_ON(1);
}
@@ -1501,7 +1585,7 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
{
if (!cpus_have_final_cap(ARM64_HAS_FGT))
- return;
+ goto skip_feat_fgt;
__compute_fgt(vcpu, HFGRTR_EL2);
__compute_hfgwtr(vcpu);
@@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
__compute_fgt(vcpu, HAFGRTR_EL2);
if (!cpus_have_final_cap(ARM64_HAS_FGT2))
- return;
+ goto skip_feat_fgt;
__compute_fgt(vcpu, HFGRTR2_EL2);
__compute_fgt(vcpu, HFGWTR2_EL2);
__compute_fgt(vcpu, HFGITR2_EL2);
__compute_fgt(vcpu, HDFGRTR2_EL2);
__compute_fgt(vcpu, HDFGWTR2_EL2);
+
+skip_feat_fgt:
+ if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+ return;
+
+ __compute_fgt(vcpu, ICH_HFGRTR_EL2);
+ __compute_fgt(vcpu, ICH_HFGWTR_EL2);
+ __compute_fgt(vcpu, ICH_HFGITR_EL2);
}
diff --git a/arch/arm64/kvm/emulate-nested.c b/arch/arm64/kvm/emulate-nested.c
index 75d49f83342a5..de316bdf90d46 100644
--- a/arch/arm64/kvm/emulate-nested.c
+++ b/arch/arm64/kvm/emulate-nested.c
@@ -2044,6 +2044,60 @@ static const struct encoding_to_trap_config encoding_to_fgt[] __initconst = {
SR_FGT(SYS_AMEVCNTR0_EL0(2), HAFGRTR, AMEVCNTR02_EL0, 1),
SR_FGT(SYS_AMEVCNTR0_EL0(1), HAFGRTR, AMEVCNTR01_EL0, 1),
SR_FGT(SYS_AMEVCNTR0_EL0(0), HAFGRTR, AMEVCNTR00_EL0, 1),
+
+ /*
+ * ICH_HFGRTR_EL2 & ICH_HFGWTR_EL2
+ */
+ SR_FGT(SYS_ICC_APR_EL1, ICH_HFGRTR, ICC_APR_EL1, 0),
+ SR_FGT(SYS_ICC_IDR0_EL1, ICH_HFGRTR, ICC_IDRn_EL1, 0),
+ SR_FGT(SYS_ICC_CR0_EL1, ICH_HFGRTR, ICC_CR0_EL1, 0),
+ SR_FGT(SYS_ICC_HPPIR_EL1, ICH_HFGRTR, ICC_HPPIR_EL1, 0),
+ SR_FGT(SYS_ICC_PCR_EL1, ICH_HFGRTR, ICC_PCR_EL1, 0),
+ SR_FGT(SYS_ICC_ICSR_EL1, ICH_HFGRTR, ICC_ICSR_EL1, 0),
+ SR_FGT(SYS_ICC_IAFFIDR_EL1, ICH_HFGRTR, ICC_IAFFIDR_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_HMR0_EL1, ICH_HFGRTR, ICC_PPI_HMRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_HMR1_EL1, ICH_HFGRTR, ICC_PPI_HMRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_ENABLER0_EL1, ICH_HFGRTR, ICC_PPI_ENABLERn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_ENABLER1_EL1, ICH_HFGRTR, ICC_PPI_ENABLERn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_CPENDR0_EL1, ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_CPENDR1_EL1, ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_SPENDR0_EL1, ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_SPENDR1_EL1, ICH_HFGRTR, ICC_PPI_PENDRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR0_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR1_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR2_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR3_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR4_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR5_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR6_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR7_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR8_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR9_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR10_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR11_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR12_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR13_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR14_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_PRIORITYR15_EL1, ICH_HFGRTR, ICC_PPI_PRIORITYRn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_CACTIVER0_EL1, ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_CACTIVER1_EL1, ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_SACTIVER0_EL1, ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+ SR_FGT(SYS_ICC_PPI_SACTIVER1_EL1, ICH_HFGRTR, ICC_PPI_ACTIVERn_EL1, 0),
+
+ /*
+ * ICH_HFGITR_EL2
+ */
+ SR_FGT(GICV5_OP_GIC_CDEN, ICH_HFGITR, GICCDEN, 0),
+ SR_FGT(GICV5_OP_GIC_CDDIS, ICH_HFGITR, GICCDDIS, 0),
+ SR_FGT(GICV5_OP_GIC_CDPRI, ICH_HFGITR, GICCDPRI, 0),
+ SR_FGT(GICV5_OP_GIC_CDAFF, ICH_HFGITR, GICCDAFF, 0),
+ SR_FGT(GICV5_OP_GIC_CDPEND, ICH_HFGITR, GICCDPEND, 0),
+ SR_FGT(GICV5_OP_GIC_CDRCFG, ICH_HFGITR, GICCDRCFG, 0),
+ SR_FGT(GICV5_OP_GIC_CDHM, ICH_HFGITR, GICCDHM, 0),
+ SR_FGT(GICV5_OP_GIC_CDEOI, ICH_HFGITR, GICCDEOI, 0),
+ SR_FGT(GICV5_OP_GIC_CDDI, ICH_HFGITR, GICCDDI, 0),
+ SR_FGT(GICV5_OP_GICR_CDIA, ICH_HFGITR, GICRCDIA, 0),
+ SR_FGT(GICV5_OP_GICR_CDNMIA, ICH_HFGITR, GICRCDNMIA, 0),
};
/*
@@ -2118,6 +2172,9 @@ FGT_MASKS(hfgwtr2_masks, HFGWTR2_EL2);
FGT_MASKS(hfgitr2_masks, HFGITR2_EL2);
FGT_MASKS(hdfgrtr2_masks, HDFGRTR2_EL2);
FGT_MASKS(hdfgwtr2_masks, HDFGWTR2_EL2);
+FGT_MASKS(ich_hfgrtr_masks, ICH_HFGRTR_EL2);
+FGT_MASKS(ich_hfgwtr_masks, ICH_HFGWTR_EL2);
+FGT_MASKS(ich_hfgitr_masks, ICH_HFGITR_EL2);
static __init bool aggregate_fgt(union trap_config tc)
{
@@ -2153,6 +2210,14 @@ static __init bool aggregate_fgt(union trap_config tc)
rmasks = &hfgitr2_masks;
wmasks = NULL;
break;
+ case ICH_HFGRTR_GROUP:
+ rmasks = &ich_hfgrtr_masks;
+ wmasks = &ich_hfgwtr_masks;
+ break;
+ case ICH_HFGITR_GROUP:
+ rmasks = &ich_hfgitr_masks;
+ wmasks = NULL;
+ break;
}
rresx = rmasks->res0 | rmasks->res1;
@@ -2223,6 +2288,9 @@ static __init int check_all_fgt_masks(int ret)
&hfgitr2_masks,
&hdfgrtr2_masks,
&hdfgwtr2_masks,
+ &ich_hfgrtr_masks,
+ &ich_hfgwtr_masks,
+ &ich_hfgitr_masks,
};
int err = 0;
diff --git a/arch/arm64/kvm/hyp/include/hyp/switch.h b/arch/arm64/kvm/hyp/include/hyp/switch.h
index c5d5e5b86eaf0..14805336725f5 100644
--- a/arch/arm64/kvm/hyp/include/hyp/switch.h
+++ b/arch/arm64/kvm/hyp/include/hyp/switch.h
@@ -235,6 +235,18 @@ static inline void __activate_traps_hfgxtr(struct kvm_vcpu *vcpu)
__activate_fgt(hctxt, vcpu, HDFGWTR2_EL2);
}
+static inline void __activate_traps_ich_hfgxtr(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+
+ if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+ return;
+
+ __activate_fgt(hctxt, vcpu, ICH_HFGRTR_EL2);
+ __activate_fgt(hctxt, vcpu, ICH_HFGWTR_EL2);
+ __activate_fgt(hctxt, vcpu, ICH_HFGITR_EL2);
+}
+
#define __deactivate_fgt(htcxt, vcpu, reg) \
do { \
write_sysreg_s(ctxt_sys_reg(hctxt, reg), \
@@ -267,6 +279,19 @@ static inline void __deactivate_traps_hfgxtr(struct kvm_vcpu *vcpu)
__deactivate_fgt(hctxt, vcpu, HDFGWTR2_EL2);
}
+static inline void __deactivate_traps_ich_hfgxtr(struct kvm_vcpu *vcpu)
+{
+ struct kvm_cpu_context *hctxt = host_data_ptr(host_ctxt);
+
+ if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
+ return;
+
+ __deactivate_fgt(hctxt, vcpu, ICH_HFGRTR_EL2);
+ __deactivate_fgt(hctxt, vcpu, ICH_HFGWTR_EL2);
+ __deactivate_fgt(hctxt, vcpu, ICH_HFGITR_EL2);
+
+}
+
static inline void __activate_traps_mpam(struct kvm_vcpu *vcpu)
{
u64 r = MPAM2_EL2_TRAPMPAM0EL1 | MPAM2_EL2_TRAPMPAM1EL1;
@@ -330,6 +355,7 @@ static inline void __activate_traps_common(struct kvm_vcpu *vcpu)
}
__activate_traps_hfgxtr(vcpu);
+ __activate_traps_ich_hfgxtr(vcpu);
__activate_traps_mpam(vcpu);
}
@@ -347,6 +373,7 @@ static inline void __deactivate_traps_common(struct kvm_vcpu *vcpu)
write_sysreg_s(ctxt_sys_reg(hctxt, HCRX_EL2), SYS_HCRX_EL2);
__deactivate_traps_hfgxtr(vcpu);
+ __deactivate_traps_ich_hfgxtr(vcpu);
__deactivate_traps_mpam();
}
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index d3b9ec8a7c283..c23e22ffac080 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -44,6 +44,9 @@ struct fgt_masks hfgwtr2_masks;
struct fgt_masks hfgitr2_masks;
struct fgt_masks hdfgrtr2_masks;
struct fgt_masks hdfgwtr2_masks;
+struct fgt_masks ich_hfgrtr_masks;
+struct fgt_masks ich_hfgwtr_masks;
+struct fgt_masks ich_hfgitr_masks;
extern void kvm_nvhe_prepare_backtrace(unsigned long fp, unsigned long pc);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index a065f8939bc8f..fbbd7b6ff6507 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -5630,6 +5630,8 @@ void kvm_calculate_traps(struct kvm_vcpu *vcpu)
compute_fgu(kvm, HFGRTR2_GROUP);
compute_fgu(kvm, HFGITR2_GROUP);
compute_fgu(kvm, HDFGRTR2_GROUP);
+ compute_fgu(kvm, ICH_HFGRTR_GROUP);
+ compute_fgu(kvm, ICH_HFGITR_GROUP);
set_bit(KVM_ARCH_FLAG_FGU_INITIALIZED, &kvm->arch.flags);
out:
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load
2025-12-12 15:22 ` [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load Sascha Bischoff
@ 2025-12-12 16:24 ` Marc Zyngier
2025-12-15 17:37 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-12 16:24 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:38 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> Extend the existing FGT infrastructure to calculate and activate any
> required GICv5 traps (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2, ICH_HFGITR_EL2)
> before entering the guest, and restore the original ICH_HFGxTR_EL2
> contents on the return path. This ensures that the host and guest
> behaviour remains independent.
>
> As of this change, none of the GICv5 instructions or register accesses
> are being trapped, but this will change in subsequent commits as some
> GICv5 system registers must always be trapped (ICC_IAFFIDR_EL1,
> ICH_PPI_HMRx_EL1).
nit: 90% of this patch has nothing to do with computing the FGTs at
load time. The gist of it is actually setting up the FGT
infrastructure, and activate/deactivate aspect is actually very
minor. You may want to reformulate the commit message to make that
clearer (I don't think this needs splitting though).
[...]
> @@ -1501,7 +1585,7 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
> void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> {
> if (!cpus_have_final_cap(ARM64_HAS_FGT))
> - return;
> + goto skip_feat_fgt;
How can you have GICv5, but not FGTs? I don't think this is a valid
construct as per the architecture:
(FEAT_GCIE ==> v9Ap3)
(FEAT_FGT ==> v8Ap5)
(v9Ap3 ==> (v9Ap2 && v8Ap8))
>
> __compute_fgt(vcpu, HFGRTR_EL2);
> __compute_hfgwtr(vcpu);
> @@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> __compute_fgt(vcpu, HAFGRTR_EL2);
>
> if (!cpus_have_final_cap(ARM64_HAS_FGT2))
> - return;
> + goto skip_feat_fgt;
Even FGT2 is expected, since v9.3 is congruent to v8.8:
(FEAT_FGT2 ==> v8Ap8)
>
> __compute_fgt(vcpu, HFGRTR2_EL2);
> __compute_fgt(vcpu, HFGWTR2_EL2);
> __compute_fgt(vcpu, HFGITR2_EL2);
> __compute_fgt(vcpu, HDFGRTR2_EL2);
> __compute_fgt(vcpu, HDFGWTR2_EL2);
> +
> +skip_feat_fgt:
> + if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
> + return;
> +
> + __compute_fgt(vcpu, ICH_HFGRTR_EL2);
> + __compute_fgt(vcpu, ICH_HFGWTR_EL2);
> + __compute_fgt(vcpu, ICH_HFGITR_EL2);
> }
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load
2025-12-12 16:24 ` Marc Zyngier
@ 2025-12-15 17:37 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-15 17:37 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Fri, 2025-12-12 at 16:24 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:38 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > Extend the existing FGT infrastructure to calculate and activate
> > any
> > required GICv5 traps (ICH_HFGRTR_EL2, ICH_HFGWTR_EL2,
> > ICH_HFGITR_EL2)
> > before entering the guest, and restore the original ICH_HFGxTR_EL2
> > contents on the return path. This ensures that the host and guest
> > behaviour remains independent.
> >
> > As of this change, none of the GICv5 instructions or register
> > accesses
> > are being trapped, but this will change in subsequent commits as
> > some
> > GICv5 system registers must always be trapped (ICC_IAFFIDR_EL1,
> > ICH_PPI_HMRx_EL1).
>
> nit: 90% of this patch has nothing to do with computing the FGTs at
> load time. The gist of it is actually setting up the FGT
> infrastructure, and activate/deactivate aspect is actually very
> minor. You may want to reformulate the commit message to make that
> clearer (I don't think this needs splitting though).
Good point. Have reworded the commit message accordingly.
>
> [...]
>
> > @@ -1501,7 +1585,7 @@ static void __compute_hdfgwtr(struct kvm_vcpu
> > *vcpu)
> > void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> > {
> > if (!cpus_have_final_cap(ARM64_HAS_FGT))
> > - return;
> > + goto skip_feat_fgt;
>
> How can you have GICv5, but not FGTs? I don't think this is a valid
> construct as per the architecture:
>
> (FEAT_GCIE ==> v9Ap3)
> (FEAT_FGT ==> v8Ap5)
> (v9Ap3 ==> (v9Ap2 && v8Ap8))
OK, agreed. FEAT_FGT is mandatory from v8.6. Have dropped this first
skip as we'll certainly have it if we have GICv5.
>
> >
> > __compute_fgt(vcpu, HFGRTR_EL2);
> > __compute_hfgwtr(vcpu);
> > @@ -1511,11 +1595,19 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu
> > *vcpu)
> > __compute_fgt(vcpu, HAFGRTR_EL2);
> >
> > if (!cpus_have_final_cap(ARM64_HAS_FGT2))
> > - return;
> > + goto skip_feat_fgt;
>
> Even FGT2 is expected, since v9.3 is congruent to v8.8:
>
> (FEAT_FGT2 ==> v8Ap8)
FEAT_FGT2 is optional from v8.8 (v9.3), and mandatory from v8.9 (v9.4).
This means we could have a GICv5 system without FEAT_FGT2; I'll leave
in the second skip.
Thanks,
Sascha
>
> >
> > __compute_fgt(vcpu, HFGRTR2_EL2);
> > __compute_fgt(vcpu, HFGWTR2_EL2);
> > __compute_fgt(vcpu, HFGITR2_EL2);
> > __compute_fgt(vcpu, HDFGRTR2_EL2);
> > __compute_fgt(vcpu, HDFGWTR2_EL2);
> > +
> > +skip_feat_fgt:
> > + if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
> > + return;
> > +
> > + __compute_fgt(vcpu, ICH_HFGRTR_EL2);
> > + __compute_fgt(vcpu, ICH_HFGWTR_EL2);
> > + __compute_fgt(vcpu, ICH_HFGITR_EL2);
> > }
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (8 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 09/32] KVM: arm64: gic-v5: Compute GICv5 FGTs on vcpu load Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-15 17:31 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 12/32] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
` (21 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
GICv5 doesn't include an ICV_IAFFIDR_EL1 or ICH_IAFFIDR_EL2 for
providing the IAFFID to the guest. A guest access to the
ICH_IAFFIDR_EL1 must therefore be trapped and emulated to avoid the
guest accessing the host's ICC_IAFFIDR_EL1.
For GICv5, the VPE ID corresponds to the virtual IAFFID for the
purposes of specifying the affinity of virtual interrupts. The VPE ID
is the index into the VPE Table, which will be the same as the
vcpu->vcpu_id once the various GICv5 VM tables are introduced. At this
stage, said VM tables have yet to be introduced as they are not
required for PPI support. Moreover, the IAFFID should go largely
unused by any guest using just PPIs as they are not routable to a
different PE. That said, we still need to trap and emulate the guest's
accesses to avoid leaking host state into the guest.
The virtual IAFFID is provided to the guest when it reads
ICC_IAFFID_EL1 (which always traps back to the hypervisor). Writes are
rightly ignored.
The trapping for the ICC_IAFFIDR_EL2 is always enabled when in a guest
context.
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>
---
arch/arm64/kvm/config.c | 10 +++++++++-
arch/arm64/kvm/sys_regs.c | 19 +++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 57ef67f718113..cbdd8ac90f4d0 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1582,6 +1582,14 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
*vcpu_fgt(vcpu, HDFGWTR_EL2) |= HDFGWTR_EL2_MDSCR_EL1;
}
+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;
+}
+
void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
{
if (!cpus_have_final_cap(ARM64_HAS_FGT))
@@ -1607,7 +1615,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
return;
- __compute_fgt(vcpu, ICH_HFGRTR_EL2);
+ __compute_ich_hfgrtr(vcpu);
__compute_fgt(vcpu, ICH_HFGWTR_EL2);
__compute_fgt(vcpu, ICH_HFGITR_EL2);
}
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index fbbd7b6ff6507..31c08fd591d08 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -681,6 +681,24 @@ static bool access_gic_dir(struct kvm_vcpu *vcpu,
return true;
}
+static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (!vgic_is_v5(vcpu->kvm))
+ return undef_access(vcpu, p, r);
+
+ if (p->is_write)
+ return ignore_write(vcpu, p);
+
+ /*
+ * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
+ * is the same as the VCPU's ID.
+ */
+ p->regval = FIELD_PREP(ICC_IAFFIDR_EL1_IAFFID, vcpu->vcpu_id);
+
+ return true;
+}
+
static bool trap_raz_wi(struct kvm_vcpu *vcpu,
struct sys_reg_params *p,
const struct sys_reg_desc *r)
@@ -3411,6 +3429,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_IAFFIDR_EL1), access_gicv5_iaffid },
{ 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
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses
2025-12-12 15:22 ` [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses Sascha Bischoff
@ 2025-12-15 17:31 ` Marc Zyngier
2025-12-16 10:57 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-15 17:31 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:38 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> GICv5 doesn't include an ICV_IAFFIDR_EL1 or ICH_IAFFIDR_EL2 for
> providing the IAFFID to the guest. A guest access to the
> ICH_IAFFIDR_EL1 must therefore be trapped and emulated to avoid the
nit: ICC_IAFFIDR_EL1. There is no ICH_*_EL1 register.
> guest accessing the host's ICC_IAFFIDR_EL1.
>
> For GICv5, the VPE ID corresponds to the virtual IAFFID for the
> purposes of specifying the affinity of virtual interrupts. The VPE ID
> is the index into the VPE Table, which will be the same as the
> vcpu->vcpu_id once the various GICv5 VM tables are introduced. At this
> stage, said VM tables have yet to be introduced as they are not
> required for PPI support. Moreover, the IAFFID should go largely
> unused by any guest using just PPIs as they are not routable to a
> different PE. That said, we still need to trap and emulate the guest's
> accesses to avoid leaking host state into the guest.
I think you can trim some of this. Just state that KVM makes the
IAFFIDR, VPEID and vcpu_id the same thing, and that'll be good enough.
>
> The virtual IAFFID is provided to the guest when it reads
> ICC_IAFFID_EL1 (which always traps back to the hypervisor). Writes are
> rightly ignored.
>
> The trapping for the ICC_IAFFIDR_EL2 is always enabled when in a guest
> context.
This register doesn't exist either.
>
> 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>
> ---
> arch/arm64/kvm/config.c | 10 +++++++++-
> arch/arm64/kvm/sys_regs.c | 19 +++++++++++++++++++
> 2 files changed, 28 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index 57ef67f718113..cbdd8ac90f4d0 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c
> @@ -1582,6 +1582,14 @@ static void __compute_hdfgwtr(struct kvm_vcpu *vcpu)
> *vcpu_fgt(vcpu, HDFGWTR_EL2) |= HDFGWTR_EL2_MDSCR_EL1;
> }
>
> +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;
Slightly redundant when !GICv5 in the guest, but that's not really a
problem.
> +}
> +
> void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> {
> if (!cpus_have_final_cap(ARM64_HAS_FGT))
> @@ -1607,7 +1615,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
> return;
>
> - __compute_fgt(vcpu, ICH_HFGRTR_EL2);
> + __compute_ich_hfgrtr(vcpu);
> __compute_fgt(vcpu, ICH_HFGWTR_EL2);
> __compute_fgt(vcpu, ICH_HFGITR_EL2);
> }
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index fbbd7b6ff6507..31c08fd591d08 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -681,6 +681,24 @@ static bool access_gic_dir(struct kvm_vcpu *vcpu,
> return true;
> }
>
> +static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> + const struct sys_reg_desc *r)
> +{
> + if (!vgic_is_v5(vcpu->kvm))
> + return undef_access(vcpu, p, r);
Shouldn't this be readily handled by the FGU configuration in the
absence of GICv5 in the guest?
> +
> + if (p->is_write)
> + return ignore_write(vcpu, p);
> +
> + /*
> + * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
> + * is the same as the VCPU's ID.
> + */
> + p->regval = FIELD_PREP(ICC_IAFFIDR_EL1_IAFFID, vcpu->vcpu_id);
> +
> + return true;
> +}
> +
> static bool trap_raz_wi(struct kvm_vcpu *vcpu,
> struct sys_reg_params *p,
> const struct sys_reg_desc *r)
> @@ -3411,6 +3429,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_IAFFIDR_EL1), access_gicv5_iaffid },
> { 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 },
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses
2025-12-15 17:31 ` Marc Zyngier
@ 2025-12-16 10:57 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-16 10:57 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 17:31 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:38 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > GICv5 doesn't include an ICV_IAFFIDR_EL1 or ICH_IAFFIDR_EL2 for
> > providing the IAFFID to the guest. A guest access to the
> > ICH_IAFFIDR_EL1 must therefore be trapped and emulated to avoid the
>
> nit: ICC_IAFFIDR_EL1. There is no ICH_*_EL1 register.
>
> > guest accessing the host's ICC_IAFFIDR_EL1.
> >
> > For GICv5, the VPE ID corresponds to the virtual IAFFID for the
> > purposes of specifying the affinity of virtual interrupts. The VPE
> > ID
> > is the index into the VPE Table, which will be the same as the
> > vcpu->vcpu_id once the various GICv5 VM tables are introduced. At
> > this
> > stage, said VM tables have yet to be introduced as they are not
> > required for PPI support. Moreover, the IAFFID should go largely
> > unused by any guest using just PPIs as they are not routable to a
> > different PE. That said, we still need to trap and emulate the
> > guest's
> > accesses to avoid leaking host state into the guest.
>
> I think you can trim some of this. Just state that KVM makes the
> IAFFIDR, VPEID and vcpu_id the same thing, and that'll be good
> enough.
>
> >
> > The virtual IAFFID is provided to the guest when it reads
> > ICC_IAFFID_EL1 (which always traps back to the hypervisor). Writes
> > are
> > rightly ignored.
> >
> > The trapping for the ICC_IAFFIDR_EL2 is always enabled when in a
> > guest
> > context.
>
> This register doesn't exist either.
>
Yikes. I really wasn't having a good day with my register names. I've
addressed these now and have compacted down that paragraph.
> >
> > 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>
> > ---
> > arch/arm64/kvm/config.c | 10 +++++++++-
> > arch/arm64/kvm/sys_regs.c | 19 +++++++++++++++++++
> > 2 files changed, 28 insertions(+), 1 deletion(-)
> >
> > diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> > index 57ef67f718113..cbdd8ac90f4d0 100644
> > --- a/arch/arm64/kvm/config.c
> > +++ b/arch/arm64/kvm/config.c
> > @@ -1582,6 +1582,14 @@ static void __compute_hdfgwtr(struct
> > kvm_vcpu *vcpu)
> > *vcpu_fgt(vcpu, HDFGWTR_EL2) |=
> > HDFGWTR_EL2_MDSCR_EL1;
> > }
> >
> > +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;
>
> Slightly redundant when !GICv5 in the guest, but that's not really a
> problem.
>
> > +}
> > +
> > void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> > {
> > if (!cpus_have_final_cap(ARM64_HAS_FGT))
> > @@ -1607,7 +1615,7 @@ void kvm_vcpu_load_fgt(struct kvm_vcpu *vcpu)
> > if (!cpus_have_final_cap(ARM64_HAS_GICV5_CPUIF))
> > return;
> >
> > - __compute_fgt(vcpu, ICH_HFGRTR_EL2);
> > + __compute_ich_hfgrtr(vcpu);
> > __compute_fgt(vcpu, ICH_HFGWTR_EL2);
> > __compute_fgt(vcpu, ICH_HFGITR_EL2);
> > }
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index fbbd7b6ff6507..31c08fd591d08 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -681,6 +681,24 @@ static bool access_gic_dir(struct kvm_vcpu
> > *vcpu,
> > return true;
> > }
> >
> > +static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct
> > sys_reg_params *p,
> > + const struct sys_reg_desc *r)
> > +{
> > + if (!vgic_is_v5(vcpu->kvm))
> > + return undef_access(vcpu, p, r);
>
> Shouldn't this be readily handled by the FGU configuration in the
> absence of GICv5 in the guest?
>
Yeah, it should be. This was written before GICv5 was plumbed into the
FGUs. I've just tested it, and it undefs correctly without the explicit
check, so I've dropped this.
Thanks,
Sascha
> > +
> > + if (p->is_write)
> > + return ignore_write(vcpu, p);
> > +
> > + /*
> > + * For GICv5 VMs, the IAFFID value is the same as the VPE
> > ID. The VPE ID
> > + * is the same as the VCPU's ID.
> > + */
> > + p->regval = FIELD_PREP(ICC_IAFFIDR_EL1_IAFFID, vcpu-
> > >vcpu_id);
> > +
> > + return true;
> > +}
> > +
> > static bool trap_raz_wi(struct kvm_vcpu *vcpu,
> > struct sys_reg_params *p,
> > const struct sys_reg_desc *r)
> > @@ -3411,6 +3429,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_IAFFIDR_EL1), access_gicv5_iaffid },
> > { 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 },
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 12/32] KVM: arm64: gic: Set vgic_model before initing private IRQs
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (9 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 10/32] KVM: arm64: gic-v5: Add emulation for ICC_IAFFID_EL1 accesses Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses Sascha Bischoff
` (20 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Different GIC types require the private IRQs to be initialised
differently. GICv5 is the culprit as it supports both a different
number of private IRQs, and all of these are PPIs (there are no
SGIs). Moreover, as GICv5 uses the top bits of the interrupt ID to
encode the type, the intid also needs to computed differently.
Up until now, the GIC model has been set after initialising the
private IRQs for a VCPU. Move this earlier to ensure that the GIC
model is available when configuring the private IRQs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index dc9f9db310264..b246cb6eae71b 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -140,6 +140,12 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
goto out_unlock;
}
+ kvm->arch.vgic.in_kernel = true;
+ kvm->arch.vgic.vgic_model = type;
+ kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;
+
+ kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
+
kvm_for_each_vcpu(i, vcpu, kvm) {
ret = vgic_allocate_private_irqs_locked(vcpu, type);
if (ret)
@@ -156,12 +162,6 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
goto out_unlock;
}
- kvm->arch.vgic.in_kernel = true;
- kvm->arch.vgic.vgic_model = type;
- kvm->arch.vgic.implementation_rev = KVM_VGIC_IMP_REV_LATEST;
-
- kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
-
aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) & ~ID_AA64PFR0_EL1_GIC;
pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) & ~ID_PFR1_EL1_GIC;
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (10 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 12/32] KVM: arm64: gic: Set vgic_model before initing private IRQs Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-16 10:41 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
` (19 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
Level-sensitive semantics, and which use Edge. For a GICv5 guest, the
correct view of the virtual PPIs must be provided to the guest.
The GICv5 architecture doesn't provide an ICV_PPI_HMRx_EL1 or
ICH_PPI_HMRx_EL2 register, and therefore all guest accesses must be
trapped to avoid the guest directly accessing the host's
ICC_PPI_HMRx_EL1 state. This change hence configures the FGTs to
always trap and emulate guest accesses to the HMR running a
GICv5-based guest.
This change also introduces the struct vgic_v5_cpu_if, which includes
the vgic_hmr. This is not yet populated as it can only be correctly
populated at vcpu reset time. This will be introduced in a subsquent
change.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/config.c | 6 +++++-
arch/arm64/kvm/sys_regs.c | 26 ++++++++++++++++++++++++++
include/kvm/arm_vgic.h | 5 +++++
3 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index cbdd8ac90f4d0..7683407ce052a 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -1586,8 +1586,12 @@ 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 and ICH_PPI_HMRx_EL1 *always* needs to be
+ * trapped when running a guest.
+ **/
*vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
+ *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_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 31c08fd591d08..a4ae034340040 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -699,6 +699,30 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
return true;
}
+static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+ const struct sys_reg_desc *r)
+{
+ if (!vgic_is_v5(vcpu->kvm))
+ return undef_access(vcpu, p, r);
+
+ if (p->is_write)
+ return ignore_write(vcpu, p);
+
+ /*
+ * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
+ * is the same as the VCPU's ID.
+ */
+
+ if (p->Op2 == 0) { /* ICC_PPI_HMR0_EL1 */
+ p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
+ } else { /* ICC_PPI_HMR1_EL1 */
+ p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
+ }
+
+ return true;
+}
+
+
static bool trap_raz_wi(struct kvm_vcpu *vcpu,
struct sys_reg_params *p,
const struct sys_reg_desc *r)
@@ -3429,6 +3453,8 @@ 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_PPI_HMR0_EL1), access_gicv5_ppi_hmr },
+ { SYS_DESC(SYS_ICC_PPI_HMR1_EL1), access_gicv5_ppi_hmr },
{ 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 },
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index be1f45a494f78..fbbaef4ad2114 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -358,11 +358,16 @@ struct vgic_v3_cpu_if {
unsigned int used_lrs;
};
+struct vgic_v5_cpu_if {
+ u64 vgic_ppi_hmr[2];
+};
+
struct vgic_cpu {
/* CPU vif control registers for world switch */
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;
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses
2025-12-12 15:22 ` [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses Sascha Bischoff
@ 2025-12-16 10:41 ` Marc Zyngier
2025-12-16 11:54 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-16 10:41 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:39 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
> Level-sensitive semantics, and which use Edge. For a GICv5 guest, the
> correct view of the virtual PPIs must be provided to the guest.
s/ICH/ICC/ in $SUBJECT
>
> The GICv5 architecture doesn't provide an ICV_PPI_HMRx_EL1 or
The spec disagree with you here (see 9.5.4).
> ICH_PPI_HMRx_EL2 register, and therefore all guest accesses must be
> trapped to avoid the guest directly accessing the host's
> ICC_PPI_HMRx_EL1 state. This change hence configures the FGTs to
> always trap and emulate guest accesses to the HMR running a
> GICv5-based guest.
The real question is what we gain by emulating this register, given
that virtual PPIs are only guaranteed to exist if the physical version
exist. If they exist, then the handling mode is defined by the
that HW, and we can't deviate from it.
Given that, I can't really see the point in trapping something that is
bound to be the same thing as the host, unless this comes with
additional restrictions, for example a mask of interrupts that are
actually exposed to the guest.
Or am I missing something?
>
> This change also introduces the struct vgic_v5_cpu_if, which includes
> the vgic_hmr. This is not yet populated as it can only be correctly
> populated at vcpu reset time. This will be introduced in a subsquent
> change.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/config.c | 6 +++++-
> arch/arm64/kvm/sys_regs.c | 26 ++++++++++++++++++++++++++
> include/kvm/arm_vgic.h | 5 +++++
> 3 files changed, 36 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index cbdd8ac90f4d0..7683407ce052a 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c
> @@ -1586,8 +1586,12 @@ 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 and ICH_PPI_HMRx_EL1 *always* needs to be
> + * trapped when running a guest.
> + **/
> *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
> + *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &= ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_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 31c08fd591d08..a4ae034340040 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -699,6 +699,30 @@ static bool access_gicv5_iaffid(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> return true;
> }
>
> +static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> + const struct sys_reg_desc *r)
> +{
> + if (!vgic_is_v5(vcpu->kvm))
> + return undef_access(vcpu, p, r);
> +
> + if (p->is_write)
> + return ignore_write(vcpu, p);
> +
> + /*
> + * For GICv5 VMs, the IAFFID value is the same as the VPE ID. The VPE ID
> + * is the same as the VCPU's ID.
> + */
Unrelated comment?
> +
> + if (p->Op2 == 0) { /* ICC_PPI_HMR0_EL1 */
> + p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
> + } else { /* ICC_PPI_HMR1_EL1 */
> + p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
> + }
nit: Can probably be written as:
p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[p->Op2];
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses
2025-12-16 10:41 ` Marc Zyngier
@ 2025-12-16 11:54 ` Sascha Bischoff
2025-12-16 15:09 ` Marc Zyngier
0 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-16 11:54 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Tue, 2025-12-16 at 10:41 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:39 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
> > Level-sensitive semantics, and which use Edge. For a GICv5 guest,
> > the
> > correct view of the virtual PPIs must be provided to the guest.
>
> s/ICH/ICC/ in $SUBJECT
>
> >
> > The GICv5 architecture doesn't provide an ICV_PPI_HMRx_EL1 or
>
> The spec disagree with you here (see 9.5.4).
>
> > ICH_PPI_HMRx_EL2 register, and therefore all guest accesses must be
> > trapped to avoid the guest directly accessing the host's
> > ICC_PPI_HMRx_EL1 state. This change hence configures the FGTs to
> > always trap and emulate guest accesses to the HMR running a
> > GICv5-based guest.
>
> The real question is what we gain by emulating this register, given
> that virtual PPIs are only guaranteed to exist if the physical
> version
> exist. If they exist, then the handling mode is defined by the
> that HW, and we can't deviate from it.
>
> Given that, I can't really see the point in trapping something that
> is
> bound to be the same thing as the host, unless this comes with
> additional restrictions, for example a mask of interrupts that are
> actually exposed to the guest.
>
> Or am I missing something?
No, I think you're quite correct, and this doesn't add meaningful
value.
This all stems from my misunderstanding that GICv5 vPPIs are
independent from the physical PPIs. This is not the case, however, as
the set of implemented virtual PPIs matches the physically implemented
PPIs. The handling mode for each PPI will be reflected in the
ICC/ICV_PPI_HMRx_EL1 sysregs.
This actually has wider impacts:
1. It makes sense to drop this commit altogether.
2. When initialising the GICv5 PPIs ("KVM: arm64: gic-v5: Init
Private IRQs (PPIs) for GICv5"), we skip setting their config
(LEVEL/EDGE).
3. In vgic_v5_reset ("KVM: arm64: gic-v5: Reset vcpu state"), sync
the host's PPI HMR state (ICC_PPI_HMRx_EL1) to KVM's vPPI shadow
state as the virtual PPIs should match that, and we need that to
correctly handle SW-driven PPI injection. Currently, this code
actually calculates the HMR contents for trapping and emulating,
which again can be dropped altogether.
4. vgic_hmr can be dropped from the vgic_v5 CPUIF too.
Does this sound reasonable to you?
Thanks,
Sascha
>
> >
> > This change also introduces the struct vgic_v5_cpu_if, which
> > includes
> > the vgic_hmr. This is not yet populated as it can only be correctly
> > populated at vcpu reset time. This will be introduced in a
> > subsquent
> > change.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > arch/arm64/kvm/config.c | 6 +++++-
> > arch/arm64/kvm/sys_regs.c | 26 ++++++++++++++++++++++++++
> > include/kvm/arm_vgic.h | 5 +++++
> > 3 files changed, 36 insertions(+), 1 deletion(-)
> >
> > diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> > index cbdd8ac90f4d0..7683407ce052a 100644
> > --- a/arch/arm64/kvm/config.c
> > +++ b/arch/arm64/kvm/config.c
> > @@ -1586,8 +1586,12 @@ 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 and ICH_PPI_HMRx_EL1 *always* needs to
> > be
> > + * trapped when running a guest.
> > + **/
> > *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &=
> > ~ICH_HFGRTR_EL2_ICC_IAFFIDR_EL1;
> > + *vcpu_fgt(vcpu, ICH_HFGRTR_EL2) &=
> > ~ICH_HFGRTR_EL2_ICC_PPI_HMRn_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 31c08fd591d08..a4ae034340040 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -699,6 +699,30 @@ static bool access_gicv5_iaffid(struct
> > kvm_vcpu *vcpu, struct sys_reg_params *p,
> > return true;
> > }
> >
> > +static bool access_gicv5_ppi_hmr(struct kvm_vcpu *vcpu, struct
> > sys_reg_params *p,
> > + const struct sys_reg_desc *r)
> > +{
> > + if (!vgic_is_v5(vcpu->kvm))
> > + return undef_access(vcpu, p, r);
> > +
> > + if (p->is_write)
> > + return ignore_write(vcpu, p);
> > +
> > + /*
> > + * For GICv5 VMs, the IAFFID value is the same as the VPE
> > ID. The VPE ID
> > + * is the same as the VCPU's ID.
> > + */
>
> Unrelated comment?
>
> > +
> > + if (p->Op2 == 0) { /* ICC_PPI_HMR0_EL1 */
> > + p->regval = vcpu-
> > >arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0];
> > + } else { /* ICC_PPI_HMR1_EL1 */
> > + p->regval = vcpu-
> > >arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1];
> > + }
>
> nit: Can probably be written as:
>
> p->regval = vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[p-
> >Op2];
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses
2025-12-16 11:54 ` Sascha Bischoff
@ 2025-12-16 15:09 ` Marc Zyngier
0 siblings, 0 replies; 70+ messages in thread
From: Marc Zyngier @ 2025-12-16 15:09 UTC (permalink / raw)
To: Sascha Bischoff
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Tue, 16 Dec 2025 11:54:59 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> On Tue, 2025-12-16 at 10:41 +0000, Marc Zyngier wrote:
> > On Fri, 12 Dec 2025 15:22:39 +0000,
> > Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> > >
> > > The ICC_PPI_HMRx_EL1 register is used to determine which PPIs use
> > > Level-sensitive semantics, and which use Edge. For a GICv5 guest,
> > > the
> > > correct view of the virtual PPIs must be provided to the guest.
> >
> > s/ICH/ICC/ in $SUBJECT
> >
> > >
> > > The GICv5 architecture doesn't provide an ICV_PPI_HMRx_EL1 or
> >
> > The spec disagree with you here (see 9.5.4).
> >
> > > ICH_PPI_HMRx_EL2 register, and therefore all guest accesses must be
> > > trapped to avoid the guest directly accessing the host's
> > > ICC_PPI_HMRx_EL1 state. This change hence configures the FGTs to
> > > always trap and emulate guest accesses to the HMR running a
> > > GICv5-based guest.
> >
> > The real question is what we gain by emulating this register, given
> > that virtual PPIs are only guaranteed to exist if the physical
> > version
> > exist. If they exist, then the handling mode is defined by the
> > that HW, and we can't deviate from it.
> >
> > Given that, I can't really see the point in trapping something that
> > is
> > bound to be the same thing as the host, unless this comes with
> > additional restrictions, for example a mask of interrupts that are
> > actually exposed to the guest.
> >
> > Or am I missing something?
>
> No, I think you're quite correct, and this doesn't add meaningful
> value.
>
> This all stems from my misunderstanding that GICv5 vPPIs are
> independent from the physical PPIs. This is not the case, however, as
> the set of implemented virtual PPIs matches the physically implemented
> PPIs. The handling mode for each PPI will be reflected in the
> ICC/ICV_PPI_HMRx_EL1 sysregs.
>
> This actually has wider impacts:
>
> 1. It makes sense to drop this commit altogether.
>
> 2. When initialising the GICv5 PPIs ("KVM: arm64: gic-v5: Init
> Private IRQs (PPIs) for GICv5"), we skip setting their config
> (LEVEL/EDGE).
>
> 3. In vgic_v5_reset ("KVM: arm64: gic-v5: Reset vcpu state"), sync
> the host's PPI HMR state (ICC_PPI_HMRx_EL1) to KVM's vPPI shadow
> state as the virtual PPIs should match that, and we need that to
> correctly handle SW-driven PPI injection. Currently, this code
> actually calculates the HMR contents for trapping and emulating,
> which again can be dropped altogether.
>
> 4. vgic_hmr can be dropped from the vgic_v5 CPUIF too.
>
> Does this sound reasonable to you?
It does, to some extent. The one thing I have been thinking about is
how to hide PPIs that are implemented by the host, but not exposed to
the guest.
For that, I think we need a mask of PPIs that the kernel deals with
(timers, PMU, SPE one day), and use it to sanitise the HMR. For that,
we still need to trap these registers.
But it then begs the question: what does it mean for userspace
injection of PPIs? Do we still allow it? How does userspace discover
the implemented PPIs? How does userspace tells us *in advance* about
that so that we can affect the above mask?
At this stage, I'm tempted to say "screw that, userspace doesn't get
to touch PPIs -- at least not for now".
Thoughts?
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (11 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 11/32] KVM: arm64: gic-v5: Trap and emulate ICH_PPI_HMRx_EL1 accesses Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 11:07 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
` (18 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Introduce hyp functions to save/restore the following GICv5 state:
* ICC_ICSR_EL1
* ICH_APR_EL2
* ICH_PPI_ACTIVERx_EL2
* ICH_PPI_DVIRx_EL2
* ICH_PPI_ENABLERx_EL2
* ICH_PPI_PENDRRx_EL2
* ICH_PPI_PRIORITYRx_EL2
* ICH_VMCR_EL2
All of these are saved/restored to/from the KVM vgic_v5 CPUIF shadow
state.
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 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>
---
arch/arm64/include/asm/kvm_asm.h | 4 +
arch/arm64/include/asm/kvm_hyp.h | 8 ++
arch/arm64/kvm/hyp/nvhe/Makefile | 2 +-
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 32 ++++++
arch/arm64/kvm/hyp/vgic-v5.c | 155 +++++++++++++++++++++++++++++
arch/arm64/kvm/hyp/vhe/Makefile | 2 +-
include/kvm/arm_vgic.h | 28 ++++++
7 files changed, 229 insertions(+), 2 deletions(-)
create mode 100644 arch/arm64/kvm/hyp/vgic-v5.c
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index a1ad12c72ebf1..5f669299fb956 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -89,6 +89,10 @@ 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_vmcr_aprs,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_aprs,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_save_ppi_state,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_ppi_state,
};
#define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 76ce2b94bd97e..f6cf59a719ac6 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -87,6 +87,14 @@ 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_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_restore_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
+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_icsr(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_restore_icsr(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);
diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile
index a244ec25f8c5b..d860fbe9bc476 100644
--- a/arch/arm64/kvm/hyp/nvhe/Makefile
+++ b/arch/arm64/kvm/hyp/nvhe/Makefile
@@ -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.o
hyp-obj-y += ../../../kernel/smccc-call.o
hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
hyp-obj-y += $(lib-objs)
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index a7c689152f686..6bc5a4f75fd01 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -586,6 +586,34 @@ 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_vmcr_aprs(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_save_vmcr_aprs(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_restore_vmcr_aprs(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_restore_vmcr_aprs(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_save_ppi_state(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_save_ppi_state(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_restore_ppi_state(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_restore_ppi_state(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
@@ -627,6 +655,10 @@ 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_vmcr_aprs),
+ HANDLE_FUNC(__vgic_v5_restore_vmcr_aprs),
+ HANDLE_FUNC(__vgic_v5_save_ppi_state),
+ HANDLE_FUNC(__vgic_v5_restore_ppi_state),
};
static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
diff --git a/arch/arm64/kvm/hyp/vgic-v5.c b/arch/arm64/kvm/hyp/vgic-v5.c
new file mode 100644
index 0000000000000..11b67ae09e326
--- /dev/null
+++ b/arch/arm64/kvm/hyp/vgic-v5.c
@@ -0,0 +1,155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 - ARM Ltd
+ */
+
+#include <hyp/adjust_pc.h>
+
+#include <linux/compiler.h>
+#include <linux/irqchip/arm-gic-v5.h>
+#include <linux/kvm_host.h>
+
+#include <asm/kvm_emulate.h>
+#include <asm/kvm_hyp.h>
+#include <asm/kvm_mmu.h>
+
+void __vgic_v5_save_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if)
+{
+ cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2);
+ 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_aprs(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)
+{
+ cpu_if->vgic_ppi_activer_exit[0] =
+ read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
+ cpu_if->vgic_ppi_activer_exit[1] =
+ read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);
+
+ cpu_if->vgic_ich_ppi_enabler_exit[0] =
+ read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
+ cpu_if->vgic_ich_ppi_enabler_exit[1] =
+ read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
+
+ cpu_if->vgic_ppi_pendr_exit[0] = read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
+ cpu_if->vgic_ppi_pendr_exit[1] = read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);
+
+ 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);
+ 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)
+{
+ /* Now enable DVI so that the guest's interrupt config takes over */
+ write_sysreg_s(cpu_if->vgic_ppi_dvir[0], SYS_ICH_PPI_DVIR0_EL2);
+ write_sysreg_s(cpu_if->vgic_ppi_dvir[1], SYS_ICH_PPI_DVIR1_EL2);
+
+ write_sysreg_s(cpu_if->vgic_ppi_activer_entry[0],
+ SYS_ICH_PPI_ACTIVER0_EL2);
+ write_sysreg_s(cpu_if->vgic_ppi_activer_entry[1],
+ SYS_ICH_PPI_ACTIVER1_EL2);
+
+ write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[0],
+ SYS_ICH_PPI_ENABLER0_EL2);
+ write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[1],
+ SYS_ICH_PPI_ENABLER1_EL2);
+
+ /* Update the pending state of the NON-DVI'd PPIs, only */
+ write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[0] &
+ ~cpu_if->vgic_ppi_dvir[0],
+ SYS_ICH_PPI_PENDR0_EL2);
+ write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[1] &
+ ~cpu_if->vgic_ppi_dvir[1],
+ SYS_ICH_PPI_PENDR1_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);
+ 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);
+}
+
+void __vgic_v5_save_icsr(struct vgic_v5_cpu_if *cpu_if)
+{
+ cpu_if->vgic_icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
+}
+
+void __vgic_v5_restore_icsr(struct vgic_v5_cpu_if *cpu_if)
+{
+ write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
+}
diff --git a/arch/arm64/kvm/hyp/vhe/Makefile b/arch/arm64/kvm/hyp/vhe/Makefile
index afc4aed9231ac..fcf5e68ab591c 100644
--- a/arch/arm64/kvm/hyp/vhe/Makefile
+++ b/arch/arm64/kvm/hyp/vhe/Makefile
@@ -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.o
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index fbbaef4ad2114..525c8b83e83c9 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -359,7 +359,35 @@ struct vgic_v3_cpu_if {
};
struct vgic_v5_cpu_if {
+ u64 vgic_apr;
+ u64 vgic_vmcr;
+
+ /* PPI register state */
u64 vgic_ppi_hmr[2];
+ u64 vgic_ppi_dvir[2];
+ u64 vgic_ppi_priorityr[16];
+
+ /* The pending state of the guest. This is merged with the exit state */
+ u64 vgic_ppi_pendr[2];
+
+ /* The state flushed to the regs when entering the guest */
+ u64 vgic_ppi_activer_entry[2];
+ u64 vgic_ich_ppi_enabler_entry[2];
+ u64 vgic_ppi_pendr_entry[2];
+
+ /* The saved state of the regs when leaving the guest */
+ u64 vgic_ppi_activer_exit[2];
+ u64 vgic_ich_ppi_enabler_exit[2];
+ u64 vgic_ppi_pendr_exit[2];
+
+ /*
+ * 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;
};
struct vgic_cpu {
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
2025-12-12 15:22 ` [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
@ 2025-12-17 11:07 ` Marc Zyngier
2025-12-17 21:42 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 11:07 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:39 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> Introduce hyp functions to save/restore the following GICv5 state:
>
> * ICC_ICSR_EL1
> * ICH_APR_EL2
> * ICH_PPI_ACTIVERx_EL2
> * ICH_PPI_DVIRx_EL2
> * ICH_PPI_ENABLERx_EL2
> * ICH_PPI_PENDRRx_EL2
> * ICH_PPI_PRIORITYRx_EL2
> * ICH_VMCR_EL2
>
> All of these are saved/restored to/from the KVM vgic_v5 CPUIF shadow
> state.
>
> 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 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>
> ---
> arch/arm64/include/asm/kvm_asm.h | 4 +
> arch/arm64/include/asm/kvm_hyp.h | 8 ++
> arch/arm64/kvm/hyp/nvhe/Makefile | 2 +-
> arch/arm64/kvm/hyp/nvhe/hyp-main.c | 32 ++++++
> arch/arm64/kvm/hyp/vgic-v5.c | 155 +++++++++++++++++++++++++++++
> arch/arm64/kvm/hyp/vhe/Makefile | 2 +-
> include/kvm/arm_vgic.h | 28 ++++++
> 7 files changed, 229 insertions(+), 2 deletions(-)
> create mode 100644 arch/arm64/kvm/hyp/vgic-v5.c
>
> diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
> index a1ad12c72ebf1..5f669299fb956 100644
> --- a/arch/arm64/include/asm/kvm_asm.h
> +++ b/arch/arm64/include/asm/kvm_asm.h
> @@ -89,6 +89,10 @@ 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_vmcr_aprs,
As we recently found out with v2/v3, delaying saving VMCR (and
therefore lobbing it with the APRs) is a bad idea if you need to look
at it from inside the run loop.
In general, please align the behaviour with the existing
infrastructure as often as possible. We can always optimise things
later once GICv5 becomes relevant.
> + __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_aprs,
> + __KVM_HOST_SMCCC_FUNC___vgic_v5_save_ppi_state,
> + __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_ppi_state,
> };
>
> #define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
> diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
> index 76ce2b94bd97e..f6cf59a719ac6 100644
> --- a/arch/arm64/include/asm/kvm_hyp.h
> +++ b/arch/arm64/include/asm/kvm_hyp.h
> @@ -87,6 +87,14 @@ 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_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
> +void __vgic_v5_restore_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
> +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_icsr(struct vgic_v5_cpu_if *cpu_if);
> +void __vgic_v5_restore_icsr(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);
> diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile
> index a244ec25f8c5b..d860fbe9bc476 100644
> --- a/arch/arm64/kvm/hyp/nvhe/Makefile
> +++ b/arch/arm64/kvm/hyp/nvhe/Makefile
> @@ -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.o
> hyp-obj-y += ../../../kernel/smccc-call.o
> hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
> hyp-obj-y += $(lib-objs)
> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> index a7c689152f686..6bc5a4f75fd01 100644
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> @@ -586,6 +586,34 @@ 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_vmcr_aprs(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
> +
> + __vgic_v5_save_vmcr_aprs(kern_hyp_va(cpu_if));
> +}
> +
> +static void handle___vgic_v5_restore_vmcr_aprs(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
> +
> + __vgic_v5_restore_vmcr_aprs(kern_hyp_va(cpu_if));
> +}
> +
> +static void handle___vgic_v5_save_ppi_state(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
> +
> + __vgic_v5_save_ppi_state(kern_hyp_va(cpu_if));
> +}
> +
> +static void handle___vgic_v5_restore_ppi_state(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
> +
> + __vgic_v5_restore_ppi_state(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
> @@ -627,6 +655,10 @@ 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_vmcr_aprs),
> + HANDLE_FUNC(__vgic_v5_restore_vmcr_aprs),
> + HANDLE_FUNC(__vgic_v5_save_ppi_state),
> + HANDLE_FUNC(__vgic_v5_restore_ppi_state),
> };
>
> static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
> diff --git a/arch/arm64/kvm/hyp/vgic-v5.c b/arch/arm64/kvm/hyp/vgic-v5.c
> new file mode 100644
> index 0000000000000..11b67ae09e326
> --- /dev/null
> +++ b/arch/arm64/kvm/hyp/vgic-v5.c
maz@valley-girl:~/hot-poop/arm-platforms$ find . -name vgic-v5.c
./arch/arm64/kvm/vgic/vgic-v5.c
./arch/arm64/kvm/hyp/vgic-v5.c
It doesn't look like much, but that's *very* annoying. Which is why we
have vgic-v3.c on one side, and vgic-v3-sr.c on the other. Consider
doing the same thing here.
> @@ -0,0 +1,155 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 - ARM Ltd
> + */
> +
> +#include <hyp/adjust_pc.h>
> +
> +#include <linux/compiler.h>
> +#include <linux/irqchip/arm-gic-v5.h>
> +#include <linux/kvm_host.h>
> +
> +#include <asm/kvm_emulate.h>
> +#include <asm/kvm_hyp.h>
> +#include <asm/kvm_mmu.h>
> +
> +void __vgic_v5_save_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if)
> +{
> + cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2);
> + 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_aprs(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)
> +{
> + cpu_if->vgic_ppi_activer_exit[0] =
> + read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
> + cpu_if->vgic_ppi_activer_exit[1] =
> + read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);
Please don't break assignments. Long lines are fine.
> +
> + cpu_if->vgic_ich_ppi_enabler_exit[0] =
> + read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
> + cpu_if->vgic_ich_ppi_enabler_exit[1] =
> + read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
> +
> + cpu_if->vgic_ppi_pendr_exit[0] = read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
> + cpu_if->vgic_ppi_pendr_exit[1] = read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);
> +
> + 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);
> + 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);
There is clearly scope for optimisation here. The most likely case is
that we will only care about a handful of PPIs, all grouped in the
first 4 registers. That's a good reason to track the PPIs that the
guest can see.
> +
> + /* 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)
> +{
> + /* Now enable DVI so that the guest's interrupt config takes over */
> + write_sysreg_s(cpu_if->vgic_ppi_dvir[0], SYS_ICH_PPI_DVIR0_EL2);
> + write_sysreg_s(cpu_if->vgic_ppi_dvir[1], SYS_ICH_PPI_DVIR1_EL2);
> +
> + write_sysreg_s(cpu_if->vgic_ppi_activer_entry[0],
> + SYS_ICH_PPI_ACTIVER0_EL2);
> + write_sysreg_s(cpu_if->vgic_ppi_activer_entry[1],
> + SYS_ICH_PPI_ACTIVER1_EL2);
> +
> + write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[0],
> + SYS_ICH_PPI_ENABLER0_EL2);
> + write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[1],
> + SYS_ICH_PPI_ENABLER1_EL2);
> +
> + /* Update the pending state of the NON-DVI'd PPIs, only */
> + write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[0] &
> + ~cpu_if->vgic_ppi_dvir[0],
Again, don't insert line breaks in logical operations.
> + SYS_ICH_PPI_PENDR0_EL2);
> + write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[1] &
> + ~cpu_if->vgic_ppi_dvir[1],
> + SYS_ICH_PPI_PENDR1_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);
> + 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);
> +}
> +
> +void __vgic_v5_save_icsr(struct vgic_v5_cpu_if *cpu_if)
> +{
> + cpu_if->vgic_icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
> +}
> +
> +void __vgic_v5_restore_icsr(struct vgic_v5_cpu_if *cpu_if)
> +{
> + write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
> +}
> diff --git a/arch/arm64/kvm/hyp/vhe/Makefile b/arch/arm64/kvm/hyp/vhe/Makefile
> index afc4aed9231ac..fcf5e68ab591c 100644
> --- a/arch/arm64/kvm/hyp/vhe/Makefile
> +++ b/arch/arm64/kvm/hyp/vhe/Makefile
> @@ -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.o
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index fbbaef4ad2114..525c8b83e83c9 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -359,7 +359,35 @@ struct vgic_v3_cpu_if {
> };
>
> struct vgic_v5_cpu_if {
> + u64 vgic_apr;
> + u64 vgic_vmcr;
> +
> + /* PPI register state */
> u64 vgic_ppi_hmr[2];
> + u64 vgic_ppi_dvir[2];
> + u64 vgic_ppi_priorityr[16];
> +
> + /* The pending state of the guest. This is merged with the exit state */
> + u64 vgic_ppi_pendr[2];
> +
> + /* The state flushed to the regs when entering the guest */
> + u64 vgic_ppi_activer_entry[2];
> + u64 vgic_ich_ppi_enabler_entry[2];
> + u64 vgic_ppi_pendr_entry[2];
> +
> + /* The saved state of the regs when leaving the guest */
> + u64 vgic_ppi_activer_exit[2];
> + u64 vgic_ich_ppi_enabler_exit[2];
> + u64 vgic_ppi_pendr_exit[2];
See my comment on patch 17, requesting to make these entry/exit states
per CPU, and not per vcpu.
> +
> + /*
> + * 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;
> };
>
> struct vgic_cpu {
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface
2025-12-17 11:07 ` Marc Zyngier
@ 2025-12-17 21:42 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-17 21:42 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Wed, 2025-12-17 at 11:07 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:39 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > Introduce hyp functions to save/restore the following GICv5 state:
> >
> > * ICC_ICSR_EL1
> > * ICH_APR_EL2
> > * ICH_PPI_ACTIVERx_EL2
> > * ICH_PPI_DVIRx_EL2
> > * ICH_PPI_ENABLERx_EL2
> > * ICH_PPI_PENDRRx_EL2
> > * ICH_PPI_PRIORITYRx_EL2
> > * ICH_VMCR_EL2
> >
> > All of these are saved/restored to/from the KVM vgic_v5 CPUIF
> > shadow
> > state.
> >
> > 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 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>
> > ---
> > arch/arm64/include/asm/kvm_asm.h | 4 +
> > arch/arm64/include/asm/kvm_hyp.h | 8 ++
> > arch/arm64/kvm/hyp/nvhe/Makefile | 2 +-
> > arch/arm64/kvm/hyp/nvhe/hyp-main.c | 32 ++++++
> > arch/arm64/kvm/hyp/vgic-v5.c | 155
> > +++++++++++++++++++++++++++++
> > arch/arm64/kvm/hyp/vhe/Makefile | 2 +-
> > include/kvm/arm_vgic.h | 28 ++++++
> > 7 files changed, 229 insertions(+), 2 deletions(-)
> > create mode 100644 arch/arm64/kvm/hyp/vgic-v5.c
> >
> > diff --git a/arch/arm64/include/asm/kvm_asm.h
> > b/arch/arm64/include/asm/kvm_asm.h
> > index a1ad12c72ebf1..5f669299fb956 100644
> > --- a/arch/arm64/include/asm/kvm_asm.h
> > +++ b/arch/arm64/include/asm/kvm_asm.h
> > @@ -89,6 +89,10 @@ 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_vmcr_aprs,
>
> As we recently found out with v2/v3, delaying saving VMCR (and
> therefore lobbing it with the APRs) is a bad idea if you need to look
> at it from inside the run loop.
>
> In general, please align the behaviour with the existing
> infrastructure as often as possible. We can always optimise things
> later once GICv5 becomes relevant.
Done, and noted!
> > + __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_aprs,
> > + __KVM_HOST_SMCCC_FUNC___vgic_v5_save_ppi_state,
> > + __KVM_HOST_SMCCC_FUNC___vgic_v5_restore_ppi_state,
> > };
> >
> > #define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
> > diff --git a/arch/arm64/include/asm/kvm_hyp.h
> > b/arch/arm64/include/asm/kvm_hyp.h
> > index 76ce2b94bd97e..f6cf59a719ac6 100644
> > --- a/arch/arm64/include/asm/kvm_hyp.h
> > +++ b/arch/arm64/include/asm/kvm_hyp.h
> > @@ -87,6 +87,14 @@ 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_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
> > +void __vgic_v5_restore_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if);
> > +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_icsr(struct vgic_v5_cpu_if *cpu_if);
> > +void __vgic_v5_restore_icsr(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);
> > diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile
> > b/arch/arm64/kvm/hyp/nvhe/Makefile
> > index a244ec25f8c5b..d860fbe9bc476 100644
> > --- a/arch/arm64/kvm/hyp/nvhe/Makefile
> > +++ b/arch/arm64/kvm/hyp/nvhe/Makefile
> > @@ -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.o
> > hyp-obj-y += ../../../kernel/smccc-call.o
> > hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o
> > hyp-obj-y += $(lib-objs)
> > diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > index a7c689152f686..6bc5a4f75fd01 100644
> > --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> > @@ -586,6 +586,34 @@ 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_vmcr_aprs(struct kvm_cpu_context
> > *host_ctxt)
> > +{
> > + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt,
> > 1);
> > +
> > + __vgic_v5_save_vmcr_aprs(kern_hyp_va(cpu_if));
> > +}
> > +
> > +static void handle___vgic_v5_restore_vmcr_aprs(struct
> > kvm_cpu_context *host_ctxt)
> > +{
> > + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt,
> > 1);
> > +
> > + __vgic_v5_restore_vmcr_aprs(kern_hyp_va(cpu_if));
> > +}
> > +
> > +static void handle___vgic_v5_save_ppi_state(struct kvm_cpu_context
> > *host_ctxt)
> > +{
> > + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt,
> > 1);
> > +
> > + __vgic_v5_save_ppi_state(kern_hyp_va(cpu_if));
> > +}
> > +
> > +static void handle___vgic_v5_restore_ppi_state(struct
> > kvm_cpu_context *host_ctxt)
> > +{
> > + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt,
> > 1);
> > +
> > + __vgic_v5_restore_ppi_state(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
> > @@ -627,6 +655,10 @@ 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_vmcr_aprs),
> > + HANDLE_FUNC(__vgic_v5_restore_vmcr_aprs),
> > + HANDLE_FUNC(__vgic_v5_save_ppi_state),
> > + HANDLE_FUNC(__vgic_v5_restore_ppi_state),
> > };
> >
> > static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
> > diff --git a/arch/arm64/kvm/hyp/vgic-v5.c
> > b/arch/arm64/kvm/hyp/vgic-v5.c
> > new file mode 100644
> > index 0000000000000..11b67ae09e326
> > --- /dev/null
> > +++ b/arch/arm64/kvm/hyp/vgic-v5.c
>
> maz@valley-girl:~/hot-poop/arm-platforms$ find . -name vgic-v5.c
> ./arch/arm64/kvm/vgic/vgic-v5.c
> ./arch/arm64/kvm/hyp/vgic-v5.c
>
> It doesn't look like much, but that's *very* annoying. Which is why
> we
> have vgic-v3.c on one side, and vgic-v3-sr.c on the other. Consider
> doing the same thing here.
Have done this.
> > @@ -0,0 +1,155 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * Copyright (C) 2025 - ARM Ltd
> > + */
> > +
> > +#include <hyp/adjust_pc.h>
> > +
> > +#include <linux/compiler.h>
> > +#include <linux/irqchip/arm-gic-v5.h>
> > +#include <linux/kvm_host.h>
> > +
> > +#include <asm/kvm_emulate.h>
> > +#include <asm/kvm_hyp.h>
> > +#include <asm/kvm_mmu.h>
> > +
> > +void __vgic_v5_save_vmcr_aprs(struct vgic_v5_cpu_if *cpu_if)
> > +{
> > + cpu_if->vgic_vmcr = read_sysreg_s(SYS_ICH_VMCR_EL2);
> > + 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_aprs(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)
> > +{
> > + cpu_if->vgic_ppi_activer_exit[0] =
> > + read_sysreg_s(SYS_ICH_PPI_ACTIVER0_EL2);
> > + cpu_if->vgic_ppi_activer_exit[1] =
> > + read_sysreg_s(SYS_ICH_PPI_ACTIVER1_EL2);
>
> Please don't break assignments. Long lines are fine.
Noted!
> > +
> > + cpu_if->vgic_ich_ppi_enabler_exit[0] =
> > + read_sysreg_s(SYS_ICH_PPI_ENABLER0_EL2);
> > + cpu_if->vgic_ich_ppi_enabler_exit[1] =
> > + read_sysreg_s(SYS_ICH_PPI_ENABLER1_EL2);
> > +
> > + cpu_if->vgic_ppi_pendr_exit[0] =
> > read_sysreg_s(SYS_ICH_PPI_PENDR0_EL2);
> > + cpu_if->vgic_ppi_pendr_exit[1] =
> > read_sysreg_s(SYS_ICH_PPI_PENDR1_EL2);
> > +
> > + 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);
> > + 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);
>
> There is clearly scope for optimisation here. The most likely case is
> that we will only care about a handful of PPIs, all grouped in the
> first 4 registers. That's a good reason to track the PPIs that the
> guest can see.
>
Agreed. Will most likely save this optimisation for a future round.
Once everything is back to a working state again!
> > +
> > + /* 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)
> > +{
> > + /* Now enable DVI so that the guest's interrupt config
> > takes over */
> > + write_sysreg_s(cpu_if->vgic_ppi_dvir[0],
> > SYS_ICH_PPI_DVIR0_EL2);
> > + write_sysreg_s(cpu_if->vgic_ppi_dvir[1],
> > SYS_ICH_PPI_DVIR1_EL2);
> > +
> > + write_sysreg_s(cpu_if->vgic_ppi_activer_entry[0],
> > + SYS_ICH_PPI_ACTIVER0_EL2);
> > + write_sysreg_s(cpu_if->vgic_ppi_activer_entry[1],
> > + SYS_ICH_PPI_ACTIVER1_EL2);
> > +
> > + write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[0],
> > + SYS_ICH_PPI_ENABLER0_EL2);
> > + write_sysreg_s(cpu_if->vgic_ich_ppi_enabler_entry[1],
> > + SYS_ICH_PPI_ENABLER1_EL2);
> > +
> > + /* Update the pending state of the NON-DVI'd PPIs, only
> > */
> > + write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[0] &
> > + ~cpu_if->vgic_ppi_dvir[0],
>
> Again, don't insert line breaks in logical operations.
>
Done.
> > + SYS_ICH_PPI_PENDR0_EL2);
> > + write_sysreg_s(cpu_if->vgic_ppi_pendr_entry[1] &
> > + ~cpu_if->vgic_ppi_dvir[1],
> > + SYS_ICH_PPI_PENDR1_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);
> > + 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);
> > +}
> > +
> > +void __vgic_v5_save_icsr(struct vgic_v5_cpu_if *cpu_if)
> > +{
> > + cpu_if->vgic_icsr = read_sysreg_s(SYS_ICC_ICSR_EL1);
> > +}
> > +
> > +void __vgic_v5_restore_icsr(struct vgic_v5_cpu_if *cpu_if)
> > +{
> > + write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
> > +}
> > diff --git a/arch/arm64/kvm/hyp/vhe/Makefile
> > b/arch/arm64/kvm/hyp/vhe/Makefile
> > index afc4aed9231ac..fcf5e68ab591c 100644
> > --- a/arch/arm64/kvm/hyp/vhe/Makefile
> > +++ b/arch/arm64/kvm/hyp/vhe/Makefile
> > @@ -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.o
> > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > index fbbaef4ad2114..525c8b83e83c9 100644
> > --- a/include/kvm/arm_vgic.h
> > +++ b/include/kvm/arm_vgic.h
> > @@ -359,7 +359,35 @@ struct vgic_v3_cpu_if {
> > };
> >
> > struct vgic_v5_cpu_if {
> > + u64 vgic_apr;
> > + u64 vgic_vmcr;
> > +
> > + /* PPI register state */
> > u64 vgic_ppi_hmr[2];
> > + u64 vgic_ppi_dvir[2];
> > + u64 vgic_ppi_priorityr[16];
> > +
> > + /* The pending state of the guest. This is merged with the
> > exit state */
> > + u64 vgic_ppi_pendr[2];
> > +
> > + /* The state flushed to the regs when entering the guest
> > */
> > + u64 vgic_ppi_activer_entry[2];
> > + u64 vgic_ich_ppi_enabler_entry[2];
> > + u64 vgic_ppi_pendr_entry[2];
> > +
> > + /* The saved state of the regs when leaving the guest */
> > + u64 vgic_ppi_activer_exit[2];
> > + u64 vgic_ich_ppi_enabler_exit[2];
> > + u64 vgic_ppi_pendr_exit[2];
>
> See my comment on patch 17, requesting to make these entry/exit
> states
> per CPU, and not per vcpu.
>
Yeah, I'll look into this for sure. It makes sense to do it that way.
This means that when we're outside of the guest, all of that mess
disappears, and the per-vcpu state just reflects what we're going to
present to the guest on next entry.
Thanks as always,
Sascha
> > +
> > + /*
> > + * 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;
> > };
> >
> > struct vgic_cpu {
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (12 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 13/32] KVM: arm64: gic-v5: Add vgic-v5 save/restore hyp interface Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 11:40 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
` (17 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
GICv5 is able to directly inject PPI pending state into a guest using
a mechanism called DVI whereby the pending bit for a paticular PPI is
driven directly by the physically-connected hardware. This mechanism
itself doesn't allow for any ID translation, so the host interrupt is
directly mapped into a guest with the same interrupt ID.
When mapping a virtual interrupt to a physical interrupt via
kvm_vgic_map_irq for a GICv5 guest, check if the interrupt itself is a
PPI or not. If it is, and the host's interrupt ID matches that used
for the guest DVI is enabled, and the interrupt itself is marked as
directly_injected.
When the interrupt is unmapped again, this process is reversed, and
DVI is disabled for the interrupt again.
Note: the expectation is that a directly injected PPI is disabled on
the host while the guest state is loaded. The reason is that although
DVI is enabled to drive the guest's pending state directly, the host
pending state also remains driven. In order to avoid the same PPI
firing on both the host and the guest, the host's interrupt must be
disabled (masked). This is left up to the code that owns the device
generating the PPI as this needs to be handled on a per-VM basis. One
VM might use DVI, while another might not, in which case the physical
PPI should be enabled for the latter.
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>
---
arch/arm64/kvm/vgic/vgic-v5.c | 22 ++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 16 ++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 1 +
include/kvm/arm_vgic.h | 1 +
4 files changed, 40 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2fb2db23ed39a..22558080711eb 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -54,6 +54,28 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+/*
+ * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
+ */
+int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
+
+ if (ppi >= 128)
+ return -EINVAL;
+
+ if (dvi) {
+ /* Set the bit */
+ cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
+ } else {
+ /* Clear the bit */
+ cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
+ }
+
+ return 0;
+}
+
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 1005ff5f36235..1fe3dcc997860 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -577,12 +577,28 @@ static int kvm_vgic_map_irq(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
irq->host_irq = host_irq;
irq->hwintid = data->hwirq;
irq->ops = ops;
+
+ if (vgic_is_v5(vcpu->kvm)) {
+ /* Nothing for us to do */
+ if (!irq_is_ppi_v5(irq->intid))
+ return 0;
+
+ if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
+ if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
+ irq->directly_injected = true;
+ }
+ }
+
return 0;
}
/* @irq->irq_lock must be held */
static inline void kvm_vgic_unmap_irq(struct vgic_irq *irq)
{
+ if (irq->directly_injected && vgic_is_v5(irq->target_vcpu->kvm))
+ WARN_ON(vgic_v5_set_ppi_dvi(irq->target_vcpu, irq->hwintid, false));
+
+ irq->directly_injected = false;
irq->hw = false;
irq->hwintid = 0;
irq->ops = NULL;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 6e1f386dffade..b6e3f5e3aba18 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -363,6 +363,7 @@ void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
+int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
void vgic_v5_load(struct kvm_vcpu *vcpu);
void vgic_v5_put(struct kvm_vcpu *vcpu);
void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 45d83f45b065d..ce9e149b85a58 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -163,6 +163,7 @@ struct vgic_irq {
bool enabled:1;
bool active:1;
bool hw:1; /* Tied to HW IRQ */
+ bool directly_injected:1; /* A directly injected HW IRQ */
bool on_lr:1; /* Present in a CPU LR */
refcount_t refcount; /* Used for LPIs */
u32 hwintid; /* HW INTID number */
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs
2025-12-12 15:22 ` [PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
@ 2025-12-17 11:40 ` Marc Zyngier
0 siblings, 0 replies; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 11:40 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:40 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> GICv5 is able to directly inject PPI pending state into a guest using
> a mechanism called DVI whereby the pending bit for a paticular PPI is
> driven directly by the physically-connected hardware. This mechanism
> itself doesn't allow for any ID translation, so the host interrupt is
> directly mapped into a guest with the same interrupt ID.
>
> When mapping a virtual interrupt to a physical interrupt via
> kvm_vgic_map_irq for a GICv5 guest, check if the interrupt itself is a
> PPI or not. If it is, and the host's interrupt ID matches that used
> for the guest DVI is enabled, and the interrupt itself is marked as
> directly_injected.
>
> When the interrupt is unmapped again, this process is reversed, and
> DVI is disabled for the interrupt again.
>
> Note: the expectation is that a directly injected PPI is disabled on
> the host while the guest state is loaded. The reason is that although
> DVI is enabled to drive the guest's pending state directly, the host
> pending state also remains driven. In order to avoid the same PPI
> firing on both the host and the guest, the host's interrupt must be
> disabled (masked). This is left up to the code that owns the device
> generating the PPI as this needs to be handled on a per-VM basis. One
> VM might use DVI, while another might not, in which case the physical
> PPI should be enabled for the latter.
>
> 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>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 22 ++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 16 ++++++++++++++++
> arch/arm64/kvm/vgic/vgic.h | 1 +
> include/kvm/arm_vgic.h | 1 +
> 4 files changed, 40 insertions(+)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 2fb2db23ed39a..22558080711eb 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,28 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +/*
> + * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
> + */
> +int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
> +
> + if (ppi >= 128)
> + return -EINVAL;
Surely this is bad. *very* bad. How can we get here the first place?
> +
> + if (dvi) {
> + /* Set the bit */
> + cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
> + } else {
> + /* Clear the bit */
> + cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
> + }
This should be simplified:
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d74cc3543b9a4..f434ee85f7e1a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -191,8 +191,8 @@ bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
struct vgic_irq *irq)
{
struct vgic_v5_cpu_if *cpu_if;
- const u32 id_bit = BIT_ULL(irq->intid % 64);
const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
+ unsigned long *p;
if (!vcpu || !irq)
return false;
@@ -203,10 +203,8 @@ bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
- if (irq_is_pending(irq))
- cpu_if->vgic_ppi_pendr[reg] |= id_bit;
- else
- cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
+ p = (unsigned long *)&cpu_if->vgic_ppi_pendr[reg];
+ __assign_bit(irq->intid % 64, p, irq_is_pending(irq));
return true;
}
@@ -449,17 +447,13 @@ int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
u32 ppi = FIELD_GET(GICV5_HWIRQ_ID, irq);
+ unsigned long *p;
if (ppi >= 128)
return -EINVAL;
- if (dvi) {
- /* Set the bit */
- cpu_if->vgic_ppi_dvir[ppi / 64] |= 1UL << (ppi % 64);
- } else {
- /* Clear the bit */
- cpu_if->vgic_ppi_dvir[ppi / 64] &= ~(1UL << (ppi % 64));
- }
+ p = (unsigned long *)&cpu_if->vgic_ppi_dvir[ppi / 64];
+ __assign_bit(ppi % 64, p, dvi);
return 0;
}
(yes, unsigned long and u64 are the same thing on any sane
architecture).
> +
> + return 0;
> +}
> +
> void vgic_v5_load(struct kvm_vcpu *vcpu)
> {
> struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 1005ff5f36235..1fe3dcc997860 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -577,12 +577,28 @@ static int kvm_vgic_map_irq(struct kvm_vcpu *vcpu, struct vgic_irq *irq,
> irq->host_irq = host_irq;
> irq->hwintid = data->hwirq;
> irq->ops = ops;
> +
> + if (vgic_is_v5(vcpu->kvm)) {
> + /* Nothing for us to do */
> + if (!irq_is_ppi_v5(irq->intid))
> + return 0;
> +
> + if (FIELD_GET(GICV5_HWIRQ_ID, irq->intid) == irq->hwintid) {
> + if (!vgic_v5_set_ppi_dvi(vcpu, irq->hwintid, true))
> + irq->directly_injected = true;
The error handling gives me the creeps. If we can end-up at this stage
with the wrong INTID, we're screwed.
> + }
> + }
> +
> return 0;
> }
>
> /* @irq->irq_lock must be held */
> static inline void kvm_vgic_unmap_irq(struct vgic_irq *irq)
> {
> + if (irq->directly_injected && vgic_is_v5(irq->target_vcpu->kvm))
> + WARN_ON(vgic_v5_set_ppi_dvi(irq->target_vcpu, irq->hwintid, false));
> +
> + irq->directly_injected = false;
> irq->hw = false;
> irq->hwintid = 0;
> irq->ops = NULL;
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 6e1f386dffade..b6e3f5e3aba18 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -363,6 +363,7 @@ void vgic_debug_init(struct kvm *kvm);
> void vgic_debug_destroy(struct kvm *kvm);
>
> int vgic_v5_probe(const struct gic_kvm_info *info);
> +int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
> void vgic_v5_put(struct kvm_vcpu *vcpu);
> void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 45d83f45b065d..ce9e149b85a58 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -163,6 +163,7 @@ struct vgic_irq {
> bool enabled:1;
> bool active:1;
> bool hw:1; /* Tied to HW IRQ */
> + bool directly_injected:1; /* A directly injected HW IRQ */
> bool on_lr:1; /* Present in a CPU LR */
> refcount_t refcount; /* Used for LPIs */
> u32 hwintid; /* HW INTID number */
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply related [flat|nested] 70+ messages in thread
* [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (13 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 15/32] KVM: arm64: gic-v5: Implement direct injection of PPIs Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-13 5:59 ` kernel test robot
` (2 more replies)
2025-12-12 15:22 ` [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
` (16 subsequent siblings)
31 siblings, 3 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This change introduces GICv5 load/put. Additionally, it plumbs in
save/restore for:
* PPIs (ICH_PPI_x_EL2 regs)
* ICH_VMCR_EL2
* ICH_APR_EL2
* ICC_ICSR_EL1
A GICv5-specific enable bit is added to struct vgic_vmcr as this
differs from previous GICs. On GICv5-native systems, the VMCR only
contains the enable bit (driven by the guest via ICC_CR0_EL1.EN) and
the priority mask (PCR).
A struct gicv5_vpe is also introduced. This currently only contains a
single field - bool resident - which is used to track if a VPE is
currently running or not, and is used to avoid a case of double load
or double put on the WFI path for a vCPU. This struct will be extended
as additional GICv5 support is merged, specifically for VPE doorbells.
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>
---
arch/arm64/kvm/hyp/nvhe/switch.c | 10 ++++
arch/arm64/kvm/vgic/vgic-mmio.c | 28 +++++++----
arch/arm64/kvm/vgic/vgic-v5.c | 77 ++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 32 ++++++++-----
arch/arm64/kvm/vgic/vgic.h | 7 +++
include/kvm/arm_vgic.h | 2 +
include/linux/irqchip/arm-gic-v5.h | 9 ++++
7 files changed, 146 insertions(+), 19 deletions(-)
diff --git a/arch/arm64/kvm/hyp/nvhe/switch.c b/arch/arm64/kvm/hyp/nvhe/switch.c
index c23e22ffac080..e25f1f000536b 100644
--- a/arch/arm64/kvm/hyp/nvhe/switch.c
+++ b/arch/arm64/kvm/hyp/nvhe/switch.c
@@ -113,6 +113,11 @@ static void __deactivate_traps(struct kvm_vcpu *vcpu)
/* Save VGICv3 state on non-VHE systems */
static void __hyp_vgic_save_state(struct kvm_vcpu *vcpu)
{
+ if (kern_hyp_va(vcpu->kvm)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ __vgic_v5_save_icsr(&vcpu->arch.vgic_cpu.vgic_v5);
+ __vgic_v5_save_ppi_state(&vcpu->arch.vgic_cpu.vgic_v5);
+ }
+
if (static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif)) {
__vgic_v3_save_state(&vcpu->arch.vgic_cpu.vgic_v3);
__vgic_v3_deactivate_traps(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -122,6 +127,11 @@ static void __hyp_vgic_save_state(struct kvm_vcpu *vcpu)
/* Restore VGICv3 state on non-VHE systems */
static void __hyp_vgic_restore_state(struct kvm_vcpu *vcpu)
{
+ if (kern_hyp_va(vcpu->kvm)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ __vgic_v5_restore_icsr(&vcpu->arch.vgic_cpu.vgic_v5);
+ __vgic_v5_restore_ppi_state(&vcpu->arch.vgic_cpu.vgic_v5);
+ }
+
if (static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif)) {
__vgic_v3_activate_traps(&vcpu->arch.vgic_cpu.vgic_v3);
__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index a573b1f0c6cbe..675c2844f5e5c 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -842,18 +842,30 @@ vgic_find_mmio_region(const struct vgic_register_region *regions,
void vgic_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr)
{
- if (kvm_vgic_global_state.type == VGIC_V2)
- vgic_v2_set_vmcr(vcpu, vmcr);
- else
- vgic_v3_set_vmcr(vcpu, vmcr);
+ const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+ if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ vgic_v5_set_vmcr(vcpu, vmcr);
+ } else {
+ if (kvm_vgic_global_state.type == VGIC_V2)
+ vgic_v2_set_vmcr(vcpu, vmcr);
+ else
+ vgic_v3_set_vmcr(vcpu, vmcr);
+ }
}
void vgic_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr)
{
- if (kvm_vgic_global_state.type == VGIC_V2)
- vgic_v2_get_vmcr(vcpu, vmcr);
- else
- vgic_v3_get_vmcr(vcpu, vmcr);
+ const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+ if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ vgic_v5_get_vmcr(vcpu, vmcr);
+ } else {
+ if (kvm_vgic_global_state.type == VGIC_V2)
+ vgic_v2_get_vmcr(vcpu, vmcr);
+ else
+ vgic_v3_get_vmcr(vcpu, vmcr);
+ }
}
/*
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 2d3811f4e1174..2fb2db23ed39a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1,4 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Arm Ltd.
+ */
#include <kvm/arm_vgic.h>
#include <linux/irqchip/arm-vgic-info.h>
@@ -50,3 +53,77 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+
+void vgic_v5_load(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /*
+ * On the WFI path, vgic_load is called a second time. The first is when
+ * scheduling in the vcpu thread again, and the second is when leaving
+ * WFI. Skip the second instance as it serves no purpose and just
+ * restores the same state again.
+ */
+ if (READ_ONCE(cpu_if->gicv5_vpe.resident))
+ return;
+
+ kvm_call_hyp(__vgic_v5_restore_vmcr_aprs, cpu_if);
+
+ WRITE_ONCE(cpu_if->gicv5_vpe.resident, true);
+}
+
+void vgic_v5_put(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /*
+ * Do nothing if we're not resident. This can happen in the WFI path
+ * where we do a vgic_put in the WFI path and again later when
+ * descheduling the thread. We risk losing VMCR state if we sync it
+ * twice, so instead return early in this case.
+ */
+ if (!READ_ONCE(cpu_if->gicv5_vpe.resident))
+ return;
+
+ kvm_call_hyp(__vgic_v5_save_vmcr_aprs, cpu_if);
+
+ WRITE_ONCE(cpu_if->gicv5_vpe.resident, false);
+}
+
+void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u64 vmcr = cpu_if->vgic_vmcr;
+
+ vmcrp->en = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_EN, vmcr);
+ vmcrp->pmr = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, vmcr);
+}
+
+void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcrp)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u64 vmcr;
+
+ vmcr = FIELD_PREP(FEAT_GCIE_ICH_VMCR_EL2_VPMR, vmcrp->pmr) |
+ FIELD_PREP(FEAT_GCIE_ICH_VMCR_EL2_EN, vmcrp->en);
+
+ cpu_if->vgic_vmcr = vmcr;
+}
+
+void vgic_v5_restore_state(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ __vgic_v5_restore_icsr(cpu_if);
+ kvm_call_hyp(__vgic_v5_restore_ppi_state, cpu_if);
+ dsb(sy);
+}
+
+void vgic_v5_save_state(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ __vgic_v5_save_icsr(cpu_if);
+ kvm_call_hyp(__vgic_v5_save_ppi_state, cpu_if);
+ dsb(sy);
+}
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 2c0e8803342e2..1005ff5f36235 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -996,7 +996,9 @@ static inline bool can_access_vgic_from_kernel(void)
static inline void vgic_save_state(struct kvm_vcpu *vcpu)
{
- if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+ if (vgic_is_v5(vcpu->kvm))
+ vgic_v5_save_state(vcpu);
+ else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
vgic_v2_save_state(vcpu);
else
__vgic_v3_save_state(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -1005,14 +1007,16 @@ static inline void vgic_save_state(struct kvm_vcpu *vcpu)
/* Sync back the hardware VGIC state into our emulation after a guest's run. */
void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
{
- /* If nesting, emulate the HW effect from L0 to L1 */
- if (vgic_state_is_nested(vcpu)) {
- vgic_v3_sync_nested(vcpu);
- return;
- }
+ if (!vgic_is_v5(vcpu->kvm)) {
+ /* If nesting, emulate the HW effect from L0 to L1 */
+ if (vgic_state_is_nested(vcpu)) {
+ vgic_v3_sync_nested(vcpu);
+ return;
+ }
- if (vcpu_has_nv(vcpu))
- vgic_v3_nested_update_mi(vcpu);
+ if (vcpu_has_nv(vcpu))
+ vgic_v3_nested_update_mi(vcpu);
+ }
if (can_access_vgic_from_kernel())
vgic_save_state(vcpu);
@@ -1034,7 +1038,9 @@ void kvm_vgic_process_async_update(struct kvm_vcpu *vcpu)
static inline void vgic_restore_state(struct kvm_vcpu *vcpu)
{
- if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+ if (vgic_is_v5(vcpu->kvm))
+ vgic_v5_restore_state(vcpu);
+ else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
vgic_v2_restore_state(vcpu);
else
__vgic_v3_restore_state(&vcpu->arch.vgic_cpu.vgic_v3);
@@ -1094,7 +1100,9 @@ void kvm_vgic_load(struct kvm_vcpu *vcpu)
return;
}
- if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+ if (vgic_is_v5(vcpu->kvm))
+ vgic_v5_load(vcpu);
+ else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
vgic_v2_load(vcpu);
else
vgic_v3_load(vcpu);
@@ -1108,7 +1116,9 @@ void kvm_vgic_put(struct kvm_vcpu *vcpu)
return;
}
- if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
+ if (vgic_is_v5(vcpu->kvm))
+ vgic_v5_put(vcpu);
+ else if (!static_branch_unlikely(&kvm_vgic_global_state.gicv3_cpuif))
vgic_v2_put(vcpu);
else
vgic_v3_put(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index bf5bae023751b..6e1f386dffade 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -187,6 +187,7 @@ static inline u64 vgic_ich_hcr_trap_bits(void)
* registers regardless of the hardware backed GIC used.
*/
struct vgic_vmcr {
+ u32 en; /* GICv5-specific */
u32 grpen0;
u32 grpen1;
@@ -362,6 +363,12 @@ void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
+void vgic_v5_load(struct kvm_vcpu *vcpu);
+void vgic_v5_put(struct kvm_vcpu *vcpu);
+void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
+void vgic_v5_get_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
+void vgic_v5_restore_state(struct kvm_vcpu *vcpu);
+void vgic_v5_save_state(struct kvm_vcpu *vcpu);
static inline int vgic_v3_max_apr_idx(struct kvm_vcpu *vcpu)
{
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 525c8b83e83c9..45d83f45b065d 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -388,6 +388,8 @@ struct vgic_v5_cpu_if {
* it is the hyp's responsibility to keep the state constistent.
*/
u64 vgic_icsr;
+
+ struct gicv5_vpe gicv5_vpe;
};
struct vgic_cpu {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 68ddcdb1cec5a..ff10d6c7be2ae 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -354,6 +354,15 @@ int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
int gicv5_irs_iste_alloc(u32 lpi);
void gicv5_irs_syncr(void);
+#ifdef CONFIG_KVM
+
+/* Embedded in kvm.arch */
+struct gicv5_vpe {
+ bool resident;
+};
+
+#endif // CONFIG_KVM
+
struct gicv5_its_devtab_cfg {
union {
struct {
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
2025-12-12 15:22 ` [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
@ 2025-12-13 5:59 ` kernel test robot
2025-12-15 10:54 ` Sascha Bischoff
2025-12-13 8:05 ` kernel test robot
2025-12-22 16:52 ` kernel test robot
2 siblings, 1 reply; 70+ messages in thread
From: kernel test robot @ 2025-12-13 5:59 UTC (permalink / raw)
To: Sascha Bischoff, linux-arm-kernel@lists.infradead.org,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: oe-kbuild-all, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on next-20251212]
[cannot apply to kvmarm/next arm64/for-next/core kvm/queue kvm/next kvm/linux-next v6.18]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sascha-Bischoff/KVM-arm64-Account-for-RES1-bits-in-DECLARE_FEAT_MAP-and-co/20251212-233140
base: linus/master
patch link: https://lore.kernel.org/r/20251212152215.675767-15-sascha.bischoff%40arm.com
patch subject: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
config: arm64-allnoconfig (https://download.01.org/0day-ci/archive/20251213/202512131338.pYhd9ptc-lkp@intel.com/config)
compiler: aarch64-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251213/202512131338.pYhd9ptc-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512131338.pYhd9ptc-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from arch/arm64/include/asm/kvm_host.h:36,
from include/linux/kvm_host.h:45,
from arch/arm64/kernel/asm-offsets.c:16:
>> include/kvm/arm_vgic.h:392:26: error: field 'gicv5_vpe' has incomplete type
392 | struct gicv5_vpe gicv5_vpe;
| ^~~~~~~~~
make[3]: *** [scripts/Makefile.build:182: arch/arm64/kernel/asm-offsets.s] Error 1
make[3]: Target 'prepare' not remade because of errors.
make[2]: *** [Makefile:1314: prepare0] Error 2
make[2]: Target 'prepare' not remade because of errors.
make[1]: *** [Makefile:248: __sub-make] Error 2
make[1]: Target 'prepare' not remade because of errors.
make: *** [Makefile:248: __sub-make] Error 2
make: Target 'prepare' not remade because of errors.
vim +/gicv5_vpe +392 include/kvm/arm_vgic.h
360
361 struct vgic_v5_cpu_if {
362 u64 vgic_apr;
363 u64 vgic_vmcr;
364
365 /* PPI register state */
366 u64 vgic_ppi_hmr[2];
367 u64 vgic_ppi_dvir[2];
368 u64 vgic_ppi_priorityr[16];
369
370 /* The pending state of the guest. This is merged with the exit state */
371 u64 vgic_ppi_pendr[2];
372
373 /* The state flushed to the regs when entering the guest */
374 u64 vgic_ppi_activer_entry[2];
375 u64 vgic_ich_ppi_enabler_entry[2];
376 u64 vgic_ppi_pendr_entry[2];
377
378 /* The saved state of the regs when leaving the guest */
379 u64 vgic_ppi_activer_exit[2];
380 u64 vgic_ich_ppi_enabler_exit[2];
381 u64 vgic_ppi_pendr_exit[2];
382
383 /*
384 * The ICSR is re-used across host and guest, and hence it needs to be
385 * saved/restored. Only one copy is required as the host should block
386 * preemption between executing GIC CDRCFG and acccessing the
387 * ICC_ICSR_EL1. A guest, of course, can never guarantee this, and hence
388 * it is the hyp's responsibility to keep the state constistent.
389 */
390 u64 vgic_icsr;
391
> 392 struct gicv5_vpe gicv5_vpe;
393 };
394
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
2025-12-13 5:59 ` kernel test robot
@ 2025-12-15 10:54 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-15 10:54 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, lkp@intel.com
Cc: yuzenghui@huawei.com, oe-kbuild-all@lists.linux.dev,
Timothy Hayes, Suzuki Poulose, nd, peter.maydell@linaro.org,
oliver.upton@linux.dev, maz@kernel.org, Joey Gouly,
lpieralisi@kernel.org
On Sat, 2025-12-13 at 13:59 +0800, kernel test robot wrote:
> All errors (new ones prefixed by >>):
>
> In file included from arch/arm64/include/asm/kvm_host.h:36,
> from include/linux/kvm_host.h:45,
> from arch/arm64/kernel/asm-offsets.c:16:
> > > include/kvm/arm_vgic.h:392:26: error: field 'gicv5_vpe' has
> > > incomplete type
> 392 | struct gicv5_vpe gicv5_vpe;
> | ^~~~~~~~~
> make[3]: *** [scripts/Makefile.build:182: arch/arm64/kernel/asm-
> offsets.s] Error 1
> make[3]: Target 'prepare' not remade because of errors.
> make[2]: *** [Makefile:1314: prepare0] Error 2
> make[2]: Target 'prepare' not remade because of errors.
> make[1]: *** [Makefile:248: __sub-make] Error 2
> make[1]: Target 'prepare' not remade because of errors.
> make: *** [Makefile:248: __sub-make] Error 2
> make: Target 'prepare' not remade because of errors.
>
I've located the underlying issue - the definition of struct gicv5_vpe
was wrapped in an #ifdef CONFIG_KVM block, and the two failing builds
have KVM disabled.
I've fixed the issue locally (dropped the #ifdef), and will post a
fixed commit as part of v2 of this series.
Sascha
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
2025-12-12 15:22 ` [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
2025-12-13 5:59 ` kernel test robot
@ 2025-12-13 8:05 ` kernel test robot
2025-12-22 16:52 ` kernel test robot
2 siblings, 0 replies; 70+ messages in thread
From: kernel test robot @ 2025-12-13 8:05 UTC (permalink / raw)
To: Sascha Bischoff, linux-arm-kernel@lists.infradead.org,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: llvm, oe-kbuild-all, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on next-20251212]
[cannot apply to kvmarm/next arm64/for-next/core kvm/queue kvm/next kvm/linux-next v6.18]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sascha-Bischoff/KVM-arm64-Account-for-RES1-bits-in-DECLARE_FEAT_MAP-and-co/20251212-233140
base: linus/master
patch link: https://lore.kernel.org/r/20251212152215.675767-15-sascha.bischoff%40arm.com
patch subject: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
config: arm64-randconfig-002-20251213 (https://download.01.org/0day-ci/archive/20251213/202512131539.zPrweF7e-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 1335a05ab8bc8339ce24be3a9da89d8c3f4e0571)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251213/202512131539.zPrweF7e-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512131539.zPrweF7e-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from arch/arm64/kernel/asm-offsets.c:16:
In file included from include/linux/kvm_host.h:45:
In file included from arch/arm64/include/asm/kvm_host.h:36:
>> include/kvm/arm_vgic.h:392:19: error: field has incomplete type 'struct gicv5_vpe'
392 | struct gicv5_vpe gicv5_vpe;
| ^
include/kvm/arm_vgic.h:392:9: note: forward declaration of 'struct gicv5_vpe'
392 | struct gicv5_vpe gicv5_vpe;
| ^
1 error generated.
make[3]: *** [scripts/Makefile.build:182: arch/arm64/kernel/asm-offsets.s] Error 1 shuffle=1891526797
make[3]: Target 'prepare' not remade because of errors.
make[2]: *** [Makefile:1314: prepare0] Error 2 shuffle=1891526797
make[2]: Target 'prepare' not remade because of errors.
make[1]: *** [Makefile:248: __sub-make] Error 2 shuffle=1891526797
make[1]: Target 'prepare' not remade because of errors.
make: *** [Makefile:248: __sub-make] Error 2 shuffle=1891526797
make: Target 'prepare' not remade because of errors.
Kconfig warnings: (for reference only)
WARNING: unmet direct dependencies detected for CAN_DEV
Depends on [n]: NETDEVICES [=n] && CAN [=y]
Selected by [y]:
- CAN [=y] && NET [=y]
vim +392 include/kvm/arm_vgic.h
360
361 struct vgic_v5_cpu_if {
362 u64 vgic_apr;
363 u64 vgic_vmcr;
364
365 /* PPI register state */
366 u64 vgic_ppi_hmr[2];
367 u64 vgic_ppi_dvir[2];
368 u64 vgic_ppi_priorityr[16];
369
370 /* The pending state of the guest. This is merged with the exit state */
371 u64 vgic_ppi_pendr[2];
372
373 /* The state flushed to the regs when entering the guest */
374 u64 vgic_ppi_activer_entry[2];
375 u64 vgic_ich_ppi_enabler_entry[2];
376 u64 vgic_ppi_pendr_entry[2];
377
378 /* The saved state of the regs when leaving the guest */
379 u64 vgic_ppi_activer_exit[2];
380 u64 vgic_ich_ppi_enabler_exit[2];
381 u64 vgic_ppi_pendr_exit[2];
382
383 /*
384 * The ICSR is re-used across host and guest, and hence it needs to be
385 * saved/restored. Only one copy is required as the host should block
386 * preemption between executing GIC CDRCFG and acccessing the
387 * ICC_ICSR_EL1. A guest, of course, can never guarantee this, and hence
388 * it is the hyp's responsibility to keep the state constistent.
389 */
390 u64 vgic_icsr;
391
> 392 struct gicv5_vpe gicv5_vpe;
393 };
394
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
2025-12-12 15:22 ` [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
2025-12-13 5:59 ` kernel test robot
2025-12-13 8:05 ` kernel test robot
@ 2025-12-22 16:52 ` kernel test robot
2 siblings, 0 replies; 70+ messages in thread
From: kernel test robot @ 2025-12-22 16:52 UTC (permalink / raw)
To: Sascha Bischoff, linux-arm-kernel@lists.infradead.org,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: oe-kbuild-all, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on v6.19-rc2 next-20251219]
[cannot apply to kvmarm/next arm64/for-next/core kvm/queue kvm/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sascha-Bischoff/KVM-arm64-Account-for-RES1-bits-in-DECLARE_FEAT_MAP-and-co/20251212-233140
base: linus/master
patch link: https://lore.kernel.org/r/20251212152215.675767-15-sascha.bischoff%40arm.com
patch subject: [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore
config: arm64-allnoconfig-bpf (https://download.01.org/0day-ci/archive/20251222/202512221751.nHKObHNq-lkp@intel.com/config)
compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project ecaf673850beb241957352bd61e95ed34256635f)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251222/202512221751.nHKObHNq-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512221751.nHKObHNq-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from arch/arm64/kernel/asm-offsets.c:16:
In file included from ./include/linux/kvm_host.h:45:
In file included from ./arch/arm64/include/asm/kvm_host.h:36:
>> ./include/kvm/arm_vgic.h:392:19: error: field has incomplete type 'struct gicv5_vpe'
392 | struct gicv5_vpe gicv5_vpe;
| ^
./include/kvm/arm_vgic.h:392:9: note: forward declaration of 'struct gicv5_vpe'
392 | struct gicv5_vpe gicv5_vpe;
| ^
1 error generated.
vim +392 ./include/kvm/arm_vgic.h
360
361 struct vgic_v5_cpu_if {
362 u64 vgic_apr;
363 u64 vgic_vmcr;
364
365 /* PPI register state */
366 u64 vgic_ppi_hmr[2];
367 u64 vgic_ppi_dvir[2];
368 u64 vgic_ppi_priorityr[16];
369
370 /* The pending state of the guest. This is merged with the exit state */
371 u64 vgic_ppi_pendr[2];
372
373 /* The state flushed to the regs when entering the guest */
374 u64 vgic_ppi_activer_entry[2];
375 u64 vgic_ich_ppi_enabler_entry[2];
376 u64 vgic_ppi_pendr_entry[2];
377
378 /* The saved state of the regs when leaving the guest */
379 u64 vgic_ppi_activer_exit[2];
380 u64 vgic_ich_ppi_enabler_exit[2];
381 u64 vgic_ppi_pendr_exit[2];
382
383 /*
384 * The ICSR is re-used across host and guest, and hence it needs to be
385 * saved/restored. Only one copy is required as the host should block
386 * preemption between executing GIC CDRCFG and acccessing the
387 * ICC_ICSR_EL1. A guest, of course, can never guarantee this, and hence
388 * it is the hyp's responsibility to keep the state constistent.
389 */
390 u64 vgic_icsr;
391
> 392 struct gicv5_vpe gicv5_vpe;
393 };
394
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (14 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 14/32] KVM: arm64: gic-v5: Implement GICv5 load/put and save/restore Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 9:34 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 19/32] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
` (15 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
There are times when the default behaviour of vgic_queue_irq_unlock is
undesirable. This is because some GICs, such a GICv5 which is the main
driver for this change, handle the majority of the interrupt lifecycle
in hardware. In this case, there is no need for a per-VCPU AP list as
the interrupt can be made pending directly. This is done either via
the ICH_PPI_x_EL2 registers for PPIs, or with the VDPEND system
instruction for SPIs and LPIs.
The queue_irq_unlock function is made overridable using a new function
pointer in struct irq_ops. In kvm_vgic_inject_irq,
vgic_queue_irq_unlock is overridden if the function pointer is
non-null.
Additionally, a new function is added via a function pointer -
set_pending_state. The intent is for this to be used to directly set
the pending state in hardware.
Both of these new irq_ops are unused in this change - it is purely
providing the infrastructure itself. The subsequent PPI injection
changes provide a demonstration of their usage.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic.c | 9 ++++++++-
include/kvm/arm_vgic.h | 15 +++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 1fe3dcc997860..fc01c6d07fe62 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -547,7 +547,14 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
else
irq->pending_latch = true;
- vgic_queue_irq_unlock(kvm, irq, flags);
+ if (irq->ops && irq->ops->set_pending_state)
+ WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu, irq));
+
+ if (irq->ops && irq->ops->queue_irq_unlock)
+ WARN_ON_ONCE(!irq->ops->queue_irq_unlock(kvm, irq, flags));
+ else
+ vgic_queue_irq_unlock(kvm, irq, flags);
+
vgic_put_irq(kvm, irq);
return 0;
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index ce9e149b85a58..20c908730fa00 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -115,6 +115,8 @@ enum vgic_irq_config {
VGIC_CONFIG_LEVEL
};
+struct vgic_irq;
+
/*
* Per-irq ops overriding some common behavious.
*
@@ -133,6 +135,19 @@ struct irq_ops {
* peaking into the physical GIC.
*/
bool (*get_input_level)(int vintid);
+
+ /*
+ * Function pointer to directly set the pending state for interrupts
+ * that don't need to be enqueued on AP lists (for example, GICv5 PPIs).
+ */
+ bool (*set_pending_state)(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
+
+ /*
+ * Function pointer to override the queuing of an IRQ.
+ */
+ bool (*queue_irq_unlock)(struct kvm *kvm, struct vgic_irq *irq,
+ unsigned long flags) __releases(&irq->irq_lock);
+
};
struct vgic_irq {
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
2025-12-12 15:22 ` [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
@ 2025-12-17 9:34 ` Marc Zyngier
2025-12-17 20:50 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 9:34 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:40 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> There are times when the default behaviour of vgic_queue_irq_unlock is
> undesirable. This is because some GICs, such a GICv5 which is the main
> driver for this change, handle the majority of the interrupt lifecycle
> in hardware. In this case, there is no need for a per-VCPU AP list as
> the interrupt can be made pending directly. This is done either via
> the ICH_PPI_x_EL2 registers for PPIs, or with the VDPEND system
> instruction for SPIs and LPIs.
>
> The queue_irq_unlock function is made overridable using a new function
> pointer in struct irq_ops. In kvm_vgic_inject_irq,
> vgic_queue_irq_unlock is overridden if the function pointer is
> non-null.
>
> Additionally, a new function is added via a function pointer -
> set_pending_state. The intent is for this to be used to directly set
> the pending state in hardware.
>
> Both of these new irq_ops are unused in this change - it is purely
> providing the infrastructure itself. The subsequent PPI injection
> changes provide a demonstration of their usage.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic.c | 9 ++++++++-
> include/kvm/arm_vgic.h | 15 +++++++++++++++
> 2 files changed, 23 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 1fe3dcc997860..fc01c6d07fe62 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -547,7 +547,14 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
> else
> irq->pending_latch = true;
>
> - vgic_queue_irq_unlock(kvm, irq, flags);
> + if (irq->ops && irq->ops->set_pending_state)
> + WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu, irq));
> +
> + if (irq->ops && irq->ops->queue_irq_unlock)
> + WARN_ON_ONCE(!irq->ops->queue_irq_unlock(kvm, irq, flags));
> + else
> + vgic_queue_irq_unlock(kvm, irq, flags);
I find it slightly dubious to WARN() in one case but not the other.
More importantly, why isn't the per-irq queue_unlock operation tucked
into the vgic_queue_irq_unlock() primitive? We have 16 call sites for
this function, and it is odd that only the injection primitive would
benefit from this.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops
2025-12-17 9:34 ` Marc Zyngier
@ 2025-12-17 20:50 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-17 20:50 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Wed, 2025-12-17 at 09:34 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:40 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > There are times when the default behaviour of vgic_queue_irq_unlock
> > is
> > undesirable. This is because some GICs, such a GICv5 which is the
> > main
> > driver for this change, handle the majority of the interrupt
> > lifecycle
> > in hardware. In this case, there is no need for a per-VCPU AP list
> > as
> > the interrupt can be made pending directly. This is done either via
> > the ICH_PPI_x_EL2 registers for PPIs, or with the VDPEND system
> > instruction for SPIs and LPIs.
> >
> > The queue_irq_unlock function is made overridable using a new
> > function
> > pointer in struct irq_ops. In kvm_vgic_inject_irq,
> > vgic_queue_irq_unlock is overridden if the function pointer is
> > non-null.
> >
> > Additionally, a new function is added via a function pointer -
> > set_pending_state. The intent is for this to be used to directly
> > set
> > the pending state in hardware.
> >
> > Both of these new irq_ops are unused in this change - it is purely
> > providing the infrastructure itself. The subsequent PPI injection
> > changes provide a demonstration of their usage.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > arch/arm64/kvm/vgic/vgic.c | 9 ++++++++-
> > include/kvm/arm_vgic.h | 15 +++++++++++++++
> > 2 files changed, 23 insertions(+), 1 deletion(-)
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index 1fe3dcc997860..fc01c6d07fe62 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -547,7 +547,14 @@ int kvm_vgic_inject_irq(struct kvm *kvm,
> > struct kvm_vcpu *vcpu,
> > else
> > irq->pending_latch = true;
> >
> > - vgic_queue_irq_unlock(kvm, irq, flags);
> > + if (irq->ops && irq->ops->set_pending_state)
> > + WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu,
> > irq));
> > +
> > + if (irq->ops && irq->ops->queue_irq_unlock)
> > + WARN_ON_ONCE(!irq->ops->queue_irq_unlock(kvm, irq,
> > flags));
> > + else
> > + vgic_queue_irq_unlock(kvm, irq, flags);
>
> I find it slightly dubious to WARN() in one case but not the other.
Yeah, noted. That goes away as I have...
>
> More importantly, why isn't the per-irq queue_unlock operation tucked
> into the vgic_queue_irq_unlock() primitive? We have 16 call sites for
> this function, and it is odd that only the injection primitive would
> benefit from this.
...now tucked this into vgic_queue_irq_unlock() as you suggest. You're
quite right in that it should be a more general thing, and it is broken
otherwise.
Thanks,
Sascha
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 19/32] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (15 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 16/32] KVM: arm64: gic: Introduce irq_queue and set_pending_state to irq_ops Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 17:13 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
` (14 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Initialise the private interrupts (PPIs, only) for GICv5. This means
that a GICv5-style intid is generated (which encodes the PPI type in
the top bits) instead of the 0-based index that is used for older
GICs.
Additionally, set all of the GICv5 PPIs to use Level for the handling
mode, with the exception of the SW_PPI which uses Edge. This matches
the architecturally-defined set in the GICv5 specification (the CTIIRQ
handling mode is IMPDEF, so pick Level has been picked for that).
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 41 +++++++++++++++++++++++-------
include/linux/irqchip/arm-gic-v5.h | 2 ++
2 files changed, 34 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index b246cb6eae71b..51f4443cebcef 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -263,13 +263,19 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
{
struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
int i;
+ u32 num_private_irqs;
+
+ if (vgic_is_v5(vcpu->kvm))
+ num_private_irqs = VGIC_V5_NR_PRIVATE_IRQS;
+ else
+ num_private_irqs = VGIC_NR_PRIVATE_IRQS;
lockdep_assert_held(&vcpu->kvm->arch.config_lock);
if (vgic_cpu->private_irqs)
return 0;
- vgic_cpu->private_irqs = kcalloc(VGIC_NR_PRIVATE_IRQS,
+ vgic_cpu->private_irqs = kcalloc(num_private_irqs,
sizeof(struct vgic_irq),
GFP_KERNEL_ACCOUNT);
@@ -280,22 +286,39 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
* Enable and configure all SGIs to be edge-triggered and
* configure all PPIs as level-triggered.
*/
- for (i = 0; i < VGIC_NR_PRIVATE_IRQS; i++) {
+ for (i = 0; i < num_private_irqs; i++) {
struct vgic_irq *irq = &vgic_cpu->private_irqs[i];
INIT_LIST_HEAD(&irq->ap_list);
raw_spin_lock_init(&irq->irq_lock);
- irq->intid = i;
irq->vcpu = NULL;
irq->target_vcpu = vcpu;
refcount_set(&irq->refcount, 0);
- if (vgic_irq_is_sgi(i)) {
- /* SGIs */
- irq->enabled = 1;
- irq->config = VGIC_CONFIG_EDGE;
+ if (!vgic_is_v5(vcpu->kvm)) {
+ irq->intid = i;
+ if (vgic_irq_is_sgi(i)) {
+ /* SGIs */
+ irq->enabled = 1;
+ irq->config = VGIC_CONFIG_EDGE;
+ } else {
+ /* PPIs */
+ irq->config = VGIC_CONFIG_LEVEL;
+ }
} else {
- /* PPIs */
- irq->config = VGIC_CONFIG_LEVEL;
+ irq->intid = i | FIELD_PREP(GICV5_HWIRQ_TYPE,
+ GICV5_HWIRQ_TYPE_PPI);
+
+ /*
+ * The only architected PPI that is Edge is
+ * the SW PPI.
+ */
+ if (irq->intid == GICV5_SW_PPI)
+ irq->config = VGIC_CONFIG_EDGE;
+ else
+ irq->config = VGIC_CONFIG_LEVEL;
+
+ /* Register the GICv5-specific PPI ops */
+ vgic_v5_set_ppi_ops(irq);
}
switch (type) {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index ff10d6c7be2ae..9607b36f021ee 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -13,6 +13,8 @@
#define GICV5_IPIS_PER_CPU MAX_IPI
+#define GICV5_SW_PPI 0x20000003
+
/*
* INTID handling
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 19/32] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5
2025-12-12 15:22 ` [PATCH 19/32] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
@ 2025-12-17 17:13 ` Marc Zyngier
0 siblings, 0 replies; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 17:13 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:41 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> Initialise the private interrupts (PPIs, only) for GICv5. This means
> that a GICv5-style intid is generated (which encodes the PPI type in
> the top bits) instead of the 0-based index that is used for older
> GICs.
>
> Additionally, set all of the GICv5 PPIs to use Level for the handling
> mode, with the exception of the SW_PPI which uses Edge. This matches
> the architecturally-defined set in the GICv5 specification (the CTIIRQ
> handling mode is IMPDEF, so pick Level has been picked for that).
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-init.c | 41 +++++++++++++++++++++++-------
> include/linux/irqchip/arm-gic-v5.h | 2 ++
> 2 files changed, 34 insertions(+), 9 deletions(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
> index b246cb6eae71b..51f4443cebcef 100644
> --- a/arch/arm64/kvm/vgic/vgic-init.c
> +++ b/arch/arm64/kvm/vgic/vgic-init.c
> @@ -263,13 +263,19 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)
> {
> struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
> int i;
> + u32 num_private_irqs;
> +
> + if (vgic_is_v5(vcpu->kvm))
> + num_private_irqs = VGIC_V5_NR_PRIVATE_IRQS;
> + else
> + num_private_irqs = VGIC_NR_PRIVATE_IRQS;
This is another case where we need to do something about
PPIs. Allocating the full complement of PPIs (all 128 of them) is
starting to be mildly visible (at 96 bytes a pop, that's 12kB of
storage per vcpu).
And 95% of that is guaranteed to be wasted... XArray anyone?
>
> lockdep_assert_held(&vcpu->kvm->arch.config_lock);
It is good practice to leave this sort of assertions at the beginning
of the function.
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (16 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 19/32] KVM: arm64: gic-v5: Init Private IRQs (PPIs) for GICv5 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 10:33 ` Marc Zyngier
2025-12-17 15:54 ` Joey Gouly
2025-12-12 15:22 ` [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
` (13 subsequent siblings)
31 siblings, 2 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This change introduces interrupt injection for PPIs for GICv5-based
guests.
The lifecycle of PPIs is largely managed by the hardware for a GICv5
system. The hypervisor injects pending state into the guest by using
the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
pick a Highest Priority Pending Interrupt (HPPI) for the guest based
on the enable state of each individual interrupt. The enable state and
priority for each interrupt are provided by the guest itself (through
writes to the PPI registers).
When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
hypervisor is even able to skip the injection of the pending state
altogether - it all happens in hardware.
The result of the above is that no AP lists are required for GICv5,
unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
fulfil the same purpose for all 128 PPIs. Hence, as long as the
ICH_PPI_* registers are populated prior to guest entry, and merged
back into the KVM shadow state on exit, the PPI state is preserved,
and interrupts can be injected.
When injecting the state of a PPI the state is merged into the KVM's
shadow state using the set_pending_state irq_op. The directly sets the
relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
the guest (and GICv5 hardware) on next guest entry. The
queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
seems the new state. The result is that no AP lists are used for
private interrupts on GICv5.
Prior to entering the guest, vgic_v5_flush_ppi_state is called from
kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
pending state (twice - an entry and an exit copy) in order to track
any changes. These changes can come from a guest consuming an
interrupt or from a guest making an Edge-triggered interrupt pending.
When returning from running a guest, the guest's PPI state is merged
back into KVM's shadow state in vgic_v5_merge_ppi_state from
kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
all PPIs, and the pending state is synced back for Edge PPIs (Level is
driven directly by the devices generating said levels). The incoming
pending state from the guest is merged with KVM's shadow state to
avoid losing any incoming interrupts.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 157 ++++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 35 ++++++--
arch/arm64/kvm/vgic/vgic.h | 49 ++++++++---
include/kvm/arm_vgic.h | 3 +
4 files changed, 226 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 22558080711eb..d54595fbf4586 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -54,6 +54,163 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+ struct vgic_irq *irq)
+{
+ struct vgic_v5_cpu_if *cpu_if;
+ const u32 id_bit = BIT_ULL(irq->intid % 64);
+ const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
+
+ if (!vcpu || !irq)
+ return false;
+
+ /* Skip injecting the state altogether */
+ if (irq->directly_injected)
+ return true;
+
+ cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ if (irq_is_pending(irq))
+ cpu_if->vgic_ppi_pendr[reg] |= id_bit;
+ else
+ cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
+
+ return true;
+}
+
+/*
+ * For GICv5, the PPIs are mostly directly managed by the hardware. We
+ * (the hypervisor) handle the pending, active, enable state
+ * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
+ * list. Therefore, sanity check the state, unlock, and return.
+ */
+static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+ unsigned long flags)
+ __releases(&irq->irq_lock)
+{
+ struct kvm_vcpu *vcpu;
+
+ lockdep_assert_held(&irq->irq_lock);
+
+ if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
+ return false;
+
+ vcpu = irq->target_vcpu;
+ if (WARN_ON_ONCE(!vcpu))
+ return false;
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+ /* Directly kick the target VCPU to make sure it sees the IRQ */
+ kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
+ kvm_vcpu_kick(vcpu);
+
+ return true;
+}
+
+static struct irq_ops vgic_v5_ppi_irq_ops = {
+ .set_pending_state = vgic_v5_ppi_set_pending_state,
+ .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
+};
+
+void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
+{
+ if (WARN_ON(!irq) || WARN_ON(irq->ops))
+ return;
+
+ irq->ops = &vgic_v5_ppi_irq_ops;
+}
+
+/*
+ * Detect any PPIs state changes, and propagate the state with KVM's
+ * shadow structures.
+ */
+static void vgic_v5_merge_ppi_state(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ unsigned long flags;
+ int i, reg;
+
+ for (reg = 0; reg < 2; reg++) {
+ unsigned long changed_bits;
+ const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
+ const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
+ const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
+
+ /*
+ * Track what changed across enabler, activer, pendr, but mask
+ * with ~DVI.
+ */
+ changed_bits = cpu_if->vgic_ich_ppi_enabler_entry[reg] ^ enabler;
+ changed_bits |= cpu_if->vgic_ppi_activer_entry[reg] ^ activer;
+ changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg] ^ pendr;
+ changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
+
+ for_each_set_bit(i, &changed_bits, 64) {
+ struct vgic_irq *irq;
+ u32 intid;
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+ irq->enabled = !!(enabler & BIT(i));
+ irq->active = !!(activer & BIT(i));
+ /* This is an OR to avoid losing incoming edges! */
+ if (irq->config == VGIC_CONFIG_EDGE)
+ irq->pending_latch |= !!(pendr & BIT(i));
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+
+ /* Re-inject the exit state as entry state next time! */
+ cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
+ cpu_if->vgic_ppi_activer_entry[reg] = activer;
+
+ /*
+ * Pending state is a bit different. We only propagate back
+ * pending state for Edge interrupts. Moreover, this is OR'd
+ * with the incoming state to make sure we don't lose incoming
+ * edges. Use the (inverse) HMR to mask off all Level bits, and
+ * OR.
+ */
+ cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if->vgic_ppi_hmr[reg];
+ }
+}
+
+void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+
+ /*
+ * We're about to enter the guest. Copy the shadow state to the pending
+ * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
+ * guest is running we track any incoming changes to the pending state in
+ * vgic_ppi_pendr. The incoming changes are merged with the outgoing
+ * changes on the return path.
+ */
+ cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
+ cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
+
+ /*
+ * Make sure that we can correctly detect "edges" in the PPI
+ * state. There's a path where we never actually enter the guest, and
+ * failure to do this risks losing pending state
+ */
+ cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
+ cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
+
+}
+
+void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu)
+{
+ /* Sync back the guest PPI state to the KVM shadow state */
+ vgic_v5_merge_ppi_state(vcpu);
+}
+
/*
* Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
*/
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index fc01c6d07fe62..e534876656ca7 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -105,6 +105,15 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
if (WARN_ON(!vcpu))
return NULL;
+ if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
+
+ if (irq_is_ppi_v5(intid)) {
+ int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
+ return &vcpu->arch.vgic_cpu.private_irqs[int_num];
+ }
+ }
+
/* SGIs and PPIs */
if (intid < VGIC_NR_PRIVATE_IRQS) {
intid = array_index_nospec(intid, VGIC_NR_PRIVATE_IRQS);
@@ -258,10 +267,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
* If the distributor is disabled, pending interrupts shouldn't be
* forwarded.
*/
- if (irq->enabled && irq_is_pending(irq)) {
- if (unlikely(irq->target_vcpu &&
- !irq->target_vcpu->kvm->arch.vgic.enabled))
- return NULL;
+ if (irq_is_enabled(irq) && irq_is_pending(irq)) {
+ if (irq->target_vcpu) {
+ if (!vgic_is_v5(irq->target_vcpu->kvm) &&
+ unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
+ return NULL;
+ }
return irq->target_vcpu;
}
@@ -1044,7 +1055,11 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
if (can_access_vgic_from_kernel())
vgic_save_state(vcpu);
- vgic_fold_lr_state(vcpu);
+ if (!vgic_is_v5(vcpu->kvm))
+ vgic_fold_lr_state(vcpu);
+ else
+ vgic_v5_fold_irq_state(vcpu);
+
vgic_prune_ap_list(vcpu);
}
@@ -1105,13 +1120,17 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
- scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
- vgic_flush_lr_state(vcpu);
+ if (!vgic_is_v5(vcpu->kvm)) {
+ scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
+ vgic_flush_lr_state(vcpu);
+ } else {
+ vgic_v5_flush_ppi_state(vcpu);
+ }
if (can_access_vgic_from_kernel())
vgic_restore_state(vcpu);
- if (vgic_supports_direct_irqs(vcpu->kvm))
+ if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))
vgic_v4_commit(vcpu);
}
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index b6e3f5e3aba18..5a77318ddb87a 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
return irq->pending_latch || irq->line_level;
}
+/* Requires the irq_lock to be held by the caller. */
+static inline bool irq_is_enabled(struct vgic_irq *irq)
+{
+ if (irq->enabled)
+ return true;
+
+ /*
+ * We always consider GICv5 interrupts as enabled as we can
+ * always inject them. The state is handled by the hardware,
+ * and the hardware will only signal the interrupt to the
+ * guest once the guest enables it.
+ */
+ if (irq->target_vcpu) {
+ u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
+
+ if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ return true;
+ }
+
+ return false;
+}
+
static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
{
return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
@@ -306,7 +328,7 @@ static inline bool vgic_try_get_irq_ref(struct vgic_irq *irq)
if (!irq)
return false;
- if (irq->intid < VGIC_MIN_LPI)
+ if (irq->target_vcpu && !irq_is_lpi(irq->target_vcpu->kvm, irq->intid))
return true;
return refcount_inc_not_zero(&irq->refcount);
@@ -363,7 +385,10 @@ void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
+void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
+void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
+void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
void vgic_v5_load(struct kvm_vcpu *vcpu);
void vgic_v5_put(struct kvm_vcpu *vcpu);
void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
@@ -432,15 +457,6 @@ void vgic_its_invalidate_all_caches(struct kvm *kvm);
int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
int vgic_its_invall(struct kvm_vcpu *vcpu);
-bool system_supports_direct_sgis(void);
-bool vgic_supports_direct_msis(struct kvm *kvm);
-bool vgic_supports_direct_sgis(struct kvm *kvm);
-
-static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
-{
- return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
-}
-
int vgic_v4_init(struct kvm *kvm);
void vgic_v4_teardown(struct kvm *kvm);
void vgic_v4_configure_vsgis(struct kvm *kvm);
@@ -485,6 +501,19 @@ static inline bool vgic_is_v5(struct kvm *kvm)
return kvm_vgic_global_state.type == VGIC_V5 && !vgic_is_v3_compat(kvm);
}
+bool system_supports_direct_sgis(void);
+bool vgic_supports_direct_msis(struct kvm *kvm);
+bool vgic_supports_direct_sgis(struct kvm *kvm);
+
+static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
+{
+ /* GICv5 always supports direct IRQs */
+ if (vgic_is_v5(kvm))
+ return true;
+
+ return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
+}
+
int vgic_its_debug_init(struct kvm_device *dev);
void vgic_its_debug_destroy(struct kvm_device *dev);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 20c908730fa00..5a46fe3c35b5c 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -32,6 +32,9 @@
#define VGIC_MIN_LPI 8192
#define KVM_IRQCHIP_NUM_PINS (1020 - 32)
+/* GICv5 constants */
+#define VGIC_V5_NR_PRIVATE_IRQS 128
+
#define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
#define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
(irq) <= VGIC_MAX_SPI)
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection
2025-12-12 15:22 ` [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
@ 2025-12-17 10:33 ` Marc Zyngier
2025-12-17 21:10 ` Sascha Bischoff
2025-12-17 15:54 ` Joey Gouly
1 sibling, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 10:33 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:41 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> This change introduces interrupt injection for PPIs for GICv5-based
> guests.
>
> The lifecycle of PPIs is largely managed by the hardware for a GICv5
> system. The hypervisor injects pending state into the guest by using
> the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> pick a Highest Priority Pending Interrupt (HPPI) for the guest based
> on the enable state of each individual interrupt. The enable state and
> priority for each interrupt are provided by the guest itself (through
> writes to the PPI registers).
>
> When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
> hypervisor is even able to skip the injection of the pending state
> altogether - it all happens in hardware.
>
> The result of the above is that no AP lists are required for GICv5,
> unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> fulfil the same purpose for all 128 PPIs. Hence, as long as the
> ICH_PPI_* registers are populated prior to guest entry, and merged
> back into the KVM shadow state on exit, the PPI state is preserved,
> and interrupts can be injected.
>
> When injecting the state of a PPI the state is merged into the KVM's
> shadow state using the set_pending_state irq_op. The directly sets the
> relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
> the guest (and GICv5 hardware) on next guest entry. The
> queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
> seems the new state. The result is that no AP lists are used for
> private interrupts on GICv5.
>
> Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> pending state (twice - an entry and an exit copy) in order to track
> any changes. These changes can come from a guest consuming an
> interrupt or from a guest making an Edge-triggered interrupt pending.
>
> When returning from running a guest, the guest's PPI state is merged
> back into KVM's shadow state in vgic_v5_merge_ppi_state from
> kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
> all PPIs, and the pending state is synced back for Edge PPIs (Level is
> driven directly by the devices generating said levels). The incoming
> pending state from the guest is merged with KVM's shadow state to
> avoid losing any incoming interrupts.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 157 ++++++++++++++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 35 ++++++--
> arch/arm64/kvm/vgic/vgic.h | 49 ++++++++---
> include/kvm/arm_vgic.h | 3 +
> 4 files changed, 226 insertions(+), 18 deletions(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 22558080711eb..d54595fbf4586 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,163 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> + struct vgic_irq *irq)
> +{
> + struct vgic_v5_cpu_if *cpu_if;
> + const u32 id_bit = BIT_ULL(irq->intid % 64);
Try that with intid==32...
> + const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
> +
> + if (!vcpu || !irq)
> + return false;
> +
> + /* Skip injecting the state altogether */
> + if (irq->directly_injected)
> + return true;
It is unclear to me under which circumstances we could end-up here.
Wouldn't that be a bug?
> +
> + cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> + if (irq_is_pending(irq))
> + cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> + else
> + cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> +
> + return true;
> +}
> +
> +/*
> + * For GICv5, the PPIs are mostly directly managed by the hardware. We
> + * (the hypervisor) handle the pending, active, enable state
> + * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
> + * list. Therefore, sanity check the state, unlock, and return.
> + */
> +static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
> + unsigned long flags)
> + __releases(&irq->irq_lock)
> +{
> + struct kvm_vcpu *vcpu;
> +
> + lockdep_assert_held(&irq->irq_lock);
> +
> + if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
> + return false;
> +
> + vcpu = irq->target_vcpu;
> + if (WARN_ON_ONCE(!vcpu))
> + return false;
Errr... You're returning with the lock held, making a bad bug even
worse!
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + /* Directly kick the target VCPU to make sure it sees the IRQ */
> + kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> + kvm_vcpu_kick(vcpu);
> +
> + return true;
> +}
> +
> +static struct irq_ops vgic_v5_ppi_irq_ops = {
> + .set_pending_state = vgic_v5_ppi_set_pending_state,
> + .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> +};
> +
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> +{
> + if (WARN_ON(!irq) || WARN_ON(irq->ops))
> + return;
> +
> + irq->ops = &vgic_v5_ppi_irq_ops;
Is there any locking requirement here?
> +}
> +
> +/*
> + * Detect any PPIs state changes, and propagate the state with KVM's
> + * shadow structures.
> + */
> +static void vgic_v5_merge_ppi_state(struct kvm_vcpu *vcpu)
Since this is only called from vgic_v5_fold_irq_state(), do yourself a
favour and call it that.
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> +
> + for (reg = 0; reg < 2; reg++) {
> + unsigned long changed_bits;
> + const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> + const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
> + const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> +
> + /*
> + * Track what changed across enabler, activer, pendr, but mask
> + * with ~DVI.
> + */
> + changed_bits = cpu_if->vgic_ich_ppi_enabler_entry[reg] ^ enabler;
> + changed_bits |= cpu_if->vgic_ppi_activer_entry[reg] ^ activer;
> + changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg] ^ pendr;
> + changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
> +
> + for_each_set_bit(i, &changed_bits, 64) {
> + struct vgic_irq *irq;
> + u32 intid;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
Consider using a scoped_guard() for this.
> + irq->enabled = !!(enabler & BIT(i));
> + irq->active = !!(activer & BIT(i));
> + /* This is an OR to avoid losing incoming edges! */
> + if (irq->config == VGIC_CONFIG_EDGE)
> + irq->pending_latch |= !!(pendr & BIT(i));
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + vgic_put_irq(vcpu->kvm, irq);
> + }
> +
> + /* Re-inject the exit state as entry state next time! */
> + cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
> + cpu_if->vgic_ppi_activer_entry[reg] = activer;
> +
> + /*
> + * Pending state is a bit different. We only propagate back
> + * pending state for Edge interrupts. Moreover, this is OR'd
> + * with the incoming state to make sure we don't lose incoming
> + * edges. Use the (inverse) HMR to mask off all Level bits, and
> + * OR.
> + */
> + cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if->vgic_ppi_hmr[reg];
> + }
> +}
> +
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> + /*
> + * We're about to enter the guest. Copy the shadow state to the pending
> + * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
> + * guest is running we track any incoming changes to the pending state in
> + * vgic_ppi_pendr. The incoming changes are merged with the outgoing
> + * changes on the return path.
> + */
> + cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
> + cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
> +
> + /*
> + * Make sure that we can correctly detect "edges" in the PPI
> + * state. There's a path where we never actually enter the guest, and
> + * failure to do this risks losing pending state
> + */
> + cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
> + cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
I find it a bit objectionable that this entry/exit stuff is kept on a
per vcpu basis. Given that we cannot be preempted between flush and
fold, the intermediate bookkeeping should probably live in a per-CPU
structure.
> +
> +}
> +
> +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu)
> +{
> + /* Sync back the guest PPI state to the KVM shadow state */
> + vgic_v5_merge_ppi_state(vcpu);
> +}
> +
> /*
> * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
> */
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index fc01c6d07fe62..e534876656ca7 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -105,6 +105,15 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
> if (WARN_ON(!vcpu))
> return NULL;
>
> + if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
> + u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
> +
> + if (irq_is_ppi_v5(intid)) {
> + int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
> + return &vcpu->arch.vgic_cpu.private_irqs[int_num];
> + }
> + }
> +
> /* SGIs and PPIs */
> if (intid < VGIC_NR_PRIVATE_IRQS) {
> intid = array_index_nospec(intid, VGIC_NR_PRIVATE_IRQS);
> @@ -258,10 +267,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
> * If the distributor is disabled, pending interrupts shouldn't be
> * forwarded.
> */
> - if (irq->enabled && irq_is_pending(irq)) {
> - if (unlikely(irq->target_vcpu &&
> - !irq->target_vcpu->kvm->arch.vgic.enabled))
> - return NULL;
> + if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> + if (irq->target_vcpu) {
> + if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> + unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
> + return NULL;
> + }
>
> return irq->target_vcpu;
> }
> @@ -1044,7 +1055,11 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
> if (can_access_vgic_from_kernel())
> vgic_save_state(vcpu);
>
> - vgic_fold_lr_state(vcpu);
> + if (!vgic_is_v5(vcpu->kvm))
> + vgic_fold_lr_state(vcpu);
> + else
> + vgic_v5_fold_irq_state(vcpu);
> +
This is gross. We already have this:
static inline void vgic_fold_lr_state(struct kvm_vcpu *vcpu)
{
if (kvm_vgic_global_state.type == VGIC_V2)
vgic_v2_fold_lr_state(vcpu);
else
vgic_v3_fold_lr_state(vcpu);
}
Rename it to vgic_fold_state(), drop the inline, make it a switch(),
and hook the v5 stuff in it.
> vgic_prune_ap_list(vcpu);
Are you actually pruning the ap_list() on v5? What is there the first place?
> }
>
> @@ -1105,13 +1120,17 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>
> DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
>
> - scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> - vgic_flush_lr_state(vcpu);
> + if (!vgic_is_v5(vcpu->kvm)) {
> + scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> + vgic_flush_lr_state(vcpu);
> + } else {
> + vgic_v5_flush_ppi_state(vcpu);
> + }
Same thing here. Move everything into the common helper, including the
lock management.
>
> if (can_access_vgic_from_kernel())
> vgic_restore_state(vcpu);
>
> - if (vgic_supports_direct_irqs(vcpu->kvm))
> + if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))
> vgic_v4_commit(vcpu);
nit: swap the two terms of the conjunction, making it more readable
(for me...). Or move the conditions into vgic_v4_commit().
> }
>
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index b6e3f5e3aba18..5a77318ddb87a 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
> return irq->pending_latch || irq->line_level;
> }
>
> +/* Requires the irq_lock to be held by the caller. */
> +static inline bool irq_is_enabled(struct vgic_irq *irq)
> +{
> + if (irq->enabled)
> + return true;
> +
> + /*
> + * We always consider GICv5 interrupts as enabled as we can
> + * always inject them. The state is handled by the hardware,
> + * and the hardware will only signal the interrupt to the
> + * guest once the guest enables it.
> + */
> + if (irq->target_vcpu) {
Under which circumstances is target_vcpu NULL for PPIs?
> + u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
> +
> + if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> + return true;
> + }
> +
> + return false;
> +}
> +
> static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
> {
> return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
> @@ -306,7 +328,7 @@ static inline bool vgic_try_get_irq_ref(struct vgic_irq *irq)
> if (!irq)
> return false;
>
> - if (irq->intid < VGIC_MIN_LPI)
> + if (irq->target_vcpu && !irq_is_lpi(irq->target_vcpu->kvm, irq->intid))
> return true;
This change is rather obscure, and doesn't do what you think it does.
What has target_vcpu to do with anything? For example, a GICv3 SPI
that has its target_vcpu set to NULL (just point its IROUTER register
to a non-existent vcpu) would end-up using the refcount path,
something that isn't expected at all.
You should make clear what the refcounting rules are for GICv5, and
use that, instead of using odd side-effects.
>
> return refcount_inc_not_zero(&irq->refcount);
> @@ -363,7 +385,10 @@ void vgic_debug_init(struct kvm *kvm);
> void vgic_debug_destroy(struct kvm *kvm);
>
> int vgic_v5_probe(const struct gic_kvm_info *info);
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
> void vgic_v5_put(struct kvm_vcpu *vcpu);
> void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
> @@ -432,15 +457,6 @@ void vgic_its_invalidate_all_caches(struct kvm *kvm);
> int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
> int vgic_its_invall(struct kvm_vcpu *vcpu);
>
> -bool system_supports_direct_sgis(void);
> -bool vgic_supports_direct_msis(struct kvm *kvm);
> -bool vgic_supports_direct_sgis(struct kvm *kvm);
> -
> -static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> -{
> - return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> -}
> -
> int vgic_v4_init(struct kvm *kvm);
> void vgic_v4_teardown(struct kvm *kvm);
> void vgic_v4_configure_vsgis(struct kvm *kvm);
> @@ -485,6 +501,19 @@ static inline bool vgic_is_v5(struct kvm *kvm)
> return kvm_vgic_global_state.type == VGIC_V5 && !vgic_is_v3_compat(kvm);
> }
>
> +bool system_supports_direct_sgis(void);
> +bool vgic_supports_direct_msis(struct kvm *kvm);
> +bool vgic_supports_direct_sgis(struct kvm *kvm);
> +
> +static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> +{
> + /* GICv5 always supports direct IRQs */
> + if (vgic_is_v5(kvm))
> + return true;
> +
> + return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> +}
> +
> int vgic_its_debug_init(struct kvm_device *dev);
> void vgic_its_debug_destroy(struct kvm_device *dev);
>
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 20c908730fa00..5a46fe3c35b5c 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -32,6 +32,9 @@
> #define VGIC_MIN_LPI 8192
> #define KVM_IRQCHIP_NUM_PINS (1020 - 32)
>
> +/* GICv5 constants */
> +#define VGIC_V5_NR_PRIVATE_IRQS 128
> +
> #define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
> #define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> (irq) <= VGIC_MAX_SPI)
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection
2025-12-17 10:33 ` Marc Zyngier
@ 2025-12-17 21:10 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-17 21:10 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Wed, 2025-12-17 at 10:33 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:41 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > This change introduces interrupt injection for PPIs for GICv5-based
> > guests.
> >
> > The lifecycle of PPIs is largely managed by the hardware for a
> > GICv5
> > system. The hypervisor injects pending state into the guest by
> > using
> > the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> > pick a Highest Priority Pending Interrupt (HPPI) for the guest
> > based
> > on the enable state of each individual interrupt. The enable state
> > and
> > priority for each interrupt are provided by the guest itself
> > (through
> > writes to the PPI registers).
> >
> > When Direct Virtual Interrupt (DVI) is set for a particular PPI,
> > the
> > hypervisor is even able to skip the injection of the pending state
> > altogether - it all happens in hardware.
> >
> > The result of the above is that no AP lists are required for GICv5,
> > unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> > fulfil the same purpose for all 128 PPIs. Hence, as long as the
> > ICH_PPI_* registers are populated prior to guest entry, and merged
> > back into the KVM shadow state on exit, the PPI state is preserved,
> > and interrupts can be injected.
> >
> > When injecting the state of a PPI the state is merged into the
> > KVM's
> > shadow state using the set_pending_state irq_op. The directly sets
> > the
> > relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented
> > to
> > the guest (and GICv5 hardware) on next guest entry. The
> > queue_irq_unlock irq_op is required to kick the vCPU to ensure that
> > it
> > seems the new state. The result is that no AP lists are used for
> > private interrupts on GICv5.
> >
> > Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> > kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> > pending state (twice - an entry and an exit copy) in order to track
> > any changes. These changes can come from a guest consuming an
> > interrupt or from a guest making an Edge-triggered interrupt
> > pending.
> >
> > When returning from running a guest, the guest's PPI state is
> > merged
> > back into KVM's shadow state in vgic_v5_merge_ppi_state from
> > kvm_vgic_sync_hwstate. The Enable and Active state is synced back
> > for
> > all PPIs, and the pending state is synced back for Edge PPIs (Level
> > is
> > driven directly by the devices generating said levels). The
> > incoming
> > pending state from the guest is merged with KVM's shadow state to
> > avoid losing any incoming interrupts.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > arch/arm64/kvm/vgic/vgic-v5.c | 157
> > ++++++++++++++++++++++++++++++++++
> > arch/arm64/kvm/vgic/vgic.c | 35 ++++++--
> > arch/arm64/kvm/vgic/vgic.h | 49 ++++++++---
> > include/kvm/arm_vgic.h | 3 +
> > 4 files changed, 226 insertions(+), 18 deletions(-)
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > b/arch/arm64/kvm/vgic/vgic-v5.c
> > index 22558080711eb..d54595fbf4586 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -54,6 +54,163 @@ int vgic_v5_probe(const struct gic_kvm_info
> > *info)
> > return 0;
> > }
> >
> > +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> > + struct vgic_irq *irq)
> > +{
> > + struct vgic_v5_cpu_if *cpu_if;
> > + const u32 id_bit = BIT_ULL(irq->intid % 64);
>
> Try that with intid==32...
Fixed!
>
> > + const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) /
> > 64;
> > +
> > + if (!vcpu || !irq)
> > + return false;
> > +
> > + /* Skip injecting the state altogether */
> > + if (irq->directly_injected)
> > + return true;
>
> It is unclear to me under which circumstances we could end-up here.
> Wouldn't that be a bug?
I guess that depends on how GICv5-aware we want things like the Arch
Timer to be. To address your first point, this has been encountered via
the kvm_timer_update_irq() path, whereby the Arch Timer injects the
current state as part of kvm_timer_vcpu_load_gic().
There are two obvious options here IMO:
1. Skip the injection in the Arch Timer for a GICv5-based guest, or
2. Skip it deeper into the vgic_v5 code to minimise the impact to other
parts of the code line (what I did here).
We already need to handle some special cases in the Arch Timer code for
different GICs, so I guess that adding one more wouldn't be too
controversial?
>
> > +
> > + cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > +
> > + if (irq_is_pending(irq))
> > + cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> > + else
> > + cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> > +
> > + return true;
> > +}
> > +
> > +/*
> > + * For GICv5, the PPIs are mostly directly managed by the
> > hardware. We
> > + * (the hypervisor) handle the pending, active, enable state
> > + * save/restore, but don't need the PPIs to be queued on a per-
> > VCPU AP
> > + * list. Therefore, sanity check the state, unlock, and return.
> > + */
> > +static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct
> > vgic_irq *irq,
> > + unsigned long flags)
> > + __releases(&irq->irq_lock)
> > +{
> > + struct kvm_vcpu *vcpu;
> > +
> > + lockdep_assert_held(&irq->irq_lock);
> > +
> > + if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
> > + return false;
> > +
> > + vcpu = irq->target_vcpu;
> > + if (WARN_ON_ONCE(!vcpu))
> > + return false;
>
> Errr... You're returning with the lock held, making a bad bug even
> worse!
Have rejigged things to avoid that for both of those returns!
>
> > +
> > + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> > +
> > + /* Directly kick the target VCPU to make sure it sees the
> > IRQ */
> > + kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> > + kvm_vcpu_kick(vcpu);
> > +
> > + return true;
> > +}
> > +
> > +static struct irq_ops vgic_v5_ppi_irq_ops = {
> > + .set_pending_state = vgic_v5_ppi_set_pending_state,
> > + .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> > +};
> > +
> > +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> > +{
> > + if (WARN_ON(!irq) || WARN_ON(irq->ops))
> > + return;
> > +
> > + irq->ops = &vgic_v5_ppi_irq_ops;
>
> Is there any locking requirement here?
Hmm, there probably should be. Right now, this is called from
vgic_allocate_private_irqs_locked(), onyl, which has the
arch.config_lock, but doesn't take any per-irq locks as it is creating
them.
I think that the most correct thing to do is to explicitly lock each
IRQ while we change the ops (scoped_guard()). It is slow, but not on
the critical path (unlike what you've pointed out for the PPI locking
elsewhere). That at least allows this to be re-used from a more generic
context if ever required.
>
> > +}
> > +
> > +/*
> > + * Detect any PPIs state changes, and propagate the state with
> > KVM's
> > + * shadow structures.
> > + */
> > +static void vgic_v5_merge_ppi_state(struct kvm_vcpu *vcpu)
>
> Since this is only called from vgic_v5_fold_irq_state(), do yourself
> a
> favour and call it that.
Done. This is an artifact of the changes yet to be posted, but for this
series that's unhelpful.
>
> > +{
> > + struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > + unsigned long flags;
> > + int i, reg;
> > +
> > + for (reg = 0; reg < 2; reg++) {
> > + unsigned long changed_bits;
> > + const unsigned long enabler = cpu_if-
> > >vgic_ich_ppi_enabler_exit[reg];
> > + const unsigned long activer = cpu_if-
> > >vgic_ppi_activer_exit[reg];
> > + const unsigned long pendr = cpu_if-
> > >vgic_ppi_pendr_exit[reg];
> > +
> > + /*
> > + * Track what changed across enabler, activer,
> > pendr, but mask
> > + * with ~DVI.
> > + */
> > + changed_bits = cpu_if-
> > >vgic_ich_ppi_enabler_entry[reg] ^ enabler;
> > + changed_bits |= cpu_if-
> > >vgic_ppi_activer_entry[reg] ^ activer;
> > + changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg]
> > ^ pendr;
> > + changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
> > +
> > + for_each_set_bit(i, &changed_bits, 64) {
> > + struct vgic_irq *irq;
> > + u32 intid;
> > +
> > + intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > GICV5_HWIRQ_TYPE_PPI);
> > + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg *
> > 64 + i);
> > +
> > + irq = vgic_get_vcpu_irq(vcpu, intid);
> > +
> > + raw_spin_lock_irqsave(&irq->irq_lock,
> > flags);
>
> Consider using a scoped_guard() for this.
Done.
>
> > + irq->enabled = !!(enabler & BIT(i));
> > + irq->active = !!(activer & BIT(i));
> > + /* This is an OR to avoid losing incoming
> > edges! */
> > + if (irq->config == VGIC_CONFIG_EDGE)
> > + irq->pending_latch |= !!(pendr &
> > BIT(i));
> > + raw_spin_unlock_irqrestore(&irq->irq_lock,
> > flags);
> > +
> > + vgic_put_irq(vcpu->kvm, irq);
> > + }
> > +
> > + /* Re-inject the exit state as entry state next
> > time! */
> > + cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
> > + cpu_if->vgic_ppi_activer_entry[reg] = activer;
> > +
> > + /*
> > + * Pending state is a bit different. We only
> > propagate back
> > + * pending state for Edge interrupts. Moreover,
> > this is OR'd
> > + * with the incoming state to make sure we don't
> > lose incoming
> > + * edges. Use the (inverse) HMR to mask off all
> > Level bits, and
> > + * OR.
> > + */
> > + cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if-
> > >vgic_ppi_hmr[reg];
> > + }
> > +}
> > +
> > +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> > +{
> > + struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > >arch.vgic_cpu.vgic_v5;
> > +
> > + /*
> > + * We're about to enter the guest. Copy the shadow state
> > to the pending
> > + * reg that will be written to the ICH_PPI_PENDRx_EL2
> > regs. While the
> > + * guest is running we track any incoming changes to the
> > pending state in
> > + * vgic_ppi_pendr. The incoming changes are merged with
> > the outgoing
> > + * changes on the return path.
> > + */
> > + cpu_if->vgic_ppi_pendr_entry[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > + cpu_if->vgic_ppi_pendr_entry[1] = cpu_if-
> > >vgic_ppi_pendr[1];
> > +
> > + /*
> > + * Make sure that we can correctly detect "edges" in the
> > PPI
> > + * state. There's a path where we never actually enter the
> > guest, and
> > + * failure to do this risks losing pending state
> > + */
> > + cpu_if->vgic_ppi_pendr_exit[0] = cpu_if-
> > >vgic_ppi_pendr[0];
> > + cpu_if->vgic_ppi_pendr_exit[1] = cpu_if-
> > >vgic_ppi_pendr[1];
>
> I find it a bit objectionable that this entry/exit stuff is kept on a
> per vcpu basis. Given that we cannot be preempted between flush and
> fold, the intermediate bookkeeping should probably live in a per-CPU
> structure.
OK, I shall try and re-work this for v2.
>
> > +
> > +}
> > +
> > +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu)
> > +{
> > + /* Sync back the guest PPI state to the KVM shadow state
> > */
> > + vgic_v5_merge_ppi_state(vcpu);
> > +}
> > +
> > /*
> > * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
> > */
> > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > b/arch/arm64/kvm/vgic/vgic.c
> > index fc01c6d07fe62..e534876656ca7 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -105,6 +105,15 @@ struct vgic_irq *vgic_get_vcpu_irq(struct
> > kvm_vcpu *vcpu, u32 intid)
> > if (WARN_ON(!vcpu))
> > return NULL;
> >
> > + if (vcpu->kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5) {
> > + u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
> > +
> > + if (irq_is_ppi_v5(intid)) {
> > + int_num = array_index_nospec(int_num,
> > VGIC_V5_NR_PRIVATE_IRQS);
> > + return &vcpu-
> > >arch.vgic_cpu.private_irqs[int_num];
> > + }
> > + }
> > +
> > /* SGIs and PPIs */
> > if (intid < VGIC_NR_PRIVATE_IRQS) {
> > intid = array_index_nospec(intid,
> > VGIC_NR_PRIVATE_IRQS);
> > @@ -258,10 +267,12 @@ struct kvm_vcpu *vgic_target_oracle(struct
> > vgic_irq *irq)
> > * If the distributor is disabled, pending interrupts
> > shouldn't be
> > * forwarded.
> > */
> > - if (irq->enabled && irq_is_pending(irq)) {
> > - if (unlikely(irq->target_vcpu &&
> > - !irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > - return NULL;
> > + if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> > + if (irq->target_vcpu) {
> > + if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> > + unlikely(!irq->target_vcpu->kvm-
> > >arch.vgic.enabled))
> > + return NULL;
> > + }
> >
> > return irq->target_vcpu;
> > }
> > @@ -1044,7 +1055,11 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu
> > *vcpu)
> > if (can_access_vgic_from_kernel())
> > vgic_save_state(vcpu);
> >
> > - vgic_fold_lr_state(vcpu);
> > + if (!vgic_is_v5(vcpu->kvm))
> > + vgic_fold_lr_state(vcpu);
> > + else
> > + vgic_v5_fold_irq_state(vcpu);
> > +
>
> This is gross. We already have this:
>
> static inline void vgic_fold_lr_state(struct kvm_vcpu *vcpu)
> {
> if (kvm_vgic_global_state.type == VGIC_V2)
> vgic_v2_fold_lr_state(vcpu);
> else
> vgic_v3_fold_lr_state(vcpu);
> }
>
> Rename it to vgic_fold_state(), drop the inline, make it a switch(),
> and hook the v5 stuff in it.
Will do, but a switch doesn't quite work. The logic is a combination of
host global_state (GICv2, GICv3, GICv5) and vgic_model (vgic_v2_,
vgic_v3, vgic_v5) which has some overlap. Alas the logic effectively
becomes:
static void vgic_fold_state(struct kvm_vcpu *vcpu)
{
if (vgic_is_v5(vcpu->kvm)) /* GICv5 native */
vgic_v5_fold_ppi_state(vcpu);
else if(kvm_vgic_global_state.type == VGIC_V2) /* GICv2 native
*/
vgic_v2_fold_lr_state(vcpu);
else /* GICv3 native or GICv2-on-GICv3 or GICv3-on-GICv5 */
vgic_v3_fold_lr_state(vcpu);
}
Please do correct me if I am misunderstanding the GICv2/GICv3 side of
things there, but the fact that v2-on-v3 and v5-on-v3 work differently
makes this sort of logic quite meh.
>
> > vgic_prune_ap_list(vcpu);
>
> Are you actually pruning the ap_list() on v5? What is there the first
> place?
There's nothing to do for GICv5. It isn't harmful to call it as it
won't do anything, but it is of course slow. I've added a check to skip
it for vgic_v5. Let me know if you've prefer me to check in
vgic_prune_ap_list() itself.
>
> > }
> >
> > @@ -1105,13 +1120,17 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu
> > *vcpu)
> >
> > DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
> >
> > - scoped_guard(raw_spinlock, &vcpu-
> > >arch.vgic_cpu.ap_list_lock)
> > - vgic_flush_lr_state(vcpu);
> > + if (!vgic_is_v5(vcpu->kvm)) {
> > + scoped_guard(raw_spinlock, &vcpu-
> > >arch.vgic_cpu.ap_list_lock)
> > + vgic_flush_lr_state(vcpu);
> > + } else {
> > + vgic_v5_flush_ppi_state(vcpu);
> > + }
>
> Same thing here. Move everything into the common helper, including
> the
> lock management.
Have moved these to a common helper.
>
> >
> > if (can_access_vgic_from_kernel())
> > vgic_restore_state(vcpu);
> >
> > - if (vgic_supports_direct_irqs(vcpu->kvm))
> > + if (vgic_supports_direct_irqs(vcpu->kvm) &&
> > !vgic_is_v5(vcpu->kvm))
> > vgic_v4_commit(vcpu);
>
> nit: swap the two terms of the conjunction, making it more readable
> (for me...). Or move the conditions into vgic_v4_commit().
Done (the former).
>
> > }
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > b/arch/arm64/kvm/vgic/vgic.h
> > index b6e3f5e3aba18..5a77318ddb87a 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct
> > vgic_irq *irq)
> > return irq->pending_latch || irq->line_level;
> > }
> >
> > +/* Requires the irq_lock to be held by the caller. */
> > +static inline bool irq_is_enabled(struct vgic_irq *irq)
> > +{
> > + if (irq->enabled)
> > + return true;
> > +
> > + /*
> > + * We always consider GICv5 interrupts as enabled as we
> > can
> > + * always inject them. The state is handled by the
> > hardware,
> > + * and the hardware will only signal the interrupt to the
> > + * guest once the guest enables it.
> > + */
> > + if (irq->target_vcpu) {
>
> Under which circumstances is target_vcpu NULL for PPIs?
It never should be with a PPI. Other interrupts types, however, might
have a NULL target_vcpu, and this is common code to all vgics. It is a
poor attempt at avoiding a null pointer.
Effectively, for GICv5 we can consider all interrupts enabled from an
injection point of view - we can always inject interrupt state. The
guest won't see it unless it has enabled the interrupt.
This definitely does need some re-working - I don't like it. I think
the main issue is that it (and I) am somewhat conflating the ability to
inject new interrupt state with the guest's enable state.
I'm more than open to ideas here (see below about potentially adding a
struct kvm* to struct vgic_irq).
>
> > + u32 vgic_model = irq->target_vcpu->kvm-
> > >arch.vgic.vgic_model;
> > +
> > + if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> > + return true;
> > + }
> > +
> > + return false;
> > +}
> > +
> > static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
> > {
> > return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
> > @@ -306,7 +328,7 @@ static inline bool vgic_try_get_irq_ref(struct
> > vgic_irq *irq)
> > if (!irq)
> > return false;
> >
> > - if (irq->intid < VGIC_MIN_LPI)
> > + if (irq->target_vcpu && !irq_is_lpi(irq->target_vcpu->kvm,
> > irq->intid))
> > return true;
>
> This change is rather obscure, and doesn't do what you think it does.
>
> What has target_vcpu to do with anything? For example, a GICv3 SPI
> that has its target_vcpu set to NULL (just point its IROUTER register
> to a non-existent vcpu) would end-up using the refcount path,
> something that isn't expected at all.
>
> You should make clear what the refcounting rules are for GICv5, and
> use that, instead of using odd side-effects.
This stems from trying to figure out the interrupt type here, which
changes based on vgic. I I'm thinking that a more sane way to handle
this and the above enable issues is to have an explicit struct kvm *kvm
in struct vgic_irq.
Absolutely agreed that I've broken what I don't understand with this
change. Thanks for catching it.
Thanks,
Sascha
>
> >
> > return refcount_inc_not_zero(&irq->refcount);
> > @@ -363,7 +385,10 @@ void vgic_debug_init(struct kvm *kvm);
> > void vgic_debug_destroy(struct kvm *kvm);
> >
> > int vgic_v5_probe(const struct gic_kvm_info *info);
> > +void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> > int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> > +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> > +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> > void vgic_v5_load(struct kvm_vcpu *vcpu);
> > void vgic_v5_put(struct kvm_vcpu *vcpu);
> > void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr
> > *vmcr);
> > @@ -432,15 +457,6 @@ void vgic_its_invalidate_all_caches(struct kvm
> > *kvm);
> > int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
> > int vgic_its_invall(struct kvm_vcpu *vcpu);
> >
> > -bool system_supports_direct_sgis(void);
> > -bool vgic_supports_direct_msis(struct kvm *kvm);
> > -bool vgic_supports_direct_sgis(struct kvm *kvm);
> > -
> > -static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> > -{
> > - return vgic_supports_direct_msis(kvm) ||
> > vgic_supports_direct_sgis(kvm);
> > -}
> > -
> > int vgic_v4_init(struct kvm *kvm);
> > void vgic_v4_teardown(struct kvm *kvm);
> > void vgic_v4_configure_vsgis(struct kvm *kvm);
> > @@ -485,6 +501,19 @@ static inline bool vgic_is_v5(struct kvm *kvm)
> > return kvm_vgic_global_state.type == VGIC_V5 &&
> > !vgic_is_v3_compat(kvm);
> > }
> >
> > +bool system_supports_direct_sgis(void);
> > +bool vgic_supports_direct_msis(struct kvm *kvm);
> > +bool vgic_supports_direct_sgis(struct kvm *kvm);
> > +
> > +static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> > +{
> > + /* GICv5 always supports direct IRQs */
> > + if (vgic_is_v5(kvm))
> > + return true;
> > +
> > + return vgic_supports_direct_msis(kvm) ||
> > vgic_supports_direct_sgis(kvm);
> > +}
> > +
> > int vgic_its_debug_init(struct kvm_device *dev);
> > void vgic_its_debug_destroy(struct kvm_device *dev);
> >
> > diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> > index 20c908730fa00..5a46fe3c35b5c 100644
> > --- a/include/kvm/arm_vgic.h
> > +++ b/include/kvm/arm_vgic.h
> > @@ -32,6 +32,9 @@
> > #define VGIC_MIN_LPI 8192
> > #define KVM_IRQCHIP_NUM_PINS (1020 - 32)
> >
> > +/* GICv5 constants */
> > +#define VGIC_V5_NR_PRIVATE_IRQS 128
> > +
> > #define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) <
> > VGIC_NR_PRIVATE_IRQS)
> > #define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> > (irq) <= VGIC_MAX_SPI)
>
> Thanks,
>
> M.
>
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection
2025-12-12 15:22 ` [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
2025-12-17 10:33 ` Marc Zyngier
@ 2025-12-17 15:54 ` Joey Gouly
1 sibling, 0 replies; 70+ messages in thread
From: Joey Gouly @ 2025-12-17 15:54 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
small comment,
On Fri, Dec 12, 2025 at 03:22:41PM +0000, Sascha Bischoff wrote:
> This change introduces interrupt injection for PPIs for GICv5-based
> guests.
>
> The lifecycle of PPIs is largely managed by the hardware for a GICv5
> system. The hypervisor injects pending state into the guest by using
> the ICH_PPI_PENDRx_EL2 registers. These are used by the hardware to
> pick a Highest Priority Pending Interrupt (HPPI) for the guest based
> on the enable state of each individual interrupt. The enable state and
> priority for each interrupt are provided by the guest itself (through
> writes to the PPI registers).
>
> When Direct Virtual Interrupt (DVI) is set for a particular PPI, the
> hypervisor is even able to skip the injection of the pending state
> altogether - it all happens in hardware.
>
> The result of the above is that no AP lists are required for GICv5,
> unlike for older GICs. Instead, for PPIs the ICH_PPI_* registers
> fulfil the same purpose for all 128 PPIs. Hence, as long as the
> ICH_PPI_* registers are populated prior to guest entry, and merged
> back into the KVM shadow state on exit, the PPI state is preserved,
> and interrupts can be injected.
>
> When injecting the state of a PPI the state is merged into the KVM's
> shadow state using the set_pending_state irq_op. The directly sets the
> relevant bit in the shadow ICH_PPI_PENDRx_EL2, which is presented to
> the guest (and GICv5 hardware) on next guest entry. The
> queue_irq_unlock irq_op is required to kick the vCPU to ensure that it
> seems the new state. The result is that no AP lists are used for
> private interrupts on GICv5.
>
> Prior to entering the guest, vgic_v5_flush_ppi_state is called from
> kvm_vgic_flush_hwstate. The effectively snapshots the shadow PPI
> pending state (twice - an entry and an exit copy) in order to track
> any changes. These changes can come from a guest consuming an
> interrupt or from a guest making an Edge-triggered interrupt pending.
>
> When returning from running a guest, the guest's PPI state is merged
> back into KVM's shadow state in vgic_v5_merge_ppi_state from
> kvm_vgic_sync_hwstate. The Enable and Active state is synced back for
> all PPIs, and the pending state is synced back for Edge PPIs (Level is
> driven directly by the devices generating said levels). The incoming
> pending state from the guest is merged with KVM's shadow state to
> avoid losing any incoming interrupts.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 157 ++++++++++++++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 35 ++++++--
> arch/arm64/kvm/vgic/vgic.h | 49 ++++++++---
> include/kvm/arm_vgic.h | 3 +
> 4 files changed, 226 insertions(+), 18 deletions(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 22558080711eb..d54595fbf4586 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,163 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> + struct vgic_irq *irq)
> +{
> + struct vgic_v5_cpu_if *cpu_if;
> + const u32 id_bit = BIT_ULL(irq->intid % 64);
> + const u32 reg = FIELD_GET(GICV5_HWIRQ_ID, irq->intid) / 64;
> +
> + if (!vcpu || !irq)
> + return false;
> +
> + /* Skip injecting the state altogether */
> + if (irq->directly_injected)
> + return true;
> +
> + cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> + if (irq_is_pending(irq))
> + cpu_if->vgic_ppi_pendr[reg] |= id_bit;
> + else
> + cpu_if->vgic_ppi_pendr[reg] &= ~id_bit;
> +
> + return true;
> +}
> +
> +/*
> + * For GICv5, the PPIs are mostly directly managed by the hardware. We
> + * (the hypervisor) handle the pending, active, enable state
> + * save/restore, but don't need the PPIs to be queued on a per-VCPU AP
> + * list. Therefore, sanity check the state, unlock, and return.
> + */
> +static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
> + unsigned long flags)
> + __releases(&irq->irq_lock)
> +{
> + struct kvm_vcpu *vcpu;
> +
> + lockdep_assert_held(&irq->irq_lock);
> +
> + if (WARN_ON_ONCE(!irq_is_ppi_v5(irq->intid)))
> + return false;
> +
> + vcpu = irq->target_vcpu;
> + if (WARN_ON_ONCE(!vcpu))
> + return false;
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + /* Directly kick the target VCPU to make sure it sees the IRQ */
> + kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> + kvm_vcpu_kick(vcpu);
> +
> + return true;
> +}
> +
> +static struct irq_ops vgic_v5_ppi_irq_ops = {
> + .set_pending_state = vgic_v5_ppi_set_pending_state,
> + .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> +};
> +
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> +{
> + if (WARN_ON(!irq) || WARN_ON(irq->ops))
> + return;
> +
> + irq->ops = &vgic_v5_ppi_irq_ops;
> +}
> +
> +/*
> + * Detect any PPIs state changes, and propagate the state with KVM's
> + * shadow structures.
> + */
> +static void vgic_v5_merge_ppi_state(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> +
> + for (reg = 0; reg < 2; reg++) {
> + unsigned long changed_bits;
> + const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> + const unsigned long activer = cpu_if->vgic_ppi_activer_exit[reg];
> + const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> +
> + /*
> + * Track what changed across enabler, activer, pendr, but mask
> + * with ~DVI.
> + */
> + changed_bits = cpu_if->vgic_ich_ppi_enabler_entry[reg] ^ enabler;
> + changed_bits |= cpu_if->vgic_ppi_activer_entry[reg] ^ activer;
> + changed_bits |= cpu_if->vgic_ppi_pendr_entry[reg] ^ pendr;
> + changed_bits &= ~cpu_if->vgic_ppi_dvir[reg];
> +
> + for_each_set_bit(i, &changed_bits, 64) {
> + struct vgic_irq *irq;
> + u32 intid;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> + irq->enabled = !!(enabler & BIT(i));
> + irq->active = !!(activer & BIT(i));
> + /* This is an OR to avoid losing incoming edges! */
> + if (irq->config == VGIC_CONFIG_EDGE)
> + irq->pending_latch |= !!(pendr & BIT(i));
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> +
> + vgic_put_irq(vcpu->kvm, irq);
> + }
> +
> + /* Re-inject the exit state as entry state next time! */
> + cpu_if->vgic_ich_ppi_enabler_entry[reg] = enabler;
> + cpu_if->vgic_ppi_activer_entry[reg] = activer;
> +
> + /*
> + * Pending state is a bit different. We only propagate back
> + * pending state for Edge interrupts. Moreover, this is OR'd
> + * with the incoming state to make sure we don't lose incoming
> + * edges. Use the (inverse) HMR to mask off all Level bits, and
> + * OR.
> + */
> + cpu_if->vgic_ppi_pendr[reg] |= pendr & ~cpu_if->vgic_ppi_hmr[reg];
> + }
> +}
> +
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> +
> + /*
> + * We're about to enter the guest. Copy the shadow state to the pending
> + * reg that will be written to the ICH_PPI_PENDRx_EL2 regs. While the
> + * guest is running we track any incoming changes to the pending state in
> + * vgic_ppi_pendr. The incoming changes are merged with the outgoing
> + * changes on the return path.
> + */
> + cpu_if->vgic_ppi_pendr_entry[0] = cpu_if->vgic_ppi_pendr[0];
> + cpu_if->vgic_ppi_pendr_entry[1] = cpu_if->vgic_ppi_pendr[1];
> +
> + /*
> + * Make sure that we can correctly detect "edges" in the PPI
> + * state. There's a path where we never actually enter the guest, and
> + * failure to do this risks losing pending state
> + */
> + cpu_if->vgic_ppi_pendr_exit[0] = cpu_if->vgic_ppi_pendr[0];
> + cpu_if->vgic_ppi_pendr_exit[1] = cpu_if->vgic_ppi_pendr[1];
> +
> +}
> +
> +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu)
> +{
> + /* Sync back the guest PPI state to the KVM shadow state */
> + vgic_v5_merge_ppi_state(vcpu);
> +}
> +
> /*
> * Sets/clears the corresponding bit in the ICH_PPI_DVIR register.
> */
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index fc01c6d07fe62..e534876656ca7 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -105,6 +105,15 @@ struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
> if (WARN_ON(!vcpu))
> return NULL;
>
> + if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
> + u32 int_num = FIELD_GET(GICV5_HWIRQ_ID, intid);
> +
> + if (irq_is_ppi_v5(intid)) {
> + int_num = array_index_nospec(int_num, VGIC_V5_NR_PRIVATE_IRQS);
> + return &vcpu->arch.vgic_cpu.private_irqs[int_num];
> + }
> + }
> +
Should the code below this be in an else {}? I don't think it will ever be true
for gic v5 since 0 is invalid for GICV5_HWIRQ_TYPE (so some high bit 29:31 will
be set), but it might be clearer?
Thanks,
Joey
> /* SGIs and PPIs */
> if (intid < VGIC_NR_PRIVATE_IRQS) {
> intid = array_index_nospec(intid, VGIC_NR_PRIVATE_IRQS);
> @@ -258,10 +267,12 @@ struct kvm_vcpu *vgic_target_oracle(struct vgic_irq *irq)
> * If the distributor is disabled, pending interrupts shouldn't be
> * forwarded.
> */
> - if (irq->enabled && irq_is_pending(irq)) {
> - if (unlikely(irq->target_vcpu &&
> - !irq->target_vcpu->kvm->arch.vgic.enabled))
> - return NULL;
> + if (irq_is_enabled(irq) && irq_is_pending(irq)) {
> + if (irq->target_vcpu) {
> + if (!vgic_is_v5(irq->target_vcpu->kvm) &&
> + unlikely(!irq->target_vcpu->kvm->arch.vgic.enabled))
> + return NULL;
> + }
>
> return irq->target_vcpu;
> }
> @@ -1044,7 +1055,11 @@ void kvm_vgic_sync_hwstate(struct kvm_vcpu *vcpu)
> if (can_access_vgic_from_kernel())
> vgic_save_state(vcpu);
>
> - vgic_fold_lr_state(vcpu);
> + if (!vgic_is_v5(vcpu->kvm))
> + vgic_fold_lr_state(vcpu);
> + else
> + vgic_v5_fold_irq_state(vcpu);
> +
> vgic_prune_ap_list(vcpu);
> }
>
> @@ -1105,13 +1120,17 @@ void kvm_vgic_flush_hwstate(struct kvm_vcpu *vcpu)
>
> DEBUG_SPINLOCK_BUG_ON(!irqs_disabled());
>
> - scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> - vgic_flush_lr_state(vcpu);
> + if (!vgic_is_v5(vcpu->kvm)) {
> + scoped_guard(raw_spinlock, &vcpu->arch.vgic_cpu.ap_list_lock)
> + vgic_flush_lr_state(vcpu);
> + } else {
> + vgic_v5_flush_ppi_state(vcpu);
> + }
>
> if (can_access_vgic_from_kernel())
> vgic_restore_state(vcpu);
>
> - if (vgic_supports_direct_irqs(vcpu->kvm))
> + if (vgic_supports_direct_irqs(vcpu->kvm) && !vgic_is_v5(vcpu->kvm))
> vgic_v4_commit(vcpu);
> }
>
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index b6e3f5e3aba18..5a77318ddb87a 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -132,6 +132,28 @@ static inline bool irq_is_pending(struct vgic_irq *irq)
> return irq->pending_latch || irq->line_level;
> }
>
> +/* Requires the irq_lock to be held by the caller. */
> +static inline bool irq_is_enabled(struct vgic_irq *irq)
> +{
> + if (irq->enabled)
> + return true;
> +
> + /*
> + * We always consider GICv5 interrupts as enabled as we can
> + * always inject them. The state is handled by the hardware,
> + * and the hardware will only signal the interrupt to the
> + * guest once the guest enables it.
> + */
> + if (irq->target_vcpu) {
> + u32 vgic_model = irq->target_vcpu->kvm->arch.vgic.vgic_model;
> +
> + if (vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
> + return true;
> + }
> +
> + return false;
> +}
> +
> static inline bool vgic_irq_is_mapped_level(struct vgic_irq *irq)
> {
> return irq->config == VGIC_CONFIG_LEVEL && irq->hw;
> @@ -306,7 +328,7 @@ static inline bool vgic_try_get_irq_ref(struct vgic_irq *irq)
> if (!irq)
> return false;
>
> - if (irq->intid < VGIC_MIN_LPI)
> + if (irq->target_vcpu && !irq_is_lpi(irq->target_vcpu->kvm, irq->intid))
> return true;
>
> return refcount_inc_not_zero(&irq->refcount);
> @@ -363,7 +385,10 @@ void vgic_debug_init(struct kvm *kvm);
> void vgic_debug_destroy(struct kvm *kvm);
>
> int vgic_v5_probe(const struct gic_kvm_info *info);
> +void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> +void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
> void vgic_v5_put(struct kvm_vcpu *vcpu);
> void vgic_v5_set_vmcr(struct kvm_vcpu *vcpu, struct vgic_vmcr *vmcr);
> @@ -432,15 +457,6 @@ void vgic_its_invalidate_all_caches(struct kvm *kvm);
> int vgic_its_inv_lpi(struct kvm *kvm, struct vgic_irq *irq);
> int vgic_its_invall(struct kvm_vcpu *vcpu);
>
> -bool system_supports_direct_sgis(void);
> -bool vgic_supports_direct_msis(struct kvm *kvm);
> -bool vgic_supports_direct_sgis(struct kvm *kvm);
> -
> -static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> -{
> - return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> -}
> -
> int vgic_v4_init(struct kvm *kvm);
> void vgic_v4_teardown(struct kvm *kvm);
> void vgic_v4_configure_vsgis(struct kvm *kvm);
> @@ -485,6 +501,19 @@ static inline bool vgic_is_v5(struct kvm *kvm)
> return kvm_vgic_global_state.type == VGIC_V5 && !vgic_is_v3_compat(kvm);
> }
>
> +bool system_supports_direct_sgis(void);
> +bool vgic_supports_direct_msis(struct kvm *kvm);
> +bool vgic_supports_direct_sgis(struct kvm *kvm);
> +
> +static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
> +{
> + /* GICv5 always supports direct IRQs */
> + if (vgic_is_v5(kvm))
> + return true;
> +
> + return vgic_supports_direct_msis(kvm) || vgic_supports_direct_sgis(kvm);
> +}
> +
> int vgic_its_debug_init(struct kvm_device *dev);
> void vgic_its_debug_destroy(struct kvm_device *dev);
>
> diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
> index 20c908730fa00..5a46fe3c35b5c 100644
> --- a/include/kvm/arm_vgic.h
> +++ b/include/kvm/arm_vgic.h
> @@ -32,6 +32,9 @@
> #define VGIC_MIN_LPI 8192
> #define KVM_IRQCHIP_NUM_PINS (1020 - 32)
>
> +/* GICv5 constants */
> +#define VGIC_V5_NR_PRIVATE_IRQS 128
> +
> #define irq_is_ppi_legacy(irq) ((irq) >= VGIC_NR_SGIS && (irq) < VGIC_NR_PRIVATE_IRQS)
> #define irq_is_spi_legacy(irq) ((irq) >= VGIC_NR_PRIVATE_IRQS && \
> (irq) <= VGIC_MAX_SPI)
> --
> 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (17 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 17/32] KVM: arm64: gic-v5: Implement PPI interrupt injection Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-17 11:49 ` Joey Gouly
2025-12-17 14:29 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 22/32] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
` (12 subsequent siblings)
31 siblings, 2 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This change allows KVM to check for pending PPI interrupts. This has
two main components:
First of all, the effective priority mask is calculated. This is a
combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
the currently running priority as determined from the VPE's
ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
the effective priority mask, it can be signalled. Otherwise, it
cannot.
Secondly, any Enabled and Pending PPIs must be checked against this
compound priority mask. The reqires the PPI priorities to by synced
back to the KVM shadow state - this is skipped in general operation as
it isn't required and is rather expensive. If any Enabled and Pending
PPIs are of sufficient priority to be signalled, then there are
pending PPIs. Else, there are not. This ensures that a VPE is not
woken when it cannot actually process the pending interrupts.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 123 ++++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 10 ++-
arch/arm64/kvm/vgic/vgic.h | 1 +
3 files changed, 131 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d54595fbf4586..35740e88b3591 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -54,6 +54,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ unsigned highest_ap, priority_mask;
+
+ /*
+ * Counting the number of trailing zeros gives the current
+ * active priority. Explicitly use the 32-bit version here as
+ * we have 32 priorities. 0x20 then means that there are no
+ * active priorities.
+ */
+ highest_ap = __builtin_ctz(cpu_if->vgic_apr);
+
+ /*
+ * An interrupt is of sufficient priority if it is equal to or
+ * greater than the priority mask. Add 1 to the priority mask
+ * (i.e., lower priority) to match the APR logic before taking
+ * the min. This gives us the lowest priority that is masked.
+ */
+ priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
+ priority_mask = min(highest_ap, priority_mask + 1);
+
+ return priority_mask;
+}
+
static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
struct vgic_irq *irq)
{
@@ -121,6 +146,104 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
irq->ops = &vgic_v5_ppi_irq_ops;
}
+
+/*
+ * Sync back the PPI priorities to the vgic_irq shadow state
+ */
+static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ unsigned long flags;
+ int i, reg;
+
+ /* We have 16 PPI Priority regs */
+ for (reg = 0; reg < 16; reg++) {
+ const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
+
+ for (i = 0; i < 8; ++i) {
+ struct vgic_irq *irq;
+ u32 intid;
+ u8 priority;
+
+ priority = (priorityr >> (i * 8)) & 0x1f;
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+
+ irq->priority = priority;
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ vgic_put_irq(vcpu->kvm, irq);
+ }
+ }
+}
+
+bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
+{
+ struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ unsigned long flags;
+ int i, reg;
+ unsigned int priority_mask;
+
+ /* If no pending bits are set, exit early */
+ if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
+ return false;
+
+ priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
+
+ /* If the combined priority mask is 0, nothing can be signalled! */
+ if (!priority_mask)
+ return false;
+
+ /* The shadow priority is only updated on demand, sync it across first */
+ vgic_v5_sync_ppi_priorities(vcpu);
+
+ for (reg = 0; reg < 2; reg++) {
+ unsigned long possible_bits;
+ const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
+ const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
+ bool has_pending = false;
+
+ /* Check all interrupts that are enabled and pending */
+ possible_bits = enabler & pendr;
+
+ /*
+ * Optimisation: pending and enabled with no active priorities
+ */
+ if (possible_bits && priority_mask > 0x1f)
+ return true;
+
+ for_each_set_bit(i, &possible_bits, 64) {
+ struct vgic_irq *irq;
+ u32 intid;
+
+ intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
+
+ irq = vgic_get_vcpu_irq(vcpu, intid);
+ raw_spin_lock_irqsave(&irq->irq_lock, flags);
+
+ /*
+ * We know that the interrupt is enabled and pending, so
+ * only check the priority.
+ */
+ if (irq->priority <= priority_mask)
+ has_pending = true;
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ vgic_put_irq(vcpu->kvm, irq);
+
+ if (has_pending)
+ return true;
+ }
+ }
+
+ return false;
+}
+
/*
* Detect any PPIs state changes, and propagate the state with KVM's
* shadow structures.
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index e534876656ca7..5d18a03cc11d5 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1174,11 +1174,15 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
unsigned long flags;
struct vgic_vmcr vmcr;
- if (!vcpu->kvm->arch.vgic.enabled)
+ if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
return false;
- if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
- return true;
+ if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ return vgic_v5_has_pending_ppi(vcpu);
+ } else {
+ if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
+ return true;
+ }
vgic_get_vmcr(vcpu, &vmcr);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 5a77318ddb87a..4b3a1e7ca3fb4 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -387,6 +387,7 @@ void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
+bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
void vgic_v5_load(struct kvm_vcpu *vcpu);
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs
2025-12-12 15:22 ` [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
@ 2025-12-17 11:49 ` Joey Gouly
2025-12-17 12:00 ` Joey Gouly
2025-12-17 14:29 ` Marc Zyngier
1 sibling, 1 reply; 70+ messages in thread
From: Joey Gouly @ 2025-12-17 11:49 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
On Fri, Dec 12, 2025 at 03:22:41PM +0000, Sascha Bischoff wrote:
> This change allows KVM to check for pending PPI interrupts. This has
> two main components:
>
> First of all, the effective priority mask is calculated. This is a
> combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
> the currently running priority as determined from the VPE's
> ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
> the effective priority mask, it can be signalled. Otherwise, it
> cannot.
>
> Secondly, any Enabled and Pending PPIs must be checked against this
> compound priority mask. The reqires the PPI priorities to by synced
> back to the KVM shadow state - this is skipped in general operation as
> it isn't required and is rather expensive. If any Enabled and Pending
> PPIs are of sufficient priority to be signalled, then there are
> pending PPIs. Else, there are not. This ensures that a VPE is not
> woken when it cannot actually process the pending interrupts.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 123 ++++++++++++++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 10 ++-
> arch/arm64/kvm/vgic/vgic.h | 1 +
> 3 files changed, 131 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index d54595fbf4586..35740e88b3591 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned highest_ap, priority_mask;
> +
> + /*
> + * Counting the number of trailing zeros gives the current
> + * active priority. Explicitly use the 32-bit version here as
> + * we have 32 priorities. 0x20 then means that there are no
> + * active priorities.
> + */
> + highest_ap = __builtin_ctz(cpu_if->vgic_apr);
__builtin_ctz(0) is undefined (https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html)
Looking at __vgic_v3_clear_highest_active_priority(), it handles that like this:
c0 = ap0 ? __ffs(ap0) : 32;
Thanks,
Joey
> +
> + /*
> + * An interrupt is of sufficient priority if it is equal to or
> + * greater than the priority mask. Add 1 to the priority mask
> + * (i.e., lower priority) to match the APR logic before taking
> + * the min. This gives us the lowest priority that is masked.
> + */
> + priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
> + priority_mask = min(highest_ap, priority_mask + 1);
> +
> + return priority_mask;
> +}
> +
> static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> struct vgic_irq *irq)
> {
> @@ -121,6 +146,104 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> irq->ops = &vgic_v5_ppi_irq_ops;
> }
>
> +
> +/*
> + * Sync back the PPI priorities to the vgic_irq shadow state
> + */
> +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> +
> + /* We have 16 PPI Priority regs */
> + for (reg = 0; reg < 16; reg++) {
> + const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
> +
> + for (i = 0; i < 8; ++i) {
> + struct vgic_irq *irq;
> + u32 intid;
> + u8 priority;
> +
> + priority = (priorityr >> (i * 8)) & 0x1f;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> +
> + irq->priority = priority;
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + vgic_put_irq(vcpu->kvm, irq);
> + }
> + }
> +}
> +
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> + unsigned int priority_mask;
> +
> + /* If no pending bits are set, exit early */
> + if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
> + return false;
> +
> + priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> +
> + /* If the combined priority mask is 0, nothing can be signalled! */
> + if (!priority_mask)
> + return false;
> +
> + /* The shadow priority is only updated on demand, sync it across first */
> + vgic_v5_sync_ppi_priorities(vcpu);
> +
> + for (reg = 0; reg < 2; reg++) {
> + unsigned long possible_bits;
> + const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> + const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> + bool has_pending = false;
> +
> + /* Check all interrupts that are enabled and pending */
> + possible_bits = enabler & pendr;
> +
> + /*
> + * Optimisation: pending and enabled with no active priorities
> + */
> + if (possible_bits && priority_mask > 0x1f)
> + return true;
> +
> + for_each_set_bit(i, &possible_bits, 64) {
> + struct vgic_irq *irq;
> + u32 intid;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> +
> + /*
> + * We know that the interrupt is enabled and pending, so
> + * only check the priority.
> + */
> + if (irq->priority <= priority_mask)
> + has_pending = true;
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + vgic_put_irq(vcpu->kvm, irq);
> +
> + if (has_pending)
> + return true;
> + }
> + }
> +
> + return false;
> +}
> +
> /*
> * Detect any PPIs state changes, and propagate the state with KVM's
> * shadow structures.
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index e534876656ca7..5d18a03cc11d5 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -1174,11 +1174,15 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
> unsigned long flags;
> struct vgic_vmcr vmcr;
>
> - if (!vcpu->kvm->arch.vgic.enabled)
> + if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
> return false;
>
> - if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> - return true;
> + if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
> + return vgic_v5_has_pending_ppi(vcpu);
> + } else {
> + if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> + return true;
> + }
>
> vgic_get_vmcr(vcpu, &vmcr);
>
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 5a77318ddb87a..4b3a1e7ca3fb4 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -387,6 +387,7 @@ void vgic_debug_destroy(struct kvm *kvm);
> int vgic_v5_probe(const struct gic_kvm_info *info);
> void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
> void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
> --
> 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs
2025-12-17 11:49 ` Joey Gouly
@ 2025-12-17 12:00 ` Joey Gouly
2025-12-18 8:17 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Joey Gouly @ 2025-12-17 12:00 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Wed, Dec 17, 2025 at 11:49:32AM +0000, Joey Gouly wrote:
> Hi Sascha,
>
> On Fri, Dec 12, 2025 at 03:22:41PM +0000, Sascha Bischoff wrote:
> > This change allows KVM to check for pending PPI interrupts. This has
> > two main components:
> >
> > First of all, the effective priority mask is calculated. This is a
> > combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
> > the currently running priority as determined from the VPE's
> > ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
> > the effective priority mask, it can be signalled. Otherwise, it
> > cannot.
> >
> > Secondly, any Enabled and Pending PPIs must be checked against this
> > compound priority mask. The reqires the PPI priorities to by synced
> > back to the KVM shadow state - this is skipped in general operation as
> > it isn't required and is rather expensive. If any Enabled and Pending
> > PPIs are of sufficient priority to be signalled, then there are
> > pending PPIs. Else, there are not. This ensures that a VPE is not
> > woken when it cannot actually process the pending interrupts.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > arch/arm64/kvm/vgic/vgic-v5.c | 123 ++++++++++++++++++++++++++++++++++
> > arch/arm64/kvm/vgic/vgic.c | 10 ++-
> > arch/arm64/kvm/vgic/vgic.h | 1 +
> > 3 files changed, 131 insertions(+), 3 deletions(-)
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> > index d54595fbf4586..35740e88b3591 100644
> > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > @@ -54,6 +54,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> > return 0;
> > }
> >
> > +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> > +{
> > + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > + unsigned highest_ap, priority_mask;
> > +
> > + /*
> > + * Counting the number of trailing zeros gives the current
> > + * active priority. Explicitly use the 32-bit version here as
> > + * we have 32 priorities. 0x20 then means that there are no
> > + * active priorities.
> > + */
> > + highest_ap = __builtin_ctz(cpu_if->vgic_apr);
>
> __builtin_ctz(0) is undefined (https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html)
>
> Looking at __vgic_v3_clear_highest_active_priority(), it handles that like this:
>
> c0 = ap0 ? __ffs(ap0) : 32;
Sorry forgot ffs() was 1-based, so:
highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if->vgic_apr) : 32;
Thanks,
Joey
>
> Thanks,
> Joey
>
> > +
> > + /*
> > + * An interrupt is of sufficient priority if it is equal to or
> > + * greater than the priority mask. Add 1 to the priority mask
> > + * (i.e., lower priority) to match the APR logic before taking
> > + * the min. This gives us the lowest priority that is masked.
> > + */
> > + priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
> > + priority_mask = min(highest_ap, priority_mask + 1);
> > +
> > + return priority_mask;
> > +}
> > +
> > static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> > struct vgic_irq *irq)
> > {
> > @@ -121,6 +146,104 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> > irq->ops = &vgic_v5_ppi_irq_ops;
> > }
> >
> > +
> > +/*
> > + * Sync back the PPI priorities to the vgic_irq shadow state
> > + */
> > +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> > +{
> > + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > + unsigned long flags;
> > + int i, reg;
> > +
> > + /* We have 16 PPI Priority regs */
> > + for (reg = 0; reg < 16; reg++) {
> > + const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
> > +
> > + for (i = 0; i < 8; ++i) {
> > + struct vgic_irq *irq;
> > + u32 intid;
> > + u8 priority;
> > +
> > + priority = (priorityr >> (i * 8)) & 0x1f;
> > +
> > + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> > + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
> > +
> > + irq = vgic_get_vcpu_irq(vcpu, intid);
> > + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> > +
> > + irq->priority = priority;
> > +
> > + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> > + vgic_put_irq(vcpu->kvm, irq);
> > + }
> > + }
> > +}
> > +
> > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> > +{
> > + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> > + unsigned long flags;
> > + int i, reg;
> > + unsigned int priority_mask;
> > +
> > + /* If no pending bits are set, exit early */
> > + if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
> > + return false;
> > +
> > + priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> > +
> > + /* If the combined priority mask is 0, nothing can be signalled! */
> > + if (!priority_mask)
> > + return false;
> > +
> > + /* The shadow priority is only updated on demand, sync it across first */
> > + vgic_v5_sync_ppi_priorities(vcpu);
> > +
> > + for (reg = 0; reg < 2; reg++) {
> > + unsigned long possible_bits;
> > + const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> > + const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> > + bool has_pending = false;
> > +
> > + /* Check all interrupts that are enabled and pending */
> > + possible_bits = enabler & pendr;
> > +
> > + /*
> > + * Optimisation: pending and enabled with no active priorities
> > + */
> > + if (possible_bits && priority_mask > 0x1f)
> > + return true;
> > +
> > + for_each_set_bit(i, &possible_bits, 64) {
> > + struct vgic_irq *irq;
> > + u32 intid;
> > +
> > + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> > + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> > +
> > + irq = vgic_get_vcpu_irq(vcpu, intid);
> > + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> > +
> > + /*
> > + * We know that the interrupt is enabled and pending, so
> > + * only check the priority.
> > + */
> > + if (irq->priority <= priority_mask)
> > + has_pending = true;
> > +
> > + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> > + vgic_put_irq(vcpu->kvm, irq);
> > +
> > + if (has_pending)
> > + return true;
> > + }
> > + }
> > +
> > + return false;
> > +}
> > +
> > /*
> > * Detect any PPIs state changes, and propagate the state with KVM's
> > * shadow structures.
> > diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> > index e534876656ca7..5d18a03cc11d5 100644
> > --- a/arch/arm64/kvm/vgic/vgic.c
> > +++ b/arch/arm64/kvm/vgic/vgic.c
> > @@ -1174,11 +1174,15 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
> > unsigned long flags;
> > struct vgic_vmcr vmcr;
> >
> > - if (!vcpu->kvm->arch.vgic.enabled)
> > + if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
> > return false;
> >
> > - if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> > - return true;
> > + if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
> > + return vgic_v5_has_pending_ppi(vcpu);
> > + } else {
> > + if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> > + return true;
> > + }
> >
> > vgic_get_vmcr(vcpu, &vmcr);
> >
> > diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> > index 5a77318ddb87a..4b3a1e7ca3fb4 100644
> > --- a/arch/arm64/kvm/vgic/vgic.h
> > +++ b/arch/arm64/kvm/vgic/vgic.h
> > @@ -387,6 +387,7 @@ void vgic_debug_destroy(struct kvm *kvm);
> > int vgic_v5_probe(const struct gic_kvm_info *info);
> > void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> > int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
> > void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> > void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> > void vgic_v5_load(struct kvm_vcpu *vcpu);
> > --
> > 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs
2025-12-17 12:00 ` Joey Gouly
@ 2025-12-18 8:17 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-18 8:17 UTC (permalink / raw)
To: Joey Gouly
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
lpieralisi@kernel.org, maz@kernel.org, oliver.upton@linux.dev
On Wed, 2025-12-17 at 12:00 +0000, Joey Gouly wrote:
> On Wed, Dec 17, 2025 at 11:49:32AM +0000, Joey Gouly wrote:
> > Hi Sascha,
> >
> > On Fri, Dec 12, 2025 at 03:22:41PM +0000, Sascha Bischoff wrote:
> > > This change allows KVM to check for pending PPI interrupts. This
> > > has
> > > two main components:
> > >
> > > First of all, the effective priority mask is calculated. This is
> > > a
> > > combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY
> > > and
> > > the currently running priority as determined from the VPE's
> > > ICH_APR_EL1. If an interrupt's prioirity is greater than or equal
> > > to
> > > the effective priority mask, it can be signalled. Otherwise, it
> > > cannot.
> > >
> > > Secondly, any Enabled and Pending PPIs must be checked against
> > > this
> > > compound priority mask. The reqires the PPI priorities to by
> > > synced
> > > back to the KVM shadow state - this is skipped in general
> > > operation as
> > > it isn't required and is rather expensive. If any Enabled and
> > > Pending
> > > PPIs are of sufficient priority to be signalled, then there are
> > > pending PPIs. Else, there are not. This ensures that a VPE is
> > > not
> > > woken when it cannot actually process the pending interrupts.
> > >
> > > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > > ---
> > > arch/arm64/kvm/vgic/vgic-v5.c | 123
> > > ++++++++++++++++++++++++++++++++++
> > > arch/arm64/kvm/vgic/vgic.c | 10 ++-
> > > arch/arm64/kvm/vgic/vgic.h | 1 +
> > > 3 files changed, 131 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/arch/arm64/kvm/vgic/vgic-v5.c
> > > b/arch/arm64/kvm/vgic/vgic-v5.c
> > > index d54595fbf4586..35740e88b3591 100644
> > > --- a/arch/arm64/kvm/vgic/vgic-v5.c
> > > +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> > > @@ -54,6 +54,31 @@ int vgic_v5_probe(const struct gic_kvm_info
> > > *info)
> > > return 0;
> > > }
> > >
> > > +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu
> > > *vcpu)
> > > +{
> > > + struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > > >arch.vgic_cpu.vgic_v5;
> > > + unsigned highest_ap, priority_mask;
> > > +
> > > + /*
> > > + * Counting the number of trailing zeros gives the
> > > current
> > > + * active priority. Explicitly use the 32-bit version
> > > here as
> > > + * we have 32 priorities. 0x20 then means that there are
> > > no
> > > + * active priorities.
> > > + */
> > > + highest_ap = __builtin_ctz(cpu_if->vgic_apr);
> >
> > __builtin_ctz(0) is undefined
> > (https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html)
> >
> > Looking at __vgic_v3_clear_highest_active_priority(), it handles
> > that like this:
> >
> > c0 = ap0 ? __ffs(ap0) : 32;
>
> Sorry forgot ffs() was 1-based, so:
>
> highest_ap = cpu_if->vgic_apr ? __builtin_ctz(cpu_if-
> >vgic_apr) : 32;
>
> Thanks,
> Joey
Thanks. Have made this change as I think it addresses Marc's concern
too!
Sascha
>
> >
> > Thanks,
> > Joey
> >
> > > +
> > > + /*
> > > + * An interrupt is of sufficient priority if it is equal
> > > to or
> > > + * greater than the priority mask. Add 1 to the priority
> > > mask
> > > + * (i.e., lower priority) to match the APR logic before
> > > taking
> > > + * the min. This gives us the lowest priority that is
> > > masked.
> > > + */
> > > + priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR,
> > > cpu_if->vgic_vmcr);
> > > + priority_mask = min(highest_ap, priority_mask + 1);
> > > +
> > > + return priority_mask;
> > > +}
> > > +
> > > static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> > > struct vgic_irq *irq)
> > > {
> > > @@ -121,6 +146,104 @@ void vgic_v5_set_ppi_ops(struct vgic_irq
> > > *irq)
> > > irq->ops = &vgic_v5_ppi_irq_ops;
> > > }
> > >
> > > +
> > > +/*
> > > + * Sync back the PPI priorities to the vgic_irq shadow state
> > > + */
> > > +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> > > +{
> > > + struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > > >arch.vgic_cpu.vgic_v5;
> > > + unsigned long flags;
> > > + int i, reg;
> > > +
> > > + /* We have 16 PPI Priority regs */
> > > + for (reg = 0; reg < 16; reg++) {
> > > + const unsigned long priorityr = cpu_if-
> > > >vgic_ppi_priorityr[reg];
> > > +
> > > + for (i = 0; i < 8; ++i) {
> > > + struct vgic_irq *irq;
> > > + u32 intid;
> > > + u8 priority;
> > > +
> > > + priority = (priorityr >> (i * 8)) &
> > > 0x1f;
> > > +
> > > + intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > > GICV5_HWIRQ_TYPE_PPI);
> > > + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg
> > > * 8 + i);
> > > +
> > > + irq = vgic_get_vcpu_irq(vcpu, intid);
> > > + raw_spin_lock_irqsave(&irq->irq_lock,
> > > flags);
> > > +
> > > + irq->priority = priority;
> > > +
> > > + raw_spin_unlock_irqrestore(&irq-
> > > >irq_lock, flags);
> > > + vgic_put_irq(vcpu->kvm, irq);
> > > + }
> > > + }
> > > +}
> > > +
> > > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> > > +{
> > > + struct vgic_v5_cpu_if *cpu_if = &vcpu-
> > > >arch.vgic_cpu.vgic_v5;
> > > + unsigned long flags;
> > > + int i, reg;
> > > + unsigned int priority_mask;
> > > +
> > > + /* If no pending bits are set, exit early */
> > > + if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if-
> > > >vgic_ppi_pendr[1]))
> > > + return false;
> > > +
> > > + priority_mask =
> > > vgic_v5_get_effective_priority_mask(vcpu);
> > > +
> > > + /* If the combined priority mask is 0, nothing can be
> > > signalled! */
> > > + if (!priority_mask)
> > > + return false;
> > > +
> > > + /* The shadow priority is only updated on demand, sync
> > > it across first */
> > > + vgic_v5_sync_ppi_priorities(vcpu);
> > > +
> > > + for (reg = 0; reg < 2; reg++) {
> > > + unsigned long possible_bits;
> > > + const unsigned long enabler = cpu_if-
> > > >vgic_ich_ppi_enabler_exit[reg];
> > > + const unsigned long pendr = cpu_if-
> > > >vgic_ppi_pendr_exit[reg];
> > > + bool has_pending = false;
> > > +
> > > + /* Check all interrupts that are enabled and
> > > pending */
> > > + possible_bits = enabler & pendr;
> > > +
> > > + /*
> > > + * Optimisation: pending and enabled with no
> > > active priorities
> > > + */
> > > + if (possible_bits && priority_mask > 0x1f)
> > > + return true;
> > > +
> > > + for_each_set_bit(i, &possible_bits, 64) {
> > > + struct vgic_irq *irq;
> > > + u32 intid;
> > > +
> > > + intid = FIELD_PREP(GICV5_HWIRQ_TYPE,
> > > GICV5_HWIRQ_TYPE_PPI);
> > > + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg
> > > * 64 + i);
> > > +
> > > + irq = vgic_get_vcpu_irq(vcpu, intid);
> > > + raw_spin_lock_irqsave(&irq->irq_lock,
> > > flags);
> > > +
> > > + /*
> > > + * We know that the interrupt is enabled
> > > and pending, so
> > > + * only check the priority.
> > > + */
> > > + if (irq->priority <= priority_mask)
> > > + has_pending = true;
> > > +
> > > + raw_spin_unlock_irqrestore(&irq-
> > > >irq_lock, flags);
> > > + vgic_put_irq(vcpu->kvm, irq);
> > > +
> > > + if (has_pending)
> > > + return true;
> > > + }
> > > + }
> > > +
> > > + return false;
> > > +}
> > > +
> > > /*
> > > * Detect any PPIs state changes, and propagate the state with
> > > KVM's
> > > * shadow structures.
> > > diff --git a/arch/arm64/kvm/vgic/vgic.c
> > > b/arch/arm64/kvm/vgic/vgic.c
> > > index e534876656ca7..5d18a03cc11d5 100644
> > > --- a/arch/arm64/kvm/vgic/vgic.c
> > > +++ b/arch/arm64/kvm/vgic/vgic.c
> > > @@ -1174,11 +1174,15 @@ int kvm_vgic_vcpu_pending_irq(struct
> > > kvm_vcpu *vcpu)
> > > unsigned long flags;
> > > struct vgic_vmcr vmcr;
> > >
> > > - if (!vcpu->kvm->arch.vgic.enabled)
> > > + if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu-
> > > >kvm))
> > > return false;
> > >
> > > - if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> > > - return true;
> > > + if (vcpu->kvm->arch.vgic.vgic_model ==
> > > KVM_DEV_TYPE_ARM_VGIC_V5) {
> > > + return vgic_v5_has_pending_ppi(vcpu);
> > > + } else {
> > > + if (vcpu-
> > > >arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> > > + return true;
> > > + }
> > >
> > > vgic_get_vmcr(vcpu, &vmcr);
> > >
> > > diff --git a/arch/arm64/kvm/vgic/vgic.h
> > > b/arch/arm64/kvm/vgic/vgic.h
> > > index 5a77318ddb87a..4b3a1e7ca3fb4 100644
> > > --- a/arch/arm64/kvm/vgic/vgic.h
> > > +++ b/arch/arm64/kvm/vgic/vgic.h
> > > @@ -387,6 +387,7 @@ void vgic_debug_destroy(struct kvm *kvm);
> > > int vgic_v5_probe(const struct gic_kvm_info *info);
> > > void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> > > int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool
> > > dvi);
> > > +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
> > > void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> > > void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> > > void vgic_v5_load(struct kvm_vcpu *vcpu);
> > > --
> > > 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs
2025-12-12 15:22 ` [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
2025-12-17 11:49 ` Joey Gouly
@ 2025-12-17 14:29 ` Marc Zyngier
1 sibling, 0 replies; 70+ messages in thread
From: Marc Zyngier @ 2025-12-17 14:29 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:41 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> This change allows KVM to check for pending PPI interrupts. This has
> two main components:
>
> First of all, the effective priority mask is calculated. This is a
> combination of the priority mask in the VPEs ICC_PCR_EL1.PRIORITY and
> the currently running priority as determined from the VPE's
> ICH_APR_EL1. If an interrupt's prioirity is greater than or equal to
> the effective priority mask, it can be signalled. Otherwise, it
> cannot.
>
> Secondly, any Enabled and Pending PPIs must be checked against this
> compound priority mask. The reqires the PPI priorities to by synced
> back to the KVM shadow state - this is skipped in general operation as
> it isn't required and is rather expensive. If any Enabled and Pending
> PPIs are of sufficient priority to be signalled, then there are
> pending PPIs. Else, there are not. This ensures that a VPE is not
> woken when it cannot actually process the pending interrupts.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/vgic/vgic-v5.c | 123 ++++++++++++++++++++++++++++++++++
> arch/arm64/kvm/vgic/vgic.c | 10 ++-
> arch/arm64/kvm/vgic/vgic.h | 1 +
> 3 files changed, 131 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index d54595fbf4586..35740e88b3591 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -54,6 +54,31 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
> return 0;
> }
>
> +static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned highest_ap, priority_mask;
Please use explicit types that match their assignment.
> +
> + /*
> + * Counting the number of trailing zeros gives the current
> + * active priority. Explicitly use the 32-bit version here as
> + * we have 32 priorities. 0x20 then means that there are no
> + * active priorities.
> + */
> + highest_ap = __builtin_ctz(cpu_if->vgic_apr);
From https://gcc.gnu.org/onlinedocs/gcc/Bit-Operation-Builtins.html
<quote>
Built-in Function: int __builtin_ctz (unsigned int x)
Returns the number of trailing 0-bits in x, starting at the least
significant bit position. If x is 0, the result is undefined.
</quote>
We really don't like undefined results.
> +
> + /*
> + * An interrupt is of sufficient priority if it is equal to or
> + * greater than the priority mask. Add 1 to the priority mask
> + * (i.e., lower priority) to match the APR logic before taking
> + * the min. This gives us the lowest priority that is masked.
> + */
> + priority_mask = FIELD_GET(FEAT_GCIE_ICH_VMCR_EL2_VPMR, cpu_if->vgic_vmcr);
> + priority_mask = min(highest_ap, priority_mask + 1);
> +
> + return priority_mask;
> +}
> +
> static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
> struct vgic_irq *irq)
> {
> @@ -121,6 +146,104 @@ void vgic_v5_set_ppi_ops(struct vgic_irq *irq)
> irq->ops = &vgic_v5_ppi_irq_ops;
> }
>
> +
> +/*
> + * Sync back the PPI priorities to the vgic_irq shadow state
> + */
> +static void vgic_v5_sync_ppi_priorities(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> +
> + /* We have 16 PPI Priority regs */
> + for (reg = 0; reg < 16; reg++) {
> + const unsigned long priorityr = cpu_if->vgic_ppi_priorityr[reg];
> +
> + for (i = 0; i < 8; ++i) {
Urgh... 128 locks being taken is no good. We need something better.
> + struct vgic_irq *irq;
> + u32 intid;
> + u8 priority;
> +
> + priority = (priorityr >> (i * 8)) & 0x1f;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 8 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> +
> + irq->priority = priority;
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
scoped_guard()
> + vgic_put_irq(vcpu->kvm, irq);
> + }
> + }
> +}
> +
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> + unsigned long flags;
> + int i, reg;
> + unsigned int priority_mask;
> +
> + /* If no pending bits are set, exit early */
> + if (likely(!cpu_if->vgic_ppi_pendr[0] && !cpu_if->vgic_ppi_pendr[1]))
> + return false;
> +
> + priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
> +
> + /* If the combined priority mask is 0, nothing can be signalled! */
> + if (!priority_mask)
> + return false;
> +
> + /* The shadow priority is only updated on demand, sync it across first */
> + vgic_v5_sync_ppi_priorities(vcpu);
> +
> + for (reg = 0; reg < 2; reg++) {
> + unsigned long possible_bits;
> + const unsigned long enabler = cpu_if->vgic_ich_ppi_enabler_exit[reg];
> + const unsigned long pendr = cpu_if->vgic_ppi_pendr_exit[reg];
> + bool has_pending = false;
> +
> + /* Check all interrupts that are enabled and pending */
> + possible_bits = enabler & pendr;
> +
> + /*
> + * Optimisation: pending and enabled with no active priorities
> + */
> + if (possible_bits && priority_mask > 0x1f)
> + return true;
> +
> + for_each_set_bit(i, &possible_bits, 64) {
> + struct vgic_irq *irq;
> + u32 intid;
> +
> + intid = FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
> + intid |= FIELD_PREP(GICV5_HWIRQ_ID, reg * 64 + i);
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> + raw_spin_lock_irqsave(&irq->irq_lock, flags);
> +
> + /*
> + * We know that the interrupt is enabled and pending, so
> + * only check the priority.
> + */
> + if (irq->priority <= priority_mask)
> + has_pending = true;
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + vgic_put_irq(vcpu->kvm, irq);
> +
> + if (has_pending)
> + return true;
> + }
> + }
So we do this stuff *twice*. Doesn't strike me as being optimal. It is
also not clear that we need to resync it all when calling
kvm_vgic_vcpu_pending_irq(), which can happen for any odd reason
(spurious wake-up from kvm_vcpu_check_block()).
> +
> + return false;
> +}
> +
> /*
> * Detect any PPIs state changes, and propagate the state with KVM's
> * shadow structures.
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index e534876656ca7..5d18a03cc11d5 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -1174,11 +1174,15 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
> unsigned long flags;
> struct vgic_vmcr vmcr;
>
> - if (!vcpu->kvm->arch.vgic.enabled)
> + if (!vcpu->kvm->arch.vgic.enabled && !vgic_is_v5(vcpu->kvm))
> return false;
>
> - if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> - return true;
> + if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
> + return vgic_v5_has_pending_ppi(vcpu);
> + } else {
Drop the 'else'.
> + if (vcpu->arch.vgic_cpu.vgic_v3.its_vpe.pending_last)
> + return true;
> + }
>
> vgic_get_vmcr(vcpu, &vmcr);
>
> diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
> index 5a77318ddb87a..4b3a1e7ca3fb4 100644
> --- a/arch/arm64/kvm/vgic/vgic.h
> +++ b/arch/arm64/kvm/vgic/vgic.h
> @@ -387,6 +387,7 @@ void vgic_debug_destroy(struct kvm *kvm);
> int vgic_v5_probe(const struct gic_kvm_info *info);
> void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
> int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
> +bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
> void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
> void vgic_v5_fold_irq_state(struct kvm_vcpu *vcpu);
> void vgic_v5_load(struct kvm_vcpu *vcpu);
Thanks,
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 22/32] KVM: arm64: gic-v5: Reset vcpu state
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (18 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 18/32] KVM: arm64: gic-v5: Check for pending PPIs Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 20/32] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
` (11 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Limit the number of ID and priority bits supported based on the
hardware capabilities when resetting the vcpu state. Additionally,
calculate the PPI HMR representation for the vcpu. This is presented
to the guest when it accesses the ICC_PPI_HMRx_EL1 register (by
trapping and emulating the access).
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 6 +++-
arch/arm64/kvm/vgic/vgic-v5.c | 63 ++++++++++++++++++++++++++++++++-
arch/arm64/kvm/vgic/vgic.h | 1 +
3 files changed, 68 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 69e8746516799..120f28b329738 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -396,7 +396,11 @@ int kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
static void kvm_vgic_vcpu_reset(struct kvm_vcpu *vcpu)
{
- if (kvm_vgic_global_state.type == VGIC_V2)
+ const struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
+
+ if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ vgic_v5_reset(vcpu);
+ else if (kvm_vgic_global_state.type == VGIC_V2)
vgic_v2_reset(vcpu);
else
vgic_v3_reset(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 3567754ae1459..a3d52ce066869 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -54,6 +54,45 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+static void vgic_v5_construct_hmrs(struct kvm_vcpu *vcpu);
+
+void vgic_v5_reset(struct kvm_vcpu *vcpu)
+{
+ u64 idr0;
+
+ idr0 = read_sysreg_s(SYS_ICC_IDR0_EL1);
+ switch (FIELD_GET(ICC_IDR0_EL1_ID_BITS, idr0)) {
+ case ICC_IDR0_EL1_ID_BITS_16BITS:
+ vcpu->arch.vgic_cpu.num_id_bits = 16;
+ break;
+ case ICC_IDR0_EL1_ID_BITS_24BITS:
+ vcpu->arch.vgic_cpu.num_id_bits = 24;
+ break;
+ default:
+ pr_warn("unknown value for id_bits");
+ vcpu->arch.vgic_cpu.num_id_bits = 16;
+ }
+
+ switch (FIELD_GET(ICC_IDR0_EL1_PRI_BITS, idr0)) {
+ case ICC_IDR0_EL1_PRI_BITS_4BITS:
+ vcpu->arch.vgic_cpu.num_pri_bits = 4;
+ break;
+ case ICC_IDR0_EL1_PRI_BITS_5BITS:
+ vcpu->arch.vgic_cpu.num_pri_bits = 5;
+ break;
+ default:
+ pr_warn("unknown value for priority_bits");
+ vcpu->arch.vgic_cpu.num_pri_bits = 4;
+ }
+
+ /*
+ * We're now ready to run this VCPU so no more changes to the
+ * PPI config are expected.
+ */
+ vgic_v5_construct_hmrs(vcpu);
+
+}
+
int vgic_v5_init(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
@@ -105,8 +144,30 @@ static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
return priority_mask;
}
+static void vgic_v5_construct_hmrs(struct kvm_vcpu *vcpu)
+{
+ /*
+ * Calculate the PPI HMR to present to the guest (and for
+ * internal interrupt masking).
+ */
+ vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[0] = 0;
+ vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[1] = 0;
+ for (int i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; ++i) {
+ int reg = i / 64;
+ u64 bit = BIT_ULL(i % 64);
+ struct vgic_irq *irq = &vcpu->arch.vgic_cpu.private_irqs[i];
+
+ raw_spin_lock(&irq->irq_lock);
+
+ if (irq->config == VGIC_CONFIG_LEVEL)
+ vcpu->arch.vgic_cpu.vgic_v5.vgic_ppi_hmr[reg] |= bit;
+
+ raw_spin_unlock(&irq->irq_lock);
+ }
+}
+
static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
- struct vgic_irq *irq)
+ struct vgic_irq *irq)
{
struct vgic_v5_cpu_if *cpu_if;
const u32 id_bit = BIT_ULL(irq->intid % 64);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 66698973b2872..91969b3b80d04 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -385,6 +385,7 @@ void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
+void vgic_v5_reset(struct kvm_vcpu *vcpu);
int vgic_v5_init(struct kvm *kvm);
int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 20/32] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (19 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 22/32] KVM: arm64: gic-v5: Reset vcpu state Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 21/32] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
` (10 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Interrupts under GICv5 look quite different to those from older Arm
GICs. Specifically, the type is encoded in the top bits of the
interrupt ID.
Extend KVM_IRQ_LINE to cope with GICv5 PPIs and SPIs. The requires
subtly changing the KVM_IRQ_LINE API for GICv5 guests. For older Arm
GICs, PPIs had to be in the range of 16-31, and SPIs had to be
32-1019, but this no longer holds true for GICv5. Instead, for a GICv5
guest support PPIs in the range of 0-127, and SPIs in the range
0-65535. The documentation is updated accordingly.
The SPI range doesn't cover the full SPI range that a GICv5 system can
potentially cope with (GICv5 provides up to 24-bits of SPI ID space,
and we only have 16 bits to work with in KVM_IRQ_LINE). However, 65k
SPIs is more than would be reasonably expected on systems for years to
come.
Note: As the GICv5 KVM implementation currently doesn't support
injecting SPIs attempts to do so will fail. This restruction will
lifted as the GICv5 KVM support evolves.
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>
---
Documentation/virt/kvm/api.rst | 6 ++++--
arch/arm64/kvm/arm.c | 21 ++++++++++++++++++---
arch/arm64/kvm/vgic/vgic.c | 4 ++++
3 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 01a3abef8abb9..460a5511ebcec 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -907,10 +907,12 @@ The irq_type field has the following values:
- KVM_ARM_IRQ_TYPE_CPU:
out-of-kernel GIC: irq_id 0 is IRQ, irq_id 1 is FIQ
- KVM_ARM_IRQ_TYPE_SPI:
- in-kernel GIC: SPI, irq_id between 32 and 1019 (incl.)
+ in-kernel GICv2/GICv3: SPI, irq_id between 32 and 1019 (incl.)
(the vcpu_index field is ignored)
+ in-kernel GICv5: SPI, irq_id between 0 and 65535 (incl.)
- KVM_ARM_IRQ_TYPE_PPI:
- in-kernel GIC: PPI, irq_id between 16 and 31 (incl.)
+ in-kernel GICv2/GICv3: PPI, irq_id between 16 and 31 (incl.)
+ in-kernel GICv5: PPI, irq_id between 0 and 127 (incl.)
(The irq_id field thus corresponds nicely to the IRQ ID in the ARM GIC specs)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index b7cf9d86aabb7..22f618384b199 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -45,6 +45,8 @@
#include <kvm/arm_pmu.h>
#include <kvm/arm_psci.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
#include "sys_regs.h"
static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT;
@@ -1426,16 +1428,29 @@ int kvm_vm_ioctl_irq_line(struct kvm *kvm, struct kvm_irq_level *irq_level,
if (!vcpu)
return -EINVAL;
- if (irq_num < VGIC_NR_SGIS || irq_num >= VGIC_NR_PRIVATE_IRQS)
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ if (irq_num >= VGIC_V5_NR_PRIVATE_IRQS)
+ return -EINVAL;
+
+ /* Build a GICv5-style IntID here */
+ irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI);
+ } else if (irq_num < VGIC_NR_SGIS ||
+ irq_num >= VGIC_NR_PRIVATE_IRQS) {
return -EINVAL;
+ }
return kvm_vgic_inject_irq(kvm, vcpu, irq_num, level, NULL);
case KVM_ARM_IRQ_TYPE_SPI:
if (!irqchip_in_kernel(kvm))
return -ENXIO;
- if (irq_num < VGIC_NR_PRIVATE_IRQS)
- return -EINVAL;
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ /* Build a GICv5-style IntID here */
+ irq_num |= FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_SPI);
+ } else {
+ if (irq_num < VGIC_NR_PRIVATE_IRQS)
+ return -EINVAL;
+ }
return kvm_vgic_inject_irq(kvm, NULL, irq_num, level, NULL);
}
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 5d18a03cc11d5..62d7d4c5650e4 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -86,6 +86,10 @@ static struct vgic_irq *vgic_get_lpi(struct kvm *kvm, u32 intid)
*/
struct vgic_irq *vgic_get_irq(struct kvm *kvm, u32 intid)
{
+ /* Non-private IRQs are not yet implemented for GICv5 */
+ if (vgic_is_v5(kvm))
+ return NULL;
+
/* SPIs */
if (intid >= VGIC_NR_PRIVATE_IRQS &&
intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 21/32] KVM: arm64: gic-v5: Create, init vgic_v5
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (20 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 20/32] KVM: arm64: gic-v5: Support GICv5 interrupts with KVM_IRQ_LINE Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 24/32] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
` (9 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Update kvm_vgic_create to create a vgic_v5 device. When creating a
vgic, FEAT_GCIE in the ID_AA64PFR2 is only exposed to vgic_v5-based
guests, and is hidden otherwise. GIC in ~ID_AA64PFR0_EL1 is never
exposed for a vgic_v5 guest.
When initialising a vgic_v5, skip kvm_vgic_dist_init as GICv5 doesn't
support one. The current vgic_v5 implementation only supports PPIs, so
no SPIs are initialised either.
The current vgic_v5 support doesn't extend to nested
guests. Therefore, the init of vgic_v5 for a nested guest is failed in
vgic_v5_init.
As the current vgic_v5 doesn't require any resources to be mapped,
vgic_v5_map_resources is simply used to check that the vgic has indeed
been initialised. Again, this will change as more GICv5 support is
merged in.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 51 ++++++++++++++++++++++-----------
arch/arm64/kvm/vgic/vgic-v5.c | 26 +++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 2 ++
include/kvm/arm_vgic.h | 1 +
4 files changed, 63 insertions(+), 17 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 51f4443cebcef..69e8746516799 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -66,12 +66,12 @@ static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type);
* or through the generic KVM_CREATE_DEVICE API ioctl.
* irqchip_in_kernel() tells you if this function succeeded or not.
* @kvm: kvm struct pointer
- * @type: KVM_DEV_TYPE_ARM_VGIC_V[23]
+ * @type: KVM_DEV_TYPE_ARM_VGIC_V[235]
*/
int kvm_vgic_create(struct kvm *kvm, u32 type)
{
struct kvm_vcpu *vcpu;
- u64 aa64pfr0, pfr1;
+ u64 aa64pfr0, aa64pfr2, pfr1;
unsigned long i;
int ret;
@@ -132,8 +132,11 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
if (type == KVM_DEV_TYPE_ARM_VGIC_V2)
kvm->max_vcpus = VGIC_V2_MAX_CPUS;
- else
+ else if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
kvm->max_vcpus = VGIC_V3_MAX_CPUS;
+ else if (type == KVM_DEV_TYPE_ARM_VGIC_V5)
+ kvm->max_vcpus = min(VGIC_V5_MAX_CPUS,
+ kvm_vgic_global_state.max_gic_vcpus);
if (atomic_read(&kvm->online_vcpus) > kvm->max_vcpus) {
ret = -E2BIG;
@@ -163,17 +166,21 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
}
aa64pfr0 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1) & ~ID_AA64PFR0_EL1_GIC;
+ aa64pfr2 = kvm_read_vm_id_reg(kvm, SYS_ID_AA64PFR2_EL1) & ~ID_AA64PFR2_EL1_GCIE;
pfr1 = kvm_read_vm_id_reg(kvm, SYS_ID_PFR1_EL1) & ~ID_PFR1_EL1_GIC;
if (type == KVM_DEV_TYPE_ARM_VGIC_V2) {
kvm->arch.vgic.vgic_cpu_base = VGIC_ADDR_UNDEF;
- } else {
+ } else if (type == KVM_DEV_TYPE_ARM_VGIC_V3) {
INIT_LIST_HEAD(&kvm->arch.vgic.rd_regions);
aa64pfr0 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR0_EL1, GIC, IMP);
pfr1 |= SYS_FIELD_PREP_ENUM(ID_PFR1_EL1, GIC, GICv3);
+ } else {
+ aa64pfr2 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
}
kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, aa64pfr0);
+ kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR2_EL1, aa64pfr2);
kvm_set_vm_id_reg(kvm, SYS_ID_PFR1_EL1, pfr1);
if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
@@ -420,20 +427,26 @@ int vgic_init(struct kvm *kvm)
if (kvm->created_vcpus != atomic_read(&kvm->online_vcpus))
return -EBUSY;
- /* freeze the number of spis */
- if (!dist->nr_spis)
- dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
+ if (!vgic_is_v5(kvm)) {
+ /* freeze the number of spis */
+ if (!dist->nr_spis)
+ dist->nr_spis = VGIC_NR_IRQS_LEGACY - VGIC_NR_PRIVATE_IRQS;
- ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
- if (ret)
- goto out;
+ ret = kvm_vgic_dist_init(kvm, dist->nr_spis);
+ if (ret)
+ goto out;
- /*
- * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
- * vLPIs) is supported.
- */
- if (vgic_supports_direct_irqs(kvm)) {
- ret = vgic_v4_init(kvm);
+ /*
+ * Ensure vPEs are allocated if direct IRQ injection (e.g. vSGIs,
+ * vLPIs) is supported.
+ */
+ if (vgic_supports_direct_irqs(kvm)) {
+ ret = vgic_v4_init(kvm);
+ if (ret)
+ goto out;
+ }
+ } else {
+ ret = vgic_v5_init(kvm);
if (ret)
goto out;
}
@@ -610,9 +623,13 @@ int kvm_vgic_map_resources(struct kvm *kvm)
if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V2) {
ret = vgic_v2_map_resources(kvm);
type = VGIC_V2;
- } else {
+ } else if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
ret = vgic_v3_map_resources(kvm);
type = VGIC_V3;
+ } else {
+ ret = vgic_v5_map_resources(kvm);
+ type = VGIC_V5;
+ goto out;
}
if (ret)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 35740e88b3591..3567754ae1459 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -54,6 +54,32 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+int vgic_v5_init(struct kvm *kvm)
+{
+ struct kvm_vcpu *vcpu;
+ unsigned long idx;
+
+ if (vgic_initialized(kvm))
+ return 0;
+
+ kvm_for_each_vcpu(idx, vcpu, kvm) {
+ if (vcpu_has_nv(vcpu)) {
+ kvm_err("Nested GICv5 VMs are currently unsupported\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+int vgic_v5_map_resources(struct kvm *kvm)
+{
+ if (!vgic_initialized(kvm))
+ return -EBUSY;
+
+ return 0;
+}
+
static u32 vgic_v5_get_effective_priority_mask(struct kvm_vcpu *vcpu)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 4b3a1e7ca3fb4..66698973b2872 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -385,6 +385,8 @@ void vgic_debug_init(struct kvm *kvm);
void vgic_debug_destroy(struct kvm *kvm);
int vgic_v5_probe(const struct gic_kvm_info *info);
+int vgic_v5_init(struct kvm *kvm);
+int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct vgic_irq *irq);
int vgic_v5_set_ppi_dvi(struct kvm_vcpu *vcpu, u32 irq, bool dvi);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 5a46fe3c35b5c..099b8ac02999e 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -21,6 +21,7 @@
#include <linux/irqchip/arm-gic-v4.h>
#include <linux/irqchip/arm-gic-v5.h>
+#define VGIC_V5_MAX_CPUS 512
#define VGIC_V3_MAX_CPUS 512
#define VGIC_V2_MAX_CPUS 8
#define VGIC_NR_IRQS_LEGACY 256
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 24/32] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (21 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 21/32] KVM: arm64: gic-v5: Create, init vgic_v5 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
` (8 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Make it mandatory to use the architected PPI when running a GICv5
guest. Attempts to set anything other than the architected PPI (23)
are rejected.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/pmu-emul.c | 14 ++++++++++++--
include/kvm/arm_pmu.h | 5 ++++-
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 0baf8e0fe23bd..9925f10aa4fce 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -961,8 +961,13 @@ static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu)
if (!vgic_initialized(vcpu->kvm))
return -ENODEV;
- if (!kvm_arm_pmu_irq_initialized(vcpu))
- return -ENXIO;
+ if (!kvm_arm_pmu_irq_initialized(vcpu)) {
+ /* Use the architected irq number for GICv5. */
+ if (vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ vcpu->arch.pmu.irq_num = KVM_ARMV8_PMU_GICV5_IRQ;
+ else
+ return -ENXIO;
+ }
ret = kvm_vgic_set_owner(vcpu, vcpu->arch.pmu.irq_num,
&vcpu->arch.pmu);
@@ -987,6 +992,11 @@ static bool pmu_irq_is_valid(struct kvm *kvm, int irq)
unsigned long i;
struct kvm_vcpu *vcpu;
+ /* On GICv5, the PMUIRQ is architecturally mandated to be PPI 23 */
+ if (kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5 &&
+ irq != KVM_ARMV8_PMU_GICV5_IRQ)
+ return false;
+
kvm_for_each_vcpu(i, vcpu, kvm) {
if (!kvm_arm_pmu_irq_initialized(vcpu))
continue;
diff --git a/include/kvm/arm_pmu.h b/include/kvm/arm_pmu.h
index 96754b51b4116..0a36a3d5c8944 100644
--- a/include/kvm/arm_pmu.h
+++ b/include/kvm/arm_pmu.h
@@ -12,6 +12,9 @@
#define KVM_ARMV8_PMU_MAX_COUNTERS 32
+/* PPI #23 - architecturally specified for GICv5 */
+#define KVM_ARMV8_PMU_GICV5_IRQ 0x20000017
+
#if IS_ENABLED(CONFIG_HW_PERF_EVENTS) && IS_ENABLED(CONFIG_KVM)
struct kvm_pmc {
u8 idx; /* index into the pmu->pmc array */
@@ -38,7 +41,7 @@ struct arm_pmu_entry {
};
bool kvm_supports_guest_pmuv3(void);
-#define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num >= VGIC_NR_SGIS)
+#define kvm_arm_pmu_irq_initialized(v) ((v)->arch.pmu.irq_num != 0)
u64 kvm_pmu_get_counter_value(struct kvm_vcpu *vcpu, u64 select_idx);
void kvm_pmu_set_counter_value(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
void kvm_pmu_set_counter_value_user(struct kvm_vcpu *vcpu, u64 select_idx, u64 val);
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (22 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 24/32] KVM: arm64: gic-v5: Mandate architected PPI for PMU emulation on GICv5 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-15 15:50 ` Marc Zyngier
2025-12-12 15:22 ` [PATCH 25/32] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
` (7 subsequent siblings)
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Now that GICv5 has arrived, the arch timer requires some TLC to
address some of the key differences introduced with GICv5.
For PPIs on GICv5, the set_pending_state and queue_irq_unlock irq_ops
are used as AP lists are not required at all for GICv5. The arch timer
also introduces an irq_op - get_input_level. Extend the
arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
guests.
When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
using a vgic_v5, which directly inject the pending state in to the
guest. This means that the host never sees the interrupt for the guest
for these interrupts. This has two impacts.
* First of all, the kvm_cpu_has_pending_timer check is updated to
explicitly check if the timers are expected to fire.
* Secondly, for mapped timers (which use DVI) they must be masked on
the host prior to entering a GICv5 guest, and unmasked on the return
path. This is handled in set_timer_irq_phys_masked.
The final, but rather important, change is that the architected PPIs
for the timers are made mandatory for a GICv5 guest. Attempts to set
them to anything else are actively rejected. Once a vgic_v5 is
initialised, the arch timer PPIs are also explicitly reinitialised to
ensure the correct GICv5-compatible PPIs are used - this also adds in
the GICv5 PPI type to the intid.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/arch_timer.c | 114 +++++++++++++++++++++++++++-----
arch/arm64/kvm/vgic/vgic-init.c | 9 +++
arch/arm64/kvm/vgic/vgic-v5.c | 6 +-
include/kvm/arm_arch_timer.h | 7 +-
include/kvm/arm_vgic.h | 5 ++
5 files changed, 119 insertions(+), 22 deletions(-)
diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 6f033f6644219..b0a5a6c6bf8da 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -56,6 +56,17 @@ static struct irq_ops arch_timer_irq_ops = {
.get_input_level = kvm_arch_timer_get_input_level,
};
+static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
+ .get_input_level = kvm_arch_timer_get_input_level,
+ .set_pending_state = vgic_v5_ppi_set_pending_state,
+ .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
+};
+
+static bool vgic_is_v5(struct kvm_vcpu *vcpu)
+{
+ return vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5;
+}
+
static int nr_timers(struct kvm_vcpu *vcpu)
{
if (!vcpu_has_nv(vcpu))
@@ -396,7 +407,11 @@ static bool kvm_timer_should_fire(struct arch_timer_context *timer_ctx)
int kvm_cpu_has_pending_timer(struct kvm_vcpu *vcpu)
{
- return vcpu_has_wfit_active(vcpu) && wfit_delay_ns(vcpu) == 0;
+ struct arch_timer_context *vtimer = vcpu_vtimer(vcpu);
+ struct arch_timer_context *ptimer = vcpu_ptimer(vcpu);
+
+ return kvm_timer_should_fire(vtimer) || kvm_timer_should_fire(ptimer) ||
+ (vcpu_has_wfit_active(vcpu) && wfit_delay_ns(vcpu) == 0);
}
/*
@@ -657,6 +672,24 @@ static inline void set_timer_irq_phys_active(struct arch_timer_context *ctx, boo
WARN_ON(r);
}
+/*
+ * On GICv5 we use DVI for the arch timer PPIs. This is restored later
+ * on as part of vgic_load. Therefore, in order to avoid the guest's
+ * interrupt making it to the host we mask it before entering the
+ * guest and unmask it again when we return.
+ */
+static inline void set_timer_irq_phys_masked(struct arch_timer_context *ctx, bool masked)
+{
+ if (masked) {
+ disable_percpu_irq(ctx->host_timer_irq);
+ } else {
+ if (ctx->host_timer_irq == host_vtimer_irq)
+ enable_percpu_irq(ctx->host_timer_irq, host_vtimer_irq_flags);
+ else
+ enable_percpu_irq(ctx->host_timer_irq, host_ptimer_irq_flags);
+ }
+}
+
static void kvm_timer_vcpu_load_gic(struct arch_timer_context *ctx)
{
struct kvm_vcpu *vcpu = timer_context_to_vcpu(ctx);
@@ -675,7 +708,10 @@ static void kvm_timer_vcpu_load_gic(struct arch_timer_context *ctx)
phys_active |= ctx->irq.level;
- set_timer_irq_phys_active(ctx, phys_active);
+ if (!vgic_is_v5(vcpu))
+ set_timer_irq_phys_active(ctx, phys_active);
+ else
+ set_timer_irq_phys_masked(ctx, true);
}
static void kvm_timer_vcpu_load_nogic(struct kvm_vcpu *vcpu)
@@ -719,10 +755,14 @@ static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
struct timer_map *map)
{
int hw, ret;
+ struct irq_ops *ops;
if (!irqchip_in_kernel(vcpu->kvm))
return;
+ ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
+ &arch_timer_irq_ops;
+
/*
* We only ever unmap the vtimer irq on a VHE system that runs nested
* virtualization, in which case we have both a valid emul_vtimer,
@@ -741,12 +781,12 @@ static void kvm_timer_vcpu_load_nested_switch(struct kvm_vcpu *vcpu,
ret = kvm_vgic_map_phys_irq(vcpu,
map->direct_vtimer->host_timer_irq,
timer_irq(map->direct_vtimer),
- &arch_timer_irq_ops);
+ ops);
WARN_ON_ONCE(ret);
ret = kvm_vgic_map_phys_irq(vcpu,
map->direct_ptimer->host_timer_irq,
timer_irq(map->direct_ptimer),
- &arch_timer_irq_ops);
+ ops);
WARN_ON_ONCE(ret);
}
}
@@ -864,7 +904,8 @@ void kvm_timer_vcpu_load(struct kvm_vcpu *vcpu)
get_timer_map(vcpu, &map);
if (static_branch_likely(&has_gic_active_state)) {
- if (vcpu_has_nv(vcpu))
+ /* We don't do NV on GICv5, yet */
+ if (vcpu_has_nv(vcpu) && !vgic_is_v5(vcpu))
kvm_timer_vcpu_load_nested_switch(vcpu, &map);
kvm_timer_vcpu_load_gic(map.direct_vtimer);
@@ -934,6 +975,15 @@ void kvm_timer_vcpu_put(struct kvm_vcpu *vcpu)
if (kvm_vcpu_is_blocking(vcpu))
kvm_timer_blocking(vcpu);
+
+ /* Unmask again on GICV5 */
+ if (vgic_is_v5(vcpu)) {
+ set_timer_irq_phys_masked(map.direct_vtimer, false);
+
+ if (map.direct_ptimer)
+ set_timer_irq_phys_masked(map.direct_ptimer, false);
+
+ }
}
void kvm_timer_sync_nested(struct kvm_vcpu *vcpu)
@@ -1034,12 +1084,15 @@ void kvm_timer_vcpu_reset(struct kvm_vcpu *vcpu)
if (timer->enabled) {
for (int i = 0; i < nr_timers(vcpu); i++)
kvm_timer_update_irq(vcpu, false,
- vcpu_get_timer(vcpu, i));
+ vcpu_get_timer(vcpu, i));
if (irqchip_in_kernel(vcpu->kvm)) {
- kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_vtimer));
+ kvm_vgic_reset_mapped_irq(
+ vcpu, timer_irq(map.direct_vtimer));
if (map.direct_ptimer)
- kvm_vgic_reset_mapped_irq(vcpu, timer_irq(map.direct_ptimer));
+ kvm_vgic_reset_mapped_irq(
+ vcpu,
+ timer_irq(map.direct_ptimer));
}
}
@@ -1092,10 +1145,19 @@ void kvm_timer_vcpu_init(struct kvm_vcpu *vcpu)
HRTIMER_MODE_ABS_HARD);
}
+/*
+ * This is always called during kvm_arch_init_vm, but will also be
+ * called from kvm_vgic_create if we have a vGICv5.
+ */
void kvm_timer_init_vm(struct kvm *kvm)
{
+ /*
+ * Set up the default PPIs - note that we adjust them based on
+ * the model of the GIC as GICv5 uses a different way to
+ * describing interrupts.
+ */
for (int i = 0; i < NR_KVM_TIMERS; i++)
- kvm->arch.timer_data.ppi[i] = default_ppi[i];
+ kvm->arch.timer_data.ppi[i] = get_vgic_ppi(kvm, default_ppi[i]);
}
void kvm_timer_cpu_up(void)
@@ -1347,6 +1409,7 @@ static int kvm_irq_init(struct arch_timer_kvm_info *info)
}
arch_timer_irq_ops.flags |= VGIC_IRQ_SW_RESAMPLE;
+ arch_timer_irq_ops_vgic_v5.flags |= VGIC_IRQ_SW_RESAMPLE;
WARN_ON(irq_domain_push_irq(domain, host_vtimer_irq,
(void *)TIMER_VTIMER));
}
@@ -1497,10 +1560,12 @@ static bool timer_irqs_are_valid(struct kvm_vcpu *vcpu)
break;
/*
- * We know by construction that we only have PPIs, so
- * all values are less than 32.
+ * We know by construction that we only have PPIs, so all values
+ * are less than 32. However, we mask off most of the bits as we
+ * might be presented with a GICv5 style PPI where the type is
+ * encoded in the top-bits.
*/
- ppis |= BIT(irq);
+ ppis |= BIT(irq & 0x1f);
}
valid = hweight32(ppis) == nr_timers(vcpu);
@@ -1538,7 +1603,9 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
{
struct arch_timer_cpu *timer = vcpu_timer(vcpu);
struct timer_map map;
+ struct irq_ops *ops;
int ret;
+ int irq;
if (timer->enabled)
return 0;
@@ -1556,20 +1623,22 @@ int kvm_timer_enable(struct kvm_vcpu *vcpu)
return -EINVAL;
}
+ ops = vgic_is_v5(vcpu) ? &arch_timer_irq_ops_vgic_v5 :
+ &arch_timer_irq_ops;
+
get_timer_map(vcpu, &map);
- ret = kvm_vgic_map_phys_irq(vcpu,
- map.direct_vtimer->host_timer_irq,
- timer_irq(map.direct_vtimer),
- &arch_timer_irq_ops);
+ irq = timer_irq(map.direct_vtimer);
+ ret = kvm_vgic_map_phys_irq(vcpu, map.direct_vtimer->host_timer_irq,
+ irq, ops);
if (ret)
return ret;
if (map.direct_ptimer) {
+ irq = timer_irq(map.direct_ptimer);
ret = kvm_vgic_map_phys_irq(vcpu,
map.direct_ptimer->host_timer_irq,
- timer_irq(map.direct_ptimer),
- &arch_timer_irq_ops);
+ irq, ops);
}
if (ret)
@@ -1627,6 +1696,15 @@ int kvm_arm_timer_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
goto out;
}
+ /*
+ * The PPIs for the Arch Timers arch architecturally defined for
+ * GICv5. Reject anything that changes them from the specified value.
+ */
+ if (vgic_is_v5(vcpu) && vcpu->kvm->arch.timer_data.ppi[idx] != irq) {
+ ret = -EINVAL;
+ goto out;
+ }
+
/*
* We cannot validate the IRQ unicity before we run, so take it at
* face value. The verdict will be given on first vcpu run, for each
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 120f28b329738..5955dcbfd051f 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -177,6 +177,15 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
pfr1 |= SYS_FIELD_PREP_ENUM(ID_PFR1_EL1, GIC, GICv3);
} else {
aa64pfr2 |= SYS_FIELD_PREP_ENUM(ID_AA64PFR2_EL1, GCIE, IMP);
+
+ /*
+ * We now know that we have a GICv5. The Arch Timer PPI
+ * interrupts may have been initialised at this stage, but will
+ * have done so assuming that we have an older GIC, meaning that
+ * the IntIDs won't be correct. We init them again, and this
+ * time they will be correct.
+ */
+ kvm_timer_init_vm(kvm);
}
kvm_set_vm_id_reg(kvm, SYS_ID_AA64PFR0_EL1, aa64pfr0);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index a3d52ce066869..1a6c9fc86ed07 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -166,7 +166,7 @@ static void vgic_v5_construct_hmrs(struct kvm_vcpu *vcpu)
}
}
-static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
struct vgic_irq *irq)
{
struct vgic_v5_cpu_if *cpu_if;
@@ -196,8 +196,8 @@ static bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
* save/restore, but don't need the PPIs to be queued on a per-VCPU AP
* list. Therefore, sanity check the state, unlock, and return.
*/
-static bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
- unsigned long flags)
+bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+ unsigned long flags)
__releases(&irq->irq_lock)
{
struct kvm_vcpu *vcpu;
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index 7310841f45121..6cb9c20f9db65 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -10,6 +10,8 @@
#include <linux/clocksource.h>
#include <linux/hrtimer.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
enum kvm_arch_timers {
TIMER_PTIMER,
TIMER_VTIMER,
@@ -47,7 +49,7 @@ struct arch_timer_vm_data {
u64 poffset;
/* The PPI for each timer, global to the VM */
- u8 ppi[NR_KVM_TIMERS];
+ u32 ppi[NR_KVM_TIMERS];
};
struct arch_timer_context {
@@ -130,6 +132,9 @@ void kvm_timer_init_vhe(void);
#define timer_vm_data(ctx) (&(timer_context_to_vcpu(ctx)->kvm->arch.timer_data))
#define timer_irq(ctx) (timer_vm_data(ctx)->ppi[arch_timer_ctx_index(ctx)])
+#define get_vgic_ppi(k, i) (((k)->arch.vgic.vgic_model != KVM_DEV_TYPE_ARM_VGIC_V5) ? \
+ (i) : ((i) | FIELD_PREP(GICV5_HWIRQ_TYPE, GICV5_HWIRQ_TYPE_PPI)))
+
u64 kvm_arm_timer_read_sysreg(struct kvm_vcpu *vcpu,
enum kvm_arch_timers tmr,
enum kvm_arch_timer_regs treg);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 099b8ac02999e..6863e19d6eeb7 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -533,6 +533,11 @@ int vgic_v4_load(struct kvm_vcpu *vcpu);
void vgic_v4_commit(struct kvm_vcpu *vcpu);
int vgic_v4_put(struct kvm_vcpu *vcpu);
+bool vgic_v5_ppi_set_pending_state(struct kvm_vcpu *vcpu,
+ struct vgic_irq *irq);
+bool vgic_v5_ppi_queue_irq_unlock(struct kvm *kvm, struct vgic_irq *irq,
+ unsigned long flags);
+
bool vgic_state_is_nested(struct kvm_vcpu *vcpu);
/* CPU HP callbacks */
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5
2025-12-12 15:22 ` [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
@ 2025-12-15 15:50 ` Marc Zyngier
2025-12-16 10:55 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Marc Zyngier @ 2025-12-15 15:50 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 15:22:43 +0000,
Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> Now that GICv5 has arrived, the arch timer requires some TLC to
> address some of the key differences introduced with GICv5.
>
> For PPIs on GICv5, the set_pending_state and queue_irq_unlock irq_ops
> are used as AP lists are not required at all for GICv5. The arch timer
> also introduces an irq_op - get_input_level. Extend the
> arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
> guests.
>
> When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
> using a vgic_v5, which directly inject the pending state in to the
> guest. This means that the host never sees the interrupt for the guest
> for these interrupts. This has two impacts.
>
> * First of all, the kvm_cpu_has_pending_timer check is updated to
> explicitly check if the timers are expected to fire.
>
> * Secondly, for mapped timers (which use DVI) they must be masked on
> the host prior to entering a GICv5 guest, and unmasked on the return
> path. This is handled in set_timer_irq_phys_masked.
>
> The final, but rather important, change is that the architected PPIs
> for the timers are made mandatory for a GICv5 guest. Attempts to set
> them to anything else are actively rejected. Once a vgic_v5 is
> initialised, the arch timer PPIs are also explicitly reinitialised to
> ensure the correct GICv5-compatible PPIs are used - this also adds in
> the GICv5 PPI type to the intid.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> arch/arm64/kvm/arch_timer.c | 114 +++++++++++++++++++++++++++-----
> arch/arm64/kvm/vgic/vgic-init.c | 9 +++
> arch/arm64/kvm/vgic/vgic-v5.c | 6 +-
> include/kvm/arm_arch_timer.h | 7 +-
> include/kvm/arm_vgic.h | 5 ++
> 5 files changed, 119 insertions(+), 22 deletions(-)
>
> diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
> index 6f033f6644219..b0a5a6c6bf8da 100644
> --- a/arch/arm64/kvm/arch_timer.c
> +++ b/arch/arm64/kvm/arch_timer.c
> @@ -56,6 +56,17 @@ static struct irq_ops arch_timer_irq_ops = {
> .get_input_level = kvm_arch_timer_get_input_level,
> };
>
> +static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
> + .get_input_level = kvm_arch_timer_get_input_level,
> + .set_pending_state = vgic_v5_ppi_set_pending_state,
> + .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> +};
> +
> +static bool vgic_is_v5(struct kvm_vcpu *vcpu)
> +{
> + return vcpu->kvm->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5;
> +}
> +
Drive-by comment: you also have
arch/arm64/kvm/vgic/vgic.h:static inline bool vgic_is_v5(struct kvm *kvm)
include/kvm/arm_vgic.h:#define gic_is_v5(k) ((k)->arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
At least two of them have to die.
M.
--
Without deviation from the norm, progress is not possible.
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5
2025-12-15 15:50 ` Marc Zyngier
@ 2025-12-16 10:55 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-16 10:55 UTC (permalink / raw)
To: maz@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, lpieralisi@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 15:50 +0000, Marc Zyngier wrote:
> On Fri, 12 Dec 2025 15:22:43 +0000,
> Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
> >
> > Now that GICv5 has arrived, the arch timer requires some TLC to
> > address some of the key differences introduced with GICv5.
> >
> > For PPIs on GICv5, the set_pending_state and queue_irq_unlock
> > irq_ops
> > are used as AP lists are not required at all for GICv5. The arch
> > timer
> > also introduces an irq_op - get_input_level. Extend the
> > arch-timer-provided irq_ops to include the two PPI ops for vgic_v5
> > guests.
> >
> > When possible, DVI (Direct Virtual Interrupt) is set for PPIs when
> > using a vgic_v5, which directly inject the pending state in to the
> > guest. This means that the host never sees the interrupt for the
> > guest
> > for these interrupts. This has two impacts.
> >
> > * First of all, the kvm_cpu_has_pending_timer check is updated to
> > explicitly check if the timers are expected to fire.
> >
> > * Secondly, for mapped timers (which use DVI) they must be masked
> > on
> > the host prior to entering a GICv5 guest, and unmasked on the
> > return
> > path. This is handled in set_timer_irq_phys_masked.
> >
> > The final, but rather important, change is that the architected
> > PPIs
> > for the timers are made mandatory for a GICv5 guest. Attempts to
> > set
> > them to anything else are actively rejected. Once a vgic_v5 is
> > initialised, the arch timer PPIs are also explicitly reinitialised
> > to
> > ensure the correct GICv5-compatible PPIs are used - this also adds
> > in
> > the GICv5 PPI type to the intid.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > arch/arm64/kvm/arch_timer.c | 114 +++++++++++++++++++++++++++-
> > ----
> > arch/arm64/kvm/vgic/vgic-init.c | 9 +++
> > arch/arm64/kvm/vgic/vgic-v5.c | 6 +-
> > include/kvm/arm_arch_timer.h | 7 +-
> > include/kvm/arm_vgic.h | 5 ++
> > 5 files changed, 119 insertions(+), 22 deletions(-)
> >
> > diff --git a/arch/arm64/kvm/arch_timer.c
> > b/arch/arm64/kvm/arch_timer.c
> > index 6f033f6644219..b0a5a6c6bf8da 100644
> > --- a/arch/arm64/kvm/arch_timer.c
> > +++ b/arch/arm64/kvm/arch_timer.c
> > @@ -56,6 +56,17 @@ static struct irq_ops arch_timer_irq_ops = {
> > .get_input_level = kvm_arch_timer_get_input_level,
> > };
> >
> > +static struct irq_ops arch_timer_irq_ops_vgic_v5 = {
> > + .get_input_level = kvm_arch_timer_get_input_level,
> > + .set_pending_state = vgic_v5_ppi_set_pending_state,
> > + .queue_irq_unlock = vgic_v5_ppi_queue_irq_unlock,
> > +};
> > +
> > +static bool vgic_is_v5(struct kvm_vcpu *vcpu)
> > +{
> > + return vcpu->kvm->arch.vgic.vgic_model ==
> > KVM_DEV_TYPE_ARM_VGIC_V5;
> > +}
> > +
>
> Drive-by comment: you also have
>
> arch/arm64/kvm/vgic/vgic.h:static inline bool vgic_is_v5(struct kvm
> *kvm)
> include/kvm/arm_vgic.h:#define gic_is_v5(k) ((k)-
> >arch.vgic.vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
>
> At least two of them have to die.
>
> M.
>
Agreed. This has been addressed in conjunction with your int type
helper suggestions. We're now back to having ONE way of checking if the
vgic is v5.
Thanks,
Sascha
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 25/32] KVM: arm64: gic: Hide GICv5 for protected guests
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (23 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 23/32] KVM: arm64: gic-v5: Bump arch timer for GICv5 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 26/32] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
` (6 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
We don't support running protected guest with GICv5 at the
moment. Therefore, be sure that we don't expose it to the guest at all
by actively hiding it when running a protected guest.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/arm.c | 1 +
arch/arm64/kvm/hyp/nvhe/sys_regs.c | 8 ++++++++
3 files changed, 10 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index f6cf59a719ac6..824c00c3e32c8 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -144,6 +144,7 @@ void __noreturn __host_enter(struct kvm_cpu_context *host_ctxt);
extern u64 kvm_nvhe_sym(id_aa64pfr0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64pfr1_el1_sys_val);
+extern u64 kvm_nvhe_sym(id_aa64pfr2_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar0_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar1_el1_sys_val);
extern u64 kvm_nvhe_sym(id_aa64isar2_el1_sys_val);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 22f618384b199..ac4e6c4d06b0e 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -2467,6 +2467,7 @@ static void kvm_hyp_init_symbols(void)
{
kvm_nvhe_sym(id_aa64pfr0_el1_sys_val) = get_hyp_id_aa64pfr0_el1();
kvm_nvhe_sym(id_aa64pfr1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR1_EL1);
+ kvm_nvhe_sym(id_aa64pfr2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64PFR2_EL1);
kvm_nvhe_sym(id_aa64isar0_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR0_EL1);
kvm_nvhe_sym(id_aa64isar1_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR1_EL1);
kvm_nvhe_sym(id_aa64isar2_el1_sys_val) = read_sanitised_ftr_reg(SYS_ID_AA64ISAR2_EL1);
diff --git a/arch/arm64/kvm/hyp/nvhe/sys_regs.c b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
index 3108b5185c204..9652935a6ebdd 100644
--- a/arch/arm64/kvm/hyp/nvhe/sys_regs.c
+++ b/arch/arm64/kvm/hyp/nvhe/sys_regs.c
@@ -20,6 +20,7 @@
*/
u64 id_aa64pfr0_el1_sys_val;
u64 id_aa64pfr1_el1_sys_val;
+u64 id_aa64pfr2_el1_sys_val;
u64 id_aa64isar0_el1_sys_val;
u64 id_aa64isar1_el1_sys_val;
u64 id_aa64isar2_el1_sys_val;
@@ -108,6 +109,11 @@ static const struct pvm_ftr_bits pvmid_aa64pfr1[] = {
FEAT_END
};
+static const struct pvm_ftr_bits pvmid_aa64pfr2[] = {
+ MAX_FEAT(ID_AA64PFR2_EL1, GCIE, NI),
+ FEAT_END
+};
+
static const struct pvm_ftr_bits pvmid_aa64mmfr0[] = {
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, PARANGE, 40),
MAX_FEAT_ENUM(ID_AA64MMFR0_EL1, ASIDBITS, 16),
@@ -221,6 +227,8 @@ static u64 pvm_calc_id_reg(const struct kvm_vcpu *vcpu, u32 id)
return get_restricted_features(vcpu, id_aa64pfr0_el1_sys_val, pvmid_aa64pfr0);
case SYS_ID_AA64PFR1_EL1:
return get_restricted_features(vcpu, id_aa64pfr1_el1_sys_val, pvmid_aa64pfr1);
+ case SYS_ID_AA64PFR2_EL1:
+ return get_restricted_features(vcpu, id_aa64pfr2_el1_sys_val, pvmid_aa64pfr2);
case SYS_ID_AA64ISAR0_EL1:
return id_aa64isar0_el1_sys_val;
case SYS_ID_AA64ISAR1_EL1:
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 26/32] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (24 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 25/32] KVM: arm64: gic: Hide GICv5 for protected guests Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 28/32] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot Sascha Bischoff
` (5 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Currently, NV guests are not supported with GICv5. Therefore, make
sure that FEAT_GCIE is always hidden from such guests.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/nested.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index cdeeb8f09e722..66404d48405e7 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1547,6 +1547,11 @@ u64 limit_nv_id_reg(struct kvm *kvm, u32 reg, u64 val)
ID_AA64PFR1_EL1_MTE);
break;
+ case SYS_ID_AA64PFR2_EL1:
+ /* GICv5 is not yet supported for NV */
+ val &= ~ID_AA64PFR2_EL1_GCIE;
+ break;
+
case SYS_ID_AA64MMFR0_EL1:
/* Hide ExS, Secure Memory */
val &= ~(ID_AA64MMFR0_EL1_EXS |
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 28/32] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (25 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 26/32] KVM: arm64: gic-v5: Hide FEAT_GCIE from NV GICv5 guests Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 27/32] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
` (4 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This control enables virtual HPPI selection, i.e., selection and
delivery of interrupts for a guest (assuming that the guest itself has
opted to receive interrupts). This is set to enabled on boot as there
is no reason for disabling it in normal operation as virtual interrupt
signalling itself is still controlled via the HCR_EL2.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/el2_setup.h | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm64/include/asm/el2_setup.h b/arch/arm64/include/asm/el2_setup.h
index 07c12f4a69b41..e7e39117c79e5 100644
--- a/arch/arm64/include/asm/el2_setup.h
+++ b/arch/arm64/include/asm/el2_setup.h
@@ -238,6 +238,8 @@
ICH_HFGWTR_EL2_ICC_CR0_EL1 | \
ICH_HFGWTR_EL2_ICC_APR_EL1)
msr_s SYS_ICH_HFGWTR_EL2, x0 // Disable reg write traps
+ mov x0, #(ICH_VCTLR_EL2_En)
+ msr_s SYS_ICH_VCTLR_EL2, x0 // Enable vHPPI selection
.Lskip_gicv5_\@:
.endm
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 27/32] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (26 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 28/32] KVM: arm64: gic-v5: Set ICH_VCTLR_EL2.En on boot Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 30/32] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
` (3 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Only the KVM_DEV_ARM_VGIC_GRP_CTRL->KVM_DEV_ARM_VGIC_CTRL_INIT op is
currently supported. All other ops are stubbed out.
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>
---
arch/arm64/kvm/vgic/vgic-kvm-device.c | 72 +++++++++++++++++++++++++++
include/linux/kvm_host.h | 1 +
2 files changed, 73 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index b12ba99a423e5..78903182bba08 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -336,6 +336,9 @@ int kvm_register_vgic_device(unsigned long type)
break;
ret = kvm_vgic_register_its_device();
break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ ret = kvm_register_device_ops(&kvm_arm_vgic_v5_ops,
+ KVM_DEV_TYPE_ARM_VGIC_V5);
}
return ret;
@@ -715,3 +718,72 @@ struct kvm_device_ops kvm_arm_vgic_v3_ops = {
.get_attr = vgic_v3_get_attr,
.has_attr = vgic_v3_has_attr,
};
+
+static int vgic_v5_set_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_CTRL:
+ switch (attr->attr) {
+ case KVM_DEV_ARM_VGIC_CTRL_INIT:
+ return vgic_set_common_attr(dev, attr);
+ default:
+ break;
+ }
+ }
+
+ return -ENXIO;
+}
+
+static int vgic_v5_get_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_CTRL:
+ switch (attr->attr) {
+ case KVM_DEV_ARM_VGIC_CTRL_INIT:
+ return vgic_get_common_attr(dev, attr);
+ default:
+ break;
+ }
+ }
+
+ return -ENXIO;
+}
+
+static int vgic_v5_has_attr(struct kvm_device *dev,
+ struct kvm_device_attr *attr)
+{
+ switch (attr->group) {
+ case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ break;
+ case KVM_DEV_ARM_VGIC_GRP_CTRL:
+ switch (attr->attr) {
+ case KVM_DEV_ARM_VGIC_CTRL_INIT:
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ return -ENXIO;
+}
+
+struct kvm_device_ops kvm_arm_vgic_v5_ops = {
+ .name = "kvm-arm-vgic-v5",
+ .create = vgic_create,
+ .destroy = vgic_destroy,
+ .set_attr = vgic_v5_set_attr,
+ .get_attr = vgic_v5_get_attr,
+ .has_attr = vgic_v5_has_attr,
+};
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index d93f75b05ae22..d6082f06ccae3 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2368,6 +2368,7 @@ void kvm_unregister_device_ops(u32 type);
extern struct kvm_device_ops kvm_mpic_ops;
extern struct kvm_device_ops kvm_arm_vgic_v2_ops;
extern struct kvm_device_ops kvm_arm_vgic_v3_ops;
+extern struct kvm_device_ops kvm_arm_vgic_v5_ops;
#ifdef CONFIG_HAVE_KVM_CPU_RELAX_INTERCEPT
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 30/32] KVM: arm64: gic-v5: Probe for GICv5 device
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (27 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 27/32] KVM: arm64: gic-v5: Introduce kvm_arm_vgic_v5_ops and register them Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-12 15:22 ` [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
` (2 subsequent siblings)
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
The basic GICv5 PPI support is now complete. Allow probing for a
native GICv5 rather than just the legacy support.
The implementation doesn't support protected VMs with GICv5 at this
time. Therefore, if KVM has protected mode enabled the native GICv5
init is skipped, but legacy VMs are allowed if the hardware supports
it.
At this stage the GICv5 KVM implementation only supports PPIs, and
doesn't interact with the host IRS at all. This means that there is no
need to check how many concurrent VMs or vCPUs per VM are supported by
the IRS - the PPI support only requires the CPUIF. The support is
artificially limited to VGIC_V5_MAX_CPUS, i.e. 512, vCPUs per VM.
With this change it becomes possible to run basic GICv5-based VMs,
provided that they only use PPIs.
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>
---
arch/arm64/kvm/vgic/vgic-v5.c | 39 +++++++++++++++++++++++++++--------
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 1a6c9fc86ed07..d74cc3543b9a4 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -10,22 +10,13 @@
/*
* Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
- * Currently only supports GICv3-based VMs on a GICv5 host, and hence only
- * registers a VGIC_V3 device.
*/
int vgic_v5_probe(const struct gic_kvm_info *info)
{
u64 ich_vtr_el2;
int ret;
- if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
- return -ENODEV;
-
kvm_vgic_global_state.type = VGIC_V5;
- kvm_vgic_global_state.has_gcie_v3_compat = true;
-
- /* We only support v3 compat mode - use vGICv3 limits */
- kvm_vgic_global_state.max_gic_vcpus = VGIC_V3_MAX_CPUS;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
@@ -33,6 +24,32 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
kvm_vgic_global_state.has_gicv4 = false;
kvm_vgic_global_state.has_gicv4_1 = false;
+ /*
+ * GICv5 is currently not supported in Protected mode. Skip the
+ * registration of GICv5 completely to make sure no guests can create a
+ * GICv5-based guest.
+ */
+ if (is_protected_kvm_enabled()) {
+ kvm_info("GICv5-based guests are not supported with pKVM\n");
+ goto skip_v5;
+ }
+
+ kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
+
+ ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V5);
+ if (ret) {
+ kvm_err("Cannot register GICv5 KVM device.\n");
+ goto skip_v5;
+ }
+
+ kvm_info("GCIE system register CPU interface\n");
+
+skip_v5:
+ /* If we don't support the GICv3 compat mode we're done. */
+ if (!cpus_have_final_cap(ARM64_HAS_GICV5_LEGACY))
+ return 0;
+
+ kvm_vgic_global_state.has_gcie_v3_compat = true;
ich_vtr_el2 = kvm_call_hyp_ret(__vgic_v3_get_gic_config);
kvm_vgic_global_state.ich_vtr_el2 = (u32)ich_vtr_el2;
@@ -48,6 +65,10 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return ret;
}
+ /* We potentially limit the max VCPUs further than we need to here */
+ kvm_vgic_global_state.max_gic_vcpus = min(VGIC_V3_MAX_CPUS,
+ VGIC_V5_MAX_CPUS);
+
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (28 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 30/32] KVM: arm64: gic-v5: Probe for GICv5 device Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-16 15:40 ` Lorenzo Pieralisi
2025-12-12 15:22 ` [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
2025-12-12 15:22 ` [PATCH 32/32] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
31 siblings, 1 reply; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Now that there is support for creating a GICv5-based guest with KVM,
check that the hardware itself supports virtualisation, skipping the
setting of struct gic_kvm_info if not.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5-irs.c | 4 ++++
drivers/irqchip/irq-gic-v5.c | 5 +++++
include/linux/irqchip/arm-gic-v5.h | 4 ++++
3 files changed, 13 insertions(+)
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index ce2732d649a3e..eebf9f219ac8c 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -744,6 +744,10 @@ static int __init gicv5_irs_init(struct device_node *node)
*/
if (list_empty(&irs_nodes)) {
+ idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR0);
+ gicv5_global_data.virt_capable =
+ !!FIELD_GET(GICV5_IRS_IDR0_VIRT, idr);
+
idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
irs_setup_pri_bits(idr);
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index 41ef286c4d781..f5b17a2557aa1 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1064,6 +1064,11 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
static void __init gic_of_setup_kvm_info(struct device_node *node)
{
+ if (!gicv5_global_data.virt_capable) {
+ pr_info("GIC implementation is not virtualization capable\n");
+ return;
+ }
+
gic_v5_kvm_info.type = GIC_V5;
/* GIC Virtual CPU interface maintenance interrupt */
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 9607b36f021ee..36f4c0e8ef8e9 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -45,6 +45,7 @@
/*
* IRS registers and tables structures
*/
+#define GICV5_IRS_IDR0 0x0000
#define GICV5_IRS_IDR1 0x0004
#define GICV5_IRS_IDR2 0x0008
#define GICV5_IRS_IDR5 0x0014
@@ -65,6 +66,8 @@
#define GICV5_IRS_IST_STATUSR 0x0194
#define GICV5_IRS_MAP_L2_ISTR 0x01c0
+#define GICV5_IRS_IDR0_VIRT BIT(6)
+
#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
@@ -280,6 +283,7 @@ struct gicv5_chip_data {
u8 cpuif_pri_bits;
u8 cpuif_id_bits;
u8 irs_pri_bits;
+ bool virt_capable;
struct {
__le64 *l1ist_addr;
u32 l2_size;
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable
2025-12-12 15:22 ` [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
@ 2025-12-16 15:40 ` Lorenzo Pieralisi
2025-12-17 20:46 ` Sascha Bischoff
0 siblings, 1 reply; 70+ messages in thread
From: Lorenzo Pieralisi @ 2025-12-16 15:40 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, Timothy Hayes
On Fri, Dec 12, 2025 at 03:22:45PM +0000, Sascha Bischoff wrote:
> Now that there is support for creating a GICv5-based guest with KVM,
The only comment I have is - as discussed, this patch is not really
dependent on GICv5 KVM support - ie, if IRS_IDR0.VIRT == b0 there isn't
a chance GIC v3 legacy support is implemented either, maybe it is worth
clarifying that.
Otherwise LGTM:
Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
> check that the hardware itself supports virtualisation, skipping the
> setting of struct gic_kvm_info if not.
>
> Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> ---
> drivers/irqchip/irq-gic-v5-irs.c | 4 ++++
> drivers/irqchip/irq-gic-v5.c | 5 +++++
> include/linux/irqchip/arm-gic-v5.h | 4 ++++
> 3 files changed, 13 insertions(+)
>
> diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> index ce2732d649a3e..eebf9f219ac8c 100644
> --- a/drivers/irqchip/irq-gic-v5-irs.c
> +++ b/drivers/irqchip/irq-gic-v5-irs.c
> @@ -744,6 +744,10 @@ static int __init gicv5_irs_init(struct device_node *node)
> */
> if (list_empty(&irs_nodes)) {
>
> + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR0);
> + gicv5_global_data.virt_capable =
> + !!FIELD_GET(GICV5_IRS_IDR0_VIRT, idr);
> +
> idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> irs_setup_pri_bits(idr);
>
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index 41ef286c4d781..f5b17a2557aa1 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -1064,6 +1064,11 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
>
> static void __init gic_of_setup_kvm_info(struct device_node *node)
> {
> + if (!gicv5_global_data.virt_capable) {
> + pr_info("GIC implementation is not virtualization capable\n");
> + return;
> + }
> +
> gic_v5_kvm_info.type = GIC_V5;
>
> /* GIC Virtual CPU interface maintenance interrupt */
> diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
> index 9607b36f021ee..36f4c0e8ef8e9 100644
> --- a/include/linux/irqchip/arm-gic-v5.h
> +++ b/include/linux/irqchip/arm-gic-v5.h
> @@ -45,6 +45,7 @@
> /*
> * IRS registers and tables structures
> */
> +#define GICV5_IRS_IDR0 0x0000
> #define GICV5_IRS_IDR1 0x0004
> #define GICV5_IRS_IDR2 0x0008
> #define GICV5_IRS_IDR5 0x0014
> @@ -65,6 +66,8 @@
> #define GICV5_IRS_IST_STATUSR 0x0194
> #define GICV5_IRS_MAP_L2_ISTR 0x01c0
>
> +#define GICV5_IRS_IDR0_VIRT BIT(6)
> +
> #define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
> #define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
>
> @@ -280,6 +283,7 @@ struct gicv5_chip_data {
> u8 cpuif_pri_bits;
> u8 cpuif_id_bits;
> u8 irs_pri_bits;
> + bool virt_capable;
> struct {
> __le64 *l1ist_addr;
> u32 l2_size;
> --
> 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread* Re: [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable
2025-12-16 15:40 ` Lorenzo Pieralisi
@ 2025-12-17 20:46 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-17 20:46 UTC (permalink / raw)
To: lpieralisi@kernel.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
peter.maydell@linaro.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, maz@kernel.org, oliver.upton@linux.dev
On Tue, 2025-12-16 at 16:40 +0100, Lorenzo Pieralisi wrote:
> On Fri, Dec 12, 2025 at 03:22:45PM +0000, Sascha Bischoff wrote:
> > Now that there is support for creating a GICv5-based guest with
> > KVM,
>
> The only comment I have is - as discussed, this patch is not really
> dependent on GICv5 KVM support - ie, if IRS_IDR0.VIRT == b0 there
> isn't
> a chance GIC v3 legacy support is implemented either, maybe it is
> worth
> clarifying that.
I've now explicitly called that out.
> Otherwise LGTM:
>
> Reviewed-by: Lorenzo Pieralisi <lpieralisi@kernel.org>
Done, thanks!
Sascha
>
> > check that the hardware itself supports virtualisation, skipping
> > the
> > setting of struct gic_kvm_info if not.
> >
> > Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
> > ---
> > drivers/irqchip/irq-gic-v5-irs.c | 4 ++++
> > drivers/irqchip/irq-gic-v5.c | 5 +++++
> > include/linux/irqchip/arm-gic-v5.h | 4 ++++
> > 3 files changed, 13 insertions(+)
>
>
> >
> > diff --git a/drivers/irqchip/irq-gic-v5-irs.c
> > b/drivers/irqchip/irq-gic-v5-irs.c
> > index ce2732d649a3e..eebf9f219ac8c 100644
> > --- a/drivers/irqchip/irq-gic-v5-irs.c
> > +++ b/drivers/irqchip/irq-gic-v5-irs.c
> > @@ -744,6 +744,10 @@ static int __init gicv5_irs_init(struct
> > device_node *node)
> > */
> > if (list_empty(&irs_nodes)) {
> >
> > + idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR0);
> > + gicv5_global_data.virt_capable =
> > + !!FIELD_GET(GICV5_IRS_IDR0_VIRT, idr);
> > +
> > idr = irs_readl_relaxed(irs_data, GICV5_IRS_IDR1);
> > irs_setup_pri_bits(idr);
> >
> > diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-
> > gic-v5.c
> > index 41ef286c4d781..f5b17a2557aa1 100644
> > --- a/drivers/irqchip/irq-gic-v5.c
> > +++ b/drivers/irqchip/irq-gic-v5.c
> > @@ -1064,6 +1064,11 @@ static struct gic_kvm_info gic_v5_kvm_info
> > __initdata;
> >
> > static void __init gic_of_setup_kvm_info(struct device_node *node)
> > {
> > + if (!gicv5_global_data.virt_capable) {
> > + pr_info("GIC implementation is not virtualization
> > capable\n");
> > + return;
> > + }
> > +
> > gic_v5_kvm_info.type = GIC_V5;
> >
> > /* GIC Virtual CPU interface maintenance interrupt */
> > diff --git a/include/linux/irqchip/arm-gic-v5.h
> > b/include/linux/irqchip/arm-gic-v5.h
> > index 9607b36f021ee..36f4c0e8ef8e9 100644
> > --- a/include/linux/irqchip/arm-gic-v5.h
> > +++ b/include/linux/irqchip/arm-gic-v5.h
> > @@ -45,6 +45,7 @@
> > /*
> > * IRS registers and tables structures
> > */
> > +#define GICV5_IRS_IDR0 0x0000
> > #define GICV5_IRS_IDR1 0x0004
> > #define GICV5_IRS_IDR2 0x0008
> > #define GICV5_IRS_IDR5 0x0014
> > @@ -65,6 +66,8 @@
> > #define GICV5_IRS_IST_STATUSR 0x0194
> > #define GICV5_IRS_MAP_L2_ISTR 0x01c0
> >
> > +#define GICV5_IRS_IDR0_VIRT BIT(6)
> > +
> > #define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
> > #define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
> >
> > @@ -280,6 +283,7 @@ struct gicv5_chip_data {
> > u8 cpuif_pri_bits;
> > u8 cpuif_id_bits;
> > u8 irs_pri_bits;
> > + bool virt_capable;
> > struct {
> > __le64 *l1ist_addr;
> > u32 l2_size;
> > --
> > 2.34.1
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (29 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 29/32] irqchip/gic-v5: Check if impl is virt capable Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
2025-12-15 0:15 ` kernel test robot
2025-12-15 9:56 ` Peter Maydell
2025-12-12 15:22 ` [PATCH 32/32] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest Sascha Bischoff
31 siblings, 2 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
Now that it is possible to create a VGICv5 device, provide initial
documentation for it. At this stage, there is little to document.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
Documentation/virt/kvm/devices/arm-vgic-v5.rst | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 Documentation/virt/kvm/devices/arm-vgic-v5.rst
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
new file mode 100644
index 0000000000000..f6591603887bc
--- /dev/null
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -0,0 +1,18 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================================================
+ARM Virtual Generic Interrupt Controller v5 (VGICv5)
+====================================================
+
+
+Device types supported:
+ - KVM_DEV_TYPE_ARM_VGIC_V5 ARM Generic Interrupt Controller v5.0
+
+Only one VGIC instance may be instantiated through this API. The created VGIC
+will act as the VM interrupt controller, requiring emulated user-space devices
+to inject interrupts to the VGIC instead of directly to CPUs.
+
+Creating a guest GICv5 device requires a host GICv5 host. The current VGICv5
+device only supports PPI interrupts. These can either be injected from emulated
+in-kernel devices (such as the Arch Timer, or PMU), or via the KVM_IRQ_LINE
+ioctl.
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread* Re: [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5
2025-12-12 15:22 ` [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
@ 2025-12-15 0:15 ` kernel test robot
2025-12-15 9:56 ` Peter Maydell
1 sibling, 0 replies; 70+ messages in thread
From: kernel test robot @ 2025-12-15 0:15 UTC (permalink / raw)
To: Sascha Bischoff, linux-arm-kernel@lists.infradead.org,
kvmarm@lists.linux.dev, kvm@vger.kernel.org
Cc: oe-kbuild-all, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
peter.maydell@linaro.org, lpieralisi@kernel.org, Timothy Hayes
Hi Sascha,
kernel test robot noticed the following build warnings:
[auto build test WARNING on linus/master]
[also build test WARNING on v6.19-rc1 next-20251212]
[cannot apply to kvmarm/next arm64/for-next/core kvm/queue kvm/next kvm/linux-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Sascha-Bischoff/KVM-arm64-Account-for-RES1-bits-in-DECLARE_FEAT_MAP-and-co/20251212-233140
base: linus/master
patch link: https://lore.kernel.org/r/20251212152215.675767-32-sascha.bischoff%40arm.com
patch subject: [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5
reproduce: (https://download.01.org/0day-ci/archive/20251215/202512150153.QbgdGdcn-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202512150153.QbgdGdcn-lkp@intel.com/
All warnings (new ones prefixed by >>):
ERROR: Cannot find file ./include/linux/mutex.h
ERROR: Cannot find file ./include/linux/mutex.h
WARNING: No kernel-doc for file ./include/linux/mutex.h
ERROR: Cannot find file ./include/linux/fwctl.h
WARNING: No kernel-doc for file ./include/linux/fwctl.h
>> Documentation/virt/kvm/devices/arm-vgic-v5.rst: WARNING: document isn't included in any toctree [toc.not_included]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5
2025-12-12 15:22 ` [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
2025-12-15 0:15 ` kernel test robot
@ 2025-12-15 9:56 ` Peter Maydell
2025-12-15 13:01 ` Sascha Bischoff
1 sibling, 1 reply; 70+ messages in thread
From: Peter Maydell @ 2025-12-15 9:56 UTC (permalink / raw)
To: Sascha Bischoff
Cc: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org, nd, maz@kernel.org, oliver.upton@linux.dev,
Joey Gouly, Suzuki Poulose, yuzenghui@huawei.com,
lpieralisi@kernel.org, Timothy Hayes
On Fri, 12 Dec 2025 at 15:24, Sascha Bischoff <Sascha.Bischoff@arm.com> wrote:
>
> Now that it is possible to create a VGICv5 device, provide initial
> documentation for it. At this stage, there is little to document.
Is userspace access to read/write the GICv5 register state also
in the "will be added a in future patchseries" category ?
thanks
-- PMM
^ permalink raw reply [flat|nested] 70+ messages in thread
* Re: [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5
2025-12-15 9:56 ` Peter Maydell
@ 2025-12-15 13:01 ` Sascha Bischoff
0 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-15 13:01 UTC (permalink / raw)
To: peter.maydell@linaro.org
Cc: yuzenghui@huawei.com, Timothy Hayes, Suzuki Poulose, nd,
lpieralisi@kernel.org, kvmarm@lists.linux.dev,
linux-arm-kernel@lists.infradead.org, kvm@vger.kernel.org,
Joey Gouly, maz@kernel.org, oliver.upton@linux.dev
On Mon, 2025-12-15 at 09:56 +0000, Peter Maydell wrote:
> On Fri, 12 Dec 2025 at 15:24, Sascha Bischoff
> <Sascha.Bischoff@arm.com> wrote:
> >
> > Now that it is possible to create a VGICv5 device, provide initial
> > documentation for it. At this stage, there is little to document.
>
> Is userspace access to read/write the GICv5 register state also
> in the "will be added a in future patchseries" category ?
>
> thanks
> -- PMM
Hi Peter,
That was the intent for now. Given that the order in which state is
read/written as part of, e.g., saving or restoring a guest matters
greatly, I am reluctant to post the userspace access patches piecemeal
(sys regs, IRS MMIO region, IRS SPI IST, IRS LPI IST, ITS MMIO region).
Also, this series is already rather lengthy, so I don't particularly
want to drag more things in.
FWIW, the current design for userspace access to the GICv5 system
registers looks precisely like what currently exists for GICv3 using
KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS, and reuses this same ioctl.
Thanks,
Sascha
^ permalink raw reply [flat|nested] 70+ messages in thread
* [PATCH 32/32] KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest
2025-12-12 15:22 [PATCH 00/32] KVM: arm64: Introduce vGIC-v5 with PPI support Sascha Bischoff
` (30 preceding siblings ...)
2025-12-12 15:22 ` [PATCH 31/32] Documentation: KVM: Introduce documentation for VGICv5 Sascha Bischoff
@ 2025-12-12 15:22 ` Sascha Bischoff
31 siblings, 0 replies; 70+ messages in thread
From: Sascha Bischoff @ 2025-12-12 15:22 UTC (permalink / raw)
To: linux-arm-kernel@lists.infradead.org, kvmarm@lists.linux.dev,
kvm@vger.kernel.org
Cc: nd, maz@kernel.org, oliver.upton@linux.dev, Joey Gouly,
Suzuki Poulose, yuzenghui@huawei.com, peter.maydell@linaro.org,
lpieralisi@kernel.org, Timothy Hayes
This basic selftest creates a vgic_v5 device (if supported), and tests
that one of the PPI interrupts works as expected with a basic
single-vCPU guest.
Upon starting, the guest enables interrupts. That means that it is
initialising all PPIs to have reasonable priorities, but marking them
as disabled. Then the priority mask in the ICC_PCR_EL1 is set, and
interrupts are enable in ICC_CR0_EL1. At this stage the guest is able
to recieve interrupts. The first IMPDEF PPI (64) is enabled and
kvm_irq_line is used to inject the state into the guest.
The guest's interrupt handler has an explicit WFI in order to ensure
that the guest skips WFI when there are pending and enabled PPI
interrupts.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
tools/testing/selftests/kvm/arm64/vgic_v5.c | 248 ++++++++++++++++++
.../selftests/kvm/include/arm64/gic_v5.h | 148 +++++++++++
3 files changed, 397 insertions(+)
create mode 100644 tools/testing/selftests/kvm/arm64/vgic_v5.c
create mode 100644 tools/testing/selftests/kvm/include/arm64/gic_v5.h
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index ba5c2b643efaa..5c325b8a0766d 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -173,6 +173,7 @@ TEST_GEN_PROGS_arm64 += arm64/vcpu_width_config
TEST_GEN_PROGS_arm64 += arm64/vgic_init
TEST_GEN_PROGS_arm64 += arm64/vgic_irq
TEST_GEN_PROGS_arm64 += arm64/vgic_lpi_stress
+TEST_GEN_PROGS_arm64 += arm64/vgic_v5
TEST_GEN_PROGS_arm64 += arm64/vpmu_counter_access
TEST_GEN_PROGS_arm64 += arm64/no-vgic-v3
TEST_GEN_PROGS_arm64 += arm64/kvm-uuid
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
new file mode 100644
index 0000000000000..5879fbd71042d
--- /dev/null
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/kernel.h>
+#include <sys/syscall.h>
+#include <asm/kvm.h>
+#include <asm/kvm_para.h>
+
+#include <arm64/gic_v5.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+#include "vgic.h"
+
+#define NR_VCPUS 1
+
+struct vm_gic {
+ struct kvm_vm *vm;
+ int gic_fd;
+ uint32_t gic_dev_type;
+};
+
+static uint64_t max_phys_size;
+
+#define GUEST_CMD_IRQ_CDIA 10
+#define GUEST_CMD_IRQ_DIEOI 11
+#define GUEST_CMD_IS_AWAKE 12
+#define GUEST_CMD_IS_READY 13
+
+static void guest_irq_handler(struct ex_regs *regs)
+{
+ bool valid;
+ u32 hwirq;
+ u64 ia;
+ static int count;
+
+ /*
+ * We have pending interrupts. Should never actually enter WFI
+ * here!
+ */
+ wfi();
+ GUEST_SYNC(GUEST_CMD_IS_AWAKE);
+
+ ia = gicr_insn(CDIA);
+ valid = GICV5_GICR_CDIA_VALID(ia);
+
+ GUEST_SYNC(GUEST_CMD_IRQ_CDIA);
+
+ if (!valid)
+ return;
+
+ gsb_ack();
+ isb();
+
+ hwirq = FIELD_GET(GICV5_GIC_CDIA_INTID, ia);
+
+ gic_insn(hwirq, CDDI);
+ gic_insn(0, CDEOI);
+
+ ++count;
+ GUEST_SYNC(GUEST_CMD_IRQ_DIEOI);
+
+ if (count >= 2)
+ GUEST_DONE();
+
+ /* Ask for the next interrupt to be injected */
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+}
+
+static void guest_code(void)
+{
+ local_irq_disable();
+
+ gicv5_cpu_enable_interrupts();
+ local_irq_enable();
+
+ /* Enable PPI 64 */
+ write_sysreg_s(1UL, SYS_ICC_PPI_ENABLER1_EL1);
+
+ /* Ask for the first interrupt to be injected */
+ GUEST_SYNC(GUEST_CMD_IS_READY);
+
+ /* Loop forever waiting for interrupts */
+ while (1);
+}
+
+
+/* we don't want to assert on run execution, hence that helper */
+static int run_vcpu(struct kvm_vcpu *vcpu)
+{
+ return __vcpu_run(vcpu) ? -errno : 0;
+}
+
+static void vm_gic_destroy(struct vm_gic *v)
+{
+ close(v->gic_fd);
+ kvm_vm_free(v->vm);
+}
+
+static void test_vgic_v5_ppis(uint32_t gic_dev_type)
+{
+ struct ucall uc;
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ int ret, i;
+
+ v.gic_dev_type = gic_dev_type;
+ v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
+
+ v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
+
+ for (i = 0; i < NR_VCPUS; ++i)
+ vcpus[i] = vm_vcpu_add(v.vm, i, guest_code);
+
+ vm_init_descriptor_tables(v.vm);
+ vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
+
+ for (i = 0; i < NR_VCPUS; i++)
+ vcpu_init_descriptor_tables(vcpus[i]);
+
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
+ KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
+
+ while (1) {
+ ret = run_vcpu(vcpus[0]);
+
+ switch (get_ucall(vcpus[0], &uc)) {
+ case UCALL_SYNC:
+ /*
+ * The guest is ready for the next level
+ * change. Set high if ready, and lower if it
+ * has been consumed.
+ */
+ if (uc.args[1] == GUEST_CMD_IS_READY ||
+ uc.args[1] == GUEST_CMD_IRQ_DIEOI) {
+ u64 irq = 64;
+ bool level = uc.args[1] == GUEST_CMD_IRQ_DIEOI ? 0 : 1;
+
+ irq &= KVM_ARM_IRQ_NUM_MASK;
+ irq |= KVM_ARM_IRQ_TYPE_PPI << KVM_ARM_IRQ_TYPE_SHIFT;
+
+ _kvm_irq_line(v.vm, irq, level);
+ } else if (uc.args[1] == GUEST_CMD_IS_AWAKE) {
+ pr_info("Guest skipping WFI due to pending IRQ\n");
+ } else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
+ pr_info("Guest acknowledged IRQ\n");
+ }
+
+ continue;
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_DONE:
+ goto done;
+ default:
+ TEST_FAIL("Unknown ucall %lu", uc.cmd);
+ }
+ }
+
+done:
+ TEST_ASSERT(ret == 0, "Failed to test GICv5 PPIs");
+
+ vm_gic_destroy(&v);
+}
+
+/*
+ * Returns 0 if it's possible to create GIC device of a given type (V2 or V3).
+ */
+int test_kvm_device(uint32_t gic_dev_type)
+{
+ struct kvm_vcpu *vcpus[NR_VCPUS];
+ struct vm_gic v;
+ uint32_t other;
+ int ret;
+
+ v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
+
+ /* try to create a non existing KVM device */
+ ret = __kvm_test_create_device(v.vm, 0);
+ TEST_ASSERT(ret && errno == ENODEV, "unsupported device");
+
+ /* trial mode */
+ ret = __kvm_test_create_device(v.vm, gic_dev_type);
+ if (ret)
+ return ret;
+ v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
+
+ ret = __kvm_create_device(v.vm, gic_dev_type);
+ TEST_ASSERT(ret < 0 && errno == EEXIST, "create GIC device twice");
+
+ /* try to create the other gic_dev_types */
+ other = KVM_DEV_TYPE_ARM_VGIC_V2;
+ if (!__kvm_test_create_device(v.vm, other)) {
+ ret = __kvm_create_device(v.vm, other);
+ TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+ "create GIC device while other version exists");
+ }
+
+ other = KVM_DEV_TYPE_ARM_VGIC_V3;
+ if (!__kvm_test_create_device(v.vm, other)) {
+ ret = __kvm_create_device(v.vm, other);
+ TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+ "create GIC device while other version exists");
+ }
+
+ other = KVM_DEV_TYPE_ARM_VGIC_V5;
+ if (!__kvm_test_create_device(v.vm, other)) {
+ ret = __kvm_create_device(v.vm, other);
+ TEST_ASSERT(ret < 0 && (errno == EINVAL || errno == EEXIST),
+ "create GIC device while other version exists");
+ }
+
+ vm_gic_destroy(&v);
+
+ return 0;
+}
+
+void run_tests(uint32_t gic_dev_type)
+{
+ pr_info("Test VGICv5 PPIs\n");
+ test_vgic_v5_ppis(gic_dev_type);
+}
+
+int main(int ac, char **av)
+{
+ int ret;
+ int pa_bits;
+ int cnt_impl = 0;
+
+ test_disable_default_vgic();
+
+ pa_bits = vm_guest_mode_params[VM_MODE_DEFAULT].pa_bits;
+ max_phys_size = 1ULL << pa_bits;
+
+ ret = test_kvm_device(KVM_DEV_TYPE_ARM_VGIC_V5);
+ if (!ret) {
+ pr_info("Running VGIC_V5 tests.\n");
+ run_tests(KVM_DEV_TYPE_ARM_VGIC_V5);
+ cnt_impl++;
+ } else {
+ pr_info("No GICv5 support; Not running GIC_v5 tests.\n");
+ exit(KSFT_SKIP);
+ }
+
+ return 0;
+}
+
+
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
new file mode 100644
index 0000000000000..5daaa84318bb1
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -0,0 +1,148 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __SELFTESTS_GIC_V5_H
+#define __SELFTESTS_GIC_V5_H
+
+#include <asm/barrier.h>
+#include <asm/sysreg.h>
+
+#include <linux/bitfield.h>
+
+#include "processor.h"
+
+/*
+ * Definitions for GICv5 instructions for the Current Domain
+ */
+#define GICV5_OP_GIC_CDAFF sys_insn(1, 0, 12, 1, 3)
+#define GICV5_OP_GIC_CDDI sys_insn(1, 0, 12, 2, 0)
+#define GICV5_OP_GIC_CDDIS sys_insn(1, 0, 12, 1, 0)
+#define GICV5_OP_GIC_CDHM sys_insn(1, 0, 12, 2, 1)
+#define GICV5_OP_GIC_CDEN sys_insn(1, 0, 12, 1, 1)
+#define GICV5_OP_GIC_CDEOI sys_insn(1, 0, 12, 1, 7)
+#define GICV5_OP_GIC_CDPEND sys_insn(1, 0, 12, 1, 4)
+#define GICV5_OP_GIC_CDPRI sys_insn(1, 0, 12, 1, 2)
+#define GICV5_OP_GIC_CDRCFG sys_insn(1, 0, 12, 1, 5)
+#define GICV5_OP_GICR_CDIA sys_insn(1, 0, 12, 3, 0)
+#define GICV5_OP_GICR_CDNMIA sys_insn(1, 0, 12, 3, 1)
+
+/* Definitions for GIC CDAFF */
+#define GICV5_GIC_CDAFF_IAFFID_MASK GENMASK_ULL(47, 32)
+#define GICV5_GIC_CDAFF_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDAFF_IRM_MASK BIT_ULL(28)
+#define GICV5_GIC_CDAFF_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDDI */
+#define GICV5_GIC_CDDI_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDI_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDDIS */
+#define GICV5_GIC_CDDIS_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDDIS_TYPE(r) FIELD_GET(GICV5_GIC_CDDIS_TYPE_MASK, r)
+#define GICV5_GIC_CDDIS_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDDIS_ID(r) FIELD_GET(GICV5_GIC_CDDIS_ID_MASK, r)
+
+/* Definitions for GIC CDEN */
+#define GICV5_GIC_CDEN_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDEN_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDHM */
+#define GICV5_GIC_CDHM_HM_MASK BIT_ULL(32)
+#define GICV5_GIC_CDHM_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDHM_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDPEND */
+#define GICV5_GIC_CDPEND_PENDING_MASK BIT_ULL(32)
+#define GICV5_GIC_CDPEND_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPEND_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDPRI */
+#define GICV5_GIC_CDPRI_PRIORITY_MASK GENMASK_ULL(39, 35)
+#define GICV5_GIC_CDPRI_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDPRI_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GIC CDRCFG */
+#define GICV5_GIC_CDRCFG_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDRCFG_ID_MASK GENMASK_ULL(23, 0)
+
+/* Definitions for GICR CDIA */
+#define GICV5_GIC_CDIA_VALID_MASK BIT_ULL(32)
+#define GICV5_GICR_CDIA_VALID(r) FIELD_GET(GICV5_GIC_CDIA_VALID_MASK, r)
+#define GICV5_GIC_CDIA_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDIA_ID_MASK GENMASK_ULL(23, 0)
+#define GICV5_GIC_CDIA_INTID GENMASK_ULL(31, 0)
+
+/* Definitions for GICR CDNMIA */
+#define GICV5_GIC_CDNMIA_VALID_MASK BIT_ULL(32)
+#define GICV5_GICR_CDNMIA_VALID(r) FIELD_GET(GICV5_GIC_CDNMIA_VALID_MASK, r)
+#define GICV5_GIC_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+
+#define gicr_insn(insn) read_sysreg_s(GICV5_OP_GICR_##insn)
+#define gic_insn(v, insn) write_sysreg_s(v, GICV5_OP_GIC_##insn)
+
+#define __GIC_BARRIER_INSN(op0, op1, CRn, CRm, op2, Rt) \
+ __emit_inst(0xd5000000 | \
+ sys_insn((op0), (op1), (CRn), (CRm), (op2)) | \
+ ((Rt) & 0x1f))
+
+#define GSB_SYS_BARRIER_INSN __GIC_BARRIER_INSN(1, 0, 12, 0, 0, 31)
+#define GSB_ACK_BARRIER_INSN __GIC_BARRIER_INSN(1, 0, 12, 0, 1, 31)
+
+#define gsb_ack() asm volatile(GSB_ACK_BARRIER_INSN : : : "memory")
+#define gsb_sys() asm volatile(GSB_SYS_BARRIER_INSN : : : "memory")
+
+#define REPEAT_BYTE(x) ((~0ul / 0xff) * (x))
+
+#define GICV5_IRQ_DEFAULT_PRI 0b10000
+
+void gicv5_ppi_priority_init(void)
+{
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR0_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR1_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR2_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR3_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR4_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR5_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR6_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR7_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR8_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR9_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR10_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR11_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR12_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR13_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR14_EL1);
+ write_sysreg_s(REPEAT_BYTE(GICV5_IRQ_DEFAULT_PRI), SYS_ICC_PPI_PRIORITYR15_EL1);
+
+ /*
+ * Context syncronization required to make sure system register writes
+ * effects are synchronised.
+ */
+ isb();
+}
+
+void gicv5_cpu_disable_interrupts(void)
+{
+ u64 cr0;
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+void gicv5_cpu_enable_interrupts(void)
+{
+ u64 cr0, pcr;
+
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER0_EL1);
+ write_sysreg_s(0, SYS_ICC_PPI_ENABLER1_EL1);
+
+ gicv5_ppi_priority_init();
+
+ pcr = FIELD_PREP(ICC_PCR_EL1_PRIORITY, GICV5_IRQ_DEFAULT_PRI);
+ write_sysreg_s(pcr, SYS_ICC_PCR_EL1);
+
+ cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 1);
+ write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
+}
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 70+ messages in thread