* [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
@ 2026-07-24 10:48 ` Sascha Bischoff
2026-07-24 11:21 ` sashiko-bot
2026-07-24 10:48 ` [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
` (37 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:48 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, fuad.tabba@linux.dev
GICv5 does not require a virtual CPU interface maintenance interrupt
for native GCIE operation. The interrupt is only needed when
FEAT_GCIE_LEGACY is present, as the legacy GICv3 interface still
relies on maintenance IRQ delivery.
Stop rejecting KVM setup solely because the maintenance interrupt is
absent. Parse the interrupt if present, but if none is described and
the system does not advertise FEAT_GCIE_LEGACY, tell KVM that no
maintenance interrupt is required.
This lets native GICv5 KVM support be registered on systems that do
not provide a maintenance interrupt, while requiring a maintenance
interrupt for GICv3-capable systems.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5.c | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index e9d1795235a66..c04523a438cee 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1143,10 +1143,18 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
- if (!gic_v5_kvm_info.maint_irq) {
- pr_warn("cannot find GICv5 virtual CPU interface maintenance interrupt\n");
- return;
- }
+
+ /*
+ * We require an MI if we have legacy support, but don't, otherwise.
+ * Given that there's an existing flag to convey that an MI isn't
+ * needed, we (ab)use it to tell KVM that the MI isn't needed if we
+ * don't support legacy.
+ *
+ * The check for ARM64_HAS_GICV5_LEGACY explicitly doesn't use
+ * cpus_have_final_cap() here as we run too early.
+ */
+ if (!cpus_have_cap(ARM64_HAS_GICV5_LEGACY) && !gic_v5_kvm_info.maint_irq)
+ gic_v5_kvm_info.no_maint_irq_mask = true;
vgic_set_kvm_info(&gic_v5_kvm_info);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ
2026-07-24 10:48 ` [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
@ 2026-07-24 11:21 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:21 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Oliver Upton, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Exposing a latent memory leak of `gic_kvm_info` in KVM's initialization error path when a required maintenance IRQ is absent on GICv5 legacy-capable systems.
--
--- Patch [1]: [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ ---
commit 4a1c6e5108caaddf8c492f48ac4cb4568ab60c52
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
irqchip/gic-v5: Allow KVM setup without a maintenance IRQ
This commit modifies the GICv5 initialization to no longer reject KVM setup
solely due to a missing maintenance interrupt. Instead, it delegates this
validation to KVM by passing a specific configuration flag based on legacy
support capabilities.
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index e9d1795235a66..c04523a438cee 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
> @@ -1143,10 +1143,18 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
> /* GIC Virtual CPU interface maintenance interrupt */
> gic_v5_kvm_info.no_maint_irq_mask = false;
> gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
> - if (!gic_v5_kvm_info.maint_irq) {
> - pr_warn("cannot find GICv5 virtual CPU interface maintenance interrupt\n");
> - return;
> - }
> +
> + /*
> + * We require an MI if we have legacy support, but don't, otherwise.
> + * Given that there's an existing flag to convey that an MI isn't
> + * needed, we (ab)use it to tell KVM that the MI isn't needed if we
> + * don't support legacy.
> + *
> + * The check for ARM64_HAS_GICV5_LEGACY explicitly doesn't use
> + * cpus_have_final_cap() here as we run too early.
> + */
> + if (!cpus_have_cap(ARM64_HAS_GICV5_LEGACY) && !gic_v5_kvm_info.maint_irq)
> + gic_v5_kvm_info.no_maint_irq_mask = true;
>
> vgic_set_kvm_info(&gic_v5_kvm_info);
[Severity: Medium]
By removing the early return here, does this expose a memory leak in KVM's
initialization error path?
If a system has ARM64_HAS_GICV5_LEGACY support but the maintenance interrupt
is missing, we now pass no_maint_irq_mask = false and maint_irq = 0 to
vgic_set_kvm_info() in drivers/irqchip/irq-gic-v5.c:gic_of_setup_kvm_info().
When this configuration reaches KVM:
arch/arm64/kvm/vgic/vgic-init.c:kvm_vgic_hyp_init() {
...
if (has_mask && !gic_kvm_info->maint_irq) {
kvm_err("No vgic maintenance irq\n");
return -ENXIO;
}
...
}
It looks like kvm_vgic_hyp_init() returns -ENXIO without freeing the
dynamically allocated gic_kvm_info structure, causing a memory leak.
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=1
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
2026-07-24 10:48 ` [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
@ 2026-07-24 10:48 ` Sascha Bischoff
2026-07-24 11:10 ` sashiko-bot
2026-07-24 10:49 ` [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
` (36 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:48 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, fuad.tabba@linux.dev
KVM needs to interact with the host IRS in order to, for example, make
VMs or VPEs valid. There are two potential approaches here. Either the
host irqchip driver can provide an interface, or KVM can interact
directly with the host IRS. The latter of these two is chosen as the
set of MMIO registers that KVM needs to interact with is orthogonal to
the set used by the host irqchip driver (with the exception of some of
the read-only IRS_IDRx registers).
Pass KVM a pointer to an IRS config frame - the config frame belonging
to ANY IRS is fine as long as one IRS's config frame is used
consistently - in struct gic_kvm_info. Additionally, include a flag
telling KVM whether the IRS is coherent or non-coherent in order to
make sure that KVM can do the correct cache state management, if
required.
Only OF (Device Tree) is supported with this change. ACPI is not.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5-irs.c | 7 +++++--
drivers/irqchip/irq-gic-v5.c | 5 +++++
include/linux/irqchip/arm-gic-v5.h | 3 +++
include/linux/irqchip/arm-vgic-info.h | 5 +++++
4 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index f3fce0b1e25d9..607e066821b52 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -21,8 +21,6 @@
*/
#define LPI_ID_BITS_LINEAR 12
-#define IRS_FLAGS_NON_COHERENT BIT(0)
-
static DEFINE_PER_CPU_READ_MOSTLY(struct gicv5_irs_chip_data *, per_cpu_irs_data);
static LIST_HEAD(irs_nodes);
@@ -50,6 +48,11 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
writeq_relaxed(val, irs_data->irs_base + reg_offset);
}
+struct gicv5_irs_chip_data *gicv5_irs_get_chip_data(void)
+{
+ return per_cpu(per_cpu_irs_data, 0);
+}
+
/*
* The polling wait (in gicv5_wait_for_op_s_atomic()) on a GIC register
* provides the memory barriers (through MMIO accessors)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index c04523a438cee..e7a7aedcfaf78 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1128,6 +1128,8 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
static void __init gic_of_setup_kvm_info(struct device_node *node)
{
+ struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
+
/*
* If we don't have native GICv5 virtualisation support, then
* we also don't have FEAT_GCIE_LEGACY - the architecture
@@ -1140,6 +1142,9 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
gic_v5_kvm_info.type = GIC_V5;
+ gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
+ gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
+
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index f78787e654f4c..681c5c51207d6 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -330,6 +330,8 @@ struct gicv5_irs_chip_data {
raw_spinlock_t spi_config_lock;
};
+#define IRS_FLAGS_NON_COHERENT BIT(0)
+
static inline int gicv5_wait_for_op_s_atomic(void __iomem *addr, u32 offset,
const char *reg_s, u32 mask,
u32 *val)
@@ -377,6 +379,7 @@ void __init gicv5_free_lpi_domain(void);
int gicv5_irs_of_probe(struct device_node *parent);
int gicv5_irs_acpi_probe(void);
+struct gicv5_irs_chip_data *gicv5_irs_get_chip_data(void);
void gicv5_irs_remove(void);
int gicv5_irs_enable(void);
void gicv5_irs_its_probe(void);
diff --git a/include/linux/irqchip/arm-vgic-info.h b/include/linux/irqchip/arm-vgic-info.h
index 67d9d960273b9..f05370e2debf4 100644
--- a/include/linux/irqchip/arm-vgic-info.h
+++ b/include/linux/irqchip/arm-vgic-info.h
@@ -38,6 +38,11 @@ struct gic_kvm_info {
bool has_v4_1;
/* Deactivation impared, subpar stuff */
bool no_hw_deactivation;
+ /* GICv5 IRS base */
+ struct {
+ void __iomem *base;
+ bool non_coherent;
+ } gicv5_irs;
};
#ifdef CONFIG_KVM
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM
2026-07-24 10:48 ` [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
@ 2026-07-24 11:10 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:10 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, kvm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Unchecked dereference of `irs_data` in `gic_of_setup_kvm_info()` causes a kernel panic if CPU 0 is unassociated with an IRS.
Pre-existing issues:
- [High] Failure to clear `per_cpu_irs_data` on IRS initialization error leaves dangling pointers, causing a Use-After-Free.
--
--- Patch [2]: [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM ---
commit 6022367a12dfea4087fa1185ff131015d445a1aa
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
irqchip/gic-v5: Provide OF IRS config frame attrs to KVM
This commit passes an IRS config frame pointer and coherency flag to KVM
via struct gic_kvm_info for OF configurations. It adds a helper to retrieve
the IRS chip data for CPU 0.
> diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
> index f3fce0b1e25d9..607e066821b52 100644
> --- a/drivers/irqchip/irq-gic-v5-irs.c
> +++ b/drivers/irqchip/irq-gic-v5-irs.c
[ ... ]
> @@ -50,6 +48,11 @@ static void irs_writeq_relaxed(struct gicv5_irs_chip_data *irs_data,
> writeq_relaxed(val, irs_data->irs_base + reg_offset);
> }
>
> +struct gicv5_irs_chip_data *gicv5_irs_get_chip_data(void)
> +{
> + return per_cpu(per_cpu_irs_data, 0);
> +}
[Severity: High]
This is a pre-existing issue, but does returning this per-cpu pointer risk a
use-after-free if the IRS initialization failed earlier?
In gicv5_irs_of_init(), gicv5_irs_of_init_affinity() populates
per_cpu(per_cpu_irs_data, cpu) with a newly allocated irs_data. If a
subsequent step fails, the error path frees irs_data but does not clear
the per-cpu pointers for the associated CPUs.
If CPU 0 belonged to a failed IRS, would this helper now return a dangling
pointer that gets unconditionally dereferenced by gic_of_setup_kvm_info()
during boot?
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> index c04523a438cee..e7a7aedcfaf78 100644
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
[ ... ]
> @@ -1128,6 +1128,8 @@ static struct gic_kvm_info gic_v5_kvm_info __initdata;
>
> static void __init gic_of_setup_kvm_info(struct device_node *node)
> {
> + struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
> +
> /*
> * If we don't have native GICv5 virtualisation support, then
> * we also don't have FEAT_GCIE_LEGACY - the architecture
> @@ -1140,6 +1142,9 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
>
> gic_v5_kvm_info.type = GIC_V5;
>
> + gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
> + gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
[Severity: High]
Can irs_data be NULL here, leading to a kernel panic when dereferencing
irs_data->irs_base and irs_data->flags?
If the system's Device Tree topology does not map logical CPU 0 to any IRS,
gicv5_irs_get_chip_data() returns NULL. Would it be safer to check if
irs_data is valid before assigning these fields?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=2
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
2026-07-24 10:48 ` [PATCH v4 01/48] irqchip/gic-v5: Allow KVM setup without a maintenance IRQ Sascha Bischoff
2026-07-24 10:48 ` [PATCH v4 02/48] irqchip/gic-v5: Provide OF IRS config frame attrs to KVM Sascha Bischoff
@ 2026-07-24 10:49 ` Sascha Bischoff
2026-07-24 11:19 ` sashiko-bot
2026-07-24 10:49 ` [PATCH v4 04/48] KVM: arm64: gic-v5: Define remaining IRS MMIO registers Sascha Bischoff
` (35 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:49 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, fuad.tabba@linux.dev
Device-tree based GICv5 probing already passes the IRS details and
maintenance interrupt to KVM, but the ACPI path only initialises the
irqchip and installs the ACPI IRQ model. As a result, KVM never sees
the GICv5 host information required to probe the vGIC on ACPI systems.
Add the ACPI equivalent of the DT KVM setup. Parse the MADT GICC
entries for the maintenance interrupt, require all relevant entries to
agree, register the interrupt as a GICv5 PPI-encoded GSI, and pass the
resulting IRQ together with the IRS base and coherency information to
KVM. Native GICv5 does not require a maintenance interrupt unless the
legacy GICv3-compatible CPU interface is present, so preserve the
existing no-maintenance-IRQ handling for that case.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
drivers/irqchip/irq-gic-v5.c | 100 +++++++++++++++++++++++++++++++++--
1 file changed, 96 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
index e7a7aedcfaf78..0d90675fb319b 100644
--- a/drivers/irqchip/irq-gic-v5.c
+++ b/drivers/irqchip/irq-gic-v5.c
@@ -1126,7 +1126,7 @@ static void gicv5_set_cpuif_idbits(void)
#ifdef CONFIG_KVM
static struct gic_kvm_info gic_v5_kvm_info __initdata;
-static void __init gic_of_setup_kvm_info(struct device_node *node)
+static void __init gic_setup_kvm_info(unsigned int maint_irq)
{
struct gicv5_irs_chip_data *irs_data = gicv5_irs_get_chip_data();
@@ -1137,17 +1137,19 @@ 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;
+ goto out_dispose_maint_irq;
}
- gic_v5_kvm_info.type = GIC_V5;
+ if (WARN_ON(!irs_data))
+ goto out_dispose_maint_irq;
+ gic_v5_kvm_info.type = GIC_V5;
gic_v5_kvm_info.gicv5_irs.base = irs_data->irs_base;
gic_v5_kvm_info.gicv5_irs.non_coherent = !!(irs_data->flags & IRS_FLAGS_NON_COHERENT);
/* GIC Virtual CPU interface maintenance interrupt */
gic_v5_kvm_info.no_maint_irq_mask = false;
- gic_v5_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
+ gic_v5_kvm_info.maint_irq = maint_irq;
/*
* We require an MI if we have legacy support, but don't, otherwise.
@@ -1162,11 +1164,100 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
gic_v5_kvm_info.no_maint_irq_mask = true;
vgic_set_kvm_info(&gic_v5_kvm_info);
+ return;
+
+out_dispose_maint_irq:
+ irq_dispose_mapping(maint_irq);
+}
+
+static void __init gic_of_setup_kvm_info(struct device_node *node)
+{
+ /* GIC Virtual CPU interface maintenance interrupt */
+ gic_setup_kvm_info(irq_of_parse_and_map(node, 0));
+}
+
+#ifdef CONFIG_ACPI
+struct gicv5_acpi_kvm_info {
+ u32 maint_irq;
+ int maint_irq_mode;
+};
+
+static struct gicv5_acpi_kvm_info acpi_v5_kvm_info __initdata;
+
+static int __init gic_acpi_parse_virt_madt_gicc(union acpi_subtable_headers *header,
+ const unsigned long end)
+{
+ struct acpi_madt_generic_interrupt *gicc =
+ (struct acpi_madt_generic_interrupt *)header;
+ static int first_madt = true;
+ int maint_irq_mode;
+
+ if (!(gicc->flags &
+ (ACPI_MADT_ENABLED | ACPI_MADT_GICC_ONLINE_CAPABLE)))
+ return 0;
+
+ maint_irq_mode = (gicc->flags & ACPI_MADT_VGIC_IRQ_MODE) ?
+ ACPI_EDGE_SENSITIVE : ACPI_LEVEL_SENSITIVE;
+
+ if (first_madt) {
+ first_madt = false;
+
+ acpi_v5_kvm_info.maint_irq = gicc->vgic_interrupt;
+ acpi_v5_kvm_info.maint_irq_mode = maint_irq_mode;
+ return 0;
+ }
+
+ /* The maintenance interrupt must be the same for every GICC entry. */
+ if (acpi_v5_kvm_info.maint_irq != gicc->vgic_interrupt ||
+ acpi_v5_kvm_info.maint_irq_mode != maint_irq_mode)
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool __init gic_acpi_collect_virt_info(void)
+{
+ int count;
+
+ count = acpi_table_parse_madt(ACPI_MADT_TYPE_GENERIC_INTERRUPT,
+ gic_acpi_parse_virt_madt_gicc, 0);
+
+ return count > 0;
+}
+
+static void __init gic_acpi_setup_kvm_info(void)
+{
+ unsigned int maint_irq = 0;
+ int irq;
+
+ if (!gic_acpi_collect_virt_info()) {
+ pr_warn("Unable to get hardware information used for virtualization\n");
+ return;
+ }
+
+ if (acpi_v5_kvm_info.maint_irq) {
+ irq = acpi_register_gsi(NULL, acpi_v5_kvm_info.maint_irq,
+ acpi_v5_kvm_info.maint_irq_mode,
+ ACPI_ACTIVE_HIGH);
+ if (irq > 0)
+ maint_irq = irq;
+ else
+ pr_warn("Failed to register GSI for GICv5 maintenance IRQ\n");
+ }
+
+ gic_setup_kvm_info(maint_irq);
}
+#endif
#else
static inline void __init gic_of_setup_kvm_info(struct device_node *node)
{
}
+
+#ifdef CONFIG_ACPI
+static inline void __init gic_acpi_setup_kvm_info(void)
+{
+}
+#endif
#endif // CONFIG_KVM
static int __init gicv5_init_common(struct fwnode_handle *parent_domain)
@@ -1265,6 +1356,7 @@ static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsig
goto out_irs;
acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id);
+ gic_acpi_setup_kvm_info();
return 0;
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
2026-07-24 10:49 ` [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
@ 2026-07-24 11:19 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:19 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, Marc Zyngier, kvm, kvmarm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Failed ACPI GSI registration for maintenance IRQ due to missing GICv5 hardware IRQ type encoding.
--
--- Patch [3]: [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts ---
commit 6d138977b1bc438c493851b44358f453400237da
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts
This commit adds ACPI support for setting up KVM host information on GICv5
systems. It parses MADT GICC entries to retrieve the maintenance interrupt and
registers it as a GSI to be passed to KVM.
> diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c
> --- a/drivers/irqchip/irq-gic-v5.c
> +++ b/drivers/irqchip/irq-gic-v5.c
[ ... ]
> @@ -1162,11 +1164,100 @@ static void __init gic_of_setup_kvm_info(struct device_node *node)
[ ... ]
> +static void __init gic_acpi_setup_kvm_info(void)
> +{
> + unsigned int maint_irq = 0;
> + int irq;
> +
> + if (!gic_acpi_collect_virt_info()) {
> + pr_warn("Unable to get hardware information used for virtualization\n");
> + return;
> + }
> +
> + if (acpi_v5_kvm_info.maint_irq) {
> + irq = acpi_register_gsi(NULL, acpi_v5_kvm_info.maint_irq,
> + acpi_v5_kvm_info.maint_irq_mode,
> + ACPI_ACTIVE_HIGH);
[Severity: High]
Does this fail to register the maintenance IRQ?
acpi_register_gsi() passes the raw maint_irq directly into fwspec.param[0].
However, the GICv5 PPI domain select function expects the hardware IRQ type to
be encoded in the upper bits:
drivers/irqchip/irq-gic-v5.c:gicv5_irq_ppi_domain_select() {
...
if (is_fwnode_irqchip(fwspec->fwnode))
hwirq_type = FIELD_GET(GICV5_HWIRQ_TYPE, fwspec->param[0]);
if (hwirq_type != GICV5_HWIRQ_TYPE_PPI)
return 0;
...
}
Because the raw MADT value doesn't have the GICV5_HWIRQ_TYPE_PPI bits set, the
mapping will be rejected. Should the GSI be encoded with the PPI type before
calling acpi_register_gsi()?
> + if (irq > 0)
> + maint_irq = irq;
> + else
> + pr_warn("Failed to register GSI for GICv5 maintenance IRQ\n");
> + }
> +
> + gic_setup_kvm_info(maint_irq);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=3
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 04/48] KVM: arm64: gic-v5: Define remaining IRS MMIO registers
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (2 preceding siblings ...)
2026-07-24 10:49 ` [PATCH v4 03/48] irqchip/gic-v5: Set up gic_kvm_info on ACPI hosts Sascha Bischoff
@ 2026-07-24 10:49 ` Sascha Bischoff
2026-07-24 10:49 ` [PATCH v4 05/48] arm64/sysreg: Add GICv5 GIC VDPEND encoding Sascha Bischoff
` (34 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:49 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, fuad.tabba@linux.dev
Complete the set of defined IRS MMIO registers in the GICv5 header
file. Up until now, the set of defined IRS MMIO registers has been
driven by code requirements. However, in order to properly emulate the
IRS MMIO interface in KVM, the full set of IRS MMIO registers needs to
be added.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/linux/irqchip/arm-gic-v5.h | 239 +++++++++++++++++++++++++++--
1 file changed, 230 insertions(+), 9 deletions(-)
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 681c5c51207d6..83cfcd056b250 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -62,20 +62,34 @@
#define GICV5_OUTER_SHARE 0b10
#define GICV5_INNER_SHARE 0b11
+#define GICV5_AIDR_COMPONENT_IRS 0b00
+#define GICV5_AIDR_COMPONENT_ITS 0b01
+#define GICV5_AIDR_COMPONENT_IWB 0b10
+
+#define GICV5_AIDR_ARCH_MAJ_REV_V5 0
+#define GICV5_AIDR_ARCH_MIN_REV_V0 0
+
/*
* IRS registers and tables structures
*/
#define GICV5_IRS_IDR0 0x0000
#define GICV5_IRS_IDR1 0x0004
#define GICV5_IRS_IDR2 0x0008
+#define GICV5_IRS_IDR3 0x000c
+#define GICV5_IRS_IDR4 0x0010
#define GICV5_IRS_IDR5 0x0014
#define GICV5_IRS_IDR6 0x0018
#define GICV5_IRS_IDR7 0x001c
+#define GICV5_IRS_IIDR 0x0040
+#define GICV5_IRS_AIDR 0x0044
#define GICV5_IRS_CR0 0x0080
#define GICV5_IRS_CR1 0x0084
#define GICV5_IRS_SYNCR 0x00c0
#define GICV5_IRS_SYNC_STATUSR 0x00c4
+#define GICV5_IRS_SPI_VMR 0x0100
#define GICV5_IRS_SPI_SELR 0x0108
+#define GICV5_IRS_SPI_DOMAINR 0x010c
+#define GICV5_IRS_SPI_RESAMPLER 0x0110
#define GICV5_IRS_SPI_CFGR 0x0114
#define GICV5_IRS_SPI_STATUSR 0x0118
#define GICV5_IRS_PE_SELR 0x0140
@@ -85,11 +99,51 @@
#define GICV5_IRS_IST_CFGR 0x0190
#define GICV5_IRS_IST_STATUSR 0x0194
#define GICV5_IRS_MAP_L2_ISTR 0x01c0
-
+#define GICV5_IRS_VMT_BASER 0x0200
+#define GICV5_IRS_VMT_CFGR 0x0210
+#define GICV5_IRS_VMT_STATUSR 0x0214
+#define GICV5_IRS_VPE_SELR 0x0240
+#define GICV5_IRS_VPE_DBR 0x0248
+#define GICV5_IRS_VPE_HPPIR 0x0250
+#define GICV5_IRS_VPE_CR0 0x0258
+#define GICV5_IRS_VPE_STATUSR 0x025c
+#define GICV5_IRS_VM_DBR 0x0280
+#define GICV5_IRS_VM_SELR 0x0288
+#define GICV5_IRS_VM_STATUSR 0x028c
+#define GICV5_IRS_VMAP_L2_VMTR 0x02c0
+#define GICV5_IRS_VMAP_VMR 0x02c8
+#define GICV5_IRS_VMAP_VISTR 0x02d0
+#define GICV5_IRS_VMAP_L2_VISTR 0x02d8
+#define GICV5_IRS_VMAP_VPER 0x02e0
+#define GICV5_IRS_SAVE_VMR 0x0300
+#define GICV5_IRS_SAVE_VM_STATUSR 0x0308
+#define GICV5_IRS_MEC_IDR 0x0340
+#define GICV5_IRS_MEC_MECID_R 0x0344
+#define GICV5_IRS_MPAM_IDR 0x0380
+#define GICV5_IRS_MPAM_PARTID_R 0x0384
+#define GICV5_IRS_SWERR_STATUSR 0x03c0
+#define GICV5_IRS_SWERR_SYNDROMER0 0x03c8
+#define GICV5_IRS_SWERR_SYNDROMER1 0x03d0
+
+#define GICV5_IRS_IDR0_IRSID GENMASK(31, 16)
+#define GICV5_IRS_IDR0_SWE BIT(12)
+#define GICV5_IRS_IDR0_MPAM BIT(11)
+#define GICV5_IRS_IDR0_MEC BIT(10)
+#define GICV5_IRS_IDR0_SETLPI BIT(9)
+#define GICV5_IRS_IDR0_VIRT_ONE_N BIT(8)
+#define GICV5_IRS_IDR0_ONE_N BIT(7)
#define GICV5_IRS_IDR0_VIRT BIT(6)
+#define GICV5_IRS_IDR0_PA_RANGE GENMASK(5, 2)
+#define GICV5_IRS_IDR0_INT_DOM GENMASK(1, 0)
+
+#define GICV5_IRS_IDR0_INT_DOM_SECURE 0b00
+#define GICV5_IRS_IDR0_INT_DOM_NON_SECURE 0b01
+#define GICV5_IRS_IDR0_INT_DOM_EL3 0b10
+#define GICV5_IRS_IDR0_INT_DOM_REALM 0b11
#define GICV5_IRS_IDR1_PRIORITY_BITS GENMASK(22, 20)
#define GICV5_IRS_IDR1_IAFFID_BITS GENMASK(19, 16)
+#define GICV5_IRS_IDR1_PE_CNT GENMASK(15, 0)
#define GICV5_IRS_IDR1_PRIORITY_BITS_1BITS 0b000
#define GICV5_IRS_IDR1_PRIORITY_BITS_2BITS 0b001
@@ -105,13 +159,30 @@
#define GICV5_IRS_IDR2_LPI BIT(5)
#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
+#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+
+#define GICV5_IRS_IDR3_VMT_LEVELS BIT(10)
+#define GICV5_IRS_IDR3_VM_ID_BITS GENMASK(9, 5)
+#define GICV5_IRS_IDR3_VMD_SZ GENMASK(4, 1)
+#define GICV5_IRS_IDR3_VMD BIT(0)
+
+#define GICV5_IRS_IDR4_VPE_ID_BITS GENMASK(9, 6)
+#define GICV5_IRS_IDR4_VPED_SZ GENMASK(5, 0)
+
#define GICV5_IRS_IDR5_SPI_RANGE GENMASK(24, 0)
#define GICV5_IRS_IDR6_SPI_IRS_RANGE GENMASK(24, 0)
#define GICV5_IRS_IDR7_SPI_BASE GENMASK(23, 0)
-#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+#define GICV5_IRS_IIDR_PRODUCT_ID GENMASK(31, 20)
+#define GICV5_IRS_IIDR_VARIANT GENMASK(19, 16)
+#define GICV5_IRS_IIDR_REVISION GENMASK(15, 12)
+#define GICV5_IRS_IIDR_IMPLEMENTER GENMASK(11, 0)
+
+#define GICV5_IRS_AIDR_COMPONENT GENMASK(11, 8)
+#define GICV5_IRS_AIDR_ARCHMAJORREV GENMASK(7, 4)
+#define GICV5_IRS_AIDR_ARCHMINORREV GENMASK(3, 0)
#define GICV5_IRS_CR0_IDLE BIT(1)
#define GICV5_IRS_CR0_IRSEN BIT(0)
@@ -134,21 +205,39 @@
#define GICV5_IRS_SYNC_STATUSR_IDLE BIT(0)
-#define GICV5_IRS_SPI_STATUSR_V BIT(1)
-#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SPI_VMR_VIRT BIT_ULL(63)
+#define GICV5_IRS_SPI_VMR_VM_ID GENMASK_ULL(15, 0)
#define GICV5_IRS_SPI_SELR_ID GENMASK(23, 0)
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN GENMASK(1, 0)
+
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_SECURE 0b00
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_NON_SECURE 0b01
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_EL3 0b10
+#define GICV5_IRS_SPI_DOMAINR_DOMAIN_REALM 0b11
+
+#define GICV5_IRS_SPI_RESAMPLER_ID GENMASK(23, 0)
+
#define GICV5_IRS_SPI_CFGR_TM BIT(0)
+#define GICV5_IRS_SPI_CFGR_TM_EDGE 0b0
+#define GICV5_IRS_SPI_CFGR_TM_LEVEL 0b1
+
+#define GICV5_IRS_SPI_STATUSR_V BIT(1)
+#define GICV5_IRS_SPI_STATUSR_IDLE BIT(0)
+
#define GICV5_IRS_PE_SELR_IAFFID GENMASK(15, 0)
+#define GICV5_IRS_PE_STATUSR_ONLINE BIT(2)
#define GICV5_IRS_PE_STATUSR_V BIT(1)
#define GICV5_IRS_PE_STATUSR_IDLE BIT(0)
#define GICV5_IRS_PE_CR0_DPS BIT(0)
-#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
+#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_BASER_ADDR_SHIFT 6ULL
#define GICV5_IRS_IST_CFGR_STRUCTURE BIT(16)
#define GICV5_IRS_IST_CFGR_ISTSZ GENMASK(8, 7)
@@ -166,15 +255,111 @@
#define GICV5_IRS_IST_CFGR_L2SZ_16K 0b01
#define GICV5_IRS_IST_CFGR_L2SZ_64K 0b10
-#define GICV5_IRS_IST_BASER_ADDR_MASK GENMASK_ULL(55, 6)
-#define GICV5_IRS_IST_BASER_VALID BIT_ULL(0)
+#define GICV5_IRS_IST_STATUSR_IDLE BIT(0)
#define GICV5_IRS_MAP_L2_ISTR_ID GENMASK(23, 0)
+#define GICV5_IRS_VMT_BASER_ADDR GENMASK_ULL(55, 3)
+#define GICV5_IRS_VMT_BASER_ADDR_SHIFT 3ULL
+#define GICV5_IRS_VMT_BASER_VALID BIT_ULL(0)
+
+#define GICV5_IRS_VMT_CFGR_STRUCTURE_TWO_LEVEL 0b1
+#define GICV5_IRS_VMT_CFGR_STRUCTURE_LINEAR 0b0
+
+#define GICV5_IRS_VMT_CFGR_STRUCTURE BIT(16)
+#define GICV5_IRS_VMT_CFGR_VM_ID_BITS GENMASK(4, 0)
+
+#define GICV5_IRS_VMT_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VPE_SELR_S BIT_ULL(63)
+#define GICV5_IRS_VPE_SELR_VPE_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VPE_SELR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VPE_DBR_DBV BIT_ULL(63)
+#define GICV5_IRS_VPE_DBR_REQ_DB BIT_ULL(62)
+#define GICV5_IRS_VPE_DBR_DBPM GENMASK_ULL(36, 32)
+#define GICV5_IRS_VPE_DBR_INTID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VPE_HPPIR_HPPIV BIT_ULL(32)
+#define GICV5_IRS_VPE_HPPIR_TYPE GENMASK_ULL(31, 29)
+#define GICV5_IRS_VPE_HPPIR_ID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VPE_CR0_DPS BIT(0)
+
+#define GICV5_IRS_VPE_STATUSR_V BIT(1)
+#define GICV5_IRS_VPE_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VM_DBR_EN BIT_ULL(63)
+#define GICV5_IRS_VM_DBR_VPE_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VM_SELR_VM_ID GENMASK(15, 0)
+
+#define GICV5_IRS_VM_STATUSR_V BIT(1)
+#define GICV5_IRS_VM_STATUSR_IDLE BIT(0)
+
+#define GICV5_IRS_VMAP_L2_VMTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_L2_VMTR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VMAP_VMR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VMR_U BIT_ULL(62)
+#define GICV5_IRS_VMAP_VMR_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_VMAP_VISTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VISTR_U BIT_ULL(62)
+#define GICV5_IRS_VMAP_VISTR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_VISTR_TYPE GENMASK_ULL(31, 29)
+
+#define GICV5_IRS_VMAP_L2_VISTR_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_L2_VISTR_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_L2_VISTR_TYPE GENMASK_ULL(31, 29)
+#define GICV5_IRS_VMAP_L2_VISTR_ID GENMASK_ULL(23, 0)
+
+#define GICV5_IRS_VMAP_VPER_M BIT_ULL(63)
+#define GICV5_IRS_VMAP_VPER_VM_ID GENMASK_ULL(47, 32)
+#define GICV5_IRS_VMAP_VPER_VPE_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_SAVE_VMR_VM_ID GENMASK_ULL(15, 0)
+#define GICV5_IRS_SAVE_VMR_Q BIT_ULL(62)
+#define GICV5_IRS_SAVE_VMR_S BIT_ULL(63)
+
+#define GICV5_IRS_SAVE_VM_STATUSR_IDLE BIT(0)
+#define GICV5_IRS_SAVE_VM_STATUSR_Q BIT(1)
+
+#define GICV5_IRS_MEC_IDR_MECIDSIZE GENMASK(3, 0)
+
+#define GICV5_IRS_MEC_MECID_R_MECID GENMASK(15, 0)
+
+#define GICV5_IRS_MPAM_IDR_HAS_MPAM_SP BIT(24)
+#define GICV5_IRS_MPAM_IDR_PMG_MAX GENMASK(23, 16)
+#define GICV5_IRS_MPAM_IDR_PARTID_MAX GENMASK(15, 0)
+
+#define GICV5_IRS_MPAM_PARTID_R_IDLE BIT(31)
+#define GICV5_IRS_MPAM_PARTID_R_MPAM_SP GENMASK(25, 24)
+#define GICV5_IRS_MPAM_PARTID_R_PMG GENMASK(23, 16)
+#define GICV5_IRS_MPAM_PARTID_R_PARTID GENMASK(15, 0)
+
+#define GICV5_IRS_SWERR_STATUSR_IMP_EC GENMASK_ULL(31, 24)
+#define GICV5_IRS_SWERR_STATUSR_EC GENMASK_ULL(23, 16)
+#define GICV5_IRS_SWERR_STATUSR_OF BIT_ULL(3)
+#define GICV5_IRS_SWERR_STATUSR_S1V BIT_ULL(2)
+#define GICV5_IRS_SWERR_STATUSR_S0V BIT_ULL(1)
+#define GICV5_IRS_SWERR_STATUSR_V BIT_ULL(0)
+
+#define GICV5_IRS_SWERR_SYNDROMER0_VIRTUAL BIT_ULL(63)
+#define GICV5_IRS_SWERR_SYNDROMER0_TYPE GENMASK_ULL(62, 60)
+#define GICV5_IRS_SWERR_SYNDROMER0_ID GENMASK_ULL(55, 32)
+#define GICV5_IRS_SWERR_SYNDROMER0_VM_ID GENMASK_ULL(15, 0)
+
+#define GICV5_IRS_SWERR_SYNDROMER1_ADDR GENMASK_ULL(55, 3)
+
#define GICV5_ISTL1E_VALID BIT_ULL(0)
+#define GICV5_IRS_ISTL1E_SIZE 8UL
#define GICV5_ISTL1E_L2_ADDR_MASK GENMASK_ULL(55, 12)
+#define GICV5_IRS_SETLPIR 0x0000
+#define GICV5_IRS_SETLPIR_ID GENMASK(23, 0)
+
/*
* ITS registers and tables structures
*/
@@ -296,6 +481,42 @@
#define GICV5_GSI_IWB_FRAME_ID GENMASK(28, 16)
#define GICV5_GSI_IWB_WIRE GENMASK(15, 0)
+/*
+ * CoreSight identification registers - ordered by increasing offset.
+ */
+#define GICV5_CORESIGHT_DEVARCH 0xffbc
+#define GICV5_CORESIGHT_PIDR4 0xffd0
+#define GICV5_CORESIGHT_PIDR5 0xffd4
+#define GICV5_CORESIGHT_PIDR6 0xffd8
+#define GICV5_CORESIGHT_PIDR7 0xffdc
+#define GICV5_CORESIGHT_PIDR0 0xffe0
+#define GICV5_CORESIGHT_PIDR1 0xffe4
+#define GICV5_CORESIGHT_PIDR2 0xffe8
+#define GICV5_CORESIGHT_PIDR3 0xffec
+#define GICV5_CORESIGHT_CIDR0 0xfff0
+#define GICV5_CORESIGHT_CIDR1 0xfff4
+#define GICV5_CORESIGHT_CIDR2 0xfff8
+#define GICV5_CORESIGHT_CIDR3 0xfffc
+
+#define GICV5_CORESIGHT_DEVARCH_VAL \
+ (FIELD_PREP(GENMASK(31, 21), 0x23b) | \
+ BIT(20) | \
+ 0x5a19)
+
+#define GICV5_CORESIGHT_PIDR4_JEP106_CONT 0x04
+#define GICV5_CORESIGHT_PIDR5_RES0 0x00
+#define GICV5_CORESIGHT_PIDR6_RES0 0x00
+#define GICV5_CORESIGHT_PIDR7_RES0 0x00
+#define GICV5_CORESIGHT_PIDR0_PART_0 0x4b
+#define GICV5_CORESIGHT_PIDR1_DES_0_PART_1 0xb0
+#define GICV5_CORESIGHT_PIDR2_DES_1 0x0b
+#define GICV5_CORESIGHT_PIDR3_REVAND_CMOD 0x00
+
+#define GICV5_CORESIGHT_CIDR0_VAL 0x0d
+#define GICV5_CORESIGHT_CIDR1_VAL 0xf0
+#define GICV5_CORESIGHT_CIDR2_VAL 0x05
+#define GICV5_CORESIGHT_CIDR3_VAL 0xb1
+
/*
* Global Data structures and functions
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 05/48] arm64/sysreg: Add GICv5 GIC VDPEND encoding
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (3 preceding siblings ...)
2026-07-24 10:49 ` [PATCH v4 04/48] KVM: arm64: gic-v5: Define remaining IRS MMIO registers Sascha Bischoff
@ 2026-07-24 10:49 ` Sascha Bischoff
2026-07-24 10:49 ` [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
` (33 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:49 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, fuad.tabba@linux.dev
Add the encoding for the GIC VDPEND system instruction. This operates on
the virtual interrupt domain, and is used to make interrupts pending for
a VM.
This is part of enabling GICv5 KVM support, and is required for injection
of SPIs and LPIs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/sysreg.h | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/sysreg.h b/arch/arm64/include/asm/sysreg.h
index 7aa08d59d4944..c840b37adb2fb 100644
--- a/arch/arm64/include/asm/sysreg.h
+++ b/arch/arm64/include/asm/sysreg.h
@@ -1040,7 +1040,7 @@
#define GCS_CAP(x) ((((unsigned long)x) & GCS_CAP_ADDR_MASK) | \
GCS_CAP_VALID_TOKEN)
/*
- * Definitions for GICv5 instructions
+ * 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)
@@ -1105,6 +1105,17 @@
#define GICV5_GICR_CDNMIA_TYPE_MASK GENMASK_ULL(31, 29)
#define GICV5_GICR_CDNMIA_ID_MASK GENMASK_ULL(23, 0)
+/*
+ * Definitions for GICv5 instructions for the Virtual Domain
+ */
+#define GICV5_OP_GIC_VDPEND sys_insn(1, 4, 12, 1, 4)
+
+/* Shift and mask definitions for GIC VDPEND */
+#define GICV5_GIC_VDPEND_PENDING_MASK BIT_ULL(63)
+#define GICV5_GIC_VDPEND_VM_MASK GENMASK_ULL(47, 32)
+#define GICV5_GIC_VDPEND_TYPE_MASK GENMASK_ULL(31, 29)
+#define GICV5_GIC_VDPEND_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] 60+ messages in thread* [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (4 preceding siblings ...)
2026-07-24 10:49 ` [PATCH v4 05/48] arm64/sysreg: Add GICv5 GIC VDPEND encoding Sascha Bischoff
@ 2026-07-24 10:49 ` Sascha Bischoff
2026-07-24 11:14 ` sashiko-bot
2026-07-24 10:50 ` [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame Sascha Bischoff
` (32 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:49 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, fuad.tabba@linux.dev
These fields have been added to the architecture since this register
was added to the generator, and were hence missing.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/tools/sysreg | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 7cb61aca3797f..03f5e1755a633 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3736,7 +3736,9 @@ Sysreg ICC_CR0_EL1 3 1 12 0 1
Res0 63:39
Field 38 PID
Field 37:32 IPPT
-Res0 31:1
+Res0 31:3
+Field 2 LINK_IDLE
+Field 1 LINK
Field 0 EN
EndSysreg
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields
2026-07-24 10:49 ` [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
@ 2026-07-24 11:14 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:14 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Oliver Upton, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing Context Synchronization Event (`isb()`) immediately following writes to `ICC_*_EL1` control registers.
- [High] CONSTRAINED UNPREDICTABLE behavior risk in `GIC CDEOI` system instruction encoding.
--
--- Patch [6]: [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields ---
commit 5e4759df2a3d62aee0191d703caed6bd54abb692
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields
These fields have been added to the architecture since this register
was added to the generator, and were hence missing.
> diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
> index 7cb61aca3797f..03f5e1755a633 100644
> --- a/arch/arm64/tools/sysreg
> +++ b/arch/arm64/tools/sysreg
> @@ -3736,7 +3736,9 @@ Sysreg ICC_CR0_EL1 3 1 12 0 1
> Res0 63:39
> Field 38 PID
> Field 37:32 IPPT
> -Res0 31:1
> +Res0 31:3
> +Field 2 LINK_IDLE
> +Field 1 LINK
> Field 0 EN
> EndSysreg
[Severity: High]
This is a pre-existing issue, but since this patch updates the ICC_CR0_EL1
sysreg definitions, is it possible that context synchronization events are
missing after writes to these control registers?
For example, in drivers/irqchip/irq-gic-v5.c:gicv5_cpu_disable_interrupts(),
SYS_ICC_CR0_EL1 is written without an immediate isb():
drivers/irqchip/irq-gic-v5.c:gicv5_cpu_disable_interrupts() {
cr0 = FIELD_PREP(ICC_CR0_EL1_EN, 0);
write_sysreg_s(cr0, SYS_ICC_CR0_EL1);
}
A similar pattern occurs in gicv5_cpu_enable_interrupts(), write_ppi_sysreg_s(),
and gicv5_ppi_priority_init() for registers like SYS_ICC_PCR_EL1,
SYS_ICC_PPI_SPENDR0_EL1, and SYS_ICC_PPI_PRIORITYR0_EL1. Is there a risk of
operating under a stale pipeline configuration if an ISB does not immediately
follow these writes?
[Severity: High]
This is a pre-existing issue, but while reviewing the GICv5 interactions,
does the CDEOI instruction encoding rely on compiler constraints instead of
hardcoding the required register?
In drivers/irqchip/irq-gic-v5.c:gicv5_handle_irq(), the instruction is
emitted using:
drivers/irqchip/irq-gic-v5.c:gicv5_handle_irq() {
gic_insn(0, CDEOI);
}
If the compiler allocates a general-purpose register containing 0 instead of
explicitly using XZR (register 31), could this lead to a misencoded system
instruction and CONSTRAINED UNPREDICTABLE behavior?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=6
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (5 preceding siblings ...)
2026-07-24 10:49 ` [PATCH v4 06/48] arm64/sysreg: Update ICC_CR0_EL1 with LINK and LINK_IDLE fields Sascha Bischoff
@ 2026-07-24 10:50 ` Sascha Bischoff
2026-07-24 11:19 ` sashiko-bot
2026-07-24 10:50 ` [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
` (31 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:50 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, fuad.tabba@linux.dev
The host irqchip driver provides KVM with a pointer to an IRS's config
frame, which allows KVM to directly interact with the host's IRS. The
MMIO registers in the config frame are used to configure VMs (in
addition to them being used by the host). The IRS's config frame also
includes a set of ID registers which describe the capabilities that
the IRS has.
Stash the pointer to the config frame, and extract the VM capabilities
(from IRS_IDR3 & IRS_IDR4), as well as the IST
capabilities/requirements (IRS_IDR2) from the IRS.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 46 +++++++++++++++++++++++++++++++++--
include/kvm/arm_vgic.h | 26 ++++++++++++++++++++
2 files changed, 70 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index d4789ff3e7402..3f7b132110114 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -11,6 +11,7 @@
#include "vgic.h"
#define ppi_caps kvm_vgic_global_state.vgic_v5_ppi_caps
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
/*
* Not all PPIs are guaranteed to be implemented for GICv5. Deterermine which
@@ -34,6 +35,45 @@ static void vgic_v5_get_implemented_ppis(void)
__assign_bit(GICV5_ARCH_PPI_PMUIRQ, ppi_caps.impl_ppi_mask, system_supports_pmuv3());
}
+static u32 irs_readl_relaxed(const u32 reg_offset)
+{
+ return readl_relaxed(irs_caps.irs_base + reg_offset);
+}
+
+static void vgic_v5_irs_extract_vm_caps(const struct gic_kvm_info *info)
+{
+ u64 idr;
+
+ irs_caps.irs_base = info->gicv5_irs.base;
+ irs_caps.non_coherent = info->gicv5_irs.non_coherent;
+
+ idr = irs_readl_relaxed(GICV5_IRS_IDR2);
+
+ /* We skip the LPI field as it only applies to physical LPIs */
+ irs_caps.ist_id_bits = FIELD_GET(GICV5_IRS_IDR2_ID_BITS, idr);
+ irs_caps.min_lpi_id_bits = FIELD_GET(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, idr);
+ irs_caps.ist_levels = (idr & GICV5_IRS_IDR2_IST_LEVELS);
+ irs_caps.ist_l2sz = FIELD_GET(GICV5_IRS_IDR2_IST_L2SZ, idr);
+ irs_caps.istmd = (idr & GICV5_IRS_IDR2_ISTMD);
+ irs_caps.istmd_sz = FIELD_GET(GICV5_IRS_IDR2_ISTMD_SZ, idr);
+
+ idr = irs_readl_relaxed(GICV5_IRS_IDR3);
+
+ irs_caps.max_vms = BIT(FIELD_GET(GICV5_IRS_IDR3_VM_ID_BITS, idr));
+ irs_caps.two_level_vmt_support = (idr & GICV5_IRS_IDR3_VMT_LEVELS);
+
+ if (idr & GICV5_IRS_IDR3_VMD)
+ irs_caps.vmd_size = BIT(FIELD_GET(GICV5_IRS_IDR3_VMD_SZ, idr));
+ else
+ irs_caps.vmd_size = 0;
+
+ idr = irs_readl_relaxed(GICV5_IRS_IDR4);
+
+ irs_caps.vped_size = BIT(FIELD_GET(GICV5_IRS_IDR4_VPED_SZ, idr));
+ /* Field stores VPE_ID_BITS - 1 */
+ irs_caps.max_vpes = BIT(FIELD_GET(GICV5_IRS_IDR4_VPE_ID_BITS, idr) + 1);
+}
+
/*
* Probe for a vGICv5 compatible interrupt controller, returning 0 on success.
*/
@@ -61,10 +101,12 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
goto skip_v5;
}
- kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
-
+ vgic_v5_irs_extract_vm_caps(info);
vgic_v5_get_implemented_ppis();
+ kvm_vgic_global_state.max_gic_vcpus = min(irs_caps.max_vpes,
+ 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");
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index fe49fb56dc3c9..8d65a18fefb80 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -182,6 +182,32 @@ struct vgic_global {
struct {
DECLARE_BITMAP(impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
} vgic_v5_ppi_caps;
+
+ /* GICv5 IRS capabilities */
+ struct {
+ /* Base address of the host IRS's CONFIG_FRAME */
+ void __iomem *irs_base;
+
+ /* IST Caps */
+ u8 ist_id_bits;
+ bool ist_levels;
+ u8 ist_l2sz;
+ bool istmd;
+ u8 istmd_sz;
+
+ /* LPI only */
+ u8 min_lpi_id_bits;
+
+ /* VM Table, VPE Table */
+ bool two_level_vmt_support;
+ u32 max_vms;
+ u32 max_vpes;
+ u16 vmd_size;
+ u16 vped_size;
+
+ /* Is the IRS coherent with us, or not? */
+ bool non_coherent;
+ } vgic_v5_irs_caps;
};
extern struct vgic_global kvm_vgic_global_state;
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame
2026-07-24 10:50 ` [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame Sascha Bischoff
@ 2026-07-24 11:19 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:19 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Oliver Upton, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Inconsistent handling of ID bit counts likely miscalculates `max_vms` by a factor of 2.
--
--- Patch [7]: [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame ---
commit 2b896f2ac90e022fb55d61f670ccdb056cff2664
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame
The host irqchip driver provides KVM with a pointer to an IRS's config
frame. This commit extracts and stashes the VM and IST capabilities
from IRS_IDR2, IRS_IDR3, and IRS_IDR4 into a capability structure.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index d4789ff3e7402..3f7b132110114 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -34,6 +35,45 @@ static void vgic_v5_get_implemented_ppis(void)
> __assign_bit(GICV5_ARCH_PPI_PMUIRQ, ppi_caps.impl_ppi_mask, system_supports_pmuv3());
> }
>
> +static u32 irs_readl_relaxed(const u32 reg_offset)
> +{
> + return readl_relaxed(irs_caps.irs_base + reg_offset);
> +}
> +
> +static void vgic_v5_irs_extract_vm_caps(const struct gic_kvm_info *info)
> +{
> + u64 idr;
> +
[ ... ]
> + idr = irs_readl_relaxed(GICV5_IRS_IDR3);
> +
> + irs_caps.max_vms = BIT(FIELD_GET(GICV5_IRS_IDR3_VM_ID_BITS, idr));
[Severity: Medium]
Should this have a + 1 applied to the field value, similar to how VPE_ID_BITS
is handled below?
GIC architecture ID bit fields canonically store the number of bits minus 1.
If VM_ID_BITS follows the same pattern, omitting the + 1 will result in KVM
calculating exactly half of the actual maximum number of VMs supported by the
hardware, leading to premature resource exhaustion.
> + irs_caps.two_level_vmt_support = (idr & GICV5_IRS_IDR3_VMT_LEVELS);
> +
> + if (idr & GICV5_IRS_IDR3_VMD)
> + irs_caps.vmd_size = BIT(FIELD_GET(GICV5_IRS_IDR3_VMD_SZ, idr));
> + else
> + irs_caps.vmd_size = 0;
> +
> + idr = irs_readl_relaxed(GICV5_IRS_IDR4);
> +
> + irs_caps.vped_size = BIT(FIELD_GET(GICV5_IRS_IDR4_VPED_SZ, idr));
> + /* Field stores VPE_ID_BITS - 1 */
> + irs_caps.max_vpes = BIT(FIELD_GET(GICV5_IRS_IDR4_VPE_ID_BITS, idr) + 1);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=7
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (6 preceding siblings ...)
2026-07-24 10:50 ` [PATCH v4 07/48] KVM: arm64: gic-v5: Extract host IRS caps from IRS config frame Sascha Bischoff
@ 2026-07-24 10:50 ` Sascha Bischoff
2026-07-24 11:11 ` sashiko-bot
2026-07-24 10:50 ` [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
` (30 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:50 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, fuad.tabba@linux.dev
VPE doorbells allow the GICv5 hardware to notify KVM when an SPI or LPI
can be signalled to a non-resident VPE. This provides the mechanism used
to wake blocked vcpus once the hardware determines that the interrupt is
eligible to be delivered.
Add support for a per-VM VPE doorbell irq domain. The domain is created
under the GICv5 LPI domain, with one doorbell allocated per VPE. Store
the allocated doorbell base in the VM's GICv5 state so that later
patches can request per-vcpu doorbell IRQs and use them for IRS
commands and wakeups.
Add the per-VPE doorbell state to the GICv5 CPU interface state. The
doorbell IRQ number is populated when the IRQs are requested, and the
db_fired state is used by later patches once doorbell delivery is wired
up.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 18 ++--
arch/arm64/kvm/vgic/vgic-v5.c | 139 +++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 1 +
include/kvm/arm_vgic.h | 4 +
include/linux/irqchip/arm-gic-v5.h | 2 +
5 files changed, 158 insertions(+), 6 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 907057881b26a..625d352756fcf 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -492,16 +492,22 @@ static void kvm_vgic_dist_destroy(struct kvm *kvm)
dist->nr_spis = 0;
dist->vgic_dist_base = VGIC_ADDR_UNDEF;
- if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) {
+ switch (dist->vgic_model) {
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
+ dist->vgic_cpu_base = VGIC_ADDR_UNDEF;
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V3:
list_for_each_entry_safe(rdreg, next, &dist->rd_regions, list)
vgic_v3_free_redist_region(kvm, rdreg);
INIT_LIST_HEAD(&dist->rd_regions);
- } else {
- dist->vgic_cpu_base = VGIC_ADDR_UNDEF;
- }
- if (vgic_supports_direct_irqs(kvm))
- vgic_v4_teardown(kvm);
+ if (vgic_supports_direct_irqs(kvm))
+ vgic_v4_teardown(kvm);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ vgic_v5_teardown(kvm);
+ break;
+ }
xa_destroy(&dist->lpi_xa);
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 3f7b132110114..ebd7c3ede975f 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -7,6 +7,7 @@
#include <linux/bitops.h>
#include <linux/irqchip/arm-vgic-info.h>
+#include <linux/irqdomain.h>
#include "vgic.h"
@@ -152,6 +153,134 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+/*
+ * This set of irq_chip functions is specific for doorbells.
+ */
+static const struct irq_chip vgic_v5_db_irq_chip = {
+ .name = "GICv5-DB",
+ .irq_mask = irq_chip_mask_parent,
+ .irq_unmask = irq_chip_unmask_parent,
+ .irq_eoi = irq_chip_eoi_parent,
+ .irq_set_affinity = irq_chip_set_affinity_parent,
+ .irq_get_irqchip_state = irq_chip_get_parent_state,
+ .irq_set_irqchip_state = irq_chip_set_parent_state,
+ .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE |
+ IRQCHIP_MASK_ON_SUSPEND,
+};
+
+static void vgic_v5_irq_db_domain_free(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs)
+{
+ int i;
+
+ for (i = 0; i < nr_irqs; i++) {
+ struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
+
+ irq_set_handler(virq + i, NULL);
+ irq_domain_reset_irq_data(d);
+ }
+
+ irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+}
+
+static int vgic_v5_irq_db_domain_alloc(struct irq_domain *domain,
+ unsigned int virq, unsigned int nr_irqs,
+ void *arg)
+{
+ const struct irq_chip *chip = &vgic_v5_db_irq_chip;
+ struct vgic_v5_vm *vm = arg;
+ struct irq_data *irqd;
+ int ret;
+
+ if (!vm) {
+ kvm_err("invalid parameter for doorbell irq allocation\n");
+ return -EINVAL;
+ }
+
+ ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, NULL);
+ if (ret)
+ return ret;
+
+ for (int i = 0; i < nr_irqs; i++) {
+ irq_domain_set_hwirq_and_chip(domain, virq + i, i, chip,
+ domain->host_data);
+ irqd = irq_desc_get_irq_data(irq_to_desc(virq + i));
+ irqd_set_single_target(irqd);
+ }
+
+ return 0;
+}
+
+static const struct irq_domain_ops vgic_v5_irq_db_domain_ops = {
+ .alloc = vgic_v5_irq_db_domain_alloc,
+ .free = vgic_v5_irq_db_domain_free,
+};
+
+static int vgic_v5_create_per_vm_domain(struct kvm *kvm)
+{
+ struct vgic_v5_vm *vm = &kvm->arch.vgic.gicv5_vm;
+ int nr_vcpus = atomic_read(&kvm->online_vcpus);
+ int id = task_pid_nr(current);
+ int ret, db_virq = 0;
+
+ if (!gicv5_global_data.lpi_domain) {
+ kvm_err("LPI domain uninitialized, can't set up KVM Doorbells\n");
+ return -ENODEV;
+ }
+
+ vm->fwnode = irq_domain_alloc_named_id_fwnode("GICv5-vpe-db", id);
+ if (!vm->fwnode)
+ return -ENOMEM;
+
+ /*
+ * KVM per-VM VPE DB domain; child of LPI domain; only ever handles
+ * doorbells. We know how many doorbells we have, and therefore we
+ * create a linear domain.
+ */
+ vm->domain = irq_domain_create_hierarchy(gicv5_global_data.lpi_domain,
+ 0, nr_vcpus, vm->fwnode,
+ &vgic_v5_irq_db_domain_ops, vm);
+ if (!vm->domain) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ db_virq = irq_domain_alloc_irqs(vm->domain, nr_vcpus, NUMA_NO_NODE, vm);
+ if (db_virq <= 0) {
+ ret = db_virq;
+ goto err;
+ }
+
+ kvm->arch.vgic.gicv5_vm.vpe_db_base = db_virq;
+
+ return 0;
+
+err:
+ if (db_virq > 0)
+ irq_domain_free_irqs(db_virq, nr_vcpus);
+ if (vm->domain)
+ irq_domain_remove(vm->domain);
+ if (vm->fwnode)
+ irq_domain_free_fwnode(vm->fwnode);
+
+ kvm->arch.vgic.gicv5_vm.vpe_db_base = 0;
+ vm->domain = NULL;
+ vm->fwnode = NULL;
+
+ return ret;
+}
+
+static void vgic_v5_teardown_per_vm_domain(struct vgic_v5_vm *vm)
+{
+ if (!vm->domain)
+ return;
+
+ irq_domain_remove(vm->domain);
+ irq_domain_free_fwnode(vm->fwnode);
+ vm->domain = NULL;
+ vm->fwnode = NULL;
+}
+
void vgic_v5_reset(struct kvm_vcpu *vcpu)
{
/*
@@ -167,10 +296,16 @@ void vgic_v5_reset(struct kvm_vcpu *vcpu)
vcpu->arch.vgic_cpu.num_pri_bits = 5;
}
+void vgic_v5_teardown(struct kvm *kvm)
+{
+ vgic_v5_teardown_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+}
+
int vgic_v5_init(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
unsigned long idx;
+ int ret;
if (vgic_initialized(kvm))
return 0;
@@ -182,6 +317,10 @@ int vgic_v5_init(struct kvm *kvm)
}
}
+ ret = vgic_v5_create_per_vm_domain(kvm);
+ if (ret)
+ return ret;
+
/* We only allow userspace to drive the SW_PPI, if it is implemented. */
bitmap_zero(kvm->arch.vgic.gicv5_vm.userspace_ppis,
VGIC_V5_NR_PRIVATE_IRQS);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f45f7e3ec4d6e..f2f5fdc3211d7 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -366,6 +366,7 @@ 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);
+void vgic_v5_teardown(struct kvm *kvm);
int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct kvm_vcpu *vcpu, u32 vintid);
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 8d65a18fefb80..bff2b7c896d55 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -392,6 +392,10 @@ struct vgic_v5_vm {
* convenient way to do that).
*/
DECLARE_BITMAP(vgic_ppi_hmr, VGIC_V5_NR_PRIVATE_IRQS);
+
+ struct fwnode_handle *fwnode;
+ struct irq_domain *domain;
+ int vpe_db_base;
};
struct vgic_dist {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 83cfcd056b250..21c8a69f99bb6 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -613,6 +613,8 @@ void gicv5_irs_syncr(void);
/* Embedded in kvm.arch */
struct gicv5_vpe {
+ int db;
+ bool db_fired;
bool resident;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain
2026-07-24 10:50 ` [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
@ 2026-07-24 11:11 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:11 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvmarm, Oliver Upton, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Allocated doorbell IRQs and their associated request handlers are leaked during VM teardown, exhausting IRQ descriptors and triggering a kernel warning.
--
--- Patch [8]: [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain ---
commit 3bd242f19027d5c3f4f9f1ea26d5e37b8f153bd4
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add VPE doorbell domain
This commit adds support for a per-VM VPE doorbell irq domain in GICv5.
It allocates one doorbell per VPE and stores the doorbell base in the VM's
state for use with IRS commands and wakeups.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 3f7b132110114..ebd7c3ede975f 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> +static void vgic_v5_teardown_per_vm_domain(struct vgic_v5_vm *vm)
> +{
> + if (!vm->domain)
> + return;
> +
[Severity: High]
Does this code leak the allocated doorbell IRQs and their request handlers?
During initialization, vgic_v5_create_per_vm_domain() allocates IRQs via
irq_domain_alloc_irqs(), and vgic_v5_init() requests them via request_irq().
Should free_irq() and irq_domain_free_irqs() be called before tearing down
the domain? Removing a domain that still has allocated IRQs can trigger a
warning in the core IRQ subsystem, and repeatedly creating and destroying
VMs could eventually exhaust the IRQ descriptor space.
> + irq_domain_remove(vm->domain);
> + irq_domain_free_fwnode(vm->fwnode);
> + vm->domain = NULL;
> + vm->fwnode = NULL;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=8
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (7 preceding siblings ...)
2026-07-24 10:50 ` [PATCH v4 08/48] KVM: arm64: gic-v5: Add VPE doorbell domain Sascha Bischoff
@ 2026-07-24 10:50 ` Sascha Bischoff
2026-07-24 11:19 ` sashiko-bot
2026-07-24 10:50 ` [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
` (29 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:50 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, fuad.tabba@linux.dev
GICv5 uses a set of in-memory tables to track and manage VM
state. These must be allocated by the hypervisor, and provided to the
IRS to use.
The VMT (Virtual Machine Table) is a linear or two level table
comprising VMT Entries (VMTE). Each VMTE describes the state for a
single VM. This state includes things such as the SPI and LPI IST
configuration (coming in a future commit), an implementation-defined
VM Descriptor, and a VPE Table (VPET).
The VPET contains one entry per VPE belonging to a VM, and is used to
mark a VPE as valid, as well as providing the address of an
implementation-defined VPE Descriptor, which is used by the hardware
to track and manage VPE state.
This commit adds support for allocating the VMT, and managing the
VMTEs. The VMTEs can be initialised or released for re-use. Allocation
and tracking of unused VMTEs is handled with an IDA.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/vgic/vgic-init.c | 2 +
arch/arm64/kvm/vgic/vgic-v5-tables.c | 630 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 76 ++++
arch/arm64/kvm/vgic/vgic-v5.c | 15 +
drivers/irqchip/irq-gic-v5-irs.c | 12 +-
include/kvm/arm_vgic.h | 4 +
include/linux/irqchip/arm-gic-v5.h | 14 +-
8 files changed, 745 insertions(+), 10 deletions(-)
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.c
create mode 100644 arch/arm64/kvm/vgic/vgic-v5-tables.h
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c1..431de9b145ca1 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
- vgic/vgic-v5.o
+ vgic/vgic-v5.o vgic/vgic-v5-tables.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 625d352756fcf..079a57c2b18f6 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -154,6 +154,8 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
case KVM_DEV_TYPE_ARM_VGIC_V3:
INIT_LIST_HEAD(&kvm->arch.vgic.rd_regions);
break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ kvm->arch.vgic.gicv5_vm.vm_id = VGIC_V5_VM_ID_INVAL;
}
/*
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
new file mode 100644
index 0000000000000..22cd03e0f1963
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -0,0 +1,630 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, 2026 Arm Ltd.
+ */
+
+#include <kvm/arm_vgic.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
+#include <linux/xarray.h>
+#include <asm/kvm_mmu.h>
+
+#include "vgic.h"
+#include "vgic-v5-tables.h"
+
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+
+static struct vgic_v5_vmt *vmt_info;
+/* Serialises lazy installation of shared second-level VMTs. */
+static DEFINE_MUTEX(vmt_l2_lock);
+static DEFINE_XARRAY(vm_info);
+
+/* Level 1 Virtual Machine Table Entry */
+#define GICV5_VMTEL1E_VALID BIT_ULL(0)
+/* Note that there is no shift for the address by design */
+#define GICV5_VMTEL1E_L2_ADDR GENMASK(51, 12)
+
+#define GICV5_VMTEL2E_SIZE 32ULL
+/* An L2 table (two-level VMT) is ALWAYS 4kB! */
+#define GICV5_VMT_L2_TABLE_SIZE 4096ULL
+#define GICV5_VMT_L2_TABLE_ENTRIES (GICV5_VMT_L2_TABLE_SIZE / GICV5_VMTEL2E_SIZE)
+
+/*
+ * As the L2 VMTE is a large data structure, we are splitting it into 4 parts.
+ * We only mask and shift WITHIN each part for simplicity.
+ */
+/* First 64-bit chunk */
+#define GICV5_VMTEL2E_VALID BIT_ULL(0)
+#define GICV5_VMTEL2E_VMD_ADDR_SHIFT 3ULL
+#define GICV5_VMTEL2E_VMD_ADDR GENMASK_ULL(55, 3)
+/* Second 64-bit chunk */
+#define GICV5_VMTEL2E_VPET_ADDR_SHIFT 3ULL
+#define GICV5_VMTEL2E_VPET_ADDR GENMASK_ULL(55, 3)
+#define GICV5_VMTEL2E_VPE_ID_BITS GENMASK_ULL(63, 59)
+/* Third & fourth 64-bit chunks (the encodings are the same for each) */
+#define GICV5_VMTEL2E_IST_VALID BIT_ULL(0)
+#define GICV5_VMTEL2E_IST_L2SZ GENMASK_ULL(2, 1)
+#define GICV5_VMTEL2E_IST_ADDR_SHIFT 6ULL
+#define GICV5_VMTEL2E_IST_ADDR GENMASK_ULL(55, 6)
+#define GICV5_VMTEL2E_IST_ISTSZ GENMASK_ULL(57, 56)
+#define GICV5_VMTEL2E_IST_STRUCTURE BIT_ULL(58)
+#define GICV5_VMTEL2E_IST_ID_BITS GENMASK_ULL(63, 59)
+
+/* Virtual PE Table Entry */
+#define GICV5_VPE_VALID BIT_ULL(0)
+/* Note that there is no shift for the address by design. */
+#define GICV5_VPED_ADDR_SHIFT 3ULL
+#define GICV5_VPED_ADDR GENMASK_ULL(55, 3)
+
+/*
+ * Our IRS might be coherent or non-coherent. If coherent, we can just emit a
+ * DSB to ensure that we're in sync. However, when non-coherent, we need to
+ * manage our cached data explicitly.
+ *
+ * This helper is used to handle both coherent and non-coherent IRSes, and
+ * handles all combinations of cleaning and invalidating to the PoC.
+ */
+static void vgic_v5_clean_inval(void *va, size_t size)
+{
+ unsigned long base = (unsigned long)va;
+
+ dsb(ishst);
+
+ if (kvm_vgic_global_state.vgic_v5_irs_caps.non_coherent)
+ dcache_clean_inval_poc(base, base + size);
+}
+
+/*
+ * Create a linear VM Table. Directly using the number of entries supplied as
+ * the size of an L2 VMTE (32 bytes) guarantees that our allocation is aligned per
+ * the GICv5 requirements for the IRS_VMT_BASER.
+ */
+static int vgic_v5_alloc_vmt_linear(unsigned int num_entries)
+{
+ vmt_info->linear.vmt_base = kzalloc_objs(*vmt_info->linear.vmt_base,
+ num_entries);
+ if (!vmt_info->linear.vmt_base)
+ return -ENOMEM;
+
+ vgic_v5_clean_inval(vmt_info->linear.vmt_base,
+ num_entries * sizeof(struct vmtl2_entry));
+
+ return 0;
+}
+
+/*
+ * Allocate the first level of a two-level VM table. The second-level VM tables
+ * are allocated on demand (by vgic_v5_alloc_l2_vmt()).
+ */
+static int vgic_v5_alloc_vmt_two_level(unsigned int num_entries)
+{
+ /*
+ * Each L2 VMT array is always 4k-sized (covering 128 VMs). This is
+ * mandated by the GICv5 specification (GICv5 EAC0 Specification rule
+ * D_LSPBK). Hence, round up the number of entries to be at least 128
+ * (or the next highest power of two as we give the HW the number of VM
+ * ID bits).
+ */
+ if (num_entries < GICV5_VMT_L2_TABLE_ENTRIES)
+ num_entries = GICV5_VMT_L2_TABLE_ENTRIES;
+ num_entries = roundup_pow_of_two(num_entries);
+
+ vmt_info->l2.num_l1_ents = (num_entries / GICV5_VMT_L2_TABLE_ENTRIES);
+ vmt_info->l2.vmt_base = kzalloc_objs(*vmt_info->l2.vmt_base,
+ vmt_info->l2.num_l1_ents);
+ if (!vmt_info->l2.vmt_base)
+ return -ENOMEM;
+
+ vmt_info->l2.l2ptrs = kzalloc_objs(*vmt_info->l2.l2ptrs,
+ vmt_info->l2.num_l1_ents,
+ GFP_KERNEL);
+ if (!vmt_info->l2.l2ptrs) {
+ kfree(vmt_info->l2.vmt_base);
+ return -ENOMEM;
+ }
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base,
+ vmt_info->l2.num_l1_ents * sizeof(vmtl1_entry));
+
+ return 0;
+}
+
+/*
+ * Allocate a second level VMT, if required. This can be called eagerly, and
+ * will only perform the allocation if required.
+ */
+static int vgic_v5_alloc_l2_vmt(struct kvm *kvm)
+{
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ enum gicv5_vcpu_cmd cmd = VMT_L2_MAP;
+ struct vmtl2_entry *l2_table;
+ unsigned int l1_index;
+ int ret;
+
+ /* Nothing to do if we have linear tables! */
+ if (!vmt_info->two_level)
+ return 0;
+
+ if (vm_id == VGIC_V5_VM_ID_INVAL)
+ return -EINVAL;
+
+ /*
+ * We have 4k-sized L2 tables - this is mandated by the spec for
+ * two-level VMTs (GICv5 EAC0 Specification rule D_LSPBK). This means
+ * that we have 128 entries per L1 VMTE.
+ */
+ l1_index = vm_id / GICV5_VMT_L2_TABLE_ENTRIES;
+
+ guard(mutex)(&vmt_l2_lock);
+
+ /* Already valid? Great! */
+ if (vmt_info->l2.l2ptrs[l1_index])
+ return 0;
+
+ l2_table = kzalloc_objs(*l2_table, GICV5_VMT_L2_TABLE_ENTRIES);
+ if (!l2_table)
+ return -ENOMEM;
+
+ vgic_v5_clean_inval(l2_table, GICV5_VMT_L2_TABLE_SIZE);
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+
+ WRITE_ONCE(vmt_info->l2.vmt_base[l1_index],
+ cpu_to_le64(virt_to_phys(l2_table)));
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+
+ /*
+ * VMAP in the L2 VMT via the IRS. We use any of the VM's CPUs as a
+ * conduit for interacting with the host's IRS. In the current case,
+ * this lets us resolve the VM ID to pass to the hardware.
+ */
+ ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+
+ /* We've failed to make the L2 VMT valid - things are very broken! */
+ if (ret) {
+ /* Remove the pointer from L1 table */
+ WRITE_ONCE(vmt_info->l2.vmt_base[l1_index], 0);
+
+ vgic_v5_clean_inval(vmt_info->l2.vmt_base + l1_index,
+ sizeof(vmtl1_entry));
+
+ kfree(l2_table);
+
+ return ret;
+ }
+
+ vmt_info->l2.l2ptrs[l1_index] = l2_table;
+
+ return 0;
+}
+
+/*
+ * Allocate the top-level VMT. This can either be linear or two-level.
+ */
+int vgic_v5_vmt_allocate(unsigned int max_vpes)
+{
+ int ret;
+
+ /* Allocate the tracking structure */
+ vmt_info = kzalloc_obj(*vmt_info, GFP_KERNEL);
+ if (!vmt_info)
+ return -ENOMEM;
+
+ ida_init(&vmt_info->vm_id_ida);
+ vmt_info->max_vpes = max_vpes;
+ vmt_info->vmd_size = irs_caps.vmd_size;
+ vmt_info->vped_size = irs_caps.vped_size;
+ vmt_info->two_level = irs_caps.two_level_vmt_support;
+ vmt_info->num_entries = irs_caps.max_vms;
+
+ if (vmt_info->two_level)
+ ret = vgic_v5_alloc_vmt_two_level(vmt_info->num_entries);
+ else
+ ret = vgic_v5_alloc_vmt_linear(vmt_info->num_entries);
+
+ /* If anything failed, free our tracking structure before returning */
+ if (ret) {
+ kfree(vmt_info);
+ vmt_info = NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Free the VMT and associated tracking structures. This isn't strictly expected
+ * to be called in general operation, but instead exists for completeness.
+ */
+int vgic_v5_vmt_free(void)
+{
+ if (!vmt_info)
+ return 0;
+
+ if (!vmt_info->two_level) {
+ kfree(vmt_info->linear.vmt_base);
+ } else {
+ /* Free the L2 tables; kfree(NULL) is safe */
+ for (int i = 0; i < vmt_info->l2.num_l1_ents; ++i)
+ kfree(vmt_info->l2.l2ptrs[i]);
+ kfree(vmt_info->l2.l2ptrs);
+
+ /* And now free the L1 table */
+ kfree(vmt_info->l2.vmt_base);
+ }
+
+ ida_destroy(&vmt_info->vm_id_ida);
+ kfree(vmt_info);
+ vmt_info = NULL;
+
+ return 0;
+}
+
+/*
+ * Look up a VMT Entry by VM ID.
+ */
+static struct vmtl2_entry *vgic_v5_get_l2_vmte(u32 vm_id)
+{
+ unsigned int l1_index, l2_index;
+ struct vmtl2_entry *l2_table;
+
+ if (vm_id == VGIC_V5_VM_ID_INVAL)
+ return ERR_PTR(-EINVAL);
+
+ if (!vmt_info->two_level)
+ return &vmt_info->linear.vmt_base[vm_id];
+
+ l1_index = vm_id / GICV5_VMT_L2_TABLE_ENTRIES;
+ l2_index = vm_id % GICV5_VMT_L2_TABLE_ENTRIES;
+
+ if (l1_index >= vmt_info->l2.num_l1_ents)
+ return ERR_PTR(-E2BIG);
+
+ if (!vmt_info->l2.l2ptrs[l1_index])
+ return ERR_PTR(-EINVAL);
+
+ l2_table = vmt_info->l2.l2ptrs[l1_index];
+ return &l2_table[l2_index];
+}
+
+/*
+ * Zero a VMT Entry, and flush & invalidate to the PoC, if required.
+ */
+static int vgic_v5_reset_vmte(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /*
+ * The VMT is normal memory shared with the IRS. Invalidate before
+ * rewriting the entry so that cacheline-granular maintenance cannot
+ * later push stale data for neighbouring IRS-visible state back to
+ * memory.
+ */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ /*
+ * Prevent the compiler from eliding the individual VMTE
+ * stores. Ordering and visibility to the IRS are provided by the
+ * surrounding cache maintenance and command protocol, not by
+ * WRITE_ONCE().
+ *
+ * The same compiler-access constraint applies to READ_ONCE() users in
+ * this file: when inspecting IRS-visible table entries, read the field
+ * exactly once and prevent the compiler from reusing, merging or
+ * tearing the access. Coherency and freshness for non-coherent IRSes
+ * still come from the surrounding cache maintenance.
+ */
+ WRITE_ONCE(vmte->val[0], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[1], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[2], cpu_to_le64(0ULL));
+ WRITE_ONCE(vmte->val[3], cpu_to_le64(0ULL));
+
+ /* And make our write visible to the IRS (if non-coherent) */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ return 0;
+}
+
+/*
+ * Use the IDA to allocate a new VM ID, and track it in the gicv5_vm data
+ * structure. If we're out of VM IDs, the IDA catches that, and we return the
+ * error (-ENOSPC). If we've previously allocated a VM ID, we catch that too and
+ * return -EBUSY.
+ */
+int vgic_v5_allocate_vm_id(struct kvm *kvm)
+{
+ int id;
+
+ if (kvm->arch.vgic.gicv5_vm.vm_id != VGIC_V5_VM_ID_INVAL)
+ return -EBUSY;
+
+ id = ida_alloc_max(&vmt_info->vm_id_ida, vmt_info->num_entries - 1u,
+ GFP_KERNEL);
+ if (id < 0)
+ return id;
+
+ kvm->arch.vgic.gicv5_vm.vm_id = id;
+
+ return 0;
+}
+
+/*
+ * Release the VM ID to allow it to be reallocated in the future.
+ */
+void vgic_v5_release_vm_id(struct kvm *kvm)
+{
+ if (kvm->arch.vgic.gicv5_vm.vm_id == VGIC_V5_VM_ID_INVAL)
+ return;
+
+ ida_free(&vmt_info->vm_id_ida, kvm->arch.vgic.gicv5_vm.vm_id);
+ kvm->arch.vgic.gicv5_vm.vm_id = VGIC_V5_VM_ID_INVAL;
+}
+
+/*
+ * Initialise an entry in the VMT based on the index of the VM.
+ *
+ * Note: We don't mark the VMTE as valid as this needs to be done by
+ * the hardware.
+ */
+int vgic_v5_vmte_init(struct kvm *kvm)
+{
+ int nr_cpus = atomic_read(&kvm->online_vcpus);
+ struct vgic_v5_vm_info *vmi = NULL;
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+ void **vped_ptrs = NULL;
+ vpe_entry *vpet = NULL;
+ void *vmd = NULL;
+ int ret;
+ u64 tmp;
+
+ if (nr_cpus > vmt_info->max_vpes)
+ return -E2BIG;
+
+ /*
+ * If we're using two-level VMTs, L2 is allocated on demand. For linear
+ * VMTs, this is a NOP.
+ */
+ ret = vgic_v5_alloc_l2_vmt(kvm);
+ if (ret)
+ return ret;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /* If the entry is already valid, something went wrong */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EINVAL;
+
+ ret = vgic_v5_reset_vmte(kvm);
+ if (ret)
+ return ret;
+
+ vmi = kzalloc_obj(*vmi);
+ if (!vmi) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ ret = xa_insert(&vm_info, vm_id, vmi, GFP_KERNEL);
+ if (ret)
+ goto out_fail;
+
+ /* Allocate and assign the VM Descriptor, if required. */
+ if (vmt_info->vmd_size != 0) {
+ vmd = kzalloc(vmt_info->vmd_size, GFP_KERNEL);
+ if (!vmd) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ /* Stash the VA so we can free it later */
+ vmi->vmd_base = vmd;
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_VMD_ADDR,
+ virt_to_phys(vmd) >> GICV5_VMTEL2E_VMD_ADDR_SHIFT);
+ WRITE_ONCE(vmte->val[0], cpu_to_le64(tmp));
+ }
+
+ /*
+ * Allocate and assign the VPE Table. Round up the number of CPUs to a
+ * whole power of two as we cannot describe non-powers-of-two in the
+ * VMTE field as it conveys the number of ID bits used and not the
+ * number of vPEs. IRS_IDR1.IAFFID_BITS is encoded as N - 1, so expose
+ * at least one VPE ID bit even for a single-vCPU VM to keep the views
+ * consistent.
+ */
+ nr_cpus = max(2UL, roundup_pow_of_two(nr_cpus));
+ vmi->vpe_id_bits = fls(nr_cpus) - 1;
+
+ vpet = kzalloc_objs(*vpet, nr_cpus);
+ if (!vpet) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+
+ /* Stash the VA so we can free it later */
+ vmi->vpet_base = vpet;
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_VPET_ADDR,
+ virt_to_phys(vpet) >> GICV5_VMTEL2E_VPET_ADDR_SHIFT);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_VPE_ID_BITS, vmi->vpe_id_bits);
+ WRITE_ONCE(vmte->val[1], cpu_to_le64(tmp));
+
+ vped_ptrs = kzalloc_objs(*vped_ptrs, nr_cpus, GFP_KERNEL);
+ if (!vped_ptrs) {
+ ret = -ENOMEM;
+ goto out_fail;
+ }
+ vmi->vped_ptrs = vped_ptrs;
+
+ if (vmd)
+ vgic_v5_clean_inval(vmd, vmt_info->vmd_size);
+ vgic_v5_clean_inval(vpet, sizeof(*vpet) * nr_cpus);
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ kvm->arch.vgic.gicv5_vm.vmte_allocated = true;
+
+ return 0;
+
+out_fail:
+ /* kfree(NULL) is safe so we can just kfree() at leisure */
+ kfree(vmd);
+ kfree(vpet);
+ kfree(vped_ptrs);
+ if (vmi)
+ xa_erase(&vm_info, vm_id);
+ kfree(vmi);
+
+ vgic_v5_reset_vmte(kvm);
+
+ return ret;
+}
+
+/*
+ * Release the VMT Entry, freeing up any allocated data structures before
+ * zeroing the VMTE.
+ *
+ * The VMTE must be marked as invalid before it is released.
+ */
+int vgic_v5_vmte_release(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ int ret;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /* Reject if the VMTE has not been marked as invalid! */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EINVAL;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ goto no_vmi;
+
+ for (int i = 0; i < BIT(vmi->vpe_id_bits); i++)
+ kfree(vmi->vped_ptrs[i]);
+ kfree(vmi->vped_ptrs);
+ kfree(vmi->vpet_base);
+ kfree(vmi->vmd_base);
+
+ xa_erase(&vm_info, vm_id);
+ kfree(vmi);
+
+no_vmi:
+ /*
+ * If we didn't get far enough into allocating a VMTE to create the VM
+ * info structure, then we just zero the VMTE and move on. There's
+ * nothing else we can realistically do here.
+ */
+ ret = vgic_v5_reset_vmte(kvm);
+ if (ret)
+ return ret;
+
+ kvm->arch.vgic.gicv5_vm.vmte_allocated = false;
+
+ return 0;
+}
+
+/*
+ * Allocate a VPE descriptor and provide it to the hardware via the VPE Table.
+ */
+int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe_id = vgic_v5_vpe_id(vcpu);
+ struct vgic_v5_vm_info *vmi;
+ vpe_entry tmp, *vpet_base;
+ void *vped;
+
+ /* Make sure we're not over what the hardware supports */
+ if (vpe_id >= vmt_info->max_vpes)
+ return -E2BIG;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vpe_id >= 1 << vmi->vpe_id_bits)
+ return -E2BIG;
+
+ vpet_base = vmi->vpet_base;
+
+ /* If the VPETE for this CPU is already valid we've gone wrong */
+ vgic_v5_clean_inval(&vpet_base[vpe_id], sizeof(*vpet_base));
+ if (le64_to_cpu(READ_ONCE(vpet_base[vpe_id])) & GICV5_VPE_VALID)
+ return -EBUSY;
+
+ /* Alloc VPE Descriptor. Only used by IRS. */
+ vped = kzalloc(vmt_info->vped_size, GFP_KERNEL);
+ if (!vped)
+ return -ENOMEM;
+
+ vmi->vped_ptrs[vpe_id] = vped;
+
+ tmp = FIELD_PREP(GICV5_VPED_ADDR, virt_to_phys(vped) >> GICV5_VPED_ADDR_SHIFT);
+ WRITE_ONCE(vpet_base[vpe_id], cpu_to_le64(tmp));
+
+ vgic_v5_clean_inval(vped, vmt_info->vped_size);
+ vgic_v5_clean_inval(vpet_base + vpe_id, sizeof(vpe_entry));
+
+ return 0;
+}
+
+/*
+ * Free the memory allocated for the VPE descriptor.
+ */
+int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe_id = vgic_v5_vpe_id(vcpu);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ vpe_entry *vpet_base;
+ void *vped;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[0])) & GICV5_VMTEL2E_VALID)
+ return -EBUSY;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vpe_id >= 1 << vmi->vpe_id_bits)
+ return -E2BIG;
+
+ vpet_base = vmi->vpet_base;
+ WRITE_ONCE(vpet_base[vpe_id], 0ULL);
+
+ vgic_v5_clean_inval(vpet_base + vpe_id, sizeof(vpe_entry));
+
+ /* Free VPE Descriptor. Only used by IRS. */
+ vped = vmi->vped_ptrs[vpe_id];
+ vmi->vped_ptrs[vpe_id] = NULL;
+ kfree(vped);
+
+ return 0;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
new file mode 100644
index 0000000000000..b763dc3a60e3a
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025, 2026 Arm Ltd.
+ */
+
+#ifndef __KVM_ARM_VGICV5_TABLES_H__
+#define __KVM_ARM_VGICV5_TABLES_H__
+
+#include <linux/idr.h>
+#include <linux/irqchip/arm-gic-v5.h>
+
+/* Level 1 Virtual Machine Table Entry */
+typedef __le64 vmtl1_entry;
+
+/* Level 2 Virtual Machine Table Entry */
+struct vmtl2_entry {
+ __le64 val[4];
+};
+
+/* Virtual PE Table Entry */
+typedef __le64 vpe_entry;
+
+struct vgic_v5_vm_info {
+ void __iomem *vmd_base;
+ vpe_entry __iomem *vpet_base;
+ void __iomem **vped_ptrs;
+ u8 vpe_id_bits;
+};
+
+struct vgic_v5_vmt {
+ union {
+ struct {
+ struct vmtl2_entry *vmt_base;
+ unsigned int num_ents;
+ } linear;
+ struct {
+ vmtl1_entry *vmt_base;
+ struct vmtl2_entry **l2ptrs;
+ unsigned int num_l1_ents;
+ } l2;
+ };
+ bool two_level;
+ unsigned int num_entries;
+ unsigned int max_vpes;
+ size_t vmd_size;
+ size_t vped_size;
+ struct ida vm_id_ida;
+};
+
+static inline u32 vgic_v5_vm_id(struct kvm *kvm)
+{
+ return kvm->arch.vgic.gicv5_vm.vm_id;
+}
+
+static inline u16 vgic_v5_vpe_id(struct kvm_vcpu *vcpu)
+{
+ return vcpu->vcpu_idx;
+}
+
+static inline int vgic_v5_vpe_db(struct kvm_vcpu *vcpu)
+{
+ return vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db;
+}
+
+int vgic_v5_vmt_allocate(unsigned int max_vpes);
+int vgic_v5_vmt_free(void);
+
+int vgic_v5_allocate_vm_id(struct kvm *kvm);
+void vgic_v5_release_vm_id(struct kvm *kvm);
+
+int vgic_v5_vmte_init(struct kvm *kvm);
+int vgic_v5_vmte_release(struct kvm *kvm);
+int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
+int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
+
+#endif
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index ebd7c3ede975f..995606e0c3a39 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -153,6 +153,20 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
+{
+ enum gicv5_vcpu_cmd *cmd = vcpu_info;
+
+ switch (*cmd) {
+ case VMT_L2_MAP:
+ case VMTE_MAKE_VALID:
+ case VMTE_MAKE_INVALID:
+ /* Not yet implemented */
+ default:
+ return -EINVAL;
+ }
+}
+
/*
* This set of irq_chip functions is specific for doorbells.
*/
@@ -164,6 +178,7 @@ static const struct irq_chip vgic_v5_db_irq_chip = {
.irq_set_affinity = irq_chip_set_affinity_parent,
.irq_get_irqchip_state = irq_chip_get_parent_state,
.irq_set_irqchip_state = irq_chip_set_parent_state,
+ .irq_set_vcpu_affinity = vgic_v5_db_set_vcpu_affinity,
.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
diff --git a/drivers/irqchip/irq-gic-v5-irs.c b/drivers/irqchip/irq-gic-v5-irs.c
index 607e066821b52..70502b07ec8d7 100644
--- a/drivers/irqchip/irq-gic-v5-irs.c
+++ b/drivers/irqchip/irq-gic-v5-irs.c
@@ -269,24 +269,24 @@ int gicv5_irs_iste_alloc(const u32 lpi)
* itself is not supported) again serves to make it easier to find physically
* contiguous blocks of memory.
*/
-static unsigned int gicv5_irs_l2_sz(u32 idr2)
+unsigned int gicv5_irs_l2_sz(u32 l2sz)
{
switch (PAGE_SIZE) {
case SZ_64K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_64KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_64K;
fallthrough;
case SZ_4K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_4K;
fallthrough;
case SZ_16K:
- if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_16KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_16K;
break;
}
- if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(idr2))
+ if (GICV5_IRS_IST_L2SZ_SUPPORT_4KB(l2sz))
return GICV5_IRS_IST_CFGR_L2SZ_4K;
return GICV5_IRS_IST_CFGR_L2SZ_64K;
@@ -334,7 +334,7 @@ static int __init gicv5_irs_init_ist(struct gicv5_irs_chip_data *irs_data)
lpi_id_bits = min(lpi_id_bits, gicv5_global_data.cpuif_id_bits);
if (two_levels)
- l2sz = gicv5_irs_l2_sz(idr2);
+ l2sz = gicv5_irs_l2_sz(FIELD_GET(GICV5_IRS_IDR2_IST_L2SZ, idr2));
istmd = !!FIELD_GET(GICV5_IRS_IDR2_ISTMD, idr2);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index bff2b7c896d55..ba32cd71fe0a7 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -374,6 +374,8 @@ struct vgic_redist_region {
struct list_head list;
};
+#define VGIC_V5_VM_ID_INVAL (-1)
+
struct vgic_v5_vm {
/*
* We only expose a subset of PPIs to the guest. This subset is a
@@ -396,6 +398,8 @@ struct vgic_v5_vm {
struct fwnode_handle *fwnode;
struct irq_domain *domain;
int vpe_db_base;
+ u32 vm_id;
+ bool vmte_allocated;
};
struct vgic_dist {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 21c8a69f99bb6..27b13bf2c1e2c 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -159,9 +159,9 @@
#define GICV5_IRS_IDR2_LPI BIT(5)
#define GICV5_IRS_IDR2_ID_BITS GENMASK(4, 0)
-#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(11), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(12), (r))
-#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(13), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_4KB(r) FIELD_GET(BIT(0), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_16KB(r) FIELD_GET(BIT(1), (r))
+#define GICV5_IRS_IST_L2SZ_SUPPORT_64KB(r) FIELD_GET(BIT(2), (r))
#define GICV5_IRS_IDR3_VMT_LEVELS BIT(10)
#define GICV5_IRS_IDR3_VM_ID_BITS GENMASK(9, 5)
@@ -609,6 +609,7 @@ int gicv5_irs_cpu_to_iaffid(int cpu_id, u16 *iaffid);
struct gicv5_irs_chip_data *gicv5_irs_lookup_by_spi_id(u32 spi_id);
int gicv5_spi_irq_set_type(struct irq_data *d, unsigned int type);
int gicv5_irs_iste_alloc(u32 lpi);
+unsigned int gicv5_irs_l2_sz(u32 l2sz);
void gicv5_irs_syncr(void);
/* Embedded in kvm.arch */
@@ -653,4 +654,11 @@ void gicv5_deinit_lpis(void);
void __init gicv5_its_of_probe(struct device_node *parent);
void __init gicv5_its_acpi_probe(void);
+
+enum gicv5_vcpu_cmd {
+ VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
+ VMTE_MAKE_VALID, /* Make the VMTE valid */
+ VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+};
+
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables
2026-07-24 10:50 ` [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
@ 2026-07-24 11:19 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:19 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvmarm, Oliver Upton, kvm
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] `vgic_v5_alloc_l2_vmt()` unconditionally dereferences `kvm_get_vcpu(kvm, 0)`, causing a host kernel panic if no vCPUs are online.
- [High] Allocating hardware-modified tables (VMTE and VPETE) densely in normal cacheable memory causes silent data corruption on non-coherent IRS systems due to cacheline false sharing.
- [High] Missing cache invalidation before zeroing the VPE table entry causes false-sharing corruption of neighboring hardware state.
- [High] The error handling path in `vgic_v5_vmte_init()` incorrectly calls `xa_erase()` when `xa_insert()` fails with `-EBUSY`, destroying pre-existing valid state.
- [High] Early returns on error in `vgic_v5_vmte_release()` bypass critical cleanup steps, permanently leaking memory and leaving hardware state active.
- [Medium] Lockless read of `l2ptrs` array without `READ_ONCE()` can lead to observing a stale or partially updated pointer.
--
--- Patch [9]: [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables ---
commit 6d18ab27d2875b7a3e267a39ae7efb4397ebac92
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
Subject: KVM: arm64: gic-v5: Create and manage VM and VPE tables
GICv5 uses a set of in-memory tables to track and manage VM
state. These must be allocated by the hypervisor, and provided to the
IRS to use.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> new file mode 100644
> index 0000000000000..22cd03e0f1963
> --- /dev/null
> +++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> @@ -0,0 +1,630 @@
[ ... ]
> +static int vgic_v5_alloc_vmt_linear(unsigned int num_entries)
> +{
> + vmt_info->linear.vmt_base = kzalloc_objs(*vmt_info->linear.vmt_base,
> + num_entries);
[Severity: High]
Does allocating the hardware-modified VMTE table densely in normal cacheable
memory cause silent data corruption on non-coherent IRS systems?
Since kzalloc_objs() allocates the 32-byte VMTE entries contiguously,
multiple entries will share a 64-byte CPU cache line. If a CPU modifies one
entry, the subsequent DC CIVAC cache maintenance could write the entire cache
line back to memory, overwriting any concurrent hardware updates to
neighboring entries.
[ ... ]
> +static int vgic_v5_alloc_l2_vmt(struct kvm *kvm)
> +{
> + struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
[ ... ]
> + /*
> + * VMAP in the L2 VMT via the IRS. We use any of the VM's CPUs as a
> + * conduit for interacting with the host's IRS. In the current case,
> + * this lets us resolve the VM ID to pass to the hardware.
> + */
> + ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
[Severity: High]
Can vcpu0 be NULL here, leading to a host kernel panic?
If vgic_v5_vmte_init() is called before any vCPUs are online (which it appears
to handle by rounding nr_cpus up to 2), kvm_get_vcpu(kvm, 0) will return NULL.
vgic_v5_vpe_db(vcpu0) unconditionally dereferences the vCPU pointer, which
would cause an immediate crash.
[ ... ]
> +static struct vmtl2_entry *vgic_v5_get_l2_vmte(u32 vm_id)
> +{
[ ... ]
> + if (!vmt_info->l2.l2ptrs[l1_index])
> + return ERR_PTR(-EINVAL);
> +
> + l2_table = vmt_info->l2.l2ptrs[l1_index];
[Severity: Medium]
Could this lockless read of l2ptrs observe a stale or partially updated
pointer without READ_ONCE()?
Since the array element is accessed locklessly and read twice (once for the
NULL check and once for the assignment), the compiler might emit two separate
loads. A concurrent update in vgic_v5_alloc_l2_vmt() could cause these loads
to return inconsistent states.
[ ... ]
> +int vgic_v5_vmte_init(struct kvm *kvm)
> +{
[ ... ]
> + ret = xa_insert(&vm_info, vm_id, vmi, GFP_KERNEL);
> + if (ret)
> + goto out_fail;
[ ... ]
> + vpet = kzalloc_objs(*vpet, nr_cpus);
[Severity: High]
Does allocating the 8-byte VPETE table entries densely cause the same false
sharing and data corruption issues as with the VMTE table?
Since the entries are tightly packed into cache lines, a flush for one entry
could destroy concurrent hardware modifications to neighboring entries on
non-coherent IRS systems.
[ ... ]
> +out_fail:
> + /* kfree(NULL) is safe so we can just kfree() at leisure */
> + kfree(vmd);
> + kfree(vpet);
> + kfree(vped_ptrs);
> + if (vmi)
> + xa_erase(&vm_info, vm_id);
[Severity: High]
Is it safe to unconditionally erase the XArray entry if xa_insert() fails?
If xa_insert() fails with -EBUSY because an entry already exists for this
vm_id (e.g., due to concurrent initialization), the code jumps to out_fail.
Since vmi was successfully allocated and is non-NULL, this cleanup block calls
xa_erase(), blindly deleting the pre-existing valid entry and corrupting KVM's
tracking of an active VM.
[ ... ]
> +int vgic_v5_vmte_release(struct kvm *kvm)
> +{
[Severity: High]
Are there early return paths in this function (such as when freeing LPI or
SPI IST structures) that bypass critical cleanup steps?
If freeing the IST structures fails and returns an error early, it bypasses
the rest of the teardown logic. It fails to free the vped_ptrs, vpet_base,
and vmd_base allocations, and skips calling vgic_v5_reset_vmte(). This leaves
the hardware state active and permanently leaks memory.
[ ... ]
> +int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
> +{
[ ... ]
> + vpet_base = vmi->vpet_base;
> + WRITE_ONCE(vpet_base[vpe_id], 0ULL);
> +
> + vgic_v5_clean_inval(vpet_base + vpe_id, sizeof(vpe_entry));
[Severity: High]
Does this sequence need a cache invalidation before the write to avoid false
sharing corruption?
If the CPU holds a stale, clean copy of this cache line, the WRITE_ONCE()
dirties it without fetching the latest hardware state from RAM. The subsequent
cache flush writes this stale line back to memory, destroying concurrent
hardware modifications to other VPE entries sharing the same cache line.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=9
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (8 preceding siblings ...)
2026-07-24 10:50 ` [PATCH v4 09/48] KVM: arm64: gic-v5: Create and manage VM and VPE tables Sascha Bischoff
@ 2026-07-24 10:50 ` Sascha Bischoff
2026-07-24 11:22 ` sashiko-bot
2026-07-24 10:51 ` [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
` (28 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:50 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, fuad.tabba@linux.dev
GICv5 guests use Interrupt State Tables (ISTs) to track and manage the
interrupt state for SPIs and LPIs. These ISTs are provided to the host's
IRS via the VMTE.
On a host GICv5 system, SPIs do not require any up-front memory
allocation prior to their use, unlike LPIs which require the OS to
allocate an IST. For a GICv5 guest, the same holds from the guest's
point of view: SPIs should require no explicit memory allocation by the
guest. This means that KVM must provision the memory passed to the IRS
for managing a guest's SPI state.
Allocate the SPI IST before running the guest for the first time. As only
a small number of SPIs are expected, this is always allocated as a linear
IST. The host is responsible for freeing this memory on guest teardown.
For LPIs, the guest provisions memory for its LPI IST. KVM does not pass
that memory directly to the host IRS. Instead, allocate a shadow LPI IST
and pass that to the IRS through the VMTE. The LPI IST may be allocated
as a two-level structure when supported and required by the configured
LPI ID space, as many more LPIs are expected than SPIs. The host frees
this memory on guest teardown.
This commit also extends the doorbell domain to allow the doorbells
themselves to act as a conduit for issuing commands, similar to what
exists for GICv4 support. Effectively, irq_set_vcpu_affinity() becomes
an ioctl-like interface for issuing commands specific to either a VM or
the particular VPE that the doorbell belongs to. Add support for:
VMT_L2_MAP - Make a second level VM table valid
VMTE_MAKE_VALID - Make a single VMTE, and hence VM, valid
VMTE_MAKE_INVALID - Make a single VMTE, and hence VM, invalid
SPI_VIST_MAKE_VALID - Make the SPI IST valid
LPI_VIST_MAKE_VALID - Make the LPI IST valid
LPI_VIST_MAKE_INVALID - Make the LPI IST invalid
None of these commands are plumbed through to the host IRS at this
stage.
There is intentionally no SPI_VIST_MAKE_INVALID command. The SPI IST is
allocated as part of VM creation and is not invalidated while the VM is
live. It is freed when the VM is destroyed, after the VMTE has been made
invalid. The LPI IST, on the other hand, is driven by the guest, which is
free to invalidate and free its LPI IST at any point.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5-tables.c | 540 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.h | 22 ++
arch/arm64/kvm/vgic/vgic-v5.c | 3 +
include/linux/irqchip/arm-gic-v5.h | 3 +
4 files changed, 568 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index 22cd03e0f1963..aa737972f525c 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -59,6 +59,14 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VPED_ADDR_SHIFT 3ULL
#define GICV5_VPED_ADDR GENMASK_ULL(55, 3)
+/*
+ * The LPI and SPI configuration is stored in the 2nd and 3rd 64-bit chunks of
+ * the VMTE (0-based). We call this a section here in an attempt to simplify the
+ * code.
+ */
+#define GICV5_VMTEL2_LPI_SECTION 2
+#define GICV5_VMTEL2_SPI_SECTION 3
+
/*
* Our IRS might be coherent or non-coherent. If coherent, we can just emit a
* DSB to ensure that we're in sync. However, when non-coherent, we need to
@@ -494,6 +502,25 @@ int vgic_v5_vmte_init(struct kvm *kvm)
return ret;
}
+/*
+ * The following set of forward declarations makes the code layout a *little*
+ * clearer as it lets us keep the IST-related code together.
+ */
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits,
+ unsigned int istsz);
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2_split);
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_spi_ist_free(struct kvm *kvm);
+
/*
* Release the VMT Entry, freeing up any allocated data structures before
* zeroing the VMTE.
@@ -520,6 +547,22 @@ int vgic_v5_vmte_release(struct kvm *kvm)
if (!vmi)
goto no_vmi;
+ /* If we have an LPI IST, free it */
+ if (vmi->h_lpi_ist) {
+ ret = vgic_v5_lpi_ist_free(kvm);
+ if (ret)
+ return ret;
+ }
+ vmi->h_lpi_ist = NULL;
+
+ /* If we have an SPI IST, free it */
+ if (vmi->h_spi_ist) {
+ ret = vgic_v5_spi_ist_free(kvm);
+ if (ret)
+ return ret;
+ }
+ vmi->h_spi_ist = NULL;
+
for (int i = 0; i < BIT(vmi->vpe_id_bits); i++)
kfree(vmi->vped_ptrs[i]);
kfree(vmi->vped_ptrs);
@@ -628,3 +671,500 @@ int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
return 0;
}
+
+/*
+ * Assign an already allocated IST to the VM by populating the fields in the
+ * corresponding VMTE. We re-use this code for both an SPI IST and LPI IST, even
+ * if the paths to reach it might be vastly different.
+ */
+static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
+ bool two_level, unsigned int id_bits,
+ unsigned int l2sz, unsigned int istsz,
+ bool spi_ist)
+{
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ enum gicv5_vcpu_cmd cmd;
+ struct vmtl2_entry *vmte;
+ unsigned int section;
+ u64 tmp;
+ int ret;
+
+ /*
+ * The L2 VMTE comprises four 64-bit "sections", where sections 2 & 3
+ * describe the LPI and SPI ISTs, respectively. Both the LPI and SPI
+ * sections have the same layout, and as we are either operating on SPIs
+ * or LPIs we pick a section of the VMTE to modify up-front.
+ *
+ * See the GICv5 EAC0 Specification 11.2.2 for more details about the
+ * VMTE layout.
+ */
+ section = spi_ist ? GICV5_VMTEL2_SPI_SECTION : GICV5_VMTEL2_LPI_SECTION;
+
+ if (ist_base & ~GICV5_VMTEL2E_IST_ADDR) {
+ kvm_err("IST alignment issue! Address: 0x%llx, Mask 0x%llx\n",
+ ist_base, GICV5_VMTEL2E_IST_ADDR);
+ return -EINVAL;
+ }
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ /* Bail if already allocated */
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ if (le64_to_cpu(READ_ONCE(vmte->val[section])) & GICV5_VMTEL2E_IST_VALID)
+ return -EINVAL;
+
+ tmp = FIELD_PREP(GICV5_VMTEL2E_IST_L2SZ, l2sz);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ADDR,
+ ist_base >> GICV5_VMTEL2E_IST_ADDR_SHIFT);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ISTSZ, istsz);
+ tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ID_BITS, id_bits);
+ if (two_level)
+ tmp |= GICV5_VMTEL2E_IST_STRUCTURE;
+
+ WRITE_ONCE(vmte->val[section], cpu_to_le64(tmp));
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ /* Finally, mark the entry as valid */
+ cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
+ ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+
+ return ret;
+}
+
+/*
+ * Allocate a Linear IST - always used for SPIs and potentially LPIs.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec.
+ *
+ * NOTE: istsz is the FIELD used by GICv5, not the actual size (or log2() of the
+ * size).
+ */
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits, unsigned int istsz)
+{
+ const size_t n = id_bits + 1 + istsz;
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *ist;
+ u32 l1sz;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ /*
+ * Allocate the IST. We only have one level, so we just use the L2 ISTE.
+ */
+ l1sz = BIT(n + 1);
+ ist = kzalloc(l1sz, GFP_KERNEL_ACCOUNT);
+ if (!ist)
+ return -ENOMEM;
+
+ if (spi_ist) {
+ vmi->h_spi_ist = ist;
+ } else {
+ vmi->h_lpi_ist_structure = false;
+ vmi->h_lpi_ist = ist;
+ }
+
+ vgic_v5_clean_inval(ist, l1sz);
+
+ return 0;
+}
+
+/*
+ * Allocate the first level of a two-level IST - LPI, only.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec.
+ *
+ * NOTE: istsz and l2sz are the FIELDS used by GICv5, not the actual sizes (or
+ * log2() of the sizes).
+ */
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ const u32 l1_size = BIT(n + 1);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *ist;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ ist = kzalloc(l1_size, GFP_KERNEL_ACCOUNT);
+ if (!ist)
+ return -ENOMEM;
+
+ vmi->h_lpi_ist_structure = true;
+ vmi->h_lpi_ist = ist;
+
+ vgic_v5_clean_inval(ist, l1_size);
+
+ return 0;
+}
+
+/*
+ * Allocate ALL of the second level ISTs for a two-level IST - LPI, only.
+ *
+ * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
+ * spec. The l2_size calculation is from section 11.2.3 of the same document.
+ *
+ * NOTE: istsz and l2sz are the FIELDS used by GICv5, not the actual sizes (or
+ * log2() of the sizes).
+ */
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ const int l1_entries = BIT(n + 1) / GICV5_IRS_ISTL1E_SIZE;
+ const size_t l2_size = BIT(11 + (2 * l2sz) + 1);
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ __le64 *l2ist;
+ __le64 *l1ist;
+ int index;
+ u64 val;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ l1ist = vmi->h_lpi_ist;
+
+ /*
+ * Allocate the storage for the pointers to the L2 ISTs (used when
+ * freeing later).
+ */
+ vmi->h_lpi_l2_ists = kzalloc_objs(*vmi->h_lpi_l2_ists, l1_entries,
+ GFP_KERNEL_ACCOUNT);
+ if (!vmi->h_lpi_l2_ists)
+ return -ENOMEM;
+
+ /* Allocate the L2 IST for each L1 IST entry */
+ for (index = 0; index < l1_entries; ++index) {
+ l2ist = kzalloc(l2_size, GFP_KERNEL_ACCOUNT);
+ if (!l2ist) {
+ while (--index >= 0)
+ kfree(vmi->h_lpi_l2_ists[index]);
+
+ kfree(vmi->h_lpi_l2_ists);
+ vmi->h_lpi_l2_ists = NULL;
+
+ return -ENOMEM;
+ }
+
+ /*
+ * We are not doing on-demand allocation of the L2 ISTs, and are
+ * instead provisioning the whole IST up front. This means that
+ * we are able to mark the L2 ISTs as valid in the L1 ISTEs as
+ * the overall IST is not yet valid.
+ */
+ val = (virt_to_phys(l2ist) & GICV5_ISTL1E_L2_ADDR_MASK) |
+ GICV5_ISTL1E_VALID;
+ l1ist[index] = cpu_to_le64(val);
+
+ vmi->h_lpi_l2_ists[index] = l2ist;
+
+ vgic_v5_clean_inval(l2ist, l2_size);
+ }
+
+ /* Handle CMOs for the whole L1 IST in one go */
+ vgic_v5_clean_inval(l1ist, l1_entries * sizeof(*l1ist));
+
+ return 0;
+}
+
+/* Allocate a two-level IST - LPIs, only */
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2sz)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ int ret;
+
+ /*
+ * Allocate the L1 IST first, then all of the L2s. Everything
+ * is preallocated and we do no on-demand IST allocation. This
+ * is to avoid needing to track if and when the guest is doing
+ * on-demand IST allocation.
+ */
+ ret = vgic_v5_alloc_l1_ist(kvm, id_bits, istsz, l2sz);
+ if (ret)
+ return ret;
+
+ ret = vgic_v5_alloc_l2_ists(kvm, id_bits, istsz, l2sz);
+ if (ret) {
+ /* Free the L1 IST again */
+ vmi = xa_load(&vm_info, vm_id);
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = 0;
+
+ return ret;
+ }
+
+ return 0;
+}
+
+static void vgic_v5_free_allocated_lpi_ist(struct vgic_v5_vm_info *vmi,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2sz)
+{
+ if (!vmi->h_lpi_ist_structure) {
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = NULL;
+ return;
+ }
+
+ if (vmi->h_lpi_l2_ists) {
+ const size_t n = max(5, id_bits - ((10 - istsz) + (2 * l2sz)) + 3 - 1);
+ const int l1_entries = BIT(n + 1) / GICV5_IRS_ISTL1E_SIZE;
+ int index;
+
+ for (index = 0; index < l1_entries; ++index)
+ kfree(vmi->h_lpi_l2_ists[index]);
+
+ kfree(vmi->h_lpi_l2_ists);
+ vmi->h_lpi_l2_ists = NULL;
+ }
+
+ kfree(vmi->h_lpi_ist);
+ vmi->h_lpi_ist = NULL;
+}
+
+static void vgic_v5_free_allocated_spi_ist(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return;
+
+ kfree(vmi->h_spi_ist);
+ vmi->h_spi_ist = NULL;
+}
+
+/*
+ * Free a Linear IST. Can only happen once the VM is dead.
+ */
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vmtl2_entry *vmte;
+ struct vgic_v5_vm_info *vmi;
+ int section;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ if (spi) {
+ section = GICV5_VMTEL2_SPI_SECTION;
+ vgic_v5_free_allocated_spi_ist(kvm);
+ } else {
+ section = GICV5_VMTEL2_LPI_SECTION;
+ vgic_v5_free_allocated_lpi_ist(vmi, 0, 0, 0);
+ }
+
+ /* The VM should be dead here, so we can just zero the VMT section */
+ vmte->val[section] = cpu_to_le64(0);
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ return 0;
+}
+
+/*
+ * Free a Two-Level IST. Can only happen once the VM is dead.
+ */
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi)
+{
+ unsigned int id_bits, istsz, l2sz;
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ struct vmtl2_entry *vmte;
+ u64 tmp;
+ int section;
+
+ /* We don't create two-level SPI ISTs, so freeing is a bad idea! */
+ if (spi)
+ return -EINVAL;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ section = GICV5_VMTEL2_LPI_SECTION;
+
+ if (!vmi->h_lpi_ist_structure)
+ return -EINVAL;
+
+ vmte = vgic_v5_get_l2_vmte(vm_id);
+ if (IS_ERR(vmte))
+ return PTR_ERR(vmte);
+
+ tmp = le64_to_cpu(READ_ONCE(vmte->val[section]));
+
+ id_bits = FIELD_GET(GICV5_VMTEL2E_IST_ID_BITS, tmp);
+ istsz = FIELD_GET(GICV5_VMTEL2E_IST_ISTSZ, tmp);
+ l2sz = FIELD_GET(GICV5_VMTEL2E_IST_L2SZ, tmp);
+
+ vgic_v5_free_allocated_lpi_ist(vmi, id_bits, istsz, l2sz);
+
+ /* The VM must be dead, so we can just zero the VMT section */
+ vmte->val[section] = cpu_to_le64(0);
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+
+ return 0;
+}
+
+/* Helper to determine ISTE size based on metadata requirements */
+static unsigned int vgic_v5_ist_istsz(unsigned int id_bits)
+{
+ if (!irs_caps.istmd)
+ return GICV5_IRS_IST_CFGR_ISTSZ_4;
+
+ if (id_bits >= irs_caps.istmd_sz)
+ return GICV5_IRS_IST_CFGR_ISTSZ_16;
+
+ return GICV5_IRS_IST_CFGR_ISTSZ_8;
+}
+
+/*
+ * Allocate an IST for SPIs.
+ *
+ * We don't anticipate a large number of SPIs being allocated. Therefore, we
+ * always allocate a Linear IST for SPIs. This will need to be revisited should
+ * that assumption no longer hold.
+ */
+int vgic_v5_spi_ist_alloc(struct kvm *kvm, unsigned int id_bits)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ phys_addr_t base_addr;
+ unsigned int istsz;
+ int ret;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ istsz = vgic_v5_ist_istsz(id_bits);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vmi->h_spi_ist)
+ return -EBUSY;
+
+ ret = vgic_v5_alloc_linear_ist(kvm, true, id_bits, istsz);
+ if (ret)
+ return ret;
+ base_addr = virt_to_phys(vmi->h_spi_ist);
+
+ ret = vgic_v5_vmte_assign_ist(kvm, base_addr, false, id_bits, 0, istsz,
+ true);
+ if (ret) {
+ vgic_v5_free_allocated_spi_ist(kvm);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Free the IST for SPIs. Should only happen once the VM is dead.
+ */
+static int vgic_v5_spi_ist_free(struct kvm *kvm)
+{
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ return vgic_v5_linear_ist_free(kvm, true);
+}
+
+/*
+ * Allocate an IST for LPIs.
+ *
+ * Unlike with SPIs, we anticipate that the guest will allocate a relatively
+ * large number of LPIs. Therefore, while we support doing a linear LPI IST, it
+ * is expected that LPI ISTs will be two-level.
+ */
+int vgic_v5_lpi_ist_alloc(struct kvm *kvm, unsigned int id_bits)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+ unsigned int istsz, l2sz;
+ phys_addr_t phys_addr;
+ bool two_level;
+ int ret;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -EINVAL;
+
+ if (vmi->h_lpi_ist)
+ return -EBUSY;
+
+ istsz = vgic_v5_ist_istsz(id_bits);
+ l2sz = gicv5_irs_l2_sz(irs_caps.ist_l2sz);
+
+ /*
+ * Determine if we want to create a Linear or a Two-Level IST.
+ *
+ * A two-level IST is only required when a single L2 IST cannot cover
+ * the requested ID space. This depends on the L2 IST size selected for
+ * the IRS, not PAGE_SIZE. Using PAGE_SIZE here would switch to
+ * two-level too early when the selected L2 IST is larger than a page,
+ * and the allocation sizing arithmetic would underflow.
+ */
+ two_level = irs_caps.ist_levels &&
+ id_bits > ((10 - istsz) + (2 * l2sz));
+
+ if (!two_level)
+ ret = vgic_v5_alloc_linear_ist(kvm, false /* LPIs, not SPIs */,
+ id_bits, istsz);
+ else
+ ret = vgic_v5_alloc_two_level_lpi_ist(kvm, id_bits, istsz,
+ l2sz);
+
+ if (ret)
+ return ret;
+
+ phys_addr = virt_to_phys(vmi->h_lpi_ist);
+ ret = vgic_v5_vmte_assign_ist(kvm, phys_addr, two_level, id_bits, l2sz,
+ istsz, false);
+ if (ret)
+ vgic_v5_free_allocated_lpi_ist(vmi, id_bits, istsz, l2sz);
+
+ return ret;
+}
+
+/* Free the LPI IST again */
+int vgic_v5_lpi_ist_free(struct kvm *kvm)
+{
+ u32 vm_id = vgic_v5_vm_id(kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ lockdep_assert_held(&kvm->arch.config_lock);
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return -ENXIO;
+
+ if (!vmi->h_lpi_ist_structure)
+ return vgic_v5_linear_ist_free(kvm, false);
+ else
+ return vgic_v5_two_level_ist_free(kvm, false);
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index b763dc3a60e3a..d0b743e8d957a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -25,6 +25,24 @@ struct vgic_v5_vm_info {
vpe_entry __iomem *vpet_base;
void __iomem **vped_ptrs;
u8 vpe_id_bits;
+
+ /*
+ * Both the LPI and SPI ISTs are allocated by the hypervisor. While it
+ * would be possible to track and access them by iterating over the ISTs
+ * themselves, it makes more sense to store pointers to the ISTs.
+ *
+ * The LPI IST can either be two-level or linear. Hence, we keep track
+ * of the structure. If it is two-level, we retain pointers to the L1
+ * IST and to each L2 IST array. If it is linear, we just store the base
+ * address of the IST array.
+ *
+ * The SPI IST is linear, and therefore we just store the base address
+ * of the SPI IST array.
+ */
+ bool h_lpi_ist_structure;
+ __le64 *h_lpi_ist;
+ __le64 **h_lpi_l2_ists;
+ __le64 *h_spi_ist;
};
struct vgic_v5_vmt {
@@ -73,4 +91,8 @@ int vgic_v5_vmte_release(struct kvm *kvm);
int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
+int vgic_v5_spi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
+int vgic_v5_lpi_ist_alloc(struct kvm *kvm, unsigned int id_bits);
+int vgic_v5_lpi_ist_free(struct kvm *kvm);
+
#endif
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 995606e0c3a39..7d5a948ab972d 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -161,6 +161,9 @@ static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
case VMT_L2_MAP:
case VMTE_MAKE_VALID:
case VMTE_MAKE_INVALID:
+ case SPI_VIST_MAKE_VALID:
+ case LPI_VIST_MAKE_VALID:
+ case LPI_VIST_MAKE_INVALID:
/* Not yet implemented */
default:
return -EINVAL;
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 27b13bf2c1e2c..74152d22de070 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -659,6 +659,9 @@ enum gicv5_vcpu_cmd {
VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
VMTE_MAKE_VALID, /* Make the VMTE valid */
VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+ SPI_VIST_MAKE_VALID, /* No corresponding invalid */
+ LPI_VIST_MAKE_VALID, /* Triggered by a guest */
+ LPI_VIST_MAKE_INVALID, /* Triggered by a guest */
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management
2026-07-24 10:50 ` [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
@ 2026-07-24 11:22 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:22 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvm, kvmarm, Oliver Upton
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Missing hardware invalidation commands allow DMA Use-After-Free.
- [Critical] Integer truncation in `l1sz` calculation leads to zero-sized IST allocation and host memory corruption.
- [High] VPE descriptor pointer is published to hardware before its initialization is visible.
- [High] Null pointer dereference of `vcpu0` in `vgic_v5_vmte_assign_ist`.
- [High] Missing `GICV5_VMTEL2E_IST_VALID` bit leaves IST configurations invalid.
- [High] Unsafe DMA buffer allocations using `kzalloc()` cause cacheline sharing and lost hardware updates.
- [Medium] Missing `WRITE_ONCE` when zeroing the hardware-shared VMTE entry.
--
--- Patch [10]: [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management ---
commit 014722137813a4073b0da36f6aceff4c3e396990
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Introduce guest IST alloc and management
GICv5 guests use Interrupt State Tables (ISTs) to track and manage the
interrupt state for SPIs and LPIs. These ISTs are provided to the host's
IRS via the VMTE.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> index 22cd03e0f1963..aa737972f525c 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
[ ... ]
> @@ -628,3 +671,500 @@ int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
>
> return 0;
> }
> +
> +/*
> + * Assign an already allocated IST to the VM by populating the fields in the
> + * corresponding VMTE. We re-use this code for both an SPI IST and LPI IST, even
> + * if the paths to reach it might be vastly different.
> + */
> +static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
> + bool two_level, unsigned int id_bits,
> + unsigned int l2sz, unsigned int istsz,
> + bool spi_ist)
> +{
> + struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
[Severity: High]
Could kvm_get_vcpu(kvm, 0) return NULL here if userspace performs VGIC
initialization operations before any vCPUs have been created?
> + u32 vm_id = vgic_v5_vm_id(kvm);
> + enum gicv5_vcpu_cmd cmd;
> + struct vmtl2_entry *vmte;
> + unsigned int section;
> + u64 tmp;
> + int ret;
[ ... ]
> + tmp = FIELD_PREP(GICV5_VMTEL2E_IST_L2SZ, l2sz);
> + tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ADDR,
> + ist_base >> GICV5_VMTEL2E_IST_ADDR_SHIFT);
> + tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ISTSZ, istsz);
> + tmp |= FIELD_PREP(GICV5_VMTEL2E_IST_ID_BITS, id_bits);
> + if (two_level)
> + tmp |= GICV5_VMTEL2E_IST_STRUCTURE;
> +
> + WRITE_ONCE(vmte->val[section], cpu_to_le64(tmp));
[Severity: High]
Is the GICV5_VMTEL2E_IST_VALID bit missing when constructing this IST entry?
It appears the valid bit is never set in tmp before it is written to the VMTE,
which might leave the configuration architecturally invalid and break interrupt
routing.
> + vgic_v5_clean_inval(vmte, sizeof(*vmte));
> +
> + /* Finally, mark the entry as valid */
> + cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
> + ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
[Severity: High]
If kvm_get_vcpu(kvm, 0) returned NULL above, does passing it directly to
vgic_v5_vpe_db() here cause a NULL pointer dereference?
> +
> + return ret;
> +}
> +
> +/*
> + * Allocate a Linear IST - always used for SPIs and potentially LPIs.
> + *
> + * The calculation for n has been taken from section 11.2.2 of the GICv5 EAC0
> + * spec.
> + *
> + * NOTE: istsz is the FIELD used by GICv5, not the actual size (or log2() of the
> + * size).
> + */
> +static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
> + unsigned int id_bits, unsigned int istsz)
> +{
> + const size_t n = id_bits + 1 + istsz;
> + u32 vm_id = vgic_v5_vm_id(kvm);
> + struct vgic_v5_vm_info *vmi;
> + __le64 *ist;
> + u32 l1sz;
> +
> + vmi = xa_load(&vm_info, vm_id);
> + if (!vmi)
> + return -EINVAL;
> +
> + /*
> + * Allocate the IST. We only have one level, so we just use the L2 ISTE.
> + */
> + l1sz = BIT(n + 1);
[Severity: Critical]
Could the 32-bit truncation of l1sz evaluate to zero for large values of n
(such as when a guest requests a large LPI ID space)?
If n + 1 >= 32, the 64-bit BIT() macro result will be truncated to exactly 0
when assigned to the u32 l1sz. This would result in a zero-sized allocation
that returns ZERO_SIZE_PTR, potentially leading to host memory corruption.
> + ist = kzalloc(l1sz, GFP_KERNEL_ACCOUNT);
[Severity: High]
Is this memory structure modified by the non-coherent GIC hardware?
If so, does allocating it with kzalloc() risk cacheline sharing with adjacent
slab objects, potentially causing lost hardware updates during CPU cache
eviction?
[ ... ]
> +/*
> + * Free a Linear IST. Can only happen once the VM is dead.
> + */
> +static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi)
> +{
[ ... ]
> + /* The VM should be dead here, so we can just zero the VMT section */
> + vmte->val[section] = cpu_to_le64(0);
[Severity: Critical]
Does freeing the backing memory and zeroing the VMTE without first issuing
hardware invalidation commands allow the hardware to continue DMAing into
freed slab objects?
[Severity: Medium]
Additionally, since this is a hardware-shared data structure, does clearing
the VMTE entry with a plain assignment risk store tearing? Should this use
WRITE_ONCE() to ensure an atomic 64-bit write?
> + vgic_v5_clean_inval(vmte, sizeof(*vmte));
> +
> + return 0;
> +}
> +
> +/*
> + * Free a Two-Level IST. Can only happen once the VM is dead.
> + */
> +static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi)
> +{
[ ... ]
> + /* The VM must be dead, so we can just zero the VMT section */
> + vmte->val[section] = cpu_to_le64(0);
[Severity: Critical]
Does freeing the backing memory and zeroing the VMTE without first issuing
hardware invalidation commands allow the hardware to continue DMAing into
freed slab objects?
[Severity: Medium]
Similarly, does clearing the VMTE entry with a plain assignment risk store
tearing here?
> + vgic_v5_clean_inval(vmte, sizeof(*vmte));
> +
> + return 0;
> +}
Looking at vgic_v5_vmte_alloc_vpe() from the broader context of this file:
vgic_v5_vmte_alloc_vpe() {
...
vped = kzalloc(vmt_info->vped_size, GFP_KERNEL);
...
tmp = FIELD_PREP(GICV5_VPED_ADDR, virt_to_phys(vped) >> GICV5_VPED_ADDR_SHIFT);
WRITE_ONCE(vpet_base[vpe_id], cpu_to_le64(tmp));
vgic_v5_clean_inval(vped, vmt_info->vped_size);
...
}
[Severity: High]
Is it possible for the hardware to observe the VPE descriptor pointer before
the zeroed memory of the descriptor is flushed to the Point of Coherency?
If a CPU cache eviction writes the pointer to main memory before the cache
clean, non-coherent hardware might consume uninitialized memory.
[Severity: High]
Additionally, since vped is allocated with kzalloc(), does this risk cacheline
sharing with adjacent objects, potentially leading to lost hardware updates?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=10
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (9 preceding siblings ...)
2026-07-24 10:50 ` [PATCH v4 10/48] KVM: arm64: gic-v5: Introduce guest IST alloc and management Sascha Bischoff
@ 2026-07-24 10:51 ` Sascha Bischoff
2026-07-24 11:20 ` sashiko-bot
2026-07-24 10:51 ` [PATCH v4 12/48] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific Sascha Bischoff
` (27 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:51 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, fuad.tabba@linux.dev
GICv5 has rules about which fields of a VMTE (or L1 VMT) may be
directly written by the host once the table is valid. This ensures
that no stale state is cached by the hardware, and provides a clear
interface for making VMs, ISTs, etc, valid.
The hypervisor is responsible for populating the VMTE for a
VM. However, it is not permitted to write the Valid bit (as the VM
table is already valid). Instead, the VM is made valid via an IRS MMIO
Op. The same applies to the ISTs - they must be made valid via the
host IRS.
This commit adds support for:
* Making level 1 VMTs valid (only), allowing for dynamic level 2 array
allocation.
* Making VMTEs (VMs) valid or invalid
* Making SPI/LPI ISTs valid or invalid for a specific VM
As part of this commit, the following vcpu_affinity-based commands are
plumbed in:
VMT_L2_MAP - Make a second level VM table valid
VMTE_MAKE_VALID - Make a single VMTE (and hence VM) valid
VMTE_MAKE_INVALID - Make a single VMTE (and hence VM) invalid
SPI_VIST_MAKE_VALID - Make the SPI IST valid
LPI_VIST_MAKE_VALID - Make the LPI IST valid
LPI_VIST_MAKE_INVALID - Make the LPI IST invalid
Note: the lack of SPI_VIST_MAKE_INVALID is intentional.
When successfully probing for a GICv5, the VMT is allocated, and is
made valid via the IRS's MMIO interface. Treat failures while
allocating or assigning the VMT as hard GICv5 probe failures. At that
point the IRS VM table state is a prerequisite for vGICv5 operation,
and falling back to the legacy path would leave the host without a
valid GICv5 VM table setup.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5-tables.c | 53 +++++---
arch/arm64/kvm/vgic/vgic-v5-tables.h | 1 +
arch/arm64/kvm/vgic/vgic-v5.c | 195 ++++++++++++++++++++++++++-
3 files changed, 225 insertions(+), 24 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index aa737972f525c..d5e7923a03359 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -67,6 +67,21 @@ static DEFINE_XARRAY(vm_info);
#define GICV5_VMTEL2_LPI_SECTION 2
#define GICV5_VMTEL2_SPI_SECTION 3
+static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
+ unsigned int id_bits,
+ unsigned int istsz);
+static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
+ unsigned int istsz, unsigned int l2_split);
+static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
+ unsigned int id_bits,
+ unsigned int istsz,
+ unsigned int l2_split);
+static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
+static int vgic_v5_spi_ist_free(struct kvm *kvm);
+
/*
* Our IRS might be coherent or non-coherent. If coherent, we can just emit a
* DSB to ensure that we're in sync. However, when non-coherent, we need to
@@ -502,25 +517,6 @@ int vgic_v5_vmte_init(struct kvm *kvm)
return ret;
}
-/*
- * The following set of forward declarations makes the code layout a *little*
- * clearer as it lets us keep the IST-related code together.
- */
-static int vgic_v5_alloc_linear_ist(struct kvm *kvm, bool spi_ist,
- unsigned int id_bits,
- unsigned int istsz);
-static int vgic_v5_alloc_l1_ist(struct kvm *kvm, unsigned int id_bits,
- unsigned int istsz, unsigned int l2_split);
-static int vgic_v5_alloc_l2_ists(struct kvm *kvm, unsigned int id_bits,
- unsigned int istsz, unsigned int l2_split);
-static int vgic_v5_alloc_two_level_lpi_ist(struct kvm *kvm,
- unsigned int id_bits,
- unsigned int istsz,
- unsigned int l2_split);
-static int vgic_v5_linear_ist_free(struct kvm *kvm, bool spi);
-static int vgic_v5_two_level_ist_free(struct kvm *kvm, bool spi);
-static int vgic_v5_spi_ist_free(struct kvm *kvm);
-
/*
* Release the VMT Entry, freeing up any allocated data structures before
* zeroing the VMTE.
@@ -672,6 +668,18 @@ int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu)
return 0;
}
+phys_addr_t vgic_v5_get_vmt_base(void)
+{
+ phys_addr_t vmt_base;
+
+ if (!vmt_info->two_level)
+ vmt_base = virt_to_phys(vmt_info->linear.vmt_base);
+ else
+ vmt_base = virt_to_phys(vmt_info->l2.vmt_base);
+
+ return vmt_base;
+}
+
/*
* Assign an already allocated IST to the VM by populating the fields in the
* corresponding VMTE. We re-use this code for both an SPI IST and LPI IST, even
@@ -730,8 +738,13 @@ static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
/* Finally, mark the entry as valid */
cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
+ if (ret) {
+ WRITE_ONCE(vmte->val[section], 0ULL);
+ vgic_v5_clean_inval(vmte, sizeof(*vmte));
+ return ret;
+ }
- return ret;
+ return 0;
}
/*
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index d0b743e8d957a..6b159a7e386cf 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -82,6 +82,7 @@ static inline int vgic_v5_vpe_db(struct kvm_vcpu *vcpu)
int vgic_v5_vmt_allocate(unsigned int max_vpes);
int vgic_v5_vmt_free(void);
+phys_addr_t vgic_v5_get_vmt_base(void);
int vgic_v5_allocate_vm_id(struct kvm *kvm);
void vgic_v5_release_vm_id(struct kvm *kvm);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 7d5a948ab972d..c68ca4f63f3a2 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -10,10 +10,14 @@
#include <linux/irqdomain.h>
#include "vgic.h"
+#include "vgic-v5-tables.h"
#define ppi_caps kvm_vgic_global_state.vgic_v5_ppi_caps
#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+static int vgic_v5_irs_assign_vmt(bool two_level, u8 vm_id_bits, phys_addr_t vmt_base);
+static int vgic_v5_irs_clear_vmt(void);
+
/*
* Not all PPIs are guaranteed to be implemented for GICv5. Deterermine which
* ones are, and generate a mask.
@@ -36,11 +40,32 @@ static void vgic_v5_get_implemented_ppis(void)
__assign_bit(GICV5_ARCH_PPI_PMUIRQ, ppi_caps.impl_ppi_mask, system_supports_pmuv3());
}
+/*
+ * The IRS MMIO interface is shared between all VMs, so make sure we don't do
+ * anything stupid!
+ */
+static DEFINE_RAW_SPINLOCK(global_irs_lock);
+
static u32 irs_readl_relaxed(const u32 reg_offset)
{
return readl_relaxed(irs_caps.irs_base + reg_offset);
}
+static void irs_writel_relaxed(const u32 val, const u32 reg_offset)
+{
+ writel_relaxed(val, irs_caps.irs_base + reg_offset);
+}
+
+static u64 irs_readq_relaxed(const u32 reg_offset)
+{
+ return readq_relaxed(irs_caps.irs_base + reg_offset);
+}
+
+static void irs_writeq_relaxed(const u64 val, const u32 reg_offset)
+{
+ writeq_relaxed(val, irs_caps.irs_base + reg_offset);
+}
+
static void vgic_v5_irs_extract_vm_caps(const struct gic_kvm_info *info)
{
u64 idr;
@@ -85,6 +110,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
int ret;
kvm_vgic_global_state.type = VGIC_V5;
+ kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
@@ -105,13 +131,55 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
vgic_v5_irs_extract_vm_caps(info);
vgic_v5_get_implemented_ppis();
+ /*
+ * Even if the HW supports more per-VM vCPUs, artificially cap as we
+ * can't use them all.
+ */
+ kvm_vgic_global_state.max_gic_vcpus = min(irs_caps.max_vpes,
+ VGIC_V5_MAX_CPUS);
+
+ /*
+ * GICv5 requires a set of tables to be allocated in order to manage
+ * VMs. We allocate them in advance here, which alas means that we
+ * already have to make a decisions regarding the maximum number of VMs
+ * we want to run. For now, we match the maximum number offered by the
+ * hardware, but this might not be a wise choice in the long term.
+ */
+ ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gic_vcpus);
+ if (ret) {
+ kvm_err("Failed to allocate the GICv5 VM tables; no GICv5 support\n");
+ return -ENODEV;
+ }
+
+ /*
+ * We've now allocated the VM table, but the host's IRS doesn't know
+ * about it yet. Provide the base address of the VMT to the IRS, as well
+ * as the number of ID bits that it covers and the structure used
+ * (linear/two-level).
+ */
+ ret = vgic_v5_irs_assign_vmt(irs_caps.two_level_vmt_support,
+ ilog2(irs_caps.max_vms),
+ vgic_v5_get_vmt_base());
+ if (ret) {
+ kvm_err("Failed to assign the GICv5 VM tables to the IRS; no GICv5 support\n");
+ if (!vgic_v5_irs_clear_vmt())
+ vgic_v5_vmt_free();
+ return -ENODEV;
+ }
+
kvm_vgic_global_state.max_gic_vcpus = min(irs_caps.max_vpes,
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;
+ /*
+ * Don't free the VMT itself if the hardware still has a valid
+ * pointer to it.
+ */
+ if (!vgic_v5_irs_clear_vmt())
+ vgic_v5_vmt_free();
+ return -ENODEV;
}
v5_registered = true;
@@ -138,12 +206,13 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
ret = kvm_register_vgic_device(KVM_DEV_TYPE_ARM_VGIC_V3);
if (ret) {
kvm_err("Cannot register GICv3-legacy KVM device.\n");
- return ret;
+ /* vGICv5 should still work */
+ return v5_registered ? 0 : 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);
+ kvm_vgic_global_state.max_gic_vcpus);
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
@@ -153,18 +222,136 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}
+/*
+ * Wait for completion of a change in any of IRS_VMT_BASER, IRS_VMAP_L2_VMTR,
+ * IRS_VMAP_VMR, IRS_VMAP_VPER, IRS_VMAP_VISTR, IRS_VMAP_L2_VISTR.
+ */
+static int vgic_v5_irs_wait_for_vm_op(void)
+{
+ return gicv5_wait_for_op_atomic(irs_caps.irs_base,
+ GICV5_IRS_VMT_STATUSR,
+ GICV5_IRS_VMT_STATUSR_IDLE,
+ NULL);
+}
+
+static int vgic_v5_irs_write_vm_mmio_reg(u64 val, u32 offset)
+{
+ int ret;
+
+ guard(raw_spinlock_irqsave)(&global_irs_lock);
+
+ /* Make sure that we are idle to begin with */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ irs_writeq_relaxed(val, offset);
+
+ return vgic_v5_irs_wait_for_vm_op();
+}
+
+static int vgic_v5_irs_assign_vmt(bool two_level, u8 vm_id_bits,
+ phys_addr_t vmt_base)
+{
+ u64 vmt_baser;
+ u32 vmt_cfgr;
+
+ guard(raw_spinlock_irqsave)(&global_irs_lock);
+
+ vmt_baser = irs_readq_relaxed(GICV5_IRS_VMT_BASER);
+ if (!!FIELD_GET(GICV5_IRS_VMT_BASER_VALID, vmt_baser))
+ return -EBUSY;
+
+ vmt_cfgr = FIELD_PREP(GICV5_IRS_VMT_CFGR_VM_ID_BITS, vm_id_bits);
+ if (two_level)
+ vmt_cfgr |= FIELD_PREP(GICV5_IRS_VMT_CFGR_STRUCTURE,
+ GICV5_IRS_VMT_CFGR_STRUCTURE_TWO_LEVEL);
+
+ irs_writel_relaxed(vmt_cfgr, GICV5_IRS_VMT_CFGR);
+
+ /* The base address is intentionally only masked and not shifted */
+ vmt_baser = FIELD_PREP(GICV5_IRS_VMT_BASER_VALID, true) |
+ (vmt_base & GICV5_IRS_VMT_BASER_ADDR);
+ irs_writeq_relaxed(vmt_baser, GICV5_IRS_VMT_BASER);
+
+ return vgic_v5_irs_wait_for_vm_op();
+}
+
+static int vgic_v5_irs_clear_vmt(void)
+{
+ return vgic_v5_irs_write_vm_mmio_reg(0, GICV5_IRS_VMT_BASER);
+}
+
+static int vgic_v5_irs_vmap_l2_vmt(u16 vm_id)
+{
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_L2_VMTR_VM_ID, vm_id) |
+ GICV5_IRS_VMAP_L2_VMTR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_L2_VMTR);
+}
+
+static int __vgic_v5_irs_vmap_vm(u16 vm_id, bool unmap)
+{
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_VMR_VM_ID, vm_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VMR_U, unmap) |
+ GICV5_IRS_VMAP_VMR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_VMR);
+}
+
+static int vgic_v5_irs_set_vm_valid(u16 vm_id)
+{
+ return __vgic_v5_irs_vmap_vm(vm_id, false);
+}
+
+static int vgic_v5_irs_set_vm_invalid(u16 vm_id)
+{
+ return __vgic_v5_irs_vmap_vm(vm_id, true);
+}
+
+static int __vgic_v5_irs_update_vist_validity(u16 vm_id, bool spi_ist, bool unmap)
+{
+ u8 type = spi_ist ? 0b011 : 0b010;
+ u64 val = FIELD_PREP(GICV5_IRS_VMAP_VISTR_TYPE, type) |
+ FIELD_PREP(GICV5_IRS_VMAP_VISTR_VM_ID, vm_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VISTR_U, unmap) |
+ GICV5_IRS_VMAP_VISTR_M;
+
+ return vgic_v5_irs_write_vm_mmio_reg(val, GICV5_IRS_VMAP_VISTR);
+}
+
+static int vgic_v5_irs_set_vist_valid(u16 vm_id, bool spi_ist)
+{
+ return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, false);
+}
+
+/*
+ * LPI ISTs can be invalidated explicitly. SPI ISTs are invalidated by making
+ * the VMTE invalid during teardown.
+ */
+static int vgic_v5_irs_set_vist_invalid(u16 vm_id, bool spi_ist)
+{
+ return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, true);
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
+ struct vgic_v5_vm *vm = data->domain->host_data;
enum gicv5_vcpu_cmd *cmd = vcpu_info;
switch (*cmd) {
case VMT_L2_MAP:
+ return vgic_v5_irs_vmap_l2_vmt(vm->vm_id);
case VMTE_MAKE_VALID:
+ return vgic_v5_irs_set_vm_valid(vm->vm_id);
case VMTE_MAKE_INVALID:
+ return vgic_v5_irs_set_vm_invalid(vm->vm_id);
case SPI_VIST_MAKE_VALID:
+ return vgic_v5_irs_set_vist_valid(vm->vm_id, true);
case LPI_VIST_MAKE_VALID:
+ return vgic_v5_irs_set_vist_valid(vm->vm_id, false);
case LPI_VIST_MAKE_INVALID:
- /* Not yet implemented */
+ return vgic_v5_irs_set_vist_invalid(vm->vm_id, false);
default:
return -EINVAL;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops
2026-07-24 10:51 ` [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
@ 2026-07-24 11:20 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:20 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Marc Zyngier, kvmarm, Oliver Upton
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [Critical] Timeout of IRS MMIO commands leads to hardware Use-After-Free for IST/VMTE memory.
- [Critical] Cache line false sharing on non-coherent GICv5 VMT causes silent memory corruption.
- [Critical] Unsafe use of kzalloc for hardware data structures requiring strict alignment.
- [High] Missing wait for idle before writing to IRS VMT configuration registers.
- [High] GICv5 probe fails systematically across kexec because it rejects an already valid VMT base register.
- [Medium] The newly allocated VMT is leaked if vgic_v5_irs_assign_vmt() returns -EBUSY and vgic_v5_irs_clear_vmt() fails.
--
--- Patch [11]: [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops ---
commit ef6f1e2217f9b0055cd57ec9bca6826104eed2cf
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops
This commit adds support for making level 1 VMTs valid, allowing dynamic
level 2 array allocation. It also implements IRS MMIO operations for
making VMs and ISTs valid or invalid.
> --- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
[ ... ]
> @@ -730,8 +738,13 @@ static int vgic_v5_vmte_assign_ist(struct kvm *kvm, phys_addr_t ist_base,
> /* Finally, mark the entry as valid */
> cmd = spi_ist ? SPI_VIST_MAKE_VALID : LPI_VIST_MAKE_VALID;
> ret = irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu0), &cmd);
> + if (ret) {
> + WRITE_ONCE(vmte->val[section], 0ULL);
> + vgic_v5_clean_inval(vmte, sizeof(*vmte));
> + return ret;
> + }
>
> - return ret;
> + return 0;
> }
[Severity: Critical]
If the IRS MMIO command times out and returns an error here, KVM zeroes the
VMTE and the caller frees the IST memory. Is it possible that the hardware
IRS is still processing the delayed command?
If the hardware eventually completes the command, it might use the physical
address KVM just freed, potentially causing a Use-After-Free DMA by writing
to the freed memory.
[Severity: Critical]
Could there be a false-sharing issue leading to memory corruption on
non-coherent GICv5 VMT systems?
The vmtl2_entry structure is 32 bytes:
arch/arm64/kvm/vgic/vgic-v5-tables.h:
struct vmtl2_entry {
__le64 val[4];
};
When modifying a VMTE, the cache line is flushed:
arch/arm64/kvm/vgic/vgic-v5-tables.c:vgic_v5_clean_inval() {
...
if (kvm_vgic_global_state.vgic_v5_irs_caps.non_coherent)
dcache_clean_inval_poc(base, base + size);
}
Since ARM64 cache lines are at least 64 bytes, multiple VMTEs share a single
cache line. If the CPU writes back this dirty cache line, won't it overwrite
any concurrent updates the IRS made directly to adjacent VMTEs in main memory?
[Severity: Critical]
Are the alignment requirements for hardware data structures guaranteed when
using kzalloc()?
In vgic_v5_vmte_init(), vmd is allocated with kzalloc() and its physical
address is bit-shifted:
arch/arm64/kvm/vgic/vgic-v5-tables.c:vgic_v5_vmte_init() {
...
vmd = kzalloc(vmt_info->vmd_size, GFP_KERNEL);
...
tmp = FIELD_PREP(GICV5_VMTEL2E_VMD_ADDR,
virt_to_phys(vmd) >> GICV5_VMTEL2E_VMD_ADDR_SHIFT);
...
}
Since kzalloc() only guarantees natural alignment, shifting the address right
by GICV5_VMTEL2E_VMD_ADDR_SHIFT could truncate the lower bits if the
allocation isn't perfectly aligned to 1 << SHIFT. Could this lead to the
hardware accessing the wrong memory location?
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -105,13 +131,55 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
[ ... ]
> + ret = vgic_v5_irs_assign_vmt(irs_caps.two_level_vmt_support,
> + ilog2(irs_caps.max_vms),
> + vgic_v5_get_vmt_base());
> + if (ret) {
> + kvm_err("Failed to assign the GICv5 VM tables to the IRS; no GICv5 support\n");
> + if (!vgic_v5_irs_clear_vmt())
> + vgic_v5_vmt_free();
> + return -ENODEV;
> + }
[Severity: Medium]
If vgic_v5_irs_assign_vmt() returns -EBUSY and vgic_v5_irs_clear_vmt() fails,
this skips calling vgic_v5_vmt_free().
Since vgic_v5_irs_assign_vmt() returns -EBUSY when the hardware VMT_BASER is
already valid (holding a pointer to the old VMT), doesn't skipping the free
here leak the newly allocated VMT?
[ ... ]
> @@ -153,18 +222,136 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
[ ... ]
> +static int vgic_v5_irs_assign_vmt(bool two_level, u8 vm_id_bits,
> + phys_addr_t vmt_base)
> +{
> + u64 vmt_baser;
> + u32 vmt_cfgr;
> +
> + guard(raw_spinlock_irqsave)(&global_irs_lock);
> +
> + vmt_baser = irs_readq_relaxed(GICV5_IRS_VMT_BASER);
> + if (!!FIELD_GET(GICV5_IRS_VMT_BASER_VALID, vmt_baser))
> + return -EBUSY;
[Severity: High]
During a kexec, KVM does not clear this register on shutdown, so the
GICV5_IRS_VMT_BASER_VALID bit may already be set by the previous kernel.
Will vgic_v5_probe() systematically fail and permanently disable GICv5 on
kexec because it aborts instead of clearing the old VMT?
> + vmt_cfgr = FIELD_PREP(GICV5_IRS_VMT_CFGR_VM_ID_BITS, vm_id_bits);
> + if (two_level)
> + vmt_cfgr |= FIELD_PREP(GICV5_IRS_VMT_CFGR_STRUCTURE,
> + GICV5_IRS_VMT_CFGR_STRUCTURE_TWO_LEVEL);
> +
> + irs_writel_relaxed(vmt_cfgr, GICV5_IRS_VMT_CFGR);
[Severity: High]
Should this function call vgic_v5_irs_wait_for_vm_op() to poll for the IRS
to be idle before writing directly to GICV5_IRS_VMT_CFGR?
Other IRS writer functions enforce an idle check before writing. If the IRS
is busy processing an operation, could this direct write corrupt the hardware
state?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=11
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 12/48] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (10 preceding siblings ...)
2026-07-24 10:51 ` [PATCH v4 11/48] KVM: arm64: gic-v5: Implement VMT/vIST IRS MMIO Ops Sascha Bischoff
@ 2026-07-24 10:51 ` Sascha Bischoff
2026-07-24 10:51 ` [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
` (26 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:51 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, fuad.tabba@linux.dev
A GICv5 host with FEAT_GCIE_LEGACY can expose both a native vGICv5 or
a vGICv3 device. These models do not necessarily have the same vCPU
limit: the native GICv5 limit is probed from the IRS VPE capacity,
while the GICv3 limit remains the fixed KVM vGICv3 limit.
Keep the IRS-derived limit separately for vGICv5 creation. The
pre-VGIC KVM_CAP_MAX_VCPUS value continues to expose the largest limit
among the still-selectable models, and kvm_vgic_create() clamps the VM
to the limit of the VGIC model userspace actually selected.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 14 +++++++++-----
arch/arm64/kvm/vgic/vgic-v5.c | 19 +++++++++----------
include/kvm/arm_vgic.h | 16 ++++++++++++----
3 files changed, 30 insertions(+), 19 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 079a57c2b18f6..94632fd90b728 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -129,13 +129,17 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
}
ret = 0;
- if (type == KVM_DEV_TYPE_ARM_VGIC_V2)
+ switch (type) {
+ case KVM_DEV_TYPE_ARM_VGIC_V2:
kvm->max_vcpus = VGIC_V2_MAX_CPUS;
- else if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
+ break;
+ case 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);
+ break;
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ kvm->max_vcpus = kvm_vgic_global_state.max_gicv5_vcpus;
+ break;
+ }
if (atomic_read(&kvm->online_vcpus) > kvm->max_vcpus) {
ret = -E2BIG;
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index c68ca4f63f3a2..21252bd11d96f 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -110,7 +110,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
int ret;
kvm_vgic_global_state.type = VGIC_V5;
- kvm_vgic_global_state.max_gic_vcpus = VGIC_V5_MAX_CPUS;
+ kvm_vgic_global_state.max_gic_vcpus = 0;
+ kvm_vgic_global_state.max_gicv5_vcpus = 0;
kvm_vgic_global_state.vcpu_base = 0;
kvm_vgic_global_state.vctrl_base = NULL;
@@ -135,8 +136,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
* Even if the HW supports more per-VM vCPUs, artificially cap as we
* can't use them all.
*/
- kvm_vgic_global_state.max_gic_vcpus = min(irs_caps.max_vpes,
- VGIC_V5_MAX_CPUS);
+ kvm_vgic_global_state.max_gicv5_vcpus = min(irs_caps.max_vpes,
+ VGIC_V5_MAX_CPUS);
/*
* GICv5 requires a set of tables to be allocated in order to manage
@@ -145,7 +146,7 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
* we want to run. For now, we match the maximum number offered by the
* hardware, but this might not be a wise choice in the long term.
*/
- ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gic_vcpus);
+ ret = vgic_v5_vmt_allocate(kvm_vgic_global_state.max_gicv5_vcpus);
if (ret) {
kvm_err("Failed to allocate the GICv5 VM tables; no GICv5 support\n");
return -ENODEV;
@@ -167,9 +168,6 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return -ENODEV;
}
- kvm_vgic_global_state.max_gic_vcpus = min(irs_caps.max_vpes,
- 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");
@@ -183,6 +181,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
}
v5_registered = true;
+ kvm_vgic_global_state.max_gic_vcpus =
+ kvm_vgic_global_state.max_gicv5_vcpus;
kvm_info("GCIE system register CPU interface\n");
skip_v5:
@@ -210,9 +210,8 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return v5_registered ? 0 : 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,
- kvm_vgic_global_state.max_gic_vcpus);
+ kvm_vgic_global_state.max_gic_vcpus = max(kvm_vgic_global_state.max_gic_vcpus,
+ VGIC_V3_MAX_CPUS);
static_branch_enable(&kvm_vgic_global_state.gicv3_cpuif);
kvm_info("GCIE legacy system register CPU interface\n");
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index ba32cd71fe0a7..6f736094a0e7e 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -157,9 +157,16 @@ struct vgic_global {
/* Maintenance IRQ number */
unsigned int maint_irq;
- /* maximum number of VCPUs allowed (GICv2 limits us to 8) */
+ /*
+ * Maximum number of VCPUs exposed before userspace has selected a
+ * VGIC model. Individual VGIC models can impose a lower limit
+ * (GICv2 limits us to 8).
+ */
int max_gic_vcpus;
+ /* Maximum number of VCPUs allowed for a GICv5 VM. */
+ int max_gicv5_vcpus;
+
/* Only needed for the legacy KVM_CREATE_IRQCHIP */
bool can_emulate_gicv2;
@@ -635,10 +642,11 @@ void kvm_vgic_process_async_update(struct kvm_vcpu *vcpu);
void vgic_v3_dispatch_sgi(struct kvm_vcpu *vcpu, u64 reg, bool allow_group1);
/**
- * kvm_vgic_get_max_vcpus - Get the maximum number of VCPUs allowed by HW
+ * kvm_vgic_get_max_vcpus - Get the pre-VGIC-selection VCPU limit
*
- * The host's GIC naturally limits the maximum amount of VCPUs a guest
- * can use.
+ * Userspace can query KVM_CAP_MAX_VCPUS before selecting a VGIC model, so
+ * expose the highest model-specific limit and let kvm_vgic_create() enforce
+ * the selected model's actual limit.
*/
static inline int kvm_vgic_get_max_vcpus(void)
{
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (11 preceding siblings ...)
2026-07-24 10:51 ` [PATCH v4 12/48] KVM: arm64: gic-v5: Keep GICv5 vCPU limit model-specific Sascha Bischoff
@ 2026-07-24 10:51 ` Sascha Bischoff
2026-07-24 11:21 ` sashiko-bot
2026-07-24 10:52 ` [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
` (25 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:51 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, fuad.tabba@linux.dev
Introduce interfaces to make VPEs valid, and to configure them, via
the host's IRS. As with the other valid bits in the GICv5 VM tables,
VPEs cannot be made valid directly, and instead are made valid via an
IRS MMIO Op.
Additionally, some of the VPE configuration takes place via the IRS
MMIO interface too (via the IRS_VPE_CR0, IRS_VPE_DBR). VPE doorbells
are, for example, configured via this interface.
The existing VPE-doorbell-based commands are extended with:
VPE_MAKE_VALID - Make the VPE valid in the VPET
Note: There is no VPE_MAKE_INVALID as VPEs are only made invalid on
teardown, at which point the whole VMTE is marked as invalid. Hence,
it is not required.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 84 ++++++++++++++++++++++++++++++
include/linux/irqchip/arm-gic-v5.h | 1 +
2 files changed, 85 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 21252bd11d96f..826c2284a1453 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -233,6 +233,18 @@ static int vgic_v5_irs_wait_for_vm_op(void)
NULL);
}
+/*
+ * Wait for completion of a change in any of IRS_VPE_SELR, IRS_VPE_DBR,
+ * IRS_VPE_CR0.
+ */
+static int vgic_v5_irs_wait_for_vpe_op(void)
+{
+ return gicv5_wait_for_op_atomic(irs_caps.irs_base,
+ GICV5_IRS_VPE_STATUSR,
+ GICV5_IRS_VPE_STATUSR_IDLE,
+ NULL);
+}
+
static int vgic_v5_irs_write_vm_mmio_reg(u64 val, u32 offset)
{
int ret;
@@ -333,10 +345,73 @@ static int vgic_v5_irs_set_vist_invalid(u16 vm_id, bool spi_ist)
return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, true);
}
+static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
+ irq_hw_number_t db_hwirq)
+{
+ u64 vmap_vper, dbr, selr;
+ u32 statusr, cr0;
+ int ret;
+
+ guard(raw_spinlock_irqsave)(&global_irs_lock);
+
+ /* Make sure that we are idle to begin with */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ /* Mark the VPE as valid */
+ vmap_vper = FIELD_PREP(GICV5_IRS_VMAP_VPER_VPE_ID, vpe_id) |
+ FIELD_PREP(GICV5_IRS_VMAP_VPER_VM_ID, vm_id) |
+ GICV5_IRS_VMAP_VPER_M;
+ irs_writeq_relaxed(vmap_vper, GICV5_IRS_VMAP_VPER);
+
+ /* Wait for the VPE to be marked valid in the VPET */
+ ret = vgic_v5_irs_wait_for_vm_op();
+ if (ret)
+ return ret;
+
+ selr = FIELD_PREP(GICV5_IRS_VPE_SELR_VPE_ID, vpe_id) |
+ FIELD_PREP(GICV5_IRS_VPE_SELR_VM_ID, vm_id) |
+ GICV5_IRS_VPE_SELR_S;
+ irs_writeq_relaxed(selr, GICV5_IRS_VPE_SELR);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ statusr = irs_readl_relaxed(GICV5_IRS_VPE_STATUSR);
+ if (!FIELD_GET(GICV5_IRS_VPE_STATUSR_V, statusr))
+ return -EINVAL;
+
+ /* Set targeted only routing (disable 1ofN vPE selection) */
+ cr0 = GICV5_IRS_VPE_CR0_DPS;
+ irs_writel_relaxed(cr0, GICV5_IRS_VPE_CR0);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ /*
+ * The VPE has not yet run. Therefore, make sure that all interrupts
+ * will generate a doorbell.
+ */
+ dbr = FIELD_PREP(GICV5_IRS_VPE_DBR_INTID, db_hwirq) |
+ GICV5_IRS_VPE_DBR_DBV;
+ irs_writeq_relaxed(dbr, GICV5_IRS_VPE_DBR);
+
+ ret = vgic_v5_irs_wait_for_vpe_op();
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
struct vgic_v5_vm *vm = data->domain->host_data;
enum gicv5_vcpu_cmd *cmd = vcpu_info;
+ /* Our VPE ID is the index within the doorbell domain */
+ u16 vpe_id = data->hwirq;
switch (*cmd) {
case VMT_L2_MAP:
@@ -345,6 +420,15 @@ static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
return vgic_v5_irs_set_vm_valid(vm->vm_id);
case VMTE_MAKE_INVALID:
return vgic_v5_irs_set_vm_invalid(vm->vm_id);
+ case VPE_MAKE_VALID:
+ /*
+ * We need the actual LPI ID which lives in the top-most parent
+ * domain. This hwirq won't include the type (LPI) but that's
+ * not required for the IRS_VPE_DBR.
+ */
+ while (data->parent_data)
+ data = data->parent_data;
+ return vgic_v5_irs_set_up_vpe(vm->vm_id, vpe_id, data->hwirq);
case SPI_VIST_MAKE_VALID:
return vgic_v5_irs_set_vist_valid(vm->vm_id, true);
case LPI_VIST_MAKE_VALID:
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 74152d22de070..8b6c497d3b590 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -659,6 +659,7 @@ enum gicv5_vcpu_cmd {
VMT_L2_MAP, /* Map in a L2 VMT - *may* happen on VM init */
VMTE_MAKE_VALID, /* Make the VMTE valid */
VMTE_MAKE_INVALID, /* Make the VMTE (et al.) invalid */
+ VPE_MAKE_VALID, /* No corresponding invalid */
SPI_VIST_MAKE_VALID, /* No corresponding invalid */
LPI_VIST_MAKE_VALID, /* Triggered by a guest */
LPI_VIST_MAKE_INVALID, /* Triggered by a guest */
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops
2026-07-24 10:51 ` [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
@ 2026-07-24 11:21 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:21 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, Oliver Upton, kvm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Missing REQ_DB (Request Doorbell) flag when configuring the VPE doorbell, contradicting the explicit intent to force doorbell generation.
- [High] Missing VPE engine idle check at the beginning of `vgic_v5_irs_set_up_vpe`, risking command corruption if the engine is busy.
--
--- Patch [13]: [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops ---
commit 92182082105efc7200f554f8b53732d80e3ea711
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops
This commit introduces interfaces to validate and configure VPEs via the
host's IRS. It extends the existing doorbell-based commands by adding
VPE_MAKE_VALID.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 21252bd11d96f..826c2284a1453 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -333,10 +345,73 @@ static int vgic_v5_irs_set_vist_invalid(u16 vm_id, bool spi_ist)
> return __vgic_v5_irs_update_vist_validity(vm_id, spi_ist, true);
> }
>
> +static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
> + irq_hw_number_t db_hwirq)
> +{
> + u64 vmap_vper, dbr, selr;
> + u32 statusr, cr0;
> + int ret;
> +
> + guard(raw_spinlock_irqsave)(&global_irs_lock);
> +
> + /* Make sure that we are idle to begin with */
> + ret = vgic_v5_irs_wait_for_vm_op();
> + if (ret)
> + return ret;
[Severity: High]
Does this code also need to verify that the VPE engine is idle?
If a previous operation timed out and returned an error, the function
could exit early and leave the hardware engine busy. When called again
for the next VPE, the VM engine check above would pass, but writing to
GICV5_IRS_VPE_SELR below could corrupt the command if the VPE engine is
still busy.
> +
> + /* Mark the VPE as valid */
> + vmap_vper = FIELD_PREP(GICV5_IRS_VMAP_VPER_VPE_ID, vpe_id) |
> + FIELD_PREP(GICV5_IRS_VMAP_VPER_VM_ID, vm_id) |
> + GICV5_IRS_VMAP_VPER_M;
> + irs_writeq_relaxed(vmap_vper, GICV5_IRS_VMAP_VPER);
[ ... ]
> + /*
> + * The VPE has not yet run. Therefore, make sure that all interrupts
> + * will generate a doorbell.
> + */
> + dbr = FIELD_PREP(GICV5_IRS_VPE_DBR_INTID, db_hwirq) |
> + GICV5_IRS_VPE_DBR_DBV;
> + irs_writeq_relaxed(dbr, GICV5_IRS_VPE_DBR);
[Severity: High]
Is the GICV5_IRS_VPE_DBR_REQ_DB flag missing here?
The comment explicitly states the intent is to ensure all interrupts will
generate a doorbell. Without the request doorbell flag, could the GIC hardware
fail to generate doorbells for interrupts that become pending before the VPE
first runs?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=13
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (12 preceding siblings ...)
2026-07-24 10:51 ` [PATCH v4 13/48] KVM: arm64: gic-v5: Implement VPE IRS MMIO Ops Sascha Bischoff
@ 2026-07-24 10:52 ` Sascha Bischoff
2026-07-24 11:27 ` sashiko-bot
2026-07-24 10:52 ` [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
` (24 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:52 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, fuad.tabba@linux.dev
A GICv5 VM needs a VM table entry before it can use SPIs and LPIs,
which are backed by the host IRS. The VM table itself is created at
probe time, but each VM still needs to claim and populate one VMTE
before it can use those interrupts.
Allocate a VM ID during vgic_v5_init(). The VM ID is also the index
into the VM table, so allocating it selects the VMTE slot that will be
used for the lifetime of the VM.
Create a per-VM VPE doorbell irq domain, allocate one doorbell
interrupt per vcpu, request the interrupts, and keep the doorbell IRQ
number in the vcpu's GICv5 state. The doorbell handler itself marks
the VPE doorbell as fired, raises KVM_REQ_IRQ_PENDING, and kicks the
target vcpu so that KVM can re-evaluate pending interrupt state.
With the VM ID and doorbells in place, initialise the VMTE backing
state, including the VM descriptor and VPE table. The doorbells have
to exist before making the VMTE valid, as they provide the IRQ-side
conduit used by the IRS commands. Make the VMTE valid via the IRS,
then allocate the VPE state for each vcpu.
Add vgic_v5_teardown() to unwind the state in the reverse order. Make
the VMTE invalid, free the VPE state, release the VMTE backing state,
free the doorbell IRQs and irq domain, and finally release the VM ID
so that the VMTE slot can be reused by a later VM. If the hardware
invalidation or VMTE release fails, still free the software doorbells
and domain, but keep the VM ID allocated so that the VMTE slot is not
reused while hardware-visible state may remain.
On init failure, call the same teardown path so that partially created
state is unwound consistently.
As part of resetting VCPUs mark them as valid in the VM VPE
Table. This informs the IRS that a specific VPE may be made resident,
and without this the IRS will treat the VPE as invalid.
Also introduce a wrapper around the VPE doorbells -
vgic_v5_send_command(). This takes a struct kvm_vcpu pointer, and the
command to run, and triggers the function bound to the command via
that vcpu's doorbell. This is a convenience function to simplify the
code.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 170 ++++++++++++++++++++++++++++++++--
1 file changed, 160 insertions(+), 10 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 826c2284a1453..4a62aa7d6505a 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -406,6 +406,23 @@ static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
return 0;
}
+static irqreturn_t db_handler(int irq, void *data)
+{
+ struct kvm_vcpu *vcpu = data;
+
+ WRITE_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired, true);
+
+ kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
+ kvm_vcpu_kick(vcpu);
+
+ return IRQ_HANDLED;
+}
+
+static int vgic_v5_send_command(struct kvm_vcpu *vcpu, enum gicv5_vcpu_cmd cmd)
+{
+ return irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd);
+}
+
static int vgic_v5_db_set_vcpu_affinity(struct irq_data *data, void *vcpu_info)
{
struct vgic_v5_vm *vm = data->domain->host_data;
@@ -582,33 +599,117 @@ void vgic_v5_reset(struct kvm_vcpu *vcpu)
* CPUIF (but potentially fewer in the IRS).
*/
vcpu->arch.vgic_cpu.num_pri_bits = 5;
+
+ /* Make the VPE valid in the VPET */
+ if (WARN_ON(vgic_v5_send_command(vcpu, VPE_MAKE_VALID)))
+ return;
+}
+
+static void vgic_v5_free_doorbells(struct kvm *kvm, unsigned int nr_dbs)
+{
+ struct vgic_v5_vm *vm = &kvm->arch.vgic.gicv5_vm;
+ struct kvm_vcpu *vcpu;
+ unsigned long i;
+ int db;
+
+ for (i = 0; i < nr_dbs; i++) {
+ vcpu = kvm_get_vcpu(kvm, i);
+ db = vgic_v5_vpe_db(vcpu);
+ if (!db)
+ continue;
+
+ free_irq(db, vcpu);
+ vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db = 0;
+ }
+
+ if (vm->vpe_db_base) {
+ irq_domain_free_irqs(vm->vpe_db_base,
+ atomic_read(&kvm->online_vcpus));
+ vm->vpe_db_base = 0;
+ }
}
void vgic_v5_teardown(struct kvm *kvm)
{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct kvm_vcpu *vcpu, *vcpu0;
+ bool release_vm_id = true;
+ unsigned long i;
+ int rc;
+
+ /*
+ * If the VM's ID isn't valid, then we either failed init very early or
+ * we've been called a second time. Nothing to do here in either case.
+ */
+ if (kvm->arch.vgic.gicv5_vm.vm_id == VGIC_V5_VM_ID_INVAL)
+ return;
+
+ if (kvm->arch.vgic.gicv5_vm.vmte_allocated) {
+ /* Make the VM invalid */
+ vcpu0 = kvm_get_vcpu(kvm, 0);
+ rc = vgic_v5_send_command(vcpu0, VMTE_MAKE_INVALID);
+ if (rc) {
+ kvm_err("could not make VMTE invalid\n");
+ release_vm_id = false;
+ goto out_free_doorbells;
+ }
+
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ if (vgic_v5_vmte_free_vpe(vcpu)) {
+ kvm_err("Failed to free VPE\n");
+ release_vm_id = false;
+ }
+ }
+
+ if (vgic_v5_vmte_release(kvm)) {
+ kvm_err("Failed to release VM 0x%x\n", dist->gicv5_vm.vm_id);
+ release_vm_id = false;
+ }
+ }
+
+out_free_doorbells:
+ vgic_v5_free_doorbells(kvm, atomic_read(&kvm->online_vcpus));
vgic_v5_teardown_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+
+ /*
+ * We only release the VM ID itself if we didn't fail earlier. It does
+ * mean that we might lose the VM ID (and associated VMTE, etc), but
+ * given that we've failed to tear them down correctly there's no way to
+ * safely reuse them. The VM ID allocating IDA will make sure we don't
+ * accidentally reuse this partially torn down state.
+ */
+ if (release_vm_id)
+ vgic_v5_release_vm_id(kvm);
}
+/*
+ * Claim and populate a VMTE (optionally making a new L2 VMT valid), create VPE
+ * doorbells, allocate VPET and populate for each VPE.
+ *
+ * Note: We do need to put the cart before the horse here. The VPE doorbells are
+ * our conduit for communication with the IRS, which means we need to have those
+ * before making the VMTE valid.
+ *
+ * On failure, we clean up in the teardown path (vgic_v5_teardown()).
+ */
int vgic_v5_init(struct kvm *kvm)
{
- struct kvm_vcpu *vcpu;
- unsigned long idx;
- int ret;
+ struct kvm_vcpu *vcpu, *vcpu0;
+ int nr_vcpus, ret = 0;
+ unsigned int db_virq;
+ unsigned long i;
- if (vgic_initialized(kvm))
- return 0;
+ nr_vcpus = atomic_read(&kvm->online_vcpus);
+ if (nr_vcpus == 0)
+ return -ENODEV;
- kvm_for_each_vcpu(idx, vcpu, kvm) {
+ kvm_for_each_vcpu(i, vcpu, kvm) {
if (vcpu_has_nv(vcpu)) {
kvm_err("Nested GICv5 VMs are currently unsupported\n");
return -EINVAL;
}
}
- ret = vgic_v5_create_per_vm_domain(kvm);
- if (ret)
- return ret;
-
/* We only allow userspace to drive the SW_PPI, if it is implemented. */
bitmap_zero(kvm->arch.vgic.gicv5_vm.userspace_ppis,
VGIC_V5_NR_PRIVATE_IRQS);
@@ -617,7 +718,56 @@ int vgic_v5_init(struct kvm *kvm)
kvm->arch.vgic.gicv5_vm.userspace_ppis,
ppi_caps.impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS);
+ ret = vgic_v5_allocate_vm_id(kvm);
+ if (ret)
+ return ret;
+
+ ret = vgic_v5_create_per_vm_domain(kvm);
+ if (ret)
+ goto err;
+
+ db_virq = kvm->arch.vgic.gicv5_vm.vpe_db_base;
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ ret = request_irq(db_virq + i, db_handler, 0, "vcpu", vcpu);
+ if (ret)
+ goto err;
+
+ /* Stash it with the VCPU for easy retrieval */
+ vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db = db_virq + i;
+ }
+
+ /* Populate VMTE (with VPET and VM descriptor) */
+ ret = vgic_v5_vmte_init(kvm);
+ if (ret)
+ goto err;
+
+ /* We pick the first vcpu to make the VMTE valid - any would do */
+ vcpu0 = kvm_get_vcpu(kvm, 0);
+ ret = vgic_v5_send_command(vcpu0, VMTE_MAKE_VALID);
+ if (ret)
+ goto err;
+
+ /* Loop over all VPEs, allocate/populate their data structures */
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ ret = vgic_v5_vmte_alloc_vpe(vcpu);
+ if (ret)
+ goto err;
+ }
+
return 0;
+
+err:
+ /*
+ * Explicitly tear everything down on failure. The teardown function is
+ * written to handle any partial state we might have, so we don't need
+ * to do any clean-up first. Teardown will be called a second time on VM
+ * destruction, but that's fine - it is better to leave things in a
+ * clean state now, and doubly so because userspace could actually go
+ * and retry init.
+ */
+ vgic_v5_teardown(kvm);
+
+ return ret;
}
int vgic_v5_map_resources(struct kvm *kvm)
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells
2026-07-24 10:52 ` [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
@ 2026-07-24 11:27 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:27 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, kvmarm, kvm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] vgic_v5_send_command() unsafely passes 0 to irq_set_vcpu_affinity() if the doorbell IRQ is not allocated.
--
--- Patch [14]: [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells ---
commit 3fe87417ec724cc6e6581baab62b9b85ad04ae50
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells
This patch introduces the setup and teardown paths for GICv5 VM Table Entries
and VPE doorbells. It allocates a VM ID, creates a per-VM VPE doorbell IRQ
domain, and assigns doorbells to vCPUs for communication with the host IRS.
Initialization and teardown workflows are implemented, including unwind paths
for partial failures.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 826c2284a1453..4a62aa7d6505a 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -406,6 +406,23 @@ static int vgic_v5_irs_set_up_vpe(u16 vm_id, u16 vpe_id,
> return 0;
> }
>
> +static irqreturn_t db_handler(int irq, void *data)
> +{
> + struct kvm_vcpu *vcpu = data;
> +
> + WRITE_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired, true);
> +
> + kvm_make_request(KVM_REQ_IRQ_PENDING, vcpu);
> + kvm_vcpu_kick(vcpu);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int vgic_v5_send_command(struct kvm_vcpu *vcpu, enum gicv5_vcpu_cmd cmd)
> +{
> + return irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd);
> +}
[Severity: High]
Does this code handle the case where the doorbell IRQ is not allocated yet or
has already been freed, returning 0 from vgic_v5_vpe_db(vcpu)?
Passing 0 to irq_set_vcpu_affinity() can cause unexpected behavior, including
potential memory corruption if IRQ 0 happens to be a valid interrupt on the
platform.
There appear to be two paths where this happens. First, vgic_v5_reset() can be
invoked before the VGIC is initialized, meaning the doorbell IRQ is still 0:
arch/arm64/kvm/vgic/vgic-v5.c:vgic_v5_reset()
/* Make the VPE valid in the VPET */
if (WARN_ON(vgic_v5_send_command(vcpu, VPE_MAKE_VALID)))
return;
Second, a secondary teardown retry can trigger this in vgic_v5_teardown(). If
an initial teardown failed to invalidate the VMTE, it leaves
vmte_allocated = true but still frees the doorbells. A subsequent teardown
will execute this with a 0 doorbell IRQ:
arch/arm64/kvm/vgic/vgic-v5.c:vgic_v5_teardown()
if (kvm->arch.vgic.gicv5_vm.vmte_allocated) {
/* Make the VM invalid */
vcpu0 = kvm_get_vcpu(kvm, 0);
rc = vgic_v5_send_command(vcpu0, VMTE_MAKE_INVALID);
Could vgic_v5_send_command() verify the IRQ is non-zero before passing it
to the IRQ core?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=14
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (13 preceding siblings ...)
2026-07-24 10:52 ` [PATCH v4 14/48] KVM: arm64: gic-v5: Set up VMTEs and VPE doorbells Sascha Bischoff
@ 2026-07-24 10:52 ` Sascha Bischoff
2026-07-24 11:26 ` sashiko-bot
2026-07-24 10:52 ` [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
` (23 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:52 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, fuad.tabba@linux.dev
GICv5 introduces the concept of VPE residency - a VPE can be either
resident or non-resident. When the VPE is resident, the IRS is allowed
to select interrupts that target that VPE (or the VM) as the HPPI
(Highest Priority Pending Interrupt). As the IRS handles both SPIs and
LPIs, these will only be picked as the IRS's HPPI when a VPE is
resident.
A GICv5 VPE is made resident by writing ICH_CONTEXTR_EL2 with
ICH_CONTEXTR_EL2.V set, together with valid VM and VPE IDs. This
informs the IRS that a specific VPE is running, and that it can begin
HPPI selection for that VPE. Making a VPE non-resident (by making the
ICH_CONTEXTR_EL2 invalid) informs the IRS that the VPE is no longer
running, and it stops HPPI selection for it.
This change introduces two new hyp calls - one to make a VPE resident
and its counterpart to make a VPE non-resident. As part of making a
VPE resident, the resulting ICH_CONTEXTR_EL2.F bit is checked to catch
residency faults. Such a fault indicates a broken VM/VPE setup, so
warn and mark the VM dead.
Furthermore, this change extends vgic_v5_load() and vgic_v5_put() to
make the VPEs resident and non-resident, respectively. Hence, the VPE
is considered resident for the entire load-to-put interval.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_asm.h | 2 ++
arch/arm64/include/asm/kvm_hyp.h | 2 ++
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 16 ++++++++++++++++
arch/arm64/kvm/hyp/vgic-v5-sr.c | 26 ++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5.c | 16 ++++++++++++++--
include/kvm/arm_vgic.h | 3 +++
6 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index 043495f7fc78b..d9ff9c2999aa7 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -87,6 +87,8 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___tracing_write_event,
__KVM_HOST_SMCCC_FUNC___vgic_v3_save_aprs,
__KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_make_resident,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_make_non_resident,
__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 4974492744cc8..8fcbbff719cce 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -88,6 +88,8 @@ 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_make_resident(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_vmcr_apr(struct vgic_v5_cpu_if *cpu_if);
/* No hypercalls for the following */
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index d3c69de698f48..09756f71df08e 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -691,6 +691,20 @@ static void handle___tracing_write_event(struct kvm_cpu_context *host_ctxt)
trace_selftest(id);
}
+static void handle___vgic_v5_make_resident(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_make_resident(kern_hyp_va(cpu_if));
+}
+
+static void handle___vgic_v5_make_non_resident(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
+
+ __vgic_v5_make_non_resident(kern_hyp_va(cpu_if));
+}
+
static void handle___vgic_v5_save_apr(struct kvm_cpu_context *host_ctxt)
{
DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
@@ -738,6 +752,8 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__tracing_write_event),
HANDLE_FUNC(__vgic_v3_save_aprs),
HANDLE_FUNC(__vgic_v3_restore_vmcr_aprs),
+ HANDLE_FUNC(__vgic_v5_make_resident),
+ HANDLE_FUNC(__vgic_v5_make_non_resident),
HANDLE_FUNC(__vgic_v5_save_apr),
HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 6d69dfe89a96c..f064045a31aee 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -7,6 +7,32 @@
#include <asm/kvm_hyp.h>
+void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if)
+{
+ write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
+ isb();
+
+ /* Catch any faults */
+ cpu_if->vgic_contextr = read_sysreg_s(SYS_ICH_CONTEXTR_EL2);
+ if (!!FIELD_GET(ICH_CONTEXTR_EL2_F, cpu_if->vgic_contextr))
+ return;
+
+ cpu_if->gicv5_vpe.resident = true;
+}
+
+void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if)
+{
+ /*
+ * Make as non-resident before actually making non-resident. Avoids race
+ * with doorbell arriving.
+ */
+ cpu_if->gicv5_vpe.resident = false;
+ dsb(st);
+
+ write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
+ isb();
+}
+
void __vgic_v5_save_apr(struct vgic_v5_cpu_if *cpu_if)
{
cpu_if->vgic_apr = read_sysreg_s(SYS_ICH_APR_EL2);
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 4a62aa7d6505a..e3df8a5f9f657 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1061,6 +1061,8 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
+ u16 vm = vgic_v5_vm_id(vcpu->kvm);
+ u16 vpe = vgic_v5_vpe_id(vcpu);
/*
* On the WFI path, vgic_load is called a second time. The first is when
@@ -1073,7 +1075,15 @@ void vgic_v5_load(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
- cpu_if->gicv5_vpe.resident = true;
+ cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_V, true) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_VPE, vpe) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_VM, vm);
+
+ kvm_call_hyp(__vgic_v5_make_resident, cpu_if);
+
+ /* Failed to make the VPE resident? Bang! */
+ if (WARN_ON(!!FIELD_GET(ICH_CONTEXTR_EL2_F, cpu_if->vgic_contextr)))
+ kvm_vm_dead(vcpu->kvm);
}
void vgic_v5_put(struct kvm_vcpu *vcpu)
@@ -1091,7 +1101,9 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
- cpu_if->gicv5_vpe.resident = false;
+ cpu_if->vgic_contextr = 0;
+
+ kvm_call_hyp(__vgic_v5_make_non_resident, cpu_if);
/* The shadow priority is only updated on entering WFI */
if (vcpu_get_flag(vcpu, IN_WFI))
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 6f736094a0e7e..faecde764fea3 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -536,6 +536,9 @@ struct vgic_v5_cpu_if {
*/
u64 vgic_icsr;
+ /* The contextr used to make VPEs resident and non-resident */
+ u64 vgic_contextr;
+
struct gicv5_vpe gicv5_vpe;
};
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls
2026-07-24 10:52 ` [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
@ 2026-07-24 11:26 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:26 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, Oliver Upton, kvm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] The __vgic_v5_make_resident and __vgic_v5_make_non_resident hypercalls write unvalidated host-supplied data directly to the sensitive ICH_CONTEXTR_EL2 system register, breaking pKVM isolation.
- [Medium] EL2 double-fetches the system register value from host memory to check for residency faults, enabling a TOCTOU race.
--
--- Patch [15]: [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls ---
commit 1b185bf2c8c83d76bc583dab060f0d8f605b0939
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add resident/non-resident hyp calls
This patch introduces GICv5 VPE residency management by adding hypercalls
to make a VPE resident or non-resident via ICH_CONTEXTR_EL2. It also
updates vgic_v5_load() and vgic_v5_put() to use these hypercalls, ensuring
the VPE is considered resident during the load-to-put interval.
> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
[ ... ]
> @@ -691,6 +691,20 @@ static void handle___tracing_write_event(struct kvm_cpu_context *host_ctxt)
> trace_selftest(id);
> }
>
> +static void handle___vgic_v5_make_resident(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(struct vgic_v5_cpu_if *, cpu_if, host_ctxt, 1);
> +
> + __vgic_v5_make_resident(kern_hyp_va(cpu_if));
[Severity: Critical]
Does this hypercall act on a raw, unvalidated host memory pointer passed in
a register?
Because the host can pass an arbitrary pointer to
handle___vgic_v5_make_resident(), EL2 should copy the necessary fields to
EL2 private memory and validate them before acting on the data to prevent
isolation bypasses.
> +}
> +
> +static void handle___vgic_v5_make_non_resident(struct kvm_cpu_context *host_ctxt)
> +{
[ ... ]
> diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
> --- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
> +++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
> @@ -7,6 +7,32 @@
>
> #include <asm/kvm_hyp.h>
>
> +void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if)
> +{
> + write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
[Severity: Critical]
Does this code write unvalidated host-supplied data directly to the
sensitive ICH_CONTEXTR_EL2 system register?
Under the pKVM threat model, cpu_if points to untrusted host memory post
deprivilege. By blindly reading vgic_contextr from the host and writing it
to the hardware register, a malicious host could potentially supply an
arbitrary VMID and VPEID belonging to a protected VM. This could trick the
Interrupt Routing System into routing a protected VM's interrupts to the
host's physical CPU.
Should EL2 derive and validate the residency state from EL2-private data
instead of trusting the raw hypercall parameters in
__vgic_v5_make_resident()?
> + isb();
> +
> + /* Catch any faults */
> + cpu_if->vgic_contextr = read_sysreg_s(SYS_ICH_CONTEXTR_EL2);
> + if (!!FIELD_GET(ICH_CONTEXTR_EL2_F, cpu_if->vgic_contextr))
[Severity: Medium]
Can this sequence cause a Time-of-Check to Time-of-Use race?
The code writes the system register value to cpu_if->vgic_contextr in
host-shared memory, and then immediately evaluates it back from host memory
using FIELD_GET(). A concurrent thread on the host could overwrite this
memory location between the write and the check.
If the hardware reports a residency fault (F=1), could the host overwrite
the memory with F=0 to trick EL2 into ignoring the fault? Reading the
system register into a local EL2-private variable for validation before
writing it out to host memory might avoid this issue.
> + return;
> +
> + cpu_if->gicv5_vpe.resident = true;
> +}
> +
> +void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if)
> +{
> + /*
> + * Make as non-resident before actually making non-resident. Avoids race
> + * with doorbell arriving.
> + */
> + cpu_if->gicv5_vpe.resident = false;
> + dsb(st);
> +
> + write_sysreg_s(cpu_if->vgic_contextr, SYS_ICH_CONTEXTR_EL2);
[Severity: Critical]
Similarly to __vgic_v5_make_resident(), does __vgic_v5_make_non_resident()
write host-controlled memory to the system register instead of securely
writing a hardcoded 0 from EL2-private state?
> + isb();
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=15
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (14 preceding siblings ...)
2026-07-24 10:52 ` [PATCH v4 15/48] KVM: arm64: gic-v5: Add resident/non-resident hyp calls Sascha Bischoff
@ 2026-07-24 10:52 ` Sascha Bischoff
2026-07-24 11:41 ` sashiko-bot
2026-07-24 10:52 ` [PATCH v4 17/48] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address Sascha Bischoff
` (22 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:52 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, fuad.tabba@linux.dev
When a GICv5 VPE is made non-resident as part of the vcpu entering
WFI, request a VPE doorbell so that KVM can be notified when a
suitable SPI or LPI becomes pending for that VPE.
Program the doorbell priority mask, DBPM, from the effective virtual
priority mask before making the VPE non-resident. DBPM is the priority
threshold used by the GICv5 hardware to decide whether a pending SPI
or LPI is allowed to signal the VPE doorbell. This allows hardware to
signal the doorbell only for interrupts that the vcpu can actually
take, and avoids waking it for interrupts masked by the guest priority
state. If no interrupt can be signalled to the vcpu, leave the
doorbell request clear.
Make the doorbell interrupt affine to the current CPU before
requesting it. This nudges the wakeup back towards the CPU that last
ran the vcpu, where the relevant state is more likely to be cache-hot,
while also spreading doorbell interrupts across host PEs as different
vcpus enter WFI on different CPUs.
Clear stale db_fired state before making the VPE non-resident. Any
previous doorbell notification has already been consumed by this
point, and clearing it before the non-resident transition ensures that
a newly fired doorbell is observed.
Finally, teach kvm_vgic_vcpu_pending_irq() to report pending work for
a GICv5 vcpu when its VPE doorbell has fired, in addition to the
existing pending-PPI check.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/hyp/vgic-v5-sr.c | 9 ++++++++
arch/arm64/kvm/vgic/vgic-v5.c | 40 +++++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 6 ++++-
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index f064045a31aee..46992a6c2cacb 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -22,6 +22,15 @@ void __vgic_v5_make_resident(struct vgic_v5_cpu_if *cpu_if)
void __vgic_v5_make_non_resident(struct vgic_v5_cpu_if *cpu_if)
{
+ /*
+ * Clear the db_fired state to ensure that we're ready for the next
+ * doorbell when it is requested. If a doorbell firing caused us to
+ * enter the guest, then we've already consumed that state at this
+ * point, so this is safe to clear. Use WRITE_ONCE() to ensure we're not
+ * racing with the doorbell firing and setting the state true again.
+ */
+ WRITE_ONCE(cpu_if->gicv5_vpe.db_fired, false);
+
/*
* Make as non-resident before actually making non-resident. Avoids race
* with doorbell arriving.
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index e3df8a5f9f657..8ef53cac32414 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1102,6 +1102,46 @@ void vgic_v5_put(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_save_apr, cpu_if);
cpu_if->vgic_contextr = 0;
+ if (vcpu_get_flag(vcpu, IN_WFI)) {
+ u32 priority_mask;
+ int dbpm;
+
+ /*
+ * Find the virtual running priority and use this to calculate
+ * the doorbell priority mask. We combine the highest active
+ * priority and the CPU's priority mask. The guest can't handle
+ * interrupts with priorities less than or equal to the virtual
+ * running priority, so there's literally no point in waking the
+ * guest for these.
+ *
+ * The priority needs to be higher than the mask to signal, so
+ * pick the next higher priority (subtract 1).
+ */
+ priority_mask = vgic_v5_get_effective_priority_mask(vcpu);
+
+ /*
+ * Request a doorbell *unless* the priority is 0, indicating
+ * that no interrupt can wake the CPU up.
+ */
+ if (priority_mask) {
+ int db_irq = vgic_v5_vpe_db(vcpu);
+ struct irq_data *d = irq_get_irq_data(db_irq);
+ const struct cpumask *aff = irq_data_get_effective_affinity_mask(d);
+ int cpu = smp_processor_id();
+
+ dbpm = priority_mask - 1;
+ cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_DB, 1) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_DBPM, dbpm);
+
+ /*
+ * Make the doorbell affine to this CPU, if it isn't
+ * already. Actively check the cpumask first as it is
+ * cheaper than changing the affinity every time.
+ */
+ if (!cpumask_test_cpu(cpu, aff))
+ WARN_ON(irq_set_affinity(db_irq, cpumask_of(cpu)));
+ }
+ }
kvm_call_hyp(__vgic_v5_make_non_resident, cpu_if);
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index ccb7e3a90cd07..47315c3fe120e 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -1235,8 +1235,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
unsigned long flags;
struct vgic_vmcr vmcr;
- if (vgic_is_v5(vcpu->kvm))
+ if (vgic_is_v5(vcpu->kvm)) {
+ if (READ_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired))
+ return true;
+
return vgic_v5_has_pending_ppi(vcpu);
+ }
if (!vcpu->kvm->arch.vgic.enabled)
return false;
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI
2026-07-24 10:52 ` [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
@ 2026-07-24 11:41 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:41 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, kvm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Stale `db_fired` state causes an infinite loop bypassing WFI sleep, pegging the host CPU to 100%.
- [High] Incorrect GICv5 doorbell priority mask (DBPM) calculation drops preempting interrupts and vNMIs.
--
--- Patch [16]: [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI ---
commit 4c69e1e71f915cfc6ba78559fe449983dee20dd6
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI
This commit requests a GICv5 VPE doorbell when a vcpu enters WFI, allowing
KVM to be notified when an SPI or LPI becomes pending. It also calculates
a doorbell priority mask (DBPM) from the virtual priority state to avoid
waking the guest for masked interrupts.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> + /*
> + * Request a doorbell *unless* the priority is 0, indicating
> + * that no interrupt can wake the CPU up.
> + */
> + if (priority_mask) {
[Severity: High]
Does skipping the doorbell request when the priority is 0 break vNMI wakeups?
Architectural semantics dictate that vNMIs have superpriority and must wake
a vCPU even when the running priority is 0.
> + int db_irq = vgic_v5_vpe_db(vcpu);
> + struct irq_data *d = irq_get_irq_data(db_irq);
> + const struct cpumask *aff = irq_data_get_effective_affinity_mask(d);
> + int cpu = smp_processor_id();
> +
> + dbpm = priority_mask - 1;
[Severity: High]
Does subtracting 1 from the priority mask drop valid preempting interrupts?
The hardware doorbell condition triggers when the pending priority is less
than the threshold. If the highest active priority is 5, the vCPU should
be woken by any interrupt with a priority less than 5, for example, 4.
If the priority mask is 5, this code sets the DBPM to 4. The hardware
condition then becomes priority < 4. An interrupt with priority 4 will
not signal the doorbell, despite having a higher priority than the active
interrupt, causing missed wakeups and guest hangs.
> + cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_DB, 1) |
> + FIELD_PREP(ICH_CONTEXTR_EL2_DBPM, dbpm);
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
[ ... ]
> @@ -1227,8 +1227,12 @@ int kvm_vgic_vcpu_pending_irq(struct kvm_vcpu *vcpu)
> unsigned long flags;
> struct vgic_vmcr vmcr;
>
> - if (vgic_is_v5(vcpu->kvm))
> + if (vgic_is_v5(vcpu->kvm)) {
> + if (READ_ONCE(vcpu->arch.vgic_cpu.vgic_v5.gicv5_vpe.db_fired))
> + return true;
[Severity: High]
Can a stale db_fired state cause an infinite loop bypassing WFI sleep?
The db_fired flag is cleared only in __vgic_v5_make_non_resident(), which
executes during schedule-out.
If a doorbell fires, db_fired becomes true and the vCPU wakes up to enter
the guest. When the vCPU subsequently re-enters WFI without a schedule-out,
kvm_vcpu_block() evaluates kvm_vgic_vcpu_pending_irq(). Because db_fired
remains true, this instantly returns without sleeping.
This leaves the vCPU in an infinite loop returning from WFI and pegging
the host CPU at 100% until a host timer tick forces preemption.
> +
> return vgic_v5_has_pending_ppi(vcpu);
> + }
>
> if (!vcpu->kvm->arch.vgic.enabled)
> return false;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=16
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 17/48] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (15 preceding siblings ...)
2026-07-24 10:52 ` [PATCH v4 16/48] KVM: arm64: gic-v5: Request doorbells when VPEs enter WFI Sascha Bischoff
@ 2026-07-24 10:52 ` Sascha Bischoff
2026-07-24 10:53 ` [PATCH v4 18/48] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers Sascha Bischoff
` (21 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:52 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, fuad.tabba@linux.dev
In order to properly emulate the operation of the IRS from KVM, we
require storage for the MMIO register state. This change introduces
struct vgic_v5_irs, and adds a pointer to it to the struct vgic_dist.
This new data structure contains the storage for IRS MMIO state that
is required for emulating the MMIO interface in KVM. This provides
persistent storage, and a way to track data across MMIO writes, e.g.,
selecting an SPI and updating the configuration of it is two MMIO
writes.
Note that only a pointer to the data structure is added to struct
vgic_dist as this new structure is very large, and hence it makes
sense to dynamically allocate it and just provide a pointer to
retrieve it in struct vgic_dist.
In addition to adding a structure to store the MMIO state for the IRS,
we add the base address in GPA space to struct vgic_v5_irs.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
include/kvm/arm_vgic.h | 86 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index faecde764fea3..25368c5cda5df 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -409,6 +409,87 @@ struct vgic_v5_vm {
bool vmte_allocated;
};
+/*** GICv5 ***/
+struct vgic_v5_irs {
+ /* base addresses in guest physical address space: */
+ gpa_t vgic_v5_irs_base;
+
+ struct vgic_io_device iodev;
+ struct kvm_device *dev;
+
+ /* IRS state - used for registers etc */
+ struct {
+ u8 domain;
+ u8 pa_range;
+ bool virt;
+ bool setlpi;
+ bool mec;
+ bool mpam;
+ bool swe;
+ u16 irs_id;
+ } idr0;
+
+ struct {
+ /* PE_CNT is populated from online_vcpus at runtime */
+ u8 priority_bits;
+ } idr1;
+
+ struct {
+ u8 id_bits;
+ u8 min_lpi_id_bits;
+ bool ist_levels;
+ u8 ist_l2sz;
+ bool istmd;
+ u8 istmd_sz;
+ } idr2;
+
+ struct {
+ u32 spi_range;
+ } idr5;
+
+ struct {
+ u32 spi_irs_range;
+ } idr6;
+
+ struct {
+ u32 spi_base;
+ } idr7;
+
+ struct {
+ u8 sh;
+ u8 oc;
+ u8 ic;
+ bool ist_ra;
+ bool ist_wa;
+ bool vmt_ra;
+ bool vpet_ra;
+ bool vmd_ra;
+ bool vmd_wa;
+ bool vped_ra;
+ bool vped_wa;
+ } cr1;
+
+ struct {
+ u32 id;
+ } spi_selr;
+
+ struct {
+ u32 iaffid;
+ } pe_selr;
+
+ struct {
+ u8 lpi_id_bits;
+ u8 l2sz;
+ u8 istsz;
+ bool structure;
+ } ist_cfgr;
+
+ struct {
+ bool valid;
+ u64 addr;
+ } ist_baser;
+};
+
struct vgic_dist {
bool in_kernel;
bool ready;
@@ -486,6 +567,11 @@ struct vgic_dist {
* GICv5 per-VM data.
*/
struct vgic_v5_vm gicv5_vm;
+
+ /*
+ * GICv5 IRS data. Dynamically allocated due to the size.
+ */
+ struct vgic_v5_irs *vgic_v5_irs_data;
};
struct vgic_v2_cpu_if {
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 18/48] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (16 preceding siblings ...)
2026-07-24 10:52 ` [PATCH v4 17/48] KVM: arm64: gic-v5: Introduce struct vgic_v5_irs and IRS base address Sascha Bischoff
@ 2026-07-24 10:53 ` Sascha Bischoff
2026-07-24 10:53 ` [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
` (20 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:53 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, fuad.tabba@linux.dev
In order to support proper VMs (that support more than just PPIs) for
GICv5, it is important to emulate the GICv5 IRS too. The IRS includes
an MMIO interface which is used to interact with and configure the
IRS.
As part of providing the emulated IRS MMIO interface in KVM, extend
enum iodev_type to include a GICv5 IRS device, and extend the MMIO
code to handle reads and writes to that type of IO device. This will
allow the creation of a GICv5 IRS IO Device in KVM.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-mmio.c | 6 ++++++
arch/arm64/kvm/vgic/vgic-mmio.h | 2 ++
include/kvm/arm_vgic.h | 3 ++-
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.c b/arch/arm64/kvm/vgic/vgic-mmio.c
index 74d76dec97304..fddb9da0403d5 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.c
+++ b/arch/arm64/kvm/vgic/vgic-mmio.c
@@ -1065,6 +1065,9 @@ static int dispatch_mmio_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_ITS:
data = region->its_read(vcpu->kvm, iodev->its, addr, len);
break;
+ case IODEV_GICV5_IRS:
+ data = region->read(vcpu, addr, len);
+ break;
}
vgic_data_host_to_mmio_bus(val, len, data);
@@ -1095,6 +1098,9 @@ static int dispatch_mmio_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
case IODEV_ITS:
region->its_write(vcpu->kvm, iodev->its, addr, len, data);
break;
+ case IODEV_GICV5_IRS:
+ region->write(vcpu, addr, len, data);
+ break;
}
return 0;
diff --git a/arch/arm64/kvm/vgic/vgic-mmio.h b/arch/arm64/kvm/vgic/vgic-mmio.h
index 50dc80220b0f3..38ed730d68ac3 100644
--- a/arch/arm64/kvm/vgic/vgic-mmio.h
+++ b/arch/arm64/kvm/vgic/vgic-mmio.h
@@ -217,6 +217,8 @@ unsigned int vgic_v2_init_cpuif_iodev(struct vgic_io_device *dev);
unsigned int vgic_v3_init_dist_iodev(struct vgic_io_device *dev);
+unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev);
+
u64 vgic_sanitise_outer_cacheability(u64 reg);
u64 vgic_sanitise_inner_cacheability(u64 reg);
u64 vgic_sanitise_shareability(u64 reg);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 25368c5cda5df..4d930a2651213 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -323,7 +323,8 @@ enum iodev_type {
IODEV_CPUIF,
IODEV_DIST,
IODEV_REDIST,
- IODEV_ITS
+ IODEV_ITS,
+ IODEV_GICV5_IRS
};
struct vgic_io_device {
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (17 preceding siblings ...)
2026-07-24 10:53 ` [PATCH v4 18/48] KVM: arm64: gic-v5: Add IRS IODEV support to MMIO handlers Sascha Bischoff
@ 2026-07-24 10:53 ` Sascha Bischoff
2026-07-24 11:30 ` sashiko-bot
2026-07-24 10:53 ` [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
` (19 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:53 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, fuad.tabba@linux.dev
Define the UAPI address type used by userspace to describe the
location of the emulated IRS in guest physical address space, together
with the size reserved for that region.
As per the GICv5 specification, the IRS has one CONFIG_FRAME and
optionally one SETLPI_FRAME per interrupt domain. Within a KVM VM we
are only concerned with one interrupt domain. Each of these frames is
64kB in size, so reserve 2x64kB of contiguous memory in the GPA space
for a GICv5 IRS.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/uapi/asm/kvm.h | 5 +++++
tools/arch/arm64/include/uapi/asm/kvm.h | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aa..d1b2ca317f586 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -97,6 +97,11 @@ struct kvm_regs {
#define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
#define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
+/* Supported VGICv5 address types */
+#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+
+#define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K)
+
#define KVM_ARM_VCPU_POWER_OFF 0 /* CPU is started in OFF state */
#define KVM_ARM_VCPU_EL1_32BIT 1 /* CPU running a 32bit VM */
#define KVM_ARM_VCPU_PSCI_0_2 2 /* CPU uses PSCI v0.2 */
diff --git a/tools/arch/arm64/include/uapi/asm/kvm.h b/tools/arch/arm64/include/uapi/asm/kvm.h
index 1c13bfa2d38aa..d1b2ca317f586 100644
--- a/tools/arch/arm64/include/uapi/asm/kvm.h
+++ b/tools/arch/arm64/include/uapi/asm/kvm.h
@@ -97,6 +97,11 @@ struct kvm_regs {
#define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
#define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
+/* Supported VGICv5 address types */
+#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
+
+#define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K)
+
#define KVM_ARM_VCPU_POWER_OFF 0 /* CPU is started in OFF state */
#define KVM_ARM_VCPU_EL1_32BIT 1 /* CPU running a 32bit VM */
#define KVM_ARM_VCPU_PSCI_0_2 2 /* CPU uses PSCI v0.2 */
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI
2026-07-24 10:53 ` [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
@ 2026-07-24 11:30 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:30 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, kvmarm, Marc Zyngier, Oliver Upton
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The patch defines a new UAPI constant for configuring the GICv5 IRS address but provides no kernel implementation to handle it.
- [Low] UAPI macro KVM_VGIC_V5_IRS_SIZE uses the kernel-internal SZ_64K macro, causing compilation errors in userspace.
--
--- Patch [19]: [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI ---
commit 58ea54e3d864bdb3fe8fc046f0912b0e710377f8
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI
Define the UAPI address type used by userspace to describe the
location of the emulated IRS in guest physical address space, together
with the size reserved for that region.
As per the GICv5 specification, the IRS has one CONFIG_FRAME and
optionally one SETLPI_FRAME per interrupt domain. Within a KVM VM we
are only concerned with one interrupt domain. Each of these frames is
64kB in size, so reserve 2x64kB of contiguous memory in the GPA space
for a GICv5 IRS.
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 1c13bfa2d38aa..d1b2ca317f586 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -97,6 +97,11 @@ struct kvm_regs {
> #define KVM_VGIC_V3_REDIST_SIZE (2 * SZ_64K)
> #define KVM_VGIC_V3_ITS_SIZE (2 * SZ_64K)
>
> +/* Supported VGICv5 address types */
> +#define KVM_VGIC_V5_ADDR_TYPE_IRS 6
[Severity: High]
Is this new UAPI attribute handled by the kernel?
Looking at vgic_v5_set_attr() in arch/arm64/kvm/vgic/vgic-kvm-device.c,
it currently returns -ENXIO for the KVM_DEV_ARM_VGIC_GRP_ADDR group:
arch/arm64/kvm/vgic/vgic-kvm-device.c:vgic_v5_set_attr() {
...
case KVM_DEV_ARM_VGIC_GRP_ADDR:
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
...
}
Will userspace callers attempting to configure the IRS address via this
constant fail because the KVM_SET_DEVICE_ATTR ioctl path is unimplemented?
> +
> +#define KVM_VGIC_V5_IRS_SIZE (2 * SZ_64K)
[Severity: Low]
Does this break userspace compilation?
The SZ_64K macro is a kernel-internal definition. If a userspace
application includes <asm/kvm.h> and references KVM_VGIC_V5_IRS_SIZE,
will it fail to compile due to an undeclared identifier?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=19
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (18 preceding siblings ...)
2026-07-24 10:53 ` [PATCH v4 19/48] KVM: arm64: gic-v5: Add KVM_VGIC_V5_ADDR_TYPE_IRS to UAPI Sascha Bischoff
@ 2026-07-24 10:53 ` Sascha Bischoff
2026-07-24 11:45 ` sashiko-bot
2026-07-24 10:53 ` [PATCH v4 21/48] KVM: arm64: gic-v5: Initialise per-VM IRS state Sascha Bischoff
` (18 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:53 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, fuad.tabba@linux.dev
In order to properly support GICv5-based VMs in KVM, we need to
emulate the CONFIG_FRAME for a virtual IRS. This emulation needs to
handle all guest accesses to the MMIO region, and mimic the behaviour
of a real IRS.
Introduce an IODEV for the GICv5 IRS, and an associated init function
that sets up the SPIs and initial state for the IRS. The MMIO
emulation provides support for the guest to query the IRS_IDx
registers, manipulate SPIs, configure ISTs, and so forth.
The emulation tracks selector state across MMIO accesses. For example,
a guest writes IRS_PE_SELR to select a PE by IAFFID. This is the VPE
ID for a VM, but the guest does not know this. If the guest reads
IRS_PE_STATUSR, KVM checks whether that IAFFID selects a valid VPE and
sets the V bit accordingly. IRS_PE_CR0 is accepted as write-ignored,
because KVM does not support 1-of-N routing.
The same selector/status register model is exposed for SPIs too.
When it comes to the LPI IST this also requires KVM to perform actions
on behalf of the guest. When the emulated IRS_IST_BASER is written,
KVM re-allocates the IST on the host, matching the guest's
configuration (from the emulated IRS_IST_CFGR) where appropriate. This
is then provided to the physical IRS via the VMTE. As far as the guest
is concerned, the IST it allocated is being used by the hardware, but
in reality the host IST is used instead.
This change provides the IRS IODEV as a whole, but this is not plumbed
into the rest of KVM yet.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_host.h | 1 +
arch/arm64/kvm/Makefile | 2 +-
arch/arm64/kvm/arm.c | 8 +
arch/arm64/kvm/vgic/vgic-irs-v5.c | 756 +++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic-v5-tables.c | 16 +
arch/arm64/kvm/vgic/vgic-v5-tables.h | 1 +
arch/arm64/kvm/vgic/vgic.h | 2 +
include/kvm/arm_vgic.h | 1 +
8 files changed, 786 insertions(+), 1 deletion(-)
create mode 100644 arch/arm64/kvm/vgic/vgic-irs-v5.c
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index bae2c4f92ef5c..b04dcc9a73c28 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -55,6 +55,7 @@
#define KVM_REQ_GUEST_HYP_IRQ_PENDING KVM_ARCH_REQ(9)
#define KVM_REQ_MAP_L1_VNCR_EL2 KVM_ARCH_REQ(10)
#define KVM_REQ_VGIC_PROCESS_UPDATE KVM_ARCH_REQ(11)
+#define KVM_REQ_RELOAD_GICv5 KVM_ARCH_REQ(12)
#define KVM_DIRTY_LOG_MANUAL_CAPS (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
KVM_DIRTY_LOG_INITIALLY_SET)
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 431de9b145ca1..92dda57c08766 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -24,7 +24,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
vgic/vgic-mmio.o vgic/vgic-mmio-v2.o \
vgic/vgic-mmio-v3.o vgic/vgic-kvm-device.o \
vgic/vgic-its.o vgic/vgic-debug.o vgic/vgic-v3-nested.o \
- vgic/vgic-v5.o vgic/vgic-v5-tables.o
+ vgic/vgic-v5.o vgic/vgic-v5-tables.o vgic/vgic-irs-v5.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o
kvm-$(CONFIG_ARM64_PTR_AUTH) += pauth.o
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 50adfff75be82..e1b5cc1e01a6e 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1138,6 +1138,14 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
preempt_enable();
}
+ if (kvm_check_request(KVM_REQ_RELOAD_GICv5, vcpu)) {
+ /* The IRS enable bit was changed */
+ preempt_disable();
+ vgic_v5_put(vcpu);
+ vgic_v5_load(vcpu);
+ preempt_enable();
+ }
+
if (kvm_check_request(KVM_REQ_RELOAD_PMU, vcpu))
kvm_vcpu_reload_pmu(vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
new file mode 100644
index 0000000000000..da8914dd682fc
--- /dev/null
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -0,0 +1,756 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 ARM Limited, All Rights Reserved.
+ */
+#include <linux/bitops.h>
+#include <linux/bsearch.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kvm.h>
+#include <linux/kvm_host.h>
+#include <kvm/iodev.h>
+#include <kvm/arm_arch_timer.h>
+#include <kvm/arm_vgic.h>
+
+#include "vgic.h"
+#include "vgic-mmio.h"
+#include "vgic-v5-tables.h"
+
+#define irs_caps kvm_vgic_global_state.vgic_v5_irs_caps
+
+static struct vgic_dist *vgic_v5_get_vgic(struct kvm_vcpu *vcpu)
+{
+ return &vcpu->kvm->arch.vgic;
+}
+
+static struct vgic_v5_irs *vgic_v5_get_irs(struct kvm_vcpu *vcpu)
+{
+ return vcpu->kvm->arch.vgic.vgic_v5_irs_data;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_misc(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct kvm_vcpu *target_vcpu;
+ u8 vpe_id_bits;
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_IDR0:
+ value = FIELD_PREP(GICV5_IRS_IDR0_INT_DOM, irs->idr0.domain);
+ value |= FIELD_PREP(GICV5_IRS_IDR0_PA_RANGE, irs->idr0.pa_range);
+ if (irs->idr0.virt)
+ value |= GICV5_IRS_IDR0_VIRT;
+ if (irs->idr0.setlpi)
+ value |= GICV5_IRS_IDR0_SETLPI;
+ if (irs->idr0.mec)
+ value |= GICV5_IRS_IDR0_MEC;
+ if (irs->idr0.mpam)
+ value |= GICV5_IRS_IDR0_MPAM;
+ if (irs->idr0.swe)
+ value |= GICV5_IRS_IDR0_SWE;
+ value |= FIELD_PREP(GICV5_IRS_IDR0_IRSID, irs->idr0.irs_id);
+ break;
+ case GICV5_IRS_IDR1:
+ value = FIELD_PREP(GICV5_IRS_IDR1_PE_CNT,
+ atomic_read(&vcpu->kvm->online_vcpus));
+ /*
+ * IRS_IDR1 encodes IAFFID_BITS as N - 1.
+ */
+ vpe_id_bits = vgic_v5_vmte_vpe_id_bits(vcpu);
+ value |= FIELD_PREP(GICV5_IRS_IDR1_IAFFID_BITS, vpe_id_bits - 1);
+ value |= FIELD_PREP(GICV5_IRS_IDR1_PRIORITY_BITS, irs->idr1.priority_bits);
+ break;
+ case GICV5_IRS_IDR2:
+ value = FIELD_PREP(GICV5_IRS_IDR2_ISTMD_SZ, irs->idr2.istmd_sz);
+ if (irs->idr2.istmd)
+ value |= GICV5_IRS_IDR2_ISTMD;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_IST_L2SZ, irs->idr2.ist_l2sz);
+ if (irs->idr2.ist_levels)
+ value |= GICV5_IRS_IDR2_IST_LEVELS;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_MIN_LPI_ID_BITS, irs->idr2.min_lpi_id_bits);
+ value |= GICV5_IRS_IDR2_LPI;
+ value |= FIELD_PREP(GICV5_IRS_IDR2_ID_BITS, irs->idr2.id_bits);
+ break;
+ case GICV5_IRS_IDR5:
+ value = FIELD_PREP(GICV5_IRS_IDR5_SPI_RANGE, irs->idr5.spi_range);
+ break;
+ case GICV5_IRS_IDR6:
+ value = FIELD_PREP(GICV5_IRS_IDR6_SPI_IRS_RANGE, irs->idr6.spi_irs_range);
+ break;
+ case GICV5_IRS_IDR7:
+ value = FIELD_PREP(GICV5_IRS_IDR7_SPI_BASE, irs->idr7.spi_base);
+ break;
+ case GICV5_IRS_IIDR:
+ /* Revision, Variant, ProductID are implementation defined */
+ value = FIELD_PREP(GICV5_IRS_IIDR_PRODUCT_ID, PRODUCT_ID_KVM);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_VARIANT, 0);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_REVISION, 0);
+ value |= FIELD_PREP(GICV5_IRS_IIDR_IMPLEMENTER, IMPLEMENTER_ARM);
+ break;
+ case GICV5_IRS_AIDR:
+ value = FIELD_PREP(GICV5_IRS_AIDR_COMPONENT,
+ GICV5_AIDR_COMPONENT_IRS);
+ value |= FIELD_PREP(GICV5_IRS_AIDR_ARCHMAJORREV,
+ GICV5_AIDR_ARCH_MAJ_REV_V5);
+ value |= FIELD_PREP(GICV5_IRS_AIDR_ARCHMINORREV,
+ GICV5_AIDR_ARCH_MIN_REV_V0);
+ break;
+ case GICV5_IRS_CR0:
+ /*
+ * The IRS is ALWAYS idle as we handle things instantaneously
+ * from a guest's viewpoint.
+ */
+ value = GICV5_IRS_CR0_IDLE;
+ if (READ_ONCE(vcpu->kvm->arch.vgic.enabled))
+ value |= GICV5_IRS_CR0_IRSEN;
+ break;
+ case GICV5_IRS_CR1:
+ if (irs->cr1.vped_wa)
+ value |= GICV5_IRS_CR1_VPED_WA;
+ if (irs->cr1.vped_ra)
+ value |= GICV5_IRS_CR1_VPED_RA;
+ if (irs->cr1.vmd_wa)
+ value |= GICV5_IRS_CR1_VMD_WA;
+ if (irs->cr1.vmd_ra)
+ value |= GICV5_IRS_CR1_VMD_RA;
+ if (irs->cr1.vpet_ra)
+ value |= GICV5_IRS_CR1_VPET_RA;
+ if (irs->cr1.vmt_ra)
+ value |= GICV5_IRS_CR1_VMT_RA;
+ if (irs->cr1.ist_wa)
+ value |= GICV5_IRS_CR1_IST_WA;
+ if (irs->cr1.ist_ra)
+ value |= GICV5_IRS_CR1_IST_RA;
+ value |= FIELD_PREP(GICV5_IRS_CR1_IC, irs->cr1.ic);
+ value |= FIELD_PREP(GICV5_IRS_CR1_OC, irs->cr1.oc);
+ value |= FIELD_PREP(GICV5_IRS_CR1_SH, irs->cr1.sh);
+ break;
+ case GICV5_IRS_SYNC_STATUSR:
+ value = GICV5_IRS_SYNC_STATUSR_IDLE;
+ break;
+ case GICV5_IRS_PE_SELR:
+ value = FIELD_PREP(GICV5_IRS_PE_SELR_IAFFID, irs->pe_selr.iaffid);
+ break;
+ case GICV5_IRS_PE_STATUSR:
+ /* We assume that the PE is Online if present. Always IDLE too */
+ value = GICV5_IRS_PE_STATUSR_IDLE;
+
+ /* Set ONLINE and V if IAFFID selects a present PE */
+ if (kvm_get_vcpu_by_id(vcpu->kvm, irs->pe_selr.iaffid)) {
+ value |= GICV5_IRS_PE_STATUSR_ONLINE;
+ value |= GICV5_IRS_PE_STATUSR_V;
+ }
+ break;
+ case GICV5_IRS_PE_CR0:
+ /*
+ * Make sure that we are doing something reasonable first.
+ * Remember, the IAFFID is the same as the VPE_ID
+ */
+ target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, irs->pe_selr.iaffid);
+ if (!target_vcpu) {
+ kvm_debug("Guest programmed invalid IAFFID (0x%x) into the IRS_PE_SELR\n",
+ irs->pe_selr.iaffid);
+ break;
+ }
+
+ value = GICV5_IRS_PE_CR0_DPS;
+ break;
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_misc(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ struct vgic_dist *vgic = vgic_v5_get_vgic(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+
+ switch (offset) {
+ case GICV5_IRS_CR0:
+ mutex_lock(&vcpu->kvm->arch.config_lock);
+ WRITE_ONCE(vgic->enabled, !!(val & GICV5_IRS_CR0_IRSEN));
+ kvm_make_all_cpus_request(vcpu->kvm, KVM_REQ_RELOAD_GICv5);
+ mutex_unlock(&vcpu->kvm->arch.config_lock);
+
+ return;
+ case GICV5_IRS_CR1:
+ irs->cr1.sh = FIELD_GET(GICV5_IRS_CR1_SH, val);
+ irs->cr1.oc = FIELD_GET(GICV5_IRS_CR1_OC, val);
+ irs->cr1.ic = FIELD_GET(GICV5_IRS_CR1_IC, val);
+ irs->cr1.ist_ra = !!(val & GICV5_IRS_CR1_IST_RA);
+ irs->cr1.ist_wa = !!(val & GICV5_IRS_CR1_IST_WA);
+ irs->cr1.vmt_ra = !!(val & GICV5_IRS_CR1_VMT_RA);
+ irs->cr1.vpet_ra = !!(val & GICV5_IRS_CR1_VPET_RA);
+ irs->cr1.vmd_ra = !!(val & GICV5_IRS_CR1_VMD_RA);
+ irs->cr1.vmd_wa = !!(val & GICV5_IRS_CR1_VMD_WA);
+ irs->cr1.vped_ra = !!(val & GICV5_IRS_CR1_VPED_RA);
+ irs->cr1.vped_wa = !!(val & GICV5_IRS_CR1_VPED_WA);
+ return;
+ case GICV5_IRS_PE_SELR:
+ irs->pe_selr.iaffid = FIELD_GET(GICV5_IRS_PE_SELR_IAFFID, val);
+ return;
+ case GICV5_IRS_PE_CR0:
+ /*
+ * We actually have nothing to do here as we don't support
+ * 1-of-N routing. The only thing that the guest can correctly
+ * write here is 0x1. However, there's no way to fault if it
+ * writes something else. This is effectively a WI in our case,
+ * but we keep it here for the purposes of documenting it.
+ */
+ return;
+ default:
+ return;
+ }
+}
+
+static bool vgic_v5_is_spi_selr_valid(struct vgic_v5_irs *irs, u32 id)
+{
+ /* Invalid - we don't have any SPIs at all */
+ if (irs->idr5.spi_range == 0)
+ return false;
+
+ /* Invalid - we don't have any on this IRS */
+ if (irs->idr6.spi_irs_range == 0)
+ return false;
+
+ /* Invalid - ID is less than min */
+ if (id < irs->idr7.spi_base)
+ return false;
+
+ /* Invalid - ID is greater than max */
+ if (id >= (irs->idr7.spi_base + irs->idr6.spi_irs_range))
+ return false;
+
+ return true;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_spi(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct vgic_irq *irq;
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_SPI_SELR:
+ /* Return whatever was last written */
+ value = FIELD_PREP(GICV5_IRS_SPI_SELR_ID, READ_ONCE(irs->spi_selr.id));
+ break;
+ case GICV5_IRS_SPI_STATUSR:
+ /* We assume that we can always claim to be idle */
+ value = GICV5_IRS_SPI_STATUSR_IDLE;
+ if (vgic_v5_is_spi_selr_valid(irs, READ_ONCE(irs->spi_selr.id)))
+ value |= GICV5_IRS_SPI_STATUSR_V;
+ break;
+ case GICV5_IRS_SPI_DOMAINR:
+ value = FIELD_PREP(GICV5_IRS_SPI_DOMAINR_DOMAIN,
+ GICV5_IRS_SPI_DOMAINR_DOMAIN_NON_SECURE);
+ break;
+ case GICV5_IRS_SPI_CFGR: {
+ u32 id = READ_ONCE(irs->spi_selr.id);
+
+ if (!vgic_v5_is_spi_selr_valid(irs, id)) {
+ /* Fault with IRS_SPI_SELR; return 0*/
+ value = 0;
+ break;
+ }
+
+ irq = vgic_get_irq(vcpu->kvm, vgic_v5_make_spi(id));
+ if (!irq) {
+ kvm_debug_ratelimited("Guest trying to access SPI not backed by KVM\n");
+ value = 0;
+ break;
+ }
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (irq->config == VGIC_CONFIG_LEVEL)
+ value = GICV5_IRS_SPI_CFGR_TM;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+
+ break;
+ }
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_spi(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ struct vgic_irq *irq;
+
+ switch (offset) {
+ case GICV5_IRS_SPI_SELR:
+ WRITE_ONCE(irs->spi_selr.id,
+ FIELD_GET(GICV5_IRS_SPI_SELR_ID, val));
+ return;
+ case GICV5_IRS_SPI_CFGR: {
+ u32 id = READ_ONCE(irs->spi_selr.id);
+
+ if (!vgic_v5_is_spi_selr_valid(irs, id))
+ return;
+
+ /*
+ * Find KVM's representation of the interrupt - we need to make
+ * sure that KVM's view agrees with the guest's, else interrupt
+ * injection won't work properly for level-triggered interrupts
+ * (we fail to handle the clearing of the pending state if KVM
+ * thinks that the interrupt is edge-triggered, which is the
+ * default.)
+ */
+ irq = vgic_get_irq(vcpu->kvm, vgic_v5_make_spi(id));
+ if (!irq)
+ return;
+
+ scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
+ if (val & GICV5_IRS_SPI_CFGR_TM)
+ irq->config = VGIC_CONFIG_LEVEL;
+ else
+ irq->config = VGIC_CONFIG_EDGE;
+ }
+
+ vgic_put_irq(vcpu->kvm, irq);
+
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static bool vgic_v5_ist_cfgr_valid(struct vgic_v5_irs *irs)
+{
+ unsigned int expected_istsz;
+
+ if (irs->ist_cfgr.lpi_id_bits < irs->idr2.min_lpi_id_bits ||
+ irs->ist_cfgr.lpi_id_bits > irs->idr2.id_bits)
+ return false;
+
+ if (!irs->idr2.istmd)
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_4;
+ else if (irs->ist_cfgr.lpi_id_bits >= irs->idr2.istmd_sz)
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_16;
+ else
+ expected_istsz = GICV5_IRS_IST_CFGR_ISTSZ_8;
+
+ if (irs->ist_cfgr.istsz != expected_istsz)
+ return false;
+
+ if (irs->ist_cfgr.structure && !irs->idr2.ist_levels)
+ return false;
+
+ if (!irs->ist_cfgr.structure)
+ return true;
+
+ return irs->ist_cfgr.l2sz == irs->idr2.ist_l2sz;
+}
+
+static unsigned long vgic_v5_mmio_read_irs_ist(struct kvm_vcpu *vcpu,
+ gpa_t addr, unsigned int len)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ u64 value = 0;
+
+ switch (offset) {
+ case GICV5_IRS_IST_STATUSR:
+ return GICV5_IRS_IST_STATUSR_IDLE;
+ case GICV5_IRS_IST_CFGR:
+ if (irs->ist_cfgr.structure)
+ value |= GICV5_IRS_IST_CFGR_STRUCTURE;
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_ISTSZ, irs->ist_cfgr.istsz);
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_L2SZ, irs->ist_cfgr.l2sz);
+ value |= FIELD_PREP(GICV5_IRS_IST_CFGR_LPI_ID_BITS, irs->ist_cfgr.lpi_id_bits);
+ break;
+ case GICV5_IRS_IST_BASER:
+ value = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
+ irs->ist_baser.addr >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
+ if (irs->ist_baser.valid)
+ value |= GICV5_IRS_IST_BASER_VALID;
+ break;
+ default:
+ return 0;
+ }
+
+ return value;
+}
+
+static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
+ unsigned int len, unsigned long val)
+{
+ struct vgic_v5_irs *irs = vgic_v5_get_irs(vcpu);
+ const size_t offset = addr & (SZ_64K - 1);
+ enum gicv5_vcpu_cmd cmd = LPI_VIST_MAKE_INVALID;
+
+ switch (offset) {
+ case GICV5_IRS_IST_CFGR:
+ scoped_guard(mutex, &vcpu->kvm->arch.config_lock) {
+ if (irs->ist_baser.valid)
+ return;
+
+ irs->ist_cfgr.lpi_id_bits = FIELD_GET(GICV5_IRS_IST_CFGR_LPI_ID_BITS, val);
+ irs->ist_cfgr.l2sz = FIELD_GET(GICV5_IRS_IST_CFGR_L2SZ, val);
+ irs->ist_cfgr.istsz = FIELD_GET(GICV5_IRS_IST_CFGR_ISTSZ, val);
+ irs->ist_cfgr.structure = !!(val & GICV5_IRS_IST_CFGR_STRUCTURE);
+ }
+ return;
+ case GICV5_IRS_IST_BASER: {
+ bool valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+
+ guard(mutex)(&vcpu->kvm->arch.config_lock);
+
+ /* Valid -> Invalid */
+ if (irs->ist_baser.valid && !valid) {
+ /* Make the LPI IST invalid and then ... */
+ if (irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd))
+ break;
+
+ /*
+ * ... free the host IST if we successfully marked the
+ * IST as invalid. Frankly, if we failed to make the
+ * guest's IST as invalid, we're cooked because it means
+ * that the IRS may still be using the memory that we
+ * want to free. Hence, we leave it allocated and skip
+ * the clearing of valid bit in the baser.
+ */
+ if (vgic_v5_lpi_ist_free(vcpu->kvm))
+ break;
+ } else if (!irs->ist_baser.valid && valid) { /* Invalid -> Valid */
+ if (!vgic_v5_ist_cfgr_valid(irs)) {
+ kvm_err("Guest programmed invalid IRS_IST_CFGR\n");
+ break;
+ }
+
+ if (vgic_v5_lpi_ist_alloc(vcpu->kvm, irs->ist_cfgr.lpi_id_bits))
+ break;
+ }
+
+ /* Now that we've handled the edges, update the valid bit and addr */
+ irs->ist_baser.valid = !!(val & GICV5_IRS_IST_BASER_VALID);
+ irs->ist_baser.addr = FIELD_GET(GICV5_IRS_IST_BASER_ADDR_MASK, val)
+ << GICV5_IRS_IST_BASER_ADDR_SHIFT;
+
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+static const struct vgic_register_region vgic_v5_irs_registers[] = {
+ /*
+ * This is the IRS_CONFIG_FRAME.
+ */
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR0, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR1, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR2, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR3, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR4, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR5, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR6, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IDR7, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_AIDR, vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR0, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_CR1, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNCR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SYNC_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_SELR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_DOMAINR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_RESAMPLER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_CFGR, vgic_v5_mmio_read_irs_spi,
+ vgic_v5_mmio_write_irs_spi, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SPI_STATUSR,
+ vgic_v5_mmio_read_irs_spi, vgic_mmio_write_wi,
+ 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_SELR, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_STATUSR,
+ vgic_v5_mmio_read_irs_misc,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_PE_CR0, vgic_v5_mmio_read_irs_misc,
+ vgic_v5_mmio_write_irs_misc, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_BASER, vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_CFGR, vgic_v5_mmio_read_irs_ist,
+ vgic_v5_mmio_write_irs_ist, 4,
+ VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_STATUSR,
+ vgic_v5_mmio_read_irs_ist, vgic_mmio_write_wi,
+ 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MAP_L2_ISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+
+ /*
+ * The following registers are only for running VMs. They are not yet
+ * supported as we don't currently support nested, so expose them as
+ * read-as-zero/write-ignored.
+ */
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_BASER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_CFGR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMT_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_SELR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_DBR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_HPPIR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_CR0, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VPE_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_DBR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_SELR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VM_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_L2_VMTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_L2_VISTR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_VMAP_VPER, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SAVE_VMR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SAVE_VM_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+
+ /* MEC, MPAM, SWERR - all unimplemented */
+
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MEC_IDR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MEC_MECID_R, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MPAM_IDR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_MPAM_PARTID_R, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 4, VGIC_ACCESS_32bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_STATUSR, vgic_mmio_read_raz,
+ vgic_mmio_write_wi, 8, VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_SYNDROMER0,
+ vgic_mmio_read_raz, vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+ REGISTER_DESC_WITH_LENGTH(GICV5_IRS_SWERR_SYNDROMER1,
+ vgic_mmio_read_raz, vgic_mmio_write_wi, 8,
+ VGIC_ACCESS_64bit),
+};
+
+unsigned int vgic_v5_init_irs_iodev(struct vgic_io_device *dev)
+{
+ dev->regions = vgic_v5_irs_registers;
+ dev->nr_regions = ARRAY_SIZE(vgic_v5_irs_registers);
+
+ kvm_iodevice_init(&dev->dev, &kvm_io_gic_ops);
+
+ /* We represent both of the IRS frames back to back, so this is 128K */
+ return KVM_VGIC_V5_IRS_SIZE;
+}
+
+int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address)
+{
+ struct vgic_io_device *io_device = &kvm->arch.vgic.vgic_v5_irs_data->iodev;
+ unsigned int len;
+
+ /*
+ * Design choice: Force MMIO region to be 64k aligned. Simplifies
+ * pulling out registers.
+ */
+ if (!IS_ALIGNED(irs_base_address, SZ_64K))
+ return -EINVAL;
+
+ len = vgic_v5_init_irs_iodev(io_device);
+
+ io_device->base_addr = irs_base_address;
+ io_device->iodev_type = IODEV_GICV5_IRS;
+ io_device->redist_vcpu = NULL;
+
+ return kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, irs_base_address, len,
+ &io_device->dev);
+}
+
+/**
+ * kvm_vgic_v5_irs_init: initialize the IRS data structures
+ * @kvm: kvm struct pointer
+ * @nr_spis: number of spis, frozen by caller
+ */
+int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
+{
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ struct vgic_v5_irs *irs = dist->vgic_v5_irs_data;
+ struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
+ size_t nr_spi_bits;
+ u64 mmfr0;
+ int ret, i;
+
+ /*
+ * We (KVM) allocate an Interrupt State Table (IST) for SPIs. The
+ * hardware mandates that lower 6 bits of the address are 0. Each ISTE
+ * is 4 bytes in size (or larger if metadata storage is required), so 16
+ * entries would be enough for alignment. Keep the minimum at 32 SPIs to
+ * match KVM's vGICv3 minimum and the VGICv5 device API.
+ */
+ if (nr_spis && nr_spis < VGIC_V5_DEFAULT_NR_SPIS)
+ nr_spis = VGIC_V5_DEFAULT_NR_SPIS;
+
+ if (nr_spis) {
+ dist->spis = kcalloc(nr_spis, sizeof(struct vgic_irq),
+ GFP_KERNEL_ACCOUNT);
+ if (!dist->spis)
+ return -ENOMEM;
+
+ /*
+ * In the following code we do not take the irq struct lock since
+ * no other action on irq structs can happen while the VGIC is
+ * not initialized yet.
+ */
+ for (i = 0; i < nr_spis; i++) {
+ struct vgic_irq *irq = &dist->spis[i];
+
+ irq->intid = vgic_v5_make_spi(i);
+ INIT_LIST_HEAD(&irq->ap_list);
+ raw_spin_lock_init(&irq->irq_lock);
+ irq->vcpu = NULL;
+ irq->target_vcpu = vcpu0;
+ refcount_set(&irq->refcount, 0);
+ /*
+ * The guest controls the enable state, and again it is
+ * directly handled by the hardware. From our point of
+ * view it is always enabled.
+ */
+ irq->enabled = 1;
+ }
+
+ nr_spi_bits = fls(roundup_pow_of_two(nr_spis)) - 1;
+
+ ret = vgic_v5_spi_ist_alloc(kvm, nr_spi_bits);
+ if (ret) {
+ kfree(dist->spis);
+ dist->spis = NULL;
+ return ret;
+ }
+ }
+
+ /* Set sane initial state for the IRS MMIO registers */
+
+ irs->idr0.domain = GICV5_IRS_IDR0_INT_DOM_NON_SECURE;
+
+ mmfr0 = read_sanitised_ftr_reg(SYS_ID_AA64MMFR0_EL1);
+ irs->idr0.pa_range = cpuid_feature_extract_unsigned_field(mmfr0,
+ ID_AA64MMFR0_EL1_PARANGE_SHIFT);
+
+ irs->idr0.virt = 0;
+ irs->idr0.setlpi = 0;
+ irs->idr0.mec = 0;
+ irs->idr0.mpam = 0;
+ irs->idr0.swe = 0;
+ irs->idr0.irs_id = 0;
+
+ irs->idr1.priority_bits = gicv5_global_data.irs_pri_bits - 1;
+
+ /*
+ * Support 16-bits of ID space for the IRS. This should be sufficient
+ * for most applications, and the CPUIF is guaranteed to have at least
+ * 16-bits of ID space support (we actually present 16-bits there, even
+ * if the hardware supports more). Warn if the hardware doesn't support
+ * 16 bits, and use the smaller value. YMMV!
+ *
+ * As for the minimum number of ID bits, we match the hardware's
+ * capability.
+ */
+ if (irs_caps.ist_id_bits < 16)
+ pr_warn("Host IRS supports fewer than 16 ID bits for ISTs (%u)\n",
+ irs_caps.ist_id_bits);
+
+ irs->idr2.id_bits = min(16, irs_caps.ist_id_bits);
+ irs->idr2.min_lpi_id_bits = irs_caps.min_lpi_id_bits;
+
+ /* Only allow the guest to create Linear ISTs - simplifies Save/Restore */
+ irs->idr2.ist_levels = 0;
+ irs->idr2.ist_l2sz = GICV5_IRS_IST_CFGR_L2SZ_4K;
+ irs->idr2.istmd = 0;
+ irs->idr2.istmd_sz = 0;
+
+ /* We have a single IRS, only. All SPIs reside here! */
+ irs->idr5.spi_range = nr_spis;
+ irs->idr6.spi_irs_range = nr_spis;
+ irs->idr7.spi_base = 0;
+
+ irs->cr1.sh = 0;
+ irs->cr1.oc = 0;
+ irs->cr1.ic = 0;
+ irs->cr1.ist_ra = 0;
+ irs->cr1.ist_wa = 0;
+ irs->cr1.vmt_ra = 0;
+ irs->cr1.vpet_ra = 0;
+ irs->cr1.vmd_ra = 0;
+ irs->cr1.vmd_wa = 0;
+ irs->cr1.vped_ra = 0;
+ irs->cr1.vped_wa = 0;
+
+ irs->spi_selr.id = -1;
+
+ irs->pe_selr.iaffid = -1;
+
+ irs->ist_cfgr.lpi_id_bits = 0;
+ irs->ist_cfgr.l2sz = 0;
+ irs->ist_cfgr.istsz = 0;
+ irs->ist_cfgr.structure = 0;
+
+ irs->ist_baser.valid = 0;
+ irs->ist_baser.addr = 0;
+
+ return 0;
+}
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
index d5e7923a03359..718410e8c9f3b 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
@@ -583,6 +583,22 @@ int vgic_v5_vmte_release(struct kvm *kvm)
return 0;
}
+/*
+ * Provide a way for the IRS MMIO emulation to correctly populate the number of
+ * IAFFID bits (which correspond to our vpe_id_bits.
+ */
+u8 vgic_v5_vmte_vpe_id_bits(struct kvm_vcpu *vcpu)
+{
+ u32 vm_id = vgic_v5_vm_id(vcpu->kvm);
+ struct vgic_v5_vm_info *vmi;
+
+ vmi = xa_load(&vm_info, vm_id);
+ if (!vmi)
+ return 0;
+
+ return vmi->vpe_id_bits;
+}
+
/*
* Allocate a VPE descriptor and provide it to the hardware via the VPE Table.
*/
diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.h b/arch/arm64/kvm/vgic/vgic-v5-tables.h
index 6b159a7e386cf..bc2fc5cb2c8d9 100644
--- a/arch/arm64/kvm/vgic/vgic-v5-tables.h
+++ b/arch/arm64/kvm/vgic/vgic-v5-tables.h
@@ -89,6 +89,7 @@ void vgic_v5_release_vm_id(struct kvm *kvm);
int vgic_v5_vmte_init(struct kvm *kvm);
int vgic_v5_vmte_release(struct kvm *kvm);
+u8 vgic_v5_vmte_vpe_id_bits(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_alloc_vpe(struct kvm_vcpu *vcpu);
int vgic_v5_vmte_free_vpe(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index f2f5fdc3211d7..282278e4a6c19 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -366,6 +366,7 @@ 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 kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis);
void vgic_v5_teardown(struct kvm *kvm);
int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct kvm_vcpu *vcpu, u32 vintid);
@@ -378,6 +379,7 @@ 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);
+int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address);
#define for_each_visible_v5_ppi(__i, __k) \
for_each_set_bit(__i, (__k)->arch.vgic.gicv5_vm.vgic_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS)
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 4d930a2651213..143e75743da86 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -40,6 +40,7 @@
* in KVM for now. At a future stage, this can be bumped up to 128, if required.
*/
#define VGIC_V5_NR_PRIVATE_IRQS 64
+#define VGIC_V5_DEFAULT_NR_SPIS 32
#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation
2026-07-24 10:53 ` [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
@ 2026-07-24 11:45 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:45 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, kvmarm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 9 potential issue(s) to consider:
- [High] Unallocated `vgic_v5_irs_data` leads to a NULL pointer dereference on initialization.
- [High] Bumping `nr_spis` locally in `kvm_vgic_v5_irs_init()` without updating `kvm->arch.vgic.nr_spis` leads to inaccessible SPIs.
- [High] Missing RCU read lock around `xa_load` in `vgic_v5_vmte_vpe_id_bits()`.
- [High] Unconditional IPI storm triggered by guest writes to GICV5_IRS_CR0.
- [High] 64-bit IRS registers cannot be accessed via 32-bit UAPI or guest MMIO.
- [Medium] Unratelimited host kernel log spam via invalid IRS_IST_CFGR allows a malicious guest to flood the host log.
- [Medium] Data races and torn reads on shared VM-wide IRS MMIO registers (`ist_baser`, `ist_cfgr`, `cr1`, `pe_selr.iaffid`).
- [Medium] Underflow in `IAFFID_BITS` calculation exposes bogus IDR1 values before first vCPU run.
- [Medium] IST valid state desynchronizes from hardware on free failure.
--
--- Patch [20]: [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation ---
commit 89cd01e35340acb046b6ed1ef9d6c28883308945
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation
This commit introduces an IODEV for the GICv5 IRS and an associated
initialization function to set up SPIs and initial state. The MMIO emulation
tracks selector state and configures LPI ISTs on behalf of the guest.
> diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
> new file mode 100644
> index 0000000000000..da8914dd682fc
> --- /dev/null
> +++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
> @@ -62,4 +62,5 @@ static unsigned long vgic_v5_mmio_read_irs_misc(struct kvm_vcpu *vcpu,
> + /*
> + * IRS_IDR1 encodes IAFFID_BITS as N - 1.
> + */
> + vpe_id_bits = vgic_v5_vmte_vpe_id_bits(vcpu);
> + value |= FIELD_PREP(GICV5_IRS_IDR1_IAFFID_BITS, vpe_id_bits - 1);
[Severity: Medium]
If userspace reads GICV5_IRS_IDR1 before the VM runs,
vgic_v5_vmte_vpe_id_bits() returns 0 because the VMTE is not yet allocated.
Does vpe_id_bits - 1 underflow here, exposing an incorrect IAFFID_BITS
value before it is properly initialized?
[ ... ]
> @@ -174,4 +174,5 @@ static void vgic_v5_mmio_write_irs_misc(struct kvm_vcpu *vcpu, gpa_t addr,
> + case GICV5_IRS_CR0:
> + mutex_lock(&vcpu->kvm->arch.config_lock);
> + WRITE_ONCE(vgic->enabled, !!(val & GICV5_IRS_CR0_IRSEN));
> + kvm_make_all_cpus_request(vcpu->kvm, KVM_REQ_RELOAD_GICv5);
[Severity: High]
Does this unconditional kick allow a guest to overload physical CPUs with
IPIs?
Should this verify if the IRSEN bit actually changed before making
the all-cpus request?
> + mutex_unlock(&vcpu->kvm->arch.config_lock);
> +
> + return;
[ ... ]
> + case GICV5_IRS_PE_SELR:
> + irs->pe_selr.iaffid = FIELD_GET(GICV5_IRS_PE_SELR_IAFFID, val);
[Severity: Medium]
Are atomic primitives or locks required when accessing this VM-wide state?
If multiple vCPUs concurrently write irs->pe_selr.iaffid locklessly, can it
lead to torn writes and corrupted hardware emulation state?
> + return;
[ ... ]
> @@ -367,5 +367,6 @@ static unsigned long vgic_v5_mmio_read_irs_ist(struct kvm_vcpu *vcpu,
> + case GICV5_IRS_IST_BASER:
> + value = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
> + irs->ist_baser.addr >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
> + if (irs->ist_baser.valid)
> + value |= GICV5_IRS_IST_BASER_VALID;
[Severity: Medium]
Similarly to pe_selr, does reading the 64-bit ist_baser locklessly risk
torn reads if another vCPU is concurrently modifying it?
> + break;
[ ... ]
> @@ -414,3 +414,4 @@ static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
> + switch (offset) {
> + case GICV5_IRS_IST_CFGR:
[Severity: High]
The switch statement checks the base offset, but lacks handling for the
upper 32-bit offset (offset + 4) for 64-bit registers like
GICV5_IRS_IST_BASER.
Will 32-bit guest writes to the upper half hit the default case and be
ignored?
> + scoped_guard(mutex, &vcpu->kvm->arch.config_lock) {
> + if (irs->ist_baser.valid)
[ ... ]
> + /* Valid -> Invalid */
> + if (irs->ist_baser.valid && !valid) {
> + /* Make the LPI IST invalid and then ... */
> + if (irq_set_vcpu_affinity(vgic_v5_vpe_db(vcpu), &cmd))
> + break;
> +
> + /*
> + * ... free the host IST if we successfully marked the
> + * IST as invalid. Frankly, if we failed to make the
> + * guest's IST as invalid, we're cooked because it means
> + * that the IRS may still be using the memory that we
> + * want to free. Hence, we leave it allocated and skip
> + * the clearing of valid bit in the baser.
> + */
> + if (vgic_v5_lpi_ist_free(vcpu->kvm))
> + break;
[Severity: Medium]
If vgic_v5_lpi_ist_free() fails after successfully issuing
LPI_VIST_MAKE_INVALID, the code breaks out of the switch statement early.
Does this leave the guest-visible irs->ist_baser.valid bit out of sync with
the actual hardware state?
> + } else if (!irs->ist_baser.valid && valid) { /* Invalid -> Valid */
> + if (!vgic_v5_ist_cfgr_valid(irs)) {
> + kvm_err("Guest programmed invalid IRS_IST_CFGR\n");
[Severity: Medium]
Can a malicious guest flood the host kernel log by repeatedly writing an
invalid configuration to GICV5_IRS_IST_BASER?
Should this use a ratelimited printk to prevent a denial of service?
> + break;
> + }
[ ... ]
> @@ -490,4 +490,5 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = {
> + REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_BASER, vgic_v5_mmio_read_irs_ist,
> + vgic_v5_mmio_write_irs_ist, 8,
> + VGIC_ACCESS_64bit),
[Severity: High]
Since GICV5_IRS_IST_BASER is defined exclusively with VGIC_ACCESS_64bit,
how does the 32-bit KVM UAPI access it during VM save and restore?
Will those accesses be rejected?
> + REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_CFGR, vgic_v5_mmio_read_irs_ist,
[ ... ]
> @@ -603,3 +603,4 @@ int vgic_v5_register_irs_iodev(struct kvm *kvm, gpa_t irs_base_address)
> +{
> + struct vgic_io_device *io_device = &kvm->arch.vgic.vgic_v5_irs_data->iodev;
[Severity: High]
Is vgic_v5_irs_data allocated anywhere before it is dereferenced here?
Since it doesn't appear to be allocated via kzalloc or kcalloc in this
patch, does this lead to a NULL pointer dereference when accessed?
The same dereference appears to occur in kvm_vgic_v5_irs_init() as well:
kvm_vgic_v5_irs_init() {
struct vgic_v5_irs *irs = dist->vgic_v5_irs_data;
...
irs->idr0.domain = GICV5_IRS_IDR0_INT_DOM_NON_SECURE;
> + unsigned int len;
[ ... ]
> @@ -642,4 +642,4 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
> + if (nr_spis && nr_spis < VGIC_V5_DEFAULT_NR_SPIS)
> + nr_spis = VGIC_V5_DEFAULT_NR_SPIS;
[Severity: High]
If the requested nr_spis is bumped locally to VGIC_V5_DEFAULT_NR_SPIS here,
how does core KVM know about the updated count?
Could this prevent the guest from accessing the newly padded SPIs since
kvm->arch.vgic.nr_spis remains un-bumped?
> +
> + if (nr_spis) {
> diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> --- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> @@ -656,3 +656,4 @@ u8 vgic_v5_vmte_vpe_id_bits(struct kvm_vcpu *vcpu)
> +
> + vmi = xa_load(&vm_info, vm_id);
[Severity: High]
Does this XArray lookup need RCU protection?
Since this is called from the MMIO read handler which runs under kvm->srcu
but not rcu_read_lock(), could this violate the XArray API contracts and
cause a use-after-free?
> + if (!vmi)
> + return 0;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=20
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 21/48] KVM: arm64: gic-v5: Initialise per-VM IRS state
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (19 preceding siblings ...)
2026-07-24 10:53 ` [PATCH v4 20/48] KVM: arm64: gic-v5: Add GICv5 IRS IODEV and MMIO emulation Sascha Bischoff
@ 2026-07-24 10:53 ` Sascha Bischoff
2026-07-24 10:54 ` [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
` (17 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:53 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, fuad.tabba@linux.dev
A virtual GICv5 needs an emulated IRS in addition to the host IRS
state used to back VMTEs, VPEs, and ISTs. Without this, KVM can only
provide the CPU-local PPI state and cannot expose the IRS-backed SPI
and LPI configuration expected by a GICv5 guest.
Allocate the per-VM emulated IRS state when creating a virtual GICv5,
and initialise it from vgic_v5_init(). If userspace has not provided a
number of SPIs, use the GICv5 default of 32. The IRS init path
allocates the SPI state, initialises the virtual IRS register state,
and creates the backing SPI IST when SPIs are present.
Keep the per-VM IRS object alive for the lifetime of the virtual GICv5.
vgic_v5_teardown() only unwinds resources allocated by vgic_v5_init(), so
failed initialisation can be retried, while kvm_vgic_dist_destroy() frees
the IRS object during final VGIC destruction.
This gives virtual GICv5s the IRS backing required for SPIs and LPIs,
rather than being limited to PPIs, only. Further patches add support for
SPI injection and lifecycle tracking.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 59 +++++++++++++++++++++++----------
arch/arm64/kvm/vgic/vgic-v5.c | 8 ++++-
2 files changed, 49 insertions(+), 18 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 94632fd90b728..aa883507d00d1 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -174,28 +174,48 @@ int kvm_vgic_create(struct kvm *kvm, u32 type)
break;
}
- if (ret) {
- kvm_for_each_vcpu(i, vcpu, kvm) {
- struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
- kfree(vgic_cpu->private_irqs);
- vgic_cpu->private_irqs = NULL;
- }
-
- kvm->arch.vgic.vgic_model = 0;
- goto out_unlock;
- }
+ if (ret)
+ goto out_free_private_irqs;
if (type == KVM_DEV_TYPE_ARM_VGIC_V3)
kvm->arch.vgic.nassgicap = system_supports_direct_sgis();
- /*
- * 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.
- */
- if (type == KVM_DEV_TYPE_ARM_VGIC_V5)
+ if (type == KVM_DEV_TYPE_ARM_VGIC_V5) {
+ /* Allocate a vIRS for GICv5 systems */
+ kvm->arch.vgic.vgic_v5_irs_data = kzalloc_obj(struct vgic_v5_irs,
+ GFP_KERNEL_ACCOUNT);
+ if (!kvm->arch.vgic.vgic_v5_irs_data) {
+ ret = -ENOMEM;
+ goto out_free_private_irqs;
+ }
+
+ /*
+ * Initialization happens later, for now just explicitly
+ * disable the device and undef its base address.
+ */
+ kvm->arch.vgic.vgic_v5_irs_data->vgic_v5_irs_base = VGIC_ADDR_UNDEF;
+
+ /*
+ * 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);
+ }
+
+ goto out_unlock;
+
+out_free_private_irqs:
+ kvm_for_each_vcpu(i, vcpu, kvm) {
+ struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
+
+ kfree(vgic_cpu->private_irqs);
+ vgic_cpu->private_irqs = NULL;
+ }
+
+ kvm->arch.vgic.vgic_model = 0;
out_unlock:
mutex_unlock(&kvm->arch.config_lock);
@@ -467,6 +487,9 @@ int vgic_init(struct kvm *kvm)
return ret;
}
} else {
+ if (!dist->nr_spis)
+ dist->nr_spis = VGIC_V5_DEFAULT_NR_SPIS;
+
ret = vgic_v5_init(kvm);
if (ret)
return ret;
@@ -512,6 +535,8 @@ static void kvm_vgic_dist_destroy(struct kvm *kvm)
break;
case KVM_DEV_TYPE_ARM_VGIC_V5:
vgic_v5_teardown(kvm);
+ kfree(dist->vgic_v5_irs_data);
+ dist->vgic_v5_irs_data = NULL;
break;
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 8ef53cac32414..4042d468431b1 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -684,7 +684,8 @@ void vgic_v5_teardown(struct kvm *kvm)
/*
* Claim and populate a VMTE (optionally making a new L2 VMT valid), create VPE
- * doorbells, allocate VPET and populate for each VPE.
+ * doorbells, allocate VPET and populate for each VPE. Finally, we also init the
+ * vIRS, which means allocating and making the virtual SPI IST valid.
*
* Note: We do need to put the cart before the horse here. The VPE doorbells are
* our conduit for communication with the IRS, which means we need to have those
@@ -754,6 +755,11 @@ int vgic_v5_init(struct kvm *kvm)
goto err;
}
+ /* Init IRS (and alloc SPI IST) */
+ ret = kvm_vgic_v5_irs_init(kvm, kvm->arch.vgic.nr_spis);
+ if (ret)
+ goto err;
+
return 0;
err:
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (20 preceding siblings ...)
2026-07-24 10:53 ` [PATCH v4 21/48] KVM: arm64: gic-v5: Initialise per-VM IRS state Sascha Bischoff
@ 2026-07-24 10:54 ` Sascha Bischoff
2026-07-24 11:33 ` sashiko-bot
2026-07-24 10:54 ` [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
` (16 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:54 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, fuad.tabba@linux.dev
Now that we have an emulated IRS, it needs to be registered, which
ensures that guest accesses to the MMIO regions handled by the device
are handled appropriately in KVM. Therefore, as part of
vgic_map_resources, the GICv5 IRS IODEV is registered. If the address
for the IRS is not provided, bail out reporting an error - this is not
a supported config.
As part of this change, expose setting the address of the emulated IRS
via KVM_VGIC_V5_ADDR_TYPE_IRS to userspace. Also allow userspace to set
the number of SPIs handled by the emulated GICv5 implementation, using a
GICv5-specific SPI count rather than the legacy total interrupt count.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 18 ++++-
arch/arm64/kvm/vgic/vgic-kvm-device.c | 106 ++++++++++++++++++--------
include/kvm/arm_vgic.h | 1 +
3 files changed, 91 insertions(+), 34 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index aa883507d00d1..8a38d91528774 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -657,9 +657,8 @@ int vgic_lazy_init(struct kvm *kvm)
int kvm_vgic_map_resources(struct kvm *kvm)
{
struct vgic_dist *dist = &kvm->arch.vgic;
- bool needs_dist = true;
enum vgic_type type;
- gpa_t dist_base;
+ gpa_t dist_base, irs_base;
int ret = 0;
if (likely(smp_load_acquire(&dist->ready)))
@@ -682,13 +681,12 @@ int kvm_vgic_map_resources(struct kvm *kvm)
} else {
ret = vgic_v5_map_resources(kvm);
type = VGIC_V5;
- needs_dist = false;
}
if (ret)
goto out;
- if (needs_dist) {
+ if (type != VGIC_V5) {
dist_base = dist->vgic_dist_base;
mutex_unlock(&kvm->arch.config_lock);
@@ -698,7 +696,19 @@ int kvm_vgic_map_resources(struct kvm *kvm)
goto out_slots;
}
} else {
+ irs_base = dist->vgic_v5_irs_data->vgic_v5_irs_base;
mutex_unlock(&kvm->arch.config_lock);
+
+ if (IS_VGIC_ADDR_UNDEF(irs_base)) {
+ ret = -ENXIO;
+ goto out_slots;
+ }
+
+ ret = vgic_v5_register_irs_iodev(kvm, irs_base);
+ if (ret) {
+ kvm_err("Unable to register VGIC IRS MMIO regions\n");
+ goto out_slots;
+ }
}
smp_store_release(&dist->ready, true);
diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
index 90be99443df3b..8c2c6886af4e3 100644
--- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
+++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
@@ -181,6 +181,14 @@ static int kvm_vgic_addr(struct kvm *kvm, struct kvm_device_attr *attr, bool wri
addr |= (u64)rdreg->count << KVM_VGIC_V3_RDIST_COUNT_SHIFT;
goto out;
}
+ case KVM_VGIC_V5_ADDR_TYPE_IRS:
+ r = vgic_check_type(kvm, KVM_DEV_TYPE_ARM_VGIC_V5);
+ if (r)
+ break;
+ addr_ptr = &vgic->vgic_v5_irs_data->vgic_v5_irs_base;
+ alignment = SZ_64K;
+ size = KVM_VGIC_V5_IRS_SIZE;
+ break;
default:
r = -ENODEV;
}
@@ -224,31 +232,48 @@ static int vgic_set_common_attr(struct kvm_device *dev,
if (get_user(val, uaddr))
return -EFAULT;
- /*
- * We require:
- * - at least 32 SPIs on top of the 16 SGIs and 16 PPIs
- * - at most 1024 interrupts
- * - a multiple of 32 interrupts
- */
- if (val < (VGIC_NR_PRIVATE_IRQS + 32) ||
- val > VGIC_MAX_RESERVED ||
- (val & 31))
- return -EINVAL;
+ if (!vgic_is_v5(dev->kvm)) {
+ /*
+ * We require:
+ * - at least 32 SPIs on top of the 16 SGIs and 16 PPIs
+ * - at most 1024 interrupts
+ * - a multiple of 32 interrupts
+ */
+ if (val < (VGIC_NR_PRIVATE_IRQS + 32) ||
+ val > VGIC_MAX_RESERVED || (val & 31))
+ return -EINVAL;
- mutex_lock(&dev->kvm->arch.config_lock);
+ mutex_lock(&dev->kvm->arch.config_lock);
- /*
- * Either userspace has already configured NR_IRQS or
- * the vgic has already been initialized and vgic_init()
- * supplied a default amount of SPIs.
- */
- if (dev->kvm->arch.vgic.nr_spis)
- ret = -EBUSY;
- else
- dev->kvm->arch.vgic.nr_spis =
- val - VGIC_NR_PRIVATE_IRQS;
+ /*
+ * Either userspace has already configured NR_IRQS or
+ * the vgic has already been initialized and vgic_init()
+ * supplied a default amount of SPIs.
+ */
+ if (dev->kvm->arch.vgic.nr_spis)
+ ret = -EBUSY;
+ else
+ dev->kvm->arch.vgic.nr_spis =
+ val - VGIC_NR_PRIVATE_IRQS;
- mutex_unlock(&dev->kvm->arch.config_lock);
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ } else {
+ /*
+ * GICv5 reports a number of SPIs, not a total number of
+ * interrupts. Require a multiple of 32 SPIs.
+ */
+ if (val < VGIC_V5_DEFAULT_NR_SPIS ||
+ val > VGIC_V5_MAX_NR_SPIS ||
+ (val & 31))
+ return -EINVAL;
+
+ mutex_lock(&dev->kvm->arch.config_lock);
+ if (vgic_initialized(dev->kvm) || dev->kvm->arch.vgic.nr_spis)
+ ret = -EBUSY;
+ else
+ dev->kvm->arch.vgic.nr_spis = val;
+ mutex_unlock(&dev->kvm->arch.config_lock);
+ }
return ret;
}
@@ -299,9 +324,14 @@ static int vgic_get_common_attr(struct kvm_device *dev,
return (r == -ENODEV) ? -ENXIO : r;
case KVM_DEV_ARM_VGIC_GRP_NR_IRQS: {
u32 __user *uaddr = (u32 __user *)(long)attr->addr;
-
- r = put_user(dev->kvm->arch.vgic.nr_spis +
- VGIC_NR_PRIVATE_IRQS, uaddr);
+ /* Older GICs */
+ if (!vgic_is_v5(dev->kvm)) {
+ r = put_user(dev->kvm->arch.vgic.nr_spis +
+ VGIC_NR_PRIVATE_IRQS,
+ uaddr);
+ } else {
+ r = put_user(dev->kvm->arch.vgic.nr_spis, uaddr);
+ }
break;
}
}
@@ -748,21 +778,25 @@ static int vgic_v5_set_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ 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);
+ break;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
default:
return -ENXIO;
}
+ break;
default:
return -ENXIO;
}
+ return vgic_set_common_attr(dev, attr);
}
static int vgic_v5_get_attr(struct kvm_device *dev,
@@ -770,21 +804,26 @@ static int vgic_v5_get_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ break;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ 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);
+ break;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
return vgic_v5_get_userspace_ppis(dev, attr);
default:
return -ENXIO;
}
+ break;
default:
return -ENXIO;
}
+
+ return vgic_get_common_attr(dev, attr);
}
static int vgic_v5_has_attr(struct kvm_device *dev,
@@ -792,15 +831,22 @@ static int vgic_v5_has_attr(struct kvm_device *dev,
{
switch (attr->group) {
case KVM_DEV_ARM_VGIC_GRP_ADDR:
+ switch (attr->attr) {
+ case KVM_VGIC_V5_ADDR_TYPE_IRS:
+ return 0;
+ }
+ return -ENXIO;
case KVM_DEV_ARM_VGIC_GRP_CPU_SYSREGS:
- case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
return -ENXIO;
+ case KVM_DEV_ARM_VGIC_GRP_NR_IRQS:
+ return 0;
case KVM_DEV_ARM_VGIC_GRP_CTRL:
switch (attr->attr) {
case KVM_DEV_ARM_VGIC_CTRL_INIT:
return 0;
case KVM_DEV_ARM_VGIC_USERSPACE_PPIS:
return 0;
+ case KVM_DEV_ARM_VGIC_SAVE_PENDING_TABLES:
default:
return -ENXIO;
}
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index 143e75743da86..af926f9384b65 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -41,6 +41,7 @@
*/
#define VGIC_V5_NR_PRIVATE_IRQS 64
#define VGIC_V5_DEFAULT_NR_SPIS 32
+#define VGIC_V5_MAX_NR_SPIS BIT(16)
#define is_v5_type(t, i) (FIELD_GET(GICV5_HWIRQ_TYPE, (i)) == (t))
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV
2026-07-24 10:54 ` [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
@ 2026-07-24 11:33 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:33 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvmarm, kvm, Oliver Upton
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] GICv5 SPIs cannot be retrieved because vgic_get_irq() explicitly rejects non-private GICv5 IRQs, rendering the newly activated IRS SPI configuration broken.
- [High] Allowing userspace to configure up to 65,536 GICv5 SPIs triggers a large contiguous memory allocation via kcalloc, which can deterministically fail and cause kernel warnings, resulting in a host-side DoS.
--
--- Patch [22]: [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV ---
commit d0f49503c9e90ab6ab9f71cb5ba22d59c564bcd8
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Register the IRS IODEV
Now that we have an emulated IRS, it needs to be registered, which ensures that
guest accesses to the MMIO regions handled by the device are handled
appropriately in KVM. As part of this change, expose setting the address of
the emulated IRS via KVM_VGIC_V5_ADDR_TYPE_IRS to userspace. Also allow
userspace to set the number of SPIs handled by the emulated GICv5
implementation.
> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> index 90be99443df3b..8c2c6886af4e3 100644
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
[ ... ]
> @@ -224,31 +232,48 @@ static int vgic_set_common_attr(struct kvm_device *dev,
> if (get_user(val, uaddr))
> return -EFAULT;
>
> - /*
> - * We require:
> - * - at least 32 SPIs on top of the 16 SGIs and 16 PPIs
> - * - at most 1024 interrupts
> - * - a multiple of 32 interrupts
> - */
> - if (val < (VGIC_NR_PRIVATE_IRQS + 32) ||
> - val > VGIC_MAX_RESERVED ||
> - (val & 31))
> - return -EINVAL;
> + if (!vgic_is_v5(dev->kvm)) {
[ ... ]
> + } else {
> + /*
> + * GICv5 reports a number of SPIs, not a total number of
> + * interrupts. Require a multiple of 32 SPIs.
> + */
> + if (val < VGIC_V5_DEFAULT_NR_SPIS ||
> + val > VGIC_V5_MAX_NR_SPIS ||
> + (val & 31))
> + return -EINVAL;
[Severity: High]
Does allowing userspace to set nr_spis up to VGIC_V5_MAX_NR_SPIS (65536) risk
triggering a host-side allocation warning or failure?
If userspace sets nr_spis to the maximum, KVM will eventually call kcalloc()
for the SPIs:
arch/arm64/kvm/vgic/vgic-irs-v5.c:kvm_vgic_v5_irs_init() {
...
dist->spis = kcalloc(nr_spis, sizeof(struct vgic_irq),
GFP_KERNEL_ACCOUNT);
...
}
Since sizeof(struct vgic_irq) is over 100 bytes, this requires a physically
contiguous allocation of roughly 7MB. This exceeds the typical KMALLOC_MAX_SIZE
of 4MB, which would cause the allocation to deterministically fail and
potentially trigger a kernel warning or panic (if panic_on_warn is set),
allowing an unprivileged user to cause a host-side denial of service.
> +
> + mutex_lock(&dev->kvm->arch.config_lock);
> + if (vgic_initialized(dev->kvm) || dev->kvm->arch.vgic.nr_spis)
> + ret = -EBUSY;
> + else
> + dev->kvm->arch.vgic.nr_spis = val;
[Severity: High]
Are we able to retrieve these GICv5 SPIs once they are configured here?
Currently, vgic_get_irq() explicitly rejects non-private GICv5 IRQs:
arch/arm64/kvm/vgic/vgic.c:vgic_get_irq() {
...
/* Non-private IRQs are not yet implemented for GICv5 */
if (vgic_is_v5(kvm))
return NULL;
...
}
This means that when the guest accesses the IRS MMIO interface for SPIs, the
handler (e.g., vgic_v5_mmio_write_irs_spi) will fail to retrieve the SPI
structure and silently abort. Will this leave GICv5 SPIs unconfigurable?
> + mutex_unlock(&dev->kvm->arch.config_lock);
> + }
>
> return ret;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=22
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (21 preceding siblings ...)
2026-07-24 10:54 ` [PATCH v4 22/48] KVM: arm64: gic-v5: Register the IRS IODEV Sascha Bischoff
@ 2026-07-24 10:54 ` Sascha Bischoff
2026-07-24 11:41 ` sashiko-bot
2026-07-24 10:54 ` [PATCH v4 24/48] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address Sascha Bischoff
` (15 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:54 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, fuad.tabba@linux.dev
The GICv5 ICH_CONTEXTR_EL2 has the IRICHPPIDIS field, which allows the
hypervisor to enable/disable the HPPI selection for SPIs and
LPIs. This can be used to emulate the guest enabling/disabling the
IRS. Therefore, make the state of this controlled by the IRS enable
state. Thus, SPIs and LPIs can't be delivered to the guest, until it
enables the emulated IRS, which matches the behaviour of the real
hardware.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-v5.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 4042d468431b1..7957b9da95e7c 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1066,6 +1066,7 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
+ bool irichppidis = !vcpu->kvm->arch.vgic.enabled;
struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
u16 vm = vgic_v5_vm_id(vcpu->kvm);
u16 vpe = vgic_v5_vpe_id(vcpu);
@@ -1082,6 +1083,7 @@ void vgic_v5_load(struct kvm_vcpu *vcpu)
kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_V, true) |
+ FIELD_PREP(ICH_CONTEXTR_EL2_IRICHPPIDIS, irichppidis) |
FIELD_PREP(ICH_CONTEXTR_EL2_VPE, vpe) |
FIELD_PREP(ICH_CONTEXTR_EL2_VM, vm);
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state
2026-07-24 10:54 ` [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
@ 2026-07-24 11:41 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:41 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, kvm, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Spurious VPE doorbells and infinite wakeup loops due to unpreserved IRICHPPIDIS state.
- [Low] Missing READ_ONCE() on vgic.enabled causes a data race.
--
--- Patch [23]: [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state ---
commit 40fbad6b3a2b1617d4bc4b16c3a6b176e7be5146
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state
The GICv5 ICH_CONTEXTR_EL2 has the IRICHPPIDIS field, which allows the
hypervisor to enable/disable the HPPI selection for SPIs and
LPIs. This can be used to emulate the guest enabling/disabling the
IRS. Therefore, make the state of this controlled by the IRS enable
state. Thus, SPIs and LPIs can't be delivered to the guest, until it
enables the emulated IRS, which matches the behaviour of the real
hardware.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 4042d468431b1..7957b9da95e7c 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -1066,6 +1066,7 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
>
> void vgic_v5_load(struct kvm_vcpu *vcpu)
> {
> + bool irichppidis = !vcpu->kvm->arch.vgic.enabled;
[Severity: Low]
Is a READ_ONCE() missing here?
The vgic.enabled flag is updated concurrently by the IRS MMIO handler using
WRITE_ONCE(), so reading it locklessly here might trigger KCSAN data race
warnings.
> struct vgic_v5_cpu_if *cpu_if = &vcpu->arch.vgic_cpu.vgic_v5;
> u16 vm = vgic_v5_vm_id(vcpu->kvm);
> u16 vpe = vgic_v5_vpe_id(vcpu);
> @@ -1082,6 +1083,7 @@ void vgic_v5_load(struct kvm_vcpu *vcpu)
> kvm_call_hyp(__vgic_v5_restore_vmcr_apr, cpu_if);
>
> cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_V, true) |
> + FIELD_PREP(ICH_CONTEXTR_EL2_IRICHPPIDIS, irichppidis) |
[Severity: High]
Will this IRICHPPIDIS state get lost when the VCPU enters WFI?
When the guest executes WFI, KVM calls vgic_v5_put(). It currently clears
vgic_contextr before setting the doorbell bits:
void vgic_v5_put(struct kvm_vcpu *vcpu)
{
...
cpu_if->vgic_contextr = 0;
if (vcpu_get_flag(vcpu, IN_WFI)) {
...
cpu_if->vgic_contextr = FIELD_PREP(ICH_CONTEXTR_EL2_DB, 1) |
FIELD_PREP(ICH_CONTEXTR_EL2_DBPM, dbpm);
...
}
If the guest disabled the IRS (so IRICHPPIDIS should be 1) and enters WFI
while an SPI or LPI is pending, the hardware will see IRICHPPIDIS=0 and DB=1.
Could this cause the hardware to inadvertently evaluate the interrupt and fire
the doorbell, waking the guest?
If the guest then resumes from WFI, sees no pending interrupts, and executes
WFI again, wouldn't this result in an infinite wakeup loop?
> FIELD_PREP(ICH_CONTEXTR_EL2_VPE, vpe) |
> FIELD_PREP(ICH_CONTEXTR_EL2_VM, vm);
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=23
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 24/48] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (22 preceding siblings ...)
2026-07-24 10:54 ` [PATCH v4 23/48] KVM: arm64: gic-v5: Set IRICHPPIDIS based on IRS enable state Sascha Bischoff
@ 2026-07-24 10:54 ` Sascha Bischoff
2026-07-24 10:54 ` [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
` (14 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:54 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, fuad.tabba@linux.dev
This selftest was added before the GICv5 IRS was supported in
KVM. Therefore, there was no address to set, and the specific UAPI
didn't even exist.
Now that the IRS is supported, and setting its address is mandatory
before VGIC resources are mapped, set the emulated IRS GPA before
initialising the VGIC. Running a GICv5 VM will fail if userspace has
not provided the IRS address before the first vCPU run.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
tools/testing/selftests/kvm/arm64/vgic_v5.c | 6 ++++++
tools/testing/selftests/kvm/include/arm64/gic_v5.h | 3 +++
2 files changed, 9 insertions(+)
diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
index 96cfd6bb32f6f..19039a8940568 100644
--- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
+++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
@@ -100,6 +100,7 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
struct ucall uc;
u64 user_ppis[2];
struct vm_gic v;
+ uint64_t attr;
int ret, i;
v.gic_dev_type = gic_dev_type;
@@ -116,6 +117,11 @@ static void test_vgic_v5_ppis(u32 gic_dev_type)
for (i = 0; i < NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
+ /* Set the address of the IRS before initialising the GIC */
+ attr = GICV5_IRS_CONFIG_BASE_GPA;
+ kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
+ KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
+
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
diff --git a/tools/testing/selftests/kvm/include/arm64/gic_v5.h b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
index eb523d9277cf1..c388df8f2a2b4 100644
--- a/tools/testing/selftests/kvm/include/arm64/gic_v5.h
+++ b/tools/testing/selftests/kvm/include/arm64/gic_v5.h
@@ -10,6 +10,9 @@
#include "processor.h"
+/* GIC component base address is guest PA space */
+#define GICV5_IRS_CONFIG_BASE_GPA 0x8000000ULL
+
/*
* Definitions for GICv5 instructions for the Current Domain
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (23 preceding siblings ...)
2026-07-24 10:54 ` [PATCH v4 24/48] KVM: arm64: selftests: Update vGICv5 selftest to set IRS address Sascha Bischoff
@ 2026-07-24 10:54 ` Sascha Bischoff
2026-07-24 11:34 ` sashiko-bot
2026-07-24 10:55 ` [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op Sascha Bischoff
` (13 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:54 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, fuad.tabba@linux.dev
With PPIs, their state is injected via the ICH_PPI_x_EL2 system
registers. For SPIs and LPIs, there are no such registers as these would
limit the number of interrupts significantly. Instead, SPI and LPI
pending state can be managed from the hypervisor using the GIC VDPEND
instruction. This provides a way to set an SPI or LPI for a VM as
pending or non-pending, i.e., to inject interrupts into a guest.
This system instruction is only executable from EL2, and therefore must
be wrapped in a hypercall for NVHE/hVHE configurations.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/include/asm/kvm_hyp.h | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 10 ++++++++++
arch/arm64/kvm/hyp/vgic-v5-sr.c | 10 ++++++++++
4 files changed, 22 insertions(+)
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index d9ff9c2999aa7..36808ed0e9afe 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -89,6 +89,7 @@ enum __kvm_host_smccc_func {
__KVM_HOST_SMCCC_FUNC___vgic_v3_restore_vmcr_aprs,
__KVM_HOST_SMCCC_FUNC___vgic_v5_make_resident,
__KVM_HOST_SMCCC_FUNC___vgic_v5_make_non_resident,
+ __KVM_HOST_SMCCC_FUNC___vgic_v5_vdpend,
__KVM_HOST_SMCCC_FUNC___vgic_v5_save_apr,
__KVM_HOST_SMCCC_FUNC___vgic_v5_restore_vmcr_apr,
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 8fcbbff719cce..8e2f0280c0cc5 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -97,6 +97,7 @@ void __vgic_v5_save_ppi_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_ppi_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_save_state(struct vgic_v5_cpu_if *cpu_if);
void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if);
+void __vgic_v5_vdpend(u32 intid, bool pending, u16 vm);
#ifdef __KVM_NVHE_HYPERVISOR__
void __timer_enable_traps(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 09756f71df08e..8f9cc3cd422aa 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -719,6 +719,15 @@ static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
__vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
}
+static void handle___vgic_v5_vdpend(struct kvm_cpu_context *host_ctxt)
+{
+ DECLARE_REG(u32, intid, host_ctxt, 1);
+ DECLARE_REG(bool, pending, host_ctxt, 2);
+ DECLARE_REG(u16, vm, host_ctxt, 3);
+
+ __vgic_v5_vdpend(intid, pending, vm);
+}
+
typedef void (*hcall_t)(struct kvm_cpu_context *);
#define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
@@ -754,6 +763,7 @@ static const hcall_t host_hcall[] = {
HANDLE_FUNC(__vgic_v3_restore_vmcr_aprs),
HANDLE_FUNC(__vgic_v5_make_resident),
HANDLE_FUNC(__vgic_v5_make_non_resident),
+ HANDLE_FUNC(__vgic_v5_vdpend),
HANDLE_FUNC(__vgic_v5_save_apr),
HANDLE_FUNC(__vgic_v5_restore_vmcr_apr),
diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
index 46992a6c2cacb..94746317ca79f 100644
--- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
+++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
@@ -149,3 +149,13 @@ void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if)
{
write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
}
+
+void __vgic_v5_vdpend(u32 intid, bool pending, u16 vm)
+{
+ u64 value;
+
+ value = intid & (GICV5_GIC_VDPEND_ID_MASK | GICV5_GIC_VDPEND_TYPE_MASK);
+ value |= FIELD_PREP(GICV5_GIC_VDPEND_PENDING_MASK, pending);
+ value |= FIELD_PREP(GICV5_GIC_VDPEND_VM_MASK, vm);
+ gic_insn(value, VDPEND);
+}
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call
2026-07-24 10:54 ` [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
@ 2026-07-24 11:34 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:34 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, kvm, Oliver Upton, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Missing validation of the host-provided VMID allows the untrusted host to arbitrarily inject interrupts into protected VMs.
--
--- Patch [25]: [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call ---
commit c4db8b94f24a778070a5db75c24059095bae959b
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add GIC VDPEND hyp call
This commit adds a hypercall to wrap the GIC VDPEND system instruction, allowing
the hypervisor to manage SPI and LPI pending states for VMs. This instruction is
executable only from EL2 and provides a way to inject interrupts into guests.
> diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> index 09756f71df08e..8f9cc3cd422aa 100644
> --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
> +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
[ ... ]
> @@ -719,6 +719,15 @@ static void handle___vgic_v5_restore_vmcr_apr(struct kvm_cpu_context *host_ctxt)
> __vgic_v5_restore_vmcr_apr(kern_hyp_va(cpu_if));
> }
>
> +static void handle___vgic_v5_vdpend(struct kvm_cpu_context *host_ctxt)
> +{
> + DECLARE_REG(u32, intid, host_ctxt, 1);
> + DECLARE_REG(bool, pending, host_ctxt, 2);
> + DECLARE_REG(u16, vm, host_ctxt, 3);
> +
> + __vgic_v5_vdpend(intid, pending, vm);
[Severity: Critical]
Does this code introduce a regression by allowing an untrusted host to
arbitrarily inject interrupts into protected VMs?
The hypercall handler extracts the vm argument from the untrusted host context
and acts on it without any validation against protected VM ownership.
Since this hypercall is directly reachable from the untrusted host under pKVM,
the host could supply the VMID of a protected guest in the vm parameter.
> +}
> +
> typedef void (*hcall_t)(struct kvm_cpu_context *);
>
> #define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
> diff --git a/arch/arm64/kvm/hyp/vgic-v5-sr.c b/arch/arm64/kvm/hyp/vgic-v5-sr.c
> index 46992a6c2cacb..94746317ca79f 100644
> --- a/arch/arm64/kvm/hyp/vgic-v5-sr.c
> +++ b/arch/arm64/kvm/hyp/vgic-v5-sr.c
[ ... ]
> @@ -149,3 +149,13 @@ void __vgic_v5_restore_state(struct vgic_v5_cpu_if *cpu_if)
> {
> write_sysreg_s(cpu_if->vgic_icsr, SYS_ICC_ICSR_EL1);
> }
> +
> +void __vgic_v5_vdpend(u32 intid, bool pending, u16 vm)
> +{
> + u64 value;
> +
> + value = intid & (GICV5_GIC_VDPEND_ID_MASK | GICV5_GIC_VDPEND_TYPE_MASK);
> + value |= FIELD_PREP(GICV5_GIC_VDPEND_PENDING_MASK, pending);
> + value |= FIELD_PREP(GICV5_GIC_VDPEND_VM_MASK, vm);
> + gic_insn(value, VDPEND);
[Severity: Critical]
Could the unvalidated vm parameter cause a regression by violating guest
integrity and isolation here?
The unvalidated vm parameter is packaged directly into the VDPEND system
instruction, modifying the interrupt state of the targeted VM.
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=25
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (24 preceding siblings ...)
2026-07-24 10:54 ` [PATCH v4 25/48] KVM: arm64: gic-v5: Add GIC VDPEND hyp call Sascha Bischoff
@ 2026-07-24 10:55 ` Sascha Bischoff
2026-07-24 11:39 ` sashiko-bot
2026-07-24 10:55 ` [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
` (12 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:55 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, fuad.tabba@linux.dev
There are times, such as with GICv5 SPIs and LPIs, where the hardware
itself manages parts of the interrupt lifecycle. This means that
pending state can be directly communicated to the hardware instead of
being represented only in the VGIC shadow state.
In order to accommodate cases where the hardware handles pending state
directly, add a new set_pending_state() function pointer to
irq_ops. The intent is for this to be used after the VGIC shadow
pending state has changed, allowing the backend to mirror the updated
state into hardware.
This new function is plumbed into kvm_vgic_inject_irq(), and is only
called if irq_ops are provided and this function pointer is explicitly
set. In the general case, this has no effect.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic.c | 3 +++
include/kvm/arm_vgic.h | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 47315c3fe120e..69e0965846aaf 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -564,6 +564,9 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
else
irq->pending_latch = true;
+ if (irq->ops && irq->ops->set_pending_state)
+ WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu, irq));
+
vgic_queue_irq_unlock(kvm, irq, flags);
vgic_put_irq(kvm, irq);
diff --git a/include/kvm/arm_vgic.h b/include/kvm/arm_vgic.h
index af926f9384b65..dc09ed1a2612c 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -251,6 +251,12 @@ struct irq_ops {
*/
bool (*get_input_level)(int vintid);
+ /*
+ * Function pointer to directly update hardware pending state after the
+ * VGIC shadow pending state has changed.
+ */
+ bool (*set_pending_state)(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
+
/*
* Function pointer to override the queuing of an IRQ.
*/
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op
2026-07-24 10:55 ` [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op Sascha Bischoff
@ 2026-07-24 11:39 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:39 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, Marc Zyngier, Oliver Upton, kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Incomplete state mirroring: hardware pending state desynchronizes from VGIC shadow state on guest MMIO/ITS writes.
- [Medium] Userspace-reachable WARN_ON_ONCE when set_pending_state fails, causing potential Denial of Service.
--
--- Patch [26]: [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op ---
commit f6a8b8f38eb780090a64d051bdb9d419dcf75e26
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic: Introduce set_pending_state() to irq_op
This patch introduces a set_pending_state() function pointer to irq_ops.
This allows the VGIC to communicate pending state directly to hardware for
cases where hardware manages parts of the interrupt lifecycle, like GICv5.
It wires this new callback into kvm_vgic_inject_irq().
> The intent is for this to be used after the VGIC shadow
> pending state has changed, allowing the backend to mirror the updated
> state into hardware.
>
> This new function is plumbed into kvm_vgic_inject_irq(), and is only
> called if irq_ops are provided and this function pointer is explicitly
> set.
[Severity: High]
If the intent is to mirror the state after the VGIC shadow pending state
has changed, should this also be wired up for guest MMIO writes and ITS
commands?
For example, when a guest writes to ISPENDR or ICPENDR, the shadow state is
updated in arch/arm64/kvm/vgic/vgic-mmio.c:__set_pending() and
__clear_pending(), but set_pending_state() is not called.
Similarly, in arch/arm64/kvm/vgic/vgic-its.c:vgic_its_cmd_handle_clear(),
the ITS CLEAR command updates the LPI pending state without notifying the
hardware.
Could this omission cause the hardware pending state to become desynchronized
from the VGIC shadow state, leading to missed or spurious interrupts?
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 169a8664322d0..023792c28fc5c 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -563,6 +563,9 @@ int kvm_vgic_inject_irq(struct kvm *kvm, struct kvm_vcpu *vcpu,
> else
> irq->pending_latch = true;
>
> + if (irq->ops && irq->ops->set_pending_state)
> + WARN_ON_ONCE(!irq->ops->set_pending_state(vcpu, irq));
[Severity: Medium]
Can a guest or unprivileged host-userspace trigger this warning?
Since kvm_vgic_inject_irq() is reachable via userspace injection
(e.g., KVM_IRQ_LINE) or guest actions, if an adversary can create
conditions that cause set_pending_state() to fail, they could trigger
this WARN_ON_ONCE. This would violate the KVM guideline against
user-reachable warnings.
Could this be replaced with a pr_warn_once() or handled by returning
an error code?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=26
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (25 preceding siblings ...)
2026-07-24 10:55 ` [PATCH v4 26/48] KVM: arm64: gic: Introduce set_pending_state() to irq_op Sascha Bischoff
@ 2026-07-24 10:55 ` Sascha Bischoff
2026-07-24 11:48 ` sashiko-bot
2026-07-24 10:55 ` [PATCH v4 28/48] Documentation: KVM: Extend VGICv5 device attribute docs Sascha Bischoff
` (11 subsequent siblings)
38 siblings, 1 reply; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:55 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, fuad.tabba@linux.dev
GICv5 SPI lifecycle is handled by the GICv5 hardware once the pending
state has been injected.
This change adds support for injecting SPIs to the core VGIC code and
GICv5 code. First of all, allow GICv5 SPIs to be looked up by ID via
vgic_get_irq(). Previously, only PPIs were supported.
Two irq_ops are used for GICv5 SPI injection. The set_pending_state()
irq_op is used to inject the SPI's pending state into the guest. The
queue_irq_unlock() irq_op releases the IRQ lock without queueing the SPI
on a per-vCPU AP list, as GICv5 does not track in-flight SPI state in
KVM. Tracking the affinity and lifecycle of individual SPIs in software
would negate much of the benefit of their lifecycle being hardware
managed.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-irs-v5.c | 1 +
arch/arm64/kvm/vgic/vgic-v5.c | 41 +++++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.c | 28 +++++++++++++++------
arch/arm64/kvm/vgic/vgic.h | 2 ++
4 files changed, 64 insertions(+), 8 deletions(-)
diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
index da8914dd682fc..7a068e7db250e 100644
--- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
@@ -671,6 +671,7 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis)
* view it is always enabled.
*/
irq->enabled = 1;
+ vgic_v5_set_spi_ops(irq);
}
nr_spi_bits = fls(roundup_pow_of_two(nr_spis)) - 1;
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index 7957b9da95e7c..04392b686c2e2 100644
--- a/arch/arm64/kvm/vgic/vgic-v5.c
+++ b/arch/arm64/kvm/vgic/vgic-v5.c
@@ -1064,6 +1064,47 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
VGIC_V5_NR_PRIVATE_IRQS);
}
+static bool vgic_v5_set_spi_pending_state(struct kvm_vcpu *vcpu,
+ struct vgic_irq *irq)
+{
+ vgic_v5_set_irq_pend(irq->target_vcpu, irq);
+ return true;
+}
+
+static bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
+ struct vgic_irq *irq,
+ unsigned long flags)
+ __releases(&irq->irq_lock)
+{
+ lockdep_assert_held(&irq->irq_lock);
+
+ raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
+ return true;
+}
+
+static const struct irq_ops vgic_v5_spi_irq_ops = {
+ .set_pending_state = vgic_v5_set_spi_pending_state,
+ .queue_irq_unlock = vgic_v5_spi_queue_irq_unlock,
+};
+
+void vgic_v5_set_spi_ops(struct vgic_irq *irq)
+{
+ if (WARN_ON(!irq) || WARN_ON(irq->ops))
+ return;
+
+ irq->ops = &vgic_v5_spi_irq_ops;
+}
+
+/* Set the pending state for GICv5 SPIs and LPIs */
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
+{
+ if (WARN_ON(__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
+ return;
+
+ kvm_call_hyp(__vgic_v5_vdpend, irq->intid, irq_is_pending(irq),
+ vcpu->kvm->arch.vgic.gicv5_vm.vm_id);
+}
+
void vgic_v5_load(struct kvm_vcpu *vcpu)
{
bool irichppidis = !vcpu->kvm->arch.vgic.enabled;
diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
index 69e0965846aaf..a27895295ecbb 100644
--- a/arch/arm64/kvm/vgic/vgic.c
+++ b/arch/arm64/kvm/vgic/vgic.c
@@ -86,19 +86,31 @@ 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;
+ enum kvm_device_type type = kvm->arch.vgic.vgic_model;
/* SPIs */
- if (intid >= VGIC_NR_PRIVATE_IRQS &&
- intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
- intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS);
- return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
+ if (__irq_is_spi(type, intid)) {
+ switch (type) {
+ case KVM_DEV_TYPE_ARM_VGIC_V5:
+ intid = vgic_v5_get_hwirq_id(intid);
+
+ if (intid >= kvm->arch.vgic.nr_spis)
+ return NULL;
+
+ intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis);
+ return &kvm->arch.vgic.spis[intid];
+ default: {
+ u32 max_intid = kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS;
+
+ if (intid < max_intid) {
+ intid = array_index_nospec(intid, max_intid);
+ return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
+ }
+ }}
}
/* LPIs */
- if (irq_is_lpi(kvm, intid))
+ if (__irq_is_lpi(type, intid))
return vgic_get_lpi(kvm, intid);
return NULL;
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 282278e4a6c19..107737c77e4d5 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -370,6 +370,8 @@ int kvm_vgic_v5_irs_init(struct kvm *kvm, unsigned int nr_spis);
void vgic_v5_teardown(struct kvm *kvm);
int vgic_v5_map_resources(struct kvm *kvm);
void vgic_v5_set_ppi_ops(struct kvm_vcpu *vcpu, u32 vintid);
+void vgic_v5_set_spi_ops(struct vgic_irq *irq);
+void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq);
bool vgic_v5_has_pending_ppi(struct kvm_vcpu *vcpu);
void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu);
void vgic_v5_fold_ppi_state(struct kvm_vcpu *vcpu);
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread* Re: [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection
2026-07-24 10:55 ` [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
@ 2026-07-24 11:48 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:48 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Marc Zyngier, kvmarm, Oliver Upton
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [Critical] `vgic_v5_set_irq_pend` dereferences `vcpu->kvm` without checking for NULL, causing a host kernel panic during SPI or LPI injection.
- [High] GICv5 SPIs silently break `KVM_IRQFD_FLAG_RESAMPLE` because KVM bypasses AP list queueing and never observes guest EOIs.
- [High] GICv5 SPI injection is broken for generic KVM callers because `vgic_get_irq` expects a GICv5-encoded INTID, but external callers pass raw INTIDs.
Pre-existing issues:
- [High] Pre-existing Spectre v1 vulnerability in `vgic_get_irq()` when handling GICv2/GICv3 SPIs. Speculative execution with `intid < VGIC_NR_PRIVATE_IRQS` bypasses bounds checks and leads to negative indexing on the `spis` array.
- [High] Invalid GICv5 PPI IDs silently alias to PPI 0 (SGI 0) in `vgic_get_vcpu_irq()` due to a missing architectural bounds check.
--
--- Patch [27]: [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection ---
commit 416f67edd52d537473f18e3ac4c5b905a4c03f05
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Support SPI injection
GICv5 SPI lifecycle is handled by the GICv5 hardware once the pending
state has been injected. This change adds support for injecting SPIs to
the core VGIC code and GICv5 code.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 7957b9da95e7c..04392b686c2e2 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
> @@ -1064,6 +1064,47 @@ void vgic_v5_flush_ppi_state(struct kvm_vcpu *vcpu)
[ ... ]
> +static bool vgic_v5_spi_queue_irq_unlock(struct kvm *kvm,
> + struct vgic_irq *irq,
> + unsigned long flags)
> + __releases(&irq->irq_lock)
> +{
> + lockdep_assert_held(&irq->irq_lock);
> +
> + raw_spin_unlock_irqrestore(&irq->irq_lock, flags);
> + return true;
> +}
[Severity: High]
Does skipping the AP list queueing here break KVM_IRQFD_FLAG_RESAMPLE?
Since this function overrides the default queueing logic and prevents the SPI
from being added to the vCPU's AP list, the lifecycle becomes fully hardware
managed.
Because KVM won't fold the SPI state back, it seems we'll never observe guest
EOIs, meaning kvm_notify_acked_irq() won't be called to trigger the resample
eventfd.
Could this lead to silently hanging devices if userspace (like QEMU virtio-mmio
or VFIO) attaches an eventfd using the resample flag?
[ ... ]
> +/* Set the pending state for GICv5 SPIs and LPIs */
> +void vgic_v5_set_irq_pend(struct kvm_vcpu *vcpu, struct vgic_irq *irq)
> +{
> + if (WARN_ON(__irq_is_ppi(KVM_DEV_TYPE_ARM_VGIC_V5, irq->intid)))
> + return;
> +
> + kvm_call_hyp(__vgic_v5_vdpend, irq->intid, irq_is_pending(irq),
> + vcpu->kvm->arch.vgic.gicv5_vm.vm_id);
> +}
[Severity: Critical]
Is it possible for vcpu to be NULL here?
If userspace configures the IRS and injects an SPI or unmapped LPI (e.g., via
KVM_IRQ_LINE) before any vCPUs are created, irq->target_vcpu could be NULL.
This would unconditionally dereference vcpu->kvm, potentially causing a host
kernel panic.
Should we add a NULL check for vcpu before dereferencing it?
> diff --git a/arch/arm64/kvm/vgic/vgic.c b/arch/arm64/kvm/vgic/vgic.c
> index 023792c28fc5c..d571fafb07f32 100644
> --- a/arch/arm64/kvm/vgic/vgic.c
> +++ b/arch/arm64/kvm/vgic/vgic.c
> @@ -86,19 +86,31 @@ 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;
> + enum kvm_device_type type = kvm->arch.vgic.vgic_model;
>
> /* SPIs */
> - if (intid >= VGIC_NR_PRIVATE_IRQS &&
> - intid < (kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS)) {
> - intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS);
> - return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
> + if (__irq_is_spi(type, intid)) {
[Severity: High]
Does this break SPI injection for generic KVM callers on GICv5?
The __irq_is_spi() helper expects a GICv5-encoded INTID when type is
KVM_DEV_TYPE_ARM_VGIC_V5.
However, standard in-kernel device emulations (like the PMU) and userspace
injections via KVM_IRQ_LINE or irqfd pass raw integer INTIDs (e.g., 32).
Without the GICv5 type bits, __irq_is_spi() will return false, causing the
injection to abort with -EINVAL.
> + switch (type) {
> + case KVM_DEV_TYPE_ARM_VGIC_V5:
> + intid = vgic_v5_get_hwirq_id(intid);
> +
> + if (intid >= kvm->arch.vgic.nr_spis)
> + return NULL;
> +
> + intid = array_index_nospec(intid, kvm->arch.vgic.nr_spis);
> + return &kvm->arch.vgic.spis[intid];
> + default: {
> + u32 max_intid = kvm->arch.vgic.nr_spis + VGIC_NR_PRIVATE_IRQS;
> +
> + if (intid < max_intid) {
> + intid = array_index_nospec(intid, max_intid);
> + return &kvm->arch.vgic.spis[intid - VGIC_NR_PRIVATE_IRQS];
> + }
[Severity: High]
This is a pre-existing issue, but does this speculative bounds checking logic
have a Spectre v1 vulnerability?
The array_index_nospec() clamp is applied to intid before subtracting
VGIC_NR_PRIVATE_IRQS (32).
If intid is speculatively less than 32, the check (intid < max_intid) will
speculatively evaluate to true, and array_index_nospec will preserve the
value.
Subtracting 32 then results in a negative index, allowing speculative
out-of-bounds reads on the spis array.
> + }}
> }
[Severity: High]
This isn't a bug introduced by this patch, but looking at vgic_get_vcpu_irq()
just below this function, it appears there is a missing bounds check for GICv5
PPI IDs:
struct vgic_irq *vgic_get_vcpu_irq(struct kvm_vcpu *vcpu, u32 intid)
{
...
if (__irq_is_sgi(type, intid) || __irq_is_ppi(type, intid)) {
switch (type) {
case KVM_DEV_TYPE_ARM_VGIC_V5:
intid = vgic_v5_get_hwirq_id(intid);
intid = array_index_nospec(intid, VGIC_V5_NR_PRIVATE_IRQS);
break;
Unlike the SPI path, this extracts the hardware ID and directly applies
array_index_nospec() without an explicit bounds check (e.g. checking if
intid >= VGIC_V5_NR_PRIVATE_IRQS).
Since __irq_is_ppi() only checks the GICv5 type bits and not the ID range, an
invalid out-of-bounds PPI ID will silently alias to index 0.
Can this lead to silent corruption of SGI 0 state?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=27
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v4 28/48] Documentation: KVM: Extend VGICv5 device attribute docs
2026-07-24 10:48 [PATCH v4 00/48] KVM: arm64: Add GICv5 IRS support Sascha Bischoff
` (26 preceding siblings ...)
2026-07-24 10:55 ` [PATCH v4 27/48] KVM: arm64: gic-v5: Support SPI injection Sascha Bischoff
@ 2026-07-24 10:55 ` Sascha Bischoff
[not found] ` <20260724104819.1296803-35-sascha.bischoff@arm.com>
` (10 subsequent siblings)
38 siblings, 0 replies; 60+ messages in thread
From: Sascha Bischoff @ 2026-07-24 10:55 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, fuad.tabba@linux.dev
Document the GICv5 IRS address attribute and its GPA layout
requirements. The IRS region must be 64K aligned and covers a 128K
range.
Also document the GICv5-specific interpretation of
KVM_DEV_ARM_VGIC_GRP_NR_IRQS. Unlike GICv2/v3, the value describes
only the number of SPIs exposed by the IRS and does not include SGIs
or PPIs. The supported range starts at 32 SPIs, is configured in
multiples of 32, and defaults to 32 when userspace leaves it unset.
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
.../virt/kvm/devices/arm-vgic-v5.rst | 35 ++++++++++++++++---
1 file changed, 31 insertions(+), 4 deletions(-)
diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
index 70b9162755c7e..6dc02ca8f6fea 100644
--- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
+++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
@@ -12,12 +12,39 @@ 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 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.
+Creating a guest GICv5 device requires a GICv5 host. The VGICv5 device supports
+PPI, SPI, and LPI interrupts. The PPI and SPI interrupts can either be injected
+from emulated in-kernel devices (such as the Arch Timer, or PMU), or via the
+KVM_IRQ_LINE ioctl. LPIs are not externally injected, but are handled in
+hardware via the LPI IST. Their pending state is driven directly by the guest.
Groups:
+ KVM_DEV_ARM_VGIC_GRP_ADDR
+ Attributes:
+
+ KVM_VGIC_V5_ADDR_TYPE_IRS (rw, 64-bit)
+ Base address in the guest physical address space of the GICv5 IRS
+ (Interrupt Routing Service) register mappings. Only valid for
+ KVM_DEV_TYPE_ARM_VGIC_V5. This address needs to be 64K aligned and the
+ region covers 128 KByte - the IRS has a CONFIG_FRAME and a SETLPI_FRAME,
+ each of which is 64 KBytes in size.
+
+ Setting the address of the IRS in GPA space is mandatory before VGIC
+ resources are mapped, as the IRS is responsible for handling SPIs and
+ LPIs. Failure to set the IRS address before the first vCPU run results in
+ an error.
+
+ KVM_DEV_ARM_VGIC_GRP_NR_IRQS
+ Attributes:
+
+ A value describing the number of SPIs for this GIC instance. This is
+ GICv5-specific: unlike GICv2/v3, the value does not include SGIs or PPIs.
+ The value ranges from 32 to KVM's VGICv5 maximum of 65536 SPIs, in
+ increments of 32. If userspace does not set this attribute, KVM uses 32
+ SPIs by default.
+
+ kvm_device_attr.addr points to a __u32 value.
+
KVM_DEV_ARM_VGIC_GRP_CTRL
Attributes:
--
2.34.1
^ permalink raw reply related [flat|nested] 60+ messages in thread[parent not found: <20260724104819.1296803-35-sascha.bischoff@arm.com>]
* Re: [PATCH v4 34/48] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI
[not found] ` <20260724104819.1296803-35-sascha.bischoff@arm.com>
@ 2026-07-24 11:46 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:46 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, Marc Zyngier, kvmarm, kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The KVM implementation for the newly added VGICv5 IST attribute group is entirely missing.
- [Low] The ARM-specific `struct kvm_vgic_v5_ist` is added to the cross-architecture `include/uapi/linux/kvm.h` instead of the architecture-specific `arch/arm64/include/uapi/asm/kvm.h`, polluting the global UAPI namespace and causing a tools header desync.
- [Low] The new `KVM_DEV_ARM_VGIC_GRP_IST` device attribute group and its descriptor structure are completely undocumented.
--
--- Patch [34]: [PATCH v4 34/48] KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI ---
commit 18a475f214feec673055a712a5b6d844aa9edb1a
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add VGICv5 IST save/restore UAPI
Add the VGICv5 IST device attribute group used by userspace to save
and restore the IRS Interrupt State Tables. It introduces the
kvm_vgic_v5_ist descriptor structure for SPI and LPI IST buffers.
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 710a0d267347d..1b9bbeab18a4e 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -423,6 +423,7 @@ enum {
> #define KVM_DEV_ARM_VGIC_GRP_ITS_REGS 8
> #define KVM_DEV_ARM_VGIC_GRP_MAINT_IRQ 9
> #define KVM_DEV_ARM_VGIC_GRP_IRS_REGS 10
> +#define KVM_DEV_ARM_VGIC_GRP_IST 11
[Severity: Medium]
The commit message states this attribute group is used by userspace to save
and restore the IRS Interrupt State Tables. Is the backend implementation for
this new UAPI intentionally omitted from this patch?
Without the corresponding KVM VGIC device ops in the codebase to parse the
attribute, this UAPI currently appears unusable.
[Severity: Low]
Is there a missing documentation update for this new guest-visible feature?
Kernel guidelines typically expect new UAPI additions to be explicitly
documented. This patch omits updating the KVM device documentation to explain
the usage semantics, expected alignment, and lifecycle constraints for the
VGICv5 IST attribute.
> #define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT 10
> #define KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_MASK \
> (0x3fffffULL << KVM_DEV_ARM_VGIC_LINE_LEVEL_INFO_SHIFT)
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index 419011097fa8e..df6356fb45b08 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -1191,6 +1191,13 @@ struct kvm_device_attr {
> __u64 addr; /* userspace address of attr data */
> };
>
> +struct kvm_vgic_v5_ist {
[Severity: Low]
Should this ARM-specific structure be placed in the architecture-specific
header arch/arm64/include/uapi/asm/kvm.h instead of the global
cross-architecture include/uapi/linux/kvm.h?
Additionally, while the patch updates the tools copy of the arm64 UAPI
header, it appears to miss syncing the tools copy of the global header
(tools/include/uapi/linux/kvm.h). Does this omission lead to a tools header
desync warning?
> + __u64 spi_ist_addr;
> + __u64 spi_ist_size;
> + __u64 lpi_ist_addr;
> + __u64 lpi_ist_size;
> +};
> +
> #define KVM_DEV_VFIO_FILE 1
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=34
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-31-sascha.bischoff@arm.com>]
* Re: [PATCH v4 30/48] KVM: arm64: gic-v5: Mask per-vcpu PPI state in vgic_v5_finalize_ppi_state()
[not found] ` <20260724104819.1296803-31-sascha.bischoff@arm.com>
@ 2026-07-24 11:52 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 11:52 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Marc Zyngier, Oliver Upton, kvmarm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Restored active software-managed PPIs are not queued to the ap_list, leading to lost interrupts on VM restore.
Pre-existing issues:
- [High] Data race on the global `vgic_ppi_mask` when `SW_PPI` is not implemented in hardware, leading to guest state corruption.
--
--- Patch [30]: [PATCH v4 30/48] KVM: arm64: gic-v5: Mask per-vcpu PPI state in vgic_v5_finalize_ppi_state() ---
commit 04a9407ed3605cc393d7dce45456f17c9e1fc2c3
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Mask per-vcpu PPI state in vgic_v5_finalize_ppi_state()
This commit reworks vgic_v5_finalize_ppi_state() to discover exposed PPIs and
mask non-exposed PPI state when VCPUs are first run. It clears state for
non-exposed PPIs to ensure only the intended state is exposed to the guest
after userspace restores the state.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 04392b686c2e2..6af1264ed2371 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -795,35 +796,65 @@ int vgic_v5_finalize_ppi_state(struct kvm *kvm)
> guard(mutex)(&kvm->arch.config_lock);
>
> /*
> - * If SW_PPI has been advertised, then we know we already
> - * initialised the whole thing, and we can return early. Yes,
> - * this is pretty hackish as far as state tracking goes...
> + * Discover the set of PPIs that are exposed to the guest once per VM.
> + * Once known, apply that mask to each VCPU's restored PPI state as the
> + * VCPUs are first run.
> */
> - if (test_bit(GICV5_ARCH_PPI_SW_PPI, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask))
> - return 0;
> -
> - /* The PPI state for all VCPUs should be the same. Pick the first. */
> - vcpu0 = kvm_get_vcpu(kvm, 0);
> + if (!test_bit(GICV5_ARCH_PPI_SW_PPI, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask)) {
> + bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_mask,
> + VGIC_V5_NR_PRIVATE_IRQS);
> + bitmap_zero(kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr,
> + VGIC_V5_NR_PRIVATE_IRQS);
[Severity: High]
This is a pre-existing issue, but does this conditional in
vgic_v5_finalize_ppi_state() create a data race on the global vgic_ppi_mask
if the SW_PPI feature is not implemented in hardware?
In a multi-VCPU VM booting on GICv5 hardware without the optional SW_PPI
feature, when a subsequent VCPU runs for the first time, this test will
evaluate to false because the bit was never set.
This seems to cause the global mask to be cleared via bitmap_zero() while
earlier VCPUs might already be running in the guest. Could this lead to a
transiently zeroed vgic_ppi_mask during sysreg accesses and silently dropped
interrupts for emulated devices like timers?
> +
> + for_each_set_bit(i, ppi_caps.impl_ppi_mask, VGIC_V5_NR_PRIVATE_IRQS) {
[ ... ]
> + /* Also update the vgic_irqs */
> + for (i = 0; i < VGIC_V5_NR_PRIVATE_IRQS; i++) {
> + bool visible = test_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask);
> const u32 intid = vgic_v5_make_ppi(i);
> struct vgic_irq *irq;
>
> - irq = vgic_get_vcpu_irq(vcpu0, intid);
> + irq = vgic_get_vcpu_irq(vcpu, intid);
>
> - /* Expose PPIs with an owner or the SW_PPI, only */
> scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
> - if (irq->owner || i == GICV5_ARCH_PPI_SW_PPI) {
> - __set_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_mask);
> - __assign_bit(i, kvm->arch.vgic.gicv5_vm.vgic_ppi_hmr,
> - irq->config == VGIC_CONFIG_LEVEL);
> + if (!visible) {
> + irq->enabled = false;
> + irq->active = false;
> + irq->pending_latch = false;
> + irq->line_level = false;
> + } else {
> + irq->enabled = test_bit(i, cpu_if->vgic_ppi_enabler);
> + irq->active = test_bit(i, cpu_if->vgic_ppi_activer);
> }
> }
[Severity: High]
Is it safe to directly set irq->active here without queueing the interrupt?
When restoring a VM with active software-managed PPIs (like the PMU or timers),
we mutate irq->active inside the lock in vgic_v5_finalize_ppi_state(), but we
don't seem to invoke the corresponding queueing machinery.
Without enqueuing the IRQ onto the VCPU's ap_list, won't this cause the
interrupt to be invisible to the injection path and get lost on VM restore?
>
> - vgic_put_irq(vcpu0->kvm, irq);
> + vgic_put_irq(kvm, irq);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=30
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-36-sascha.bischoff@arm.com>]
* Re: [PATCH v4 35/48] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs
[not found] ` <20260724104819.1296803-36-sascha.bischoff@arm.com>
@ 2026-07-24 12:02 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:02 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvm, Oliver Upton, kvmarm
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Retrieving `vcpu0` without verifying it exists leads to a NULL pointer dereference.
- [Critical] Inverted lock check with `kvm_trylock_all_vcpus` leaks vCPU locks and runs lockless.
- [High] Incomplete cleanup on IST restore failure leaves a dangling pointer in `vmi->h_lpi_ist`, leading to a double free.
- [High] Missing validation of LPI INTIDs during IST restore allows userspace to inject invalid INTIDs.
- [High] SPI IST restore incorrectly uses the array index directly as the absolute INTID, causing wrong interrupts to be injected.
- [High] Quiescence check masks lost quiescence state and causes silent data corruption.
- [Medium] KVM_DEV_ARM_VGIC_GRP_IST unconditionally fails for VMs configured with 0 SPIs, breaking live migration.
- [Medium] The failure path in `vgic_v5_irs_restore_ists` incorrectly issues a redundant `VMTE_MAKE_INVALID` command, risking a memory leak.
--
--- Patch [35]: [PATCH v4 35/48] KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs ---
commit 5c47453f1884c74d1478029039189ee4ba68421a
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
Subject: KVM: arm64: gic-v5: Implement save/restore mechanisms for ISTs
This patch introduces save and restore mechanisms for GICv5 Interrupt State
Tables (ISTs). It provides userspace migration storage for SPI and LPI
buffers via KVM_DEV_ARM_VGIC_GRP_IST and ensures the VM is quiesced and
validated during operations.
> diff --git a/arch/arm64/kvm/vgic/vgic-v5-tables.c b/arch/arm64/kvm/vgic/vgic-v5-tables.c
> index 718410e8c9f3b..f220267266c29 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5-tables.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5-tables.c
[ ... ]
> @@ -1630,6 +1630,28 @@ static int vgic_v5_restore_linear_ist(struct kvm *kvm,
> const struct vgic_v5_ist_desc *ist,
> u32 __user *uaddr, size_t nr_entries,
> u32 intid_type)
> {
> __le32 h_iste;
> size_t index;
> int ret;
>
> for (index = 0; index < nr_entries; index++) {
> void *h_iste_addr = ist->base + index * ist->iste_size;
>
> ret = get_user(h_iste, uaddr);
> if (ret)
> return ret;
>
> ret = vgic_v5_restore_ist_entry(kvm, ist, h_iste_addr,
> h_iste, index, intid_type);
[Severity: High]
Should the SPI base offset be added to the index here?
When restoring SPIs, passing the raw array index as the intid will track and
inject interrupts starting at 0. Since hardware SPIs start at INTID 32, does
this incorrectly inject SGIs and PPIs instead of the intended SPIs?
> if (ret)
> return ret;
>
> uaddr++;
> }
>
> return 0;
> }
[ ... ]
> @@ -1662,6 +1684,41 @@ static int vgic_v5_restore_two_level_ist(struct kvm *kvm,
> const struct vgic_v5_ist_desc *ist,
> u32 __user *uaddr, u32 intid_type)
> {
[ ... ]
> for (h_l2_index = 0; h_l2_index < shape.l2_entries; h_l2_index++) {
> void *h_iste_addr = h_l2_ist_base +
> h_l2_index * ist->iste_size;
> u32 intid = h_l1_index * shape.l2_entries + h_l2_index;
>
> ret = get_user(h_iste, uaddr);
> if (ret)
> return ret;
>
> ret = vgic_v5_restore_ist_entry(kvm, ist, h_iste_addr,
> h_iste, intid,
> intid_type);
[Severity: High]
Is there any validation missing for the generated LPI INTIDs?
The iteration calculates intid starting from 0. If userspace sets pending
bits for these lower indices, KVM would track them and inject them via the
VDPEND instruction at EL2.
Since valid LPI INTIDs must be >= 8192, could injecting lower IDs result in
architecturally unpredictable hardware behavior?
> if (ret)
> return ret;
>
> uaddr++;
> }
> }
>
> return 0;
> }
> diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
> index 6af1264ed2371..153a8fcd4db7c 100644
> --- a/arch/arm64/kvm/vgic/vgic-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-v5.c
[ ... ]
> @@ -1301,6 +1301,292 @@ void vgic_v5_save_state(struct kvm_vcpu *vcpu)
> __vgic_v5_save_ppi_state(cpu_if);
> dsb(sy);
> }
> +
> +static int vgic_v5_irs_status_is_quiesced(u32 statusr)
> +{
> + if (statusr & GICV5_IRS_SAVE_VM_STATUSR_Q)
> + return 0;
> +
> + return -EBUSY;
> +}
> +
> +static int vgic_v5_irs_is_quiesced(u16 vm_id)
> +{
> + u32 statusr;
> + int ret;
> +
> + ret = vgic_v5_irs_save_vm_op(vm_id, false, &statusr);
[Severity: High]
Does this quiescence check potentially lose hardware state?
By calling vgic_v5_irs_save_vm_op() with save=false, it issues a SAVE_VMR
command with Q=1 and S=0 (Quiesce but don't save).
If the VM had unexpectedly unquiesced (e.g., interrupted by hardware),
forcing it to quiesce without saving would destroy its modified internal
state before it can be exported to userspace.
> + if (ret)
> + return ret;
> +
> + return vgic_v5_irs_status_is_quiesced(statusr);
> +}
> +
> +static int vgic_v5_copy_ist_attr(struct kvm_device_attr *attr,
> + struct kvm_vgic_v5_ist *ist_attr)
> +{
> + void __user *uaddr = (void __user *)(unsigned long)attr->addr;
> +
> + if (!uaddr)
> + return -EINVAL;
> +
> + if (copy_from_user(ist_attr, uaddr, sizeof(*ist_attr)))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +static int vgic_v5_validate_ist_user_buffer(__u64 addr, __u64 size,
> + size_t expected)
> +{
> + if (!addr || size != expected)
> + return -EINVAL;
[Severity: Medium]
Is there an issue here when restoring a VM configured with 0 SPIs?
If userspace passes a size of 0 and an address of 0, the check if (!addr)
will reject it with -EINVAL.
If userspace passes a dummy non-zero address to bypass this, it fails later
in vgic_v5_get_spi_ist_desc():
arch/arm64/kvm/vgic/vgic-v5-tables.c:vgic_v5_get_spi_ist_desc() {
...
if (!ist->base && userspace_buf)
return -ENOENT;
...
}
Does this catch-22 prevent live migration for VMs without SPIs?
> +
> + return 0;
> +}
[ ... ]
> +int vgic_v5_irs_save_ists(struct kvm *kvm, struct kvm_device_attr *attr)
> +{
> + struct kvm_vgic_v5_ist ist_attr;
> + u16 vm_id = vgic_v5_vm_id(kvm);
> + u32 statusr;
> + int ret = 0;
> +
> + mutex_lock(&kvm->lock);
> +
> + if (kvm_trylock_all_vcpus(kvm)) {
> + mutex_unlock(&kvm->lock);
> + return -EBUSY;
> + }
[Severity: Critical]
Is the return value of kvm_trylock_all_vcpus() handled correctly here?
The function returns true on successful lock acquisition and false on
failure. Returning -EBUSY when it is true means we leak the vCPU locks
and hang the VM.
Conversely, failing to acquire the locks lets us proceed lockless, and then
unlocking unowned mutexes in the out_unlock path would cause issues.
> +
> + mutex_lock(&kvm->arch.config_lock);
[ ... ]
> +static void vgic_v5_restore_cleanup(struct kvm *kvm,
> + struct kvm_vcpu *vcpu,
> + bool lpi_ist_allocated)
> +{
> + /*
> + * We are on the restore failure path, so we do a best-effort
> + * cleanup. These commands might fail, but at this stage this is the
> + * best we can realistically do.
> + */
> + if (lpi_ist_allocated) {
> + if (!vgic_v5_send_command(vcpu, VMTE_MAKE_INVALID))
> + vgic_v5_lpi_ist_free(kvm);
> + }
[Severity: High]
Could this leave a dangling pointer behind?
When vgic_v5_lpi_ist_free() is called on a failure path, it frees the memory
but does not set vmi->h_lpi_ist to NULL. If a subsequent teardown happens in
vgic_v5_vmte_release(), it might observe the populated pointer and attempt
to free it again, causing a double-free.
[Severity: Medium]
Are we risking a memory leak by issuing a redundant invalidate command?
If we reach the failure path from a point where the VMTE is already invalid,
this function will issue VMTE_MAKE_INVALID again because the vmte_invalid
state is not passed in. If the hardware driver returns an error when asked
to invalidate an already-invalid VMTE, vgic_v5_send_command() will fail.
This would skip the vgic_v5_lpi_ist_free() call and permanently leak the LPI
IST memory.
> +
> + vgic_v5_send_command(vcpu, VMTE_MAKE_VALID);
> +}
> +
> +int vgic_v5_irs_restore_ists(struct kvm *kvm, struct kvm_device_attr *attr)
> +{
> + bool lpi_ist_allocated = false, vmte_invalid = false;
> + struct kvm_vcpu *vcpu0 = kvm_get_vcpu(kvm, 0);
[Severity: Critical]
What happens if userspace invokes this ioctl before creating any vCPUs?
kvm_get_vcpu(kvm, 0) would return NULL. Later, this NULL vcpu0 is passed to
vgic_v5_send_command(), which dereferences it inside vgic_v5_vpe_db().
Should we verify vcpu0 exists before using it to prevent a potential NULL
pointer dereference?
> + struct kvm_vgic_v5_ist ist_attr;
> + int ret = 0;
> +
> + mutex_lock(&kvm->lock);
> +
> + if (kvm_trylock_all_vcpus(kvm)) {
> + mutex_unlock(&kvm->lock);
> + return -EBUSY;
> + }
[Severity: Critical]
As noted in vgic_v5_irs_save_ists(), does this correctly handle the boolean
return value of kvm_trylock_all_vcpus()? It looks like this behaves in the
exact opposite way as intended, leaking locks on success and running
lockless on failure.
> +
> + mutex_lock(&kvm->arch.config_lock);
> +
> + if (!vgic_initialized(kvm)) {
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=35
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-42-sascha.bischoff@arm.com>]
* Re: [PATCH v4 41/48] KVM: selftests: Add VGICv5 NR_IRQS attribute tests
[not found] ` <20260724104819.1296803-42-sascha.bischoff@arm.com>
@ 2026-07-24 12:02 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:02 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvmarm, Marc Zyngier, Oliver Upton, kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] The return value of `kvm_has_device_attr()` is silently ignored, rendering the attribute check ineffective.
- [Medium] The test for exceeding the maximum supported SPI count uses an unaligned value, failing to correctly verify the bounds check.
- [Medium] The test meant to verify that `NR_IRQS` is immutable after the VM has run is defeated by an explicit `CTRL_INIT` call.
--
--- Patch [41]: [PATCH v4 41/48] KVM: selftests: Add VGICv5 NR_IRQS attribute tests ---
commit 4c2364c5e2833c1962977d2e2805cafe763b427d
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: selftests: Add VGICv5 NR_IRQS attribute tests
Add coverage for the VGICv5 NR_IRQS attribute. VGICv5 exposes a
userspace-selectable SPI count, and KVM must reject values that cannot
be represented by the virtual IRS or that would change the interrupt
layout after it has been fixed.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> index 51ea2fe7141ad..95d74f501ef09 100644
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> @@ -125,60 +183,106 @@ static void test_vgic_v5_create(void)
> vm_gic_destroy(&v);
> }
>
> -static void guest_irq_handler(struct ex_regs *regs)
> +static void test_vgic_v5_nr_irqs_attrs(void)
> {
> - 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);
> + struct kvm_vcpu *vcpu;
> + struct vm_gic v;
> + uint64_t attr;
> + u32 nr_irqs;
> + int ret;
>
> - if (!valid)
> - return;
> + v.gic_dev_type = KVM_DEV_TYPE_ARM_VGIC_V5;
> + v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
> + v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
>
> - gsb_ack();
> - isb();
> + /* Check existing group/attribute */
> + kvm_has_device_attr(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS, 0);
[Severity: Medium]
Is the return value of kvm_has_device_attr() intentionally ignored here?
If the attribute is missing, the test will proceed and fail on subsequent
operations rather than cleanly skipping or reporting the missing attribute.
Does this check need to be wrapped in a TEST_REQUIRE() or TEST_ASSERT()?
> +
> + /* Before userspace sets NR_IRQS, no SPI count has been selected. */
> + nr_irqs = 0xbad;
[ ... ]
> + /* Not a multiple of 32 */
> + nr_irqs = VGIC_V5_DEFAULT_NR_SPIS + 1;
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
> + 0, &nr_irqs);
> + TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS not 32-aligned");
> +
> + /* Larger than KVM's supported VGICv5 SPI count */
> + nr_irqs = VGIC_V5_MAX_NR_SPIS + 1;
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
> + 0, &nr_irqs);
> + TEST_ASSERT(ret && errno == EINVAL, "GICv5 NR_IRQS above maximum");
[Severity: Medium]
Will this check actually verify the maximum limit?
Since VGIC_V5_MAX_NR_SPIS (65536) + 1 is 65537, which is not a multiple of
32, the kernel will reject it with EINVAL due to the alignment constraint
before it reaches the maximum SPI limit check.
Should this test use VGIC_V5_MAX_NR_SPIS + 32 to ensure we test the upper
bound check rather than triggering the alignment check again?
> +
> + /* Valid custom SPI count */
> + nr_irqs = VGIC_V5_DEFAULT_NR_SPIS * 2;
[ ... ]
> + /* NR_IRQS must remain immutable after the VM has run. */
> + v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
> + v.gic_fd = kvm_create_device(v.vm, v.gic_dev_type);
> + vcpu = vm_vcpu_add(v.vm, 0, guest_code);
>
> - gic_insn(hwirq, CDDI);
> - gic_insn(0, CDEOI);
> + attr = GICV5_IRS_CONFIG_BASE_GPA;
> + kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_ADDR,
> + KVM_VGIC_V5_ADDR_TYPE_IRS, &attr);
> + kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
> + KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
>
> - GUEST_SYNC(GUEST_CMD_IRQ_DIEOI);
> + ret = run_vcpu(vcpu);
> + TEST_ASSERT(!ret, "Failed to run GICv5 VM before NR_IRQS test");
[Severity: Medium]
Does this explicit KVM_DEV_ARM_VGIC_CTRL_INIT call defeat the purpose of
testing if the attribute is immutable after the VM has run?
The previous test block already verifies that an explicit INIT locks the
NR_IRQS attribute. By calling it here before run_vcpu(), the subsequent
__kvm_device_attr_set() will return EBUSY solely because of this explicit
INIT, masking whether KVM_RUN actually implicitly locks the attribute as
intended.
>
> - if (++count >= 2)
> - GUEST_DONE();
> + nr_irqs = VGIC_V5_DEFAULT_NR_SPIS * 2;
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_NR_IRQS,
> + 0, &nr_irqs);
> + TEST_ASSERT(ret && errno == EBUSY, "GICv5 NR_IRQS set after run");
>
> - /* Ask for the next interrupt to be injected */
> - GUEST_SYNC(GUEST_CMD_IS_READY);
> -}
> + vm_gic_destroy(&v);
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=41
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-33-sascha.bischoff@arm.com>]
* Re: [PATCH v4 32/48] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region
[not found] ` <20260724104819.1296803-33-sascha.bischoff@arm.com>
@ 2026-07-24 12:05 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:05 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, kvmarm, Marc Zyngier, kvm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Userspace writes to `GICV5_IRS_SPI_CFGR` are silently ignored without validation, breaking restoration of SPI configurations across live migrations.
- [Medium] The 64-bit registers GICV5_IRS_IST_BASER and GICV5_IRS_SPI_VMR lack support for 32-bit guest MMIO accesses, violating the GIC architecture.
- [Medium] KVM_HAS_DEVICE_ATTR for KVM_DEV_ARM_VGIC_GRP_IRS_REGS incorrectly fails with -EINVAL if VCPU 0 is not yet created, breaking feature enumeration.
--
--- Patch [32]: [PATCH v4 32/48] KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region ---
commit 9b79670b7941a4c70c01c7a66f84cbfecafe7755
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Handle userspace accesses to IRS MMIO region
As part of saving and restoring state of a GICv5-based system, userspace is
required to save/restore the IRS MMIO registers. Provide accessors to read
and write the IRS MMIO state. Where possible, the existing access mechanisms
are used, but for some registers the access is handled a bit differently as
they have wider effects.
> diff --git a/arch/arm64/kvm/vgic/vgic-irs-v5.c b/arch/arm64/kvm/vgic/vgic-irs-v5.c
> --- a/arch/arm64/kvm/vgic/vgic-irs-v5.c
> +++ b/arch/arm64/kvm/vgic/vgic-irs-v5.c
[ ... ]
> @@ -455,84 +584,217 @@ static void vgic_v5_mmio_write_irs_ist(struct kvm_vcpu *vcpu, gpa_t addr,
> case GICV5_IRS_CR0:
> vgic->enabled = !!(val & GICV5_IRS_CR0_IRSEN);
> break;
> + case GICV5_IRS_SPI_CFGR:
> + break;
[Severity: High]
Does this silently ignore userspace writes to restore SPI configurations?
The commit message mentions blocking userspace from setting anything that
doesn't match what has been set already. However, the uaccess write handler
here just executes a break and returns 0 without validating the write or
applying any updates.
Could this cause SPI configurations (like edge vs level triggered) to be lost
during VM migration, permanently leaving restored SPIs in their default state?
> + case GICV5_IRS_IIDR:
> + fallthrough;
> + case GICV5_IRS_AIDR:
> + break;
> default:
> - return;
> + return -EINVAL;
> }
> +
> + return 0;
> }
[ ... ]
> @@ -494,35 +673,50 @@ static const struct vgic_register_region vgic_v5_irs_registers[] = {
> - REGISTER_DESC_WITH_LENGTH(GICV5_IRS_IST_BASER, vgic_v5_mmio_read_irs_ist,
> - vgic_v5_mmio_write_irs_ist, 8,
> - VGIC_ACCESS_64bit),
> + REGISTER_DESC_WITH_LENGTH_UACCESS(GICV5_IRS_IST_BASER,
> + vgic_v5_mmio_read_irs_ist,
> + vgic_v5_mmio_write_irs_ist, NULL,
> + vgic_v5_mmio_uaccess_write_irs, 8,
> + VGIC_ACCESS_64bit),
[Severity: Medium]
Should these 64-bit registers support 32-bit MMIO accesses?
The region descriptor specifies only VGIC_ACCESS_64bit. The GIC architecture
mandates that 64-bit registers must also support 32-bit accesses, so valid
32-bit guest reads or writes might be improperly rejected.
Additionally, if 32-bit accesses were permitted, would the write handler in
vgic_v5_mmio_write_irs_ist() improperly clobber the upper 32 bits? It
appears to lack the update_64bit_reg() wrapper used elsewhere in KVM to
handle partial writes safely.
[ ... ]
> diff --git a/arch/arm64/kvm/vgic/vgic-kvm-device.c b/arch/arm64/kvm/vgic/vgic-kvm-device.c
> --- a/arch/arm64/kvm/vgic/vgic-kvm-device.c
> +++ b/arch/arm64/kvm/vgic/vgic-kvm-device.c
[ ... ]
> @@ -786,6 +786,9 @@ int vgic_v5_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
> mpidr_reg = VGIC_TO_MPIDR(vgic_mpidr);
> reg_attr->vcpu = kvm_mpidr_to_vcpu(dev->kvm, mpidr_reg);
> break;
> + case KVM_DEV_ARM_VGIC_GRP_IRS_REGS:
> + reg_attr->vcpu = kvm_get_vcpu(dev->kvm, 0);
> + break;
> default:
> return -EINVAL;
> }
[Severity: Medium]
Does this break KVM_HAS_DEVICE_ATTR feature enumeration before VCPUs are
created?
If a VMM queries KVM_HAS_DEVICE_ATTR for KVM_DEV_ARM_VGIC_GRP_IRS_REGS
immediately after creating the VGIC device, kvm_get_vcpu(dev->kvm, 0) will
return NULL.
Because vgic_v5_parse_attr() returns -EINVAL when reg_attr->vcpu is NULL,
this causes feature probing to yield false negatives. As IRS registers are
VM-wide features, can we avoid this dependency on VCPU 0 during enumeration?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=32
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-43-sascha.bischoff@arm.com>]
* Re: [PATCH v4 42/48] KVM: selftests: Add VGICv5 IRS_REGS attribute tests
[not found] ` <20260724104819.1296803-43-sascha.bischoff@arm.com>
@ 2026-07-24 12:06 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:06 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, kvm, Marc Zyngier, kvmarm
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Using a `u64` variable for 32-bit KVM device attributes causes test logic failures on Big-Endian architectures.
- [Medium] `IST_BASER` is configured to point to the VGIC MMIO region instead of guest RAM.
- [Medium] Spurious test failure on hardware supporting 8 priority bits.
--
--- Patch [42]: [PATCH v4 42/48] KVM: selftests: Add VGICv5 IRS_REGS attribute tests ---
commit 7457ae5ec0d03e64cfdb7419437558dc7acaf9df
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
Subject: KVM: selftests: Add VGICv5 IRS_REGS attribute tests
Add coverage for the VGICv5 IRS_REGS attribute group. Migration needs a
userspace-visible view of the virtual IRS register state, but KVM must
only expose valid registers and must reject state that cannot be restored
safely.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> +static void test_vgic_v5_irs_regs_attrs(void)
> +{
[ ... ]
> + ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_IDR1, &val);
> + TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR1 failed");
> + val |= FIELD_PREP(GICV5_IRS_IDR1_PRIORITY_BITS, 0x7);
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_IDR1, &val);
> + TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR1");
[Severity: Medium]
Will this assertion falsely fail on hardware that supports 8 priority bits?
The architecture allows up to 8 priority bits, which encodes as 0x7. If the
test runs on a host whose hardware legitimately supports this configuration,
KVM will likely accept the write instead of returning -EINVAL, causing a
spurious test failure.
[ ... ]
> + ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_IDR5, &val);
> + TEST_ASSERT(!ret, "GICv5 IRS_REGS get IDR5 failed");
> + val++;
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_IDR5, &val);
> + TEST_ASSERT(ret && errno == EINVAL, "GICv5 IRS_REGS accepted bad IDR5");
[Severity: Medium]
Could this cause spurious test failures on Big-Endian architectures?
KVM reads and writes 32-bit attributes to the upper 32 bits of a u64 buffer
on BE systems. The val++ operation increments the lower 32 bits, leaving
the actual 32-bit attribute value in the upper 32 bits untouched. When
this is written back, KVM might legitimately accept the unchanged value,
causing this assertion to fail.
[ ... ]
> + /* Status registers read as idle through userspace accessors. */
> + ret = __kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_SYNC_STATUSR, &val);
> + TEST_ASSERT(!ret && val == GICV5_IRS_SYNC_STATUSR_IDLE,
> + "GICv5 IRS_REGS SYNC_STATUSR is not idle");
[ ... ]
> + /* Userspace restore of IST_BASER updates state without allocating ISTs. */
> + val = FIELD_PREP(GICV5_IRS_IST_BASER_ADDR_MASK,
> + GICV5_IRS_CONFIG_BASE_GPA >> GICV5_IRS_IST_BASER_ADDR_SHIFT);
> + val |= GICV5_IRS_IST_BASER_VALID;
> + ret = __kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_IRS_REGS,
> + GICV5_IRS_IST_BASER, &val);
> + TEST_ASSERT(!ret, "GICv5 IRS_REGS failed to restore IST_BASER");
[Severity: Medium]
Is it intentional to configure the Interrupt Status Table to overlap with the
VGIC's own MMIO region here?
GICV5_IRS_CONFIG_BASE_GPA is the base address of the IRS MMIO region, but
the IST should typically reside in standard guest RAM (such as
VGIC_V5_LPI_IST_BASE_GPA). If KVM adds validation in the future to ensure
IST_BASER points to a valid memory slot instead of an MMIO region, could
this configuration be rejected and break the test?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=42
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-32-sascha.bischoff@arm.com>]
* Re: [PATCH v4 31/48] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors
[not found] ` <20260724104819.1296803-32-sascha.bischoff@arm.com>
@ 2026-07-24 12:09 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:09 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Oliver Upton, kvm, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Incorrect priority mask `ICC_PPI_PRIORITYR_PRIORITY_MASK` corrupts GICv5 PPI priorities during save/restore.
- [High] Restoring the pending state of a level-triggered PPI overwrites the hardware wire state (`line_level`) instead of the software latch (`pending_latch`).
--
--- Patch [31]: [PATCH v4 31/48] KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors ---
commit bdb3308b5e4c3913da8b93ef491dc97004fe3ff0
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add GICv5 EL1 sysreg userspace accessors
Now that KVM is at the point where it is able to run meaningful VMs
with GICv5, it is important to be able to save/restore the GICv5 state
in order to allow for VM migration.
> diff --git a/arch/arm64/kvm/vgic-sys-reg-v5.c b/arch/arm64/kvm/vgic-sys-reg-v5.c
> new file mode 100644
> index 0000000000000..6b255eacbf60d
> --- /dev/null
> +++ b/arch/arm64/kvm/vgic-sys-reg-v5.c
> @@ -0,0 +1,519 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025, 2026 Arm Ltd.
> + */
> +
> +/*
> + * VGICv5 system registers handling functions for AArch64 mode
> + */
> +
> +#include <linux/irqchip/arm-gic-v5.h>
> +
> +#include <linux/kvm.h>
> +#include <linux/kvm_host.h>
> +#include <linux/wordpart.h>
> +
> +#include <asm/kvm_emulate.h>
> +
> +#include "vgic/vgic.h"
> +#include "sys_regs.h"
> +
> +#define ICC_PPI_PRIORITYR_PRIORITY_MASK REPEAT_BYTE(0x1f)
[Severity: High]
Does this mask correctly preserve the priority bits? GIC priority fields are
typically left-aligned in their 8-bit bytes (e.g., using bits [7:3], which
corresponds to a mask of 0xf8).
Applying REPEAT_BYTE(0x1f) later in set_gic_ppi_priorityr() appears to
isolate the bottom 5 bits (including the RAZ/WI bits [2:0]) and clear the
top valid priority bits, which could silently zero out the priorities when the
VMM restores the SYS_ICC_PPI_PRIORITYR<n>_EL1 registers during migration.
[ ... ]
> +static int set_gic_ppi_pendr(struct kvm_vcpu *vcpu,
> + const struct sys_reg_desc *r, u64 val)
> +{
> + int i, start, end, reg = r->Op2 % 2;
> +
> + /* If we only support architected PPIs, return */
> + if (VGIC_V5_NR_PRIVATE_IRQS == 64 && reg == 1)
> + return 0;
> +
> + /*
> + * Update each struct vgic_irq with the pending state, treating Level
> + * and Edge interrupts differently. The exposed PPI mask is applied
> + * later, when vgic_v5_finalize_ppi_state() runs on first entry to each
> + * vCPU. See comment on set_gic_ppi_enabler() for details.
> + */
> + start = reg * 64;
> + end = min(start + 64, VGIC_V5_NR_PRIVATE_IRQS);
> + for (i = start; i < end; i++) {
> + u32 intid = vgic_v5_make_ppi(i);
> + struct vgic_irq *irq;
> +
> + irq = vgic_get_vcpu_irq(vcpu, intid);
> +
> + scoped_guard(raw_spinlock_irqsave, &irq->irq_lock) {
> + bool level = !!(val & BIT_ULL(i - start));
> +
> + if (irq->config == VGIC_CONFIG_LEVEL)
> + irq->line_level = level;
[Severity: High]
Could this corrupt the interrupt state during VM migration by writing to
the hardware wire state rather than the software pending latch?
The SYS_ICC_PPI_SPENDR<n>_EL1 restore from userspace represents the software
pending state. If we set irq->line_level here for level-triggered interrupts,
it overwrites the ephemeral physical wire state (which gets re-evaluated by
backing hardware, like the arch timer) and the software-pended state would be
lost. Should this set irq->pending_latch for level interrupts as well?
> + else
> + irq->pending_latch = level;
> + }
> +
> + vgic_put_irq(vcpu->kvm, irq);
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=31
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-47-sascha.bischoff@arm.com>]
* Re: [PATCH v4 46/48] KVM: selftests: Add VGICv5 SPI injection tests
[not found] ` <20260724104819.1296803-47-sascha.bischoff@arm.com>
@ 2026-07-24 12:15 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:15 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: Marc Zyngier, kvmarm, Oliver Upton, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Writes to GICv5 control-plane system registers are missing the mandatory immediate isb() synchronization.
--
--- Patch [46]: [PATCH v4 46/48] KVM: selftests: Add VGICv5 SPI injection tests ---
commit 194658b92777c0e76e825f04069b3e506ee164f3
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: selftests: Add VGICv5 SPI injection tests
Add VGICv5 SPI delivery coverage through KVM_IRQ_LINE. SPIs are
configured through the virtual IRS and delivered through the GICv5 CPU
interface, so the test needs to exercise both the userspace injection
path and the guest-side IRS programming path.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> index 22ae726b17be8..cec994a77c993 100644
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> @@ -41,7 +44,71 @@ static struct kvm_vgic_v5_ist vgic_v5_ist_attr(void *spi_ist, size_t spi_size,
> };
> }
>
> +static void gicv5_setup_and_enable_hwirq(u64 hwirq, u32 target_vpe)
> +{
> + u64 val;
> +
> + val = hwirq | FIELD_PREP(GICV5_GIC_CDPRI_PRIORITY_MASK,
> + GICV5_IRQ_DEFAULT_PRI);
> + gic_insn(val, CDPRI);
> +
> + val = hwirq | FIELD_PREP(GICV5_GIC_CDAFF_IAFFID_MASK, target_vpe);
> + gic_insn(val, CDAFF);
> +
> + gic_insn(hwirq, CDEN);
[Severity: Medium]
Are isb() barriers required after these control-plane system register writes?
Unless these specific GICv5 CD registers are architecturally defined as
self-synchronizing, ARM64 requires an immediate isb() after every
control-plane system register write to ensure the CPU pipeline observes
the new configuration state.
> +}
[ ... ]
> +static void guest_spi_irq_handler(struct ex_regs *regs)
> +{
> + bool valid;
> + u32 hwirq;
> + u64 ia;
> +
> + ia = gicr_insn(CDIA);
> + valid = GICV5_GICR_CDIA_VALID(ia);
> +
> + if (!valid)
> + return;
> +
> + gsb_ack();
> + isb();
> +
> + hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
> +
> + GUEST_ASSERT_EQ(hwirq, gicv5_spi_hwirq(READ_ONCE(spi_line_expected)));
> +
> + gic_insn(hwirq, CDDI);
> + gic_insn(0, CDEOI);
[Severity: Medium]
Should these system register writes also be followed by isb() barriers?
Similar to the above, without explicit synchronization, the pipeline
might not immediately observe the updated state before the exception
handler completes.
> +
> + GUEST_DONE();
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=46
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-48-sascha.bischoff@arm.com>]
* Re: [PATCH v4 47/48] KVM: selftests: Add VGICv5 LPI delivery tests
[not found] ` <20260724104819.1296803-48-sascha.bischoff@arm.com>
@ 2026-07-24 12:18 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:18 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Oliver Upton, Marc Zyngier, kvmarm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Mismatch between test comment and implementation regarding VPE1's role.
--
--- Patch [47]: [PATCH v4 47/48] KVM: selftests: Add VGICv5 LPI delivery tests ---
commit bf269c88145be7a460a3fb9663a58422687addf2
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: selftests: Add VGICv5 LPI delivery tests
This commit adds a two-VPE VGICv5 LPI delivery test. It exercises the virtual
IRS ID registers and IST base configuration path before checking interrupt
delivery between the VPEs using a host-backed, unity-mapped userspace memslot.
> diff --git a/tools/testing/selftests/kvm/arm64/vgic_v5.c b/tools/testing/selftests/kvm/arm64/vgic_v5.c
> --- a/tools/testing/selftests/kvm/arm64/vgic_v5.c
> +++ b/tools/testing/selftests/kvm/arm64/vgic_v5.c
[ ... ]
> @@ -1179,6 +1365,54 @@ static void test_vgic_v5_spis(void)
[ ... ]
> +static void test_vgic_v5_lpis(void)
> +{
[ ... ]
> + /* VPE0 programs a linear LPI IST from the virtual IRS ID registers. */
> + ret = run_vcpu(vcpus[0]);
> + TEST_ASSERT(!ret, "Failed to run GICv5 LPI vCPU0");
> + TEST_ASSERT(get_ucall(vcpus[0], &uc) == UCALL_SYNC &&
> + uc.args[1] == GUEST_CMD_IS_READY,
> + "GICv5 LPI vCPU0 did not become ready");
> +
> + /* VPE1 observes the IST and enables the LPIs it will receive/send. */
> + ret = run_vcpu(vcpus[1]);
[Severity: Low]
This comment indicates that VPE1 will enable the LPIs it receives and sends.
However, looking at guest_lpi_code(), the LPI configuration via
gicv5_configure_test_lpis() is restricted strictly to VPE0:
if (!vcpu_id) {
gicv5_enable_irs();
gicv5_configure_lpi_ist();
gicv5_configure_test_lpis();
WRITE_ONCE(lpi_ist_ready, true);
}
Could this comment in test_vgic_v5_lpis() be updated to accurately reflect
that only VPE0 performs the LPI configuration step?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=47
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-38-sascha.bischoff@arm.com>]
* Re: [PATCH v4 37/48] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs
[not found] ` <20260724104819.1296803-38-sascha.bischoff@arm.com>
@ 2026-07-24 12:19 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:19 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, kvmarm, Marc Zyngier, Oliver Upton
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The documentation for KVM_DEV_ARM_VGIC_GRP_IRS_REGS omits that writes will return -EBUSY if a VCPU has run.
Pre-existing issues:
- [High] SPI configuration (edge/level) is permanently lost during VM migration because KVM ignores userspace writes to `GICV5_IRS_SPI_CFGR` and fails to serialize the configuration into the Interrupt State Table (IST).
--
--- Patch [37]: [PATCH v4 37/48] Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs ---
commit 6c9776f9e0496eb6c3e26fe3876764cc1148cc8a
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
Documentation: KVM: Add KVM_DEV_ARM_VGIC_GRP_IRS_REGS to VGICv5 docs
Document the KVM_DEV_ARM_VGIC_GRP_IRS_REGS attribute group used to
read and write the virtual IRS's MMIO register state. This provides a
GICv5-specific interface for state that is conceptually similar to the
VGICv3 ITS register interface, but uses IRS terminology instead of ITS.
> diff --git a/Documentation/virt/kvm/devices/arm-vgic-v5.rst b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> index 50d8030e2c804..c411d2195c6a0 100644
> --- a/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> +++ b/Documentation/virt/kvm/devices/arm-vgic-v5.rst
> @@ -149,3 +149,41 @@ Groups:
[ ... ]
> + For registers without dedicated userspace accessors, getting or setting a
> + register uses the same emulated MMIO handlers as guest reads/writes.
> + Dedicated userspace accessors may instead save or restore migration state
> + without triggering guest-visible side effects. For example, restoring
> + IRS_IST_BASER only restores the emulated register state; any host LPI IST
> + allocation based on the restored IRS_IST_CFGR and IRS_IST_BASER state
> + happens when KVM_DEV_ARM_VGIC_GRP_IST is restored.
[Severity: High]
This is a pre-existing issue, but does VM migration permanently lose the
edge/level configuration for all SPIs because of how GICV5_IRS_SPI_CFGR is
handled?
The userspace write accessor explicitly ignores writes to the SPI
configuration register, making manual restoration impossible:
arch/arm64/kvm/vgic/vgic-irs-v5.c:vgic_v5_mmio_uaccess_write_irs() {
...
case GICV5_IRS_SPI_CFGR:
break;
...
}
Additionally, the IST is copied to userspace directly from hardware memory
without synthesizing KVM's software-tracked irq->config into the HM bit:
arch/arm64/kvm/vgic/vgic-v5-tables.c:vgic_v5_save_linear_ist() {
...
h_iste = READ_ONCE(*h_iste_addr);
ret = put_user(h_iste, uaddr);
...
}
Since the configuration is lost during the IST save and cannot be restored
via GICV5_IRS_SPI_CFGR, are level-triggered interrupts restored incorrectly
as edge-triggered?
> +
> + Errors:
> +
> + ======= =================================================================
> + -ENXIO Offset does not correspond to any supported register
> + -EFAULT Invalid user pointer for attr->addr
> + -EINVAL Offset is not 32-bit aligned for 32-bit MMIO registers, not
> + 64-bit aligned for 64-bit registers, or the supplied register
> + value is not compatible with the configured VGICv5 IRS state
> + -EBUSY VGIC is not initialized, or one or more VCPUs are running
> + ======= =================================================================
[Severity: Low]
Does this error description miss the case where a VCPU has already run?
Looking at vgic_v5_attr_regs_access() in arch/arm64/kvm/vgic/vgic-kvm-device.c:
if (is_write && vgic_v5_vm_has_run_once(dev->kvm)) {
ret = -EBUSY;
goto out;
}
Should the documentation also mention that writes are rejected with -EBUSY
if attempted after a VCPU has run?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=37
^ permalink raw reply [flat|nested] 60+ messages in thread
[parent not found: <20260724104819.1296803-30-sascha.bischoff@arm.com>]
* Re: [PATCH v4 29/48] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd
[not found] ` <20260724104819.1296803-30-sascha.bischoff@arm.com>
@ 2026-07-24 12:19 ` sashiko-bot
0 siblings, 0 replies; 60+ messages in thread
From: sashiko-bot @ 2026-07-24 12:19 UTC (permalink / raw)
To: Sascha Bischoff; +Cc: kvm, Oliver Upton, kvmarm, Marc Zyngier
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] GICv5 VMs configured with more than 988 SPIs will unconditionally fail to initialize because the default IRQ routing setup generates routing entries that exceed KVM_IRQCHIP_NUM_PINS.
--
--- Patch [29]: [PATCH v4 29/48] KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd ---
commit a9da966fc7b449adaa92917185e2770d6d012f6f
Author: Sascha Bischoff <Sascha.Bischoff@arm.com>
KVM: arm64: gic-v5: Add GICv5 SPI injection to irqfd
Now that there is support for GICv5 SPIs in KVM, update
vgic_irqfd_set_irq() to translate irqchip pins into GICv5 SPI IntIDs
before injecting them.
> diff --git a/arch/arm64/kvm/vgic/vgic-irqfd.c b/arch/arm64/kvm/vgic/vgic-irqfd.c
> index 19a1094536e6a..83e84151d7aa2 100644
> --- a/arch/arm64/kvm/vgic/vgic-irqfd.c
> +++ b/arch/arm64/kvm/vgic/vgic-irqfd.c
[ ... ]
> @@ -52,8 +57,8 @@ int kvm_set_routing_entry(struct kvm *kvm,
> e->set = vgic_irqfd_set_irq;
> e->irqchip.irqchip = ue->u.irqchip.irqchip;
> e->irqchip.pin = ue->u.irqchip.pin;
> - if ((e->irqchip.pin >= KVM_IRQCHIP_NUM_PINS) ||
> - (e->irqchip.irqchip >= KVM_NR_IRQCHIPS))
> + if (e->irqchip.pin >= KVM_IRQCHIP_NUM_PINS ||
> + e->irqchip.irqchip >= KVM_NR_IRQCHIPS)
[Severity: High]
This isn't a bug introduced by this patch, but does this check cause VM
initialization to fail if a GICv5 VM is configured with more than 988 SPIs?
When userspace configures a VM with nr_spis > 988,
kvm_vgic_setup_default_irq_routing() attempts to set up a 1:1 default routing
for all SPIs:
arch/arm64/kvm/vgic/vgic-irqfd.c:kvm_vgic_setup_default_irq_routing() {
for (i = 0; i < nr; i++) {
entries[i].gsi = i;
entries[i].type = KVM_IRQ_ROUTING_IRQCHIP;
entries[i].u.irqchip.irqchip = 0;
entries[i].u.irqchip.pin = i;
}
ret = kvm_set_irq_routing(kvm, entries, nr, 0);
...
}
Because kvm_set_routing_entry() caps e->irqchip.pin at KVM_IRQCHIP_NUM_PINS
(which is 988), any entries beyond 988 will be rejected here with -EINVAL,
causing VM initialization to abort.
> goto out;
> break;
> case KVM_IRQ_ROUTING_MSI:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260724104819.1296803-1-sascha.bischoff@arm.com?part=29
^ permalink raw reply [flat|nested] 60+ messages in thread