[PATCH 06/43] KVM: arm64: gic-v5: Add VPE doorbell domain

Sascha Bischoff
2026-04-27T16:09:25Z

GICv5 supports two types of doorbell - VPE doorbells and VM
doorbells. In KVM we only support Targeted interrupts, and do not
support 1ofN target selection. This means that we only implement VPE
doorbells. These doorbells are implemented as host LPIs which are
generated when a non-resident VPE has a pending interrupt of
sufficient priority and the doorbell has been requested as part of
making the VPE non-resident.

VPE doorbells allow KVM to wake VPEs (so, vcpus) as soon as the
hardware determines that sufficient conditions for the interrupt to be
signalled have been met. This simplifies the wake-up path for vcpus
with GICv5 for LPIs and SPIs. NOTE: PPI pending state must still be
checked explicitly as the IRS never sees them.

This change introduces support for the vgic_v5 doorbell domain. One
doorbell domain is created per GICv5 VM, and all VPEs have their own
doorbell within this domain. When the doorbell fires, this is tracked
(in gicv5_vpe.db_fired) and the corresponding vcpu is kicked.

Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
---
arch/arm64/kvm/vgic/vgic-init.c | 5 +-
arch/arm64/kvm/vgic/vgic-v5.c | 143 +++++++++++++++++++++++++++++
arch/arm64/kvm/vgic/vgic.h | 1 +
include/kvm/arm_vgic.h | 6 ++
include/linux/irqchip/arm-gic-v5.h | 2 +
5 files changed, 156 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/kvm/vgic/vgic-init.c b/arch/arm64/kvm/vgic/vgic-init.c
index 907057881b26a..984908a271c8d 100644
--- a/arch/arm64/kvm/vgic/vgic-init.c
+++ b/arch/arm64/kvm/vgic/vgic-init.c
@@ -500,8 +500,11 @@ static void kvm_vgic_dist_destroy(struct kvm *kvm)
dist->vgic_cpu_base = VGIC_ADDR_UNDEF;
}

- if (vgic_supports_direct_irqs(kvm))
+ if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3 &&
+ vgic_supports_direct_irqs(kvm))
vgic_v4_teardown(kvm);
+ else if (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V5)
+ vgic_v5_teardown(kvm);

xa_destroy(&dist->lpi_xa);
}
diff --git a/arch/arm64/kvm/vgic/vgic-v5.c b/arch/arm64/kvm/vgic/vgic-v5.c
index fd3d6299a2baa..4e0d52b309628 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"
#include "vgic-v5-tables.h"
@@ -162,6 +163,138 @@ int vgic_v5_probe(const struct gic_kvm_info *info)
return 0;
}

+/*
+ * This set of irq_chip functions is specific for doorbells.
+ */
+static 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 int vgic_v5_irq_db_domain_map(struct irq_domain *d, unsigned int virq,
+ u16 vpe_id)
+{
+ int ret;
+ u32 lpi;
+ irq_hw_number_t hwirq;
+ struct irq_chip *chip = &vgic_v5_db_irq_chip;
+ struct irq_data *irqd = irq_desc_get_irq_data(irq_to_desc(virq));
+
+ /*
+ * For the DB domain, we don't use the same hwirq as for LPIs.
+ */
+ hwirq = vpe_id;
+
+ ret = gicv5_alloc_lpi();
+ if (ret < 0)
+ return ret;
+ lpi = ret;
+
+ ret = irq_domain_alloc_irqs_parent(d, virq, 1, &lpi);
+ if (ret) {
+ gicv5_free_lpi(lpi);
+ return ret;
+ }
+
+ irq_domain_set_hwirq_and_chip(d, virq, hwirq, chip, d->host_data);
+ irqd_set_single_target(irqd);
+
+ return 0;
+}
+
+
+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);
+
+ gicv5_free_lpi(d->parent_data->hwirq);
+ 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)
+{
+ struct vgic_v5_vm *vm = arg;
+ int ret;
+
+ if (vm == NULL) {
+ pr_err("invalid parameter for doorbell irq allocation");
+ return -EINVAL;
+ }
+
+ if (vm->nr_vpes != nr_irqs)
+ return -EINVAL;
+
+ for (int i = 0; i < nr_irqs; i++) {
+ ret = vgic_v5_irq_db_domain_map(domain, virq + i, i);
+ if (ret) {
+ /* Free what we've allocated so far before returning */
+ while (--i >= 0)
+ vgic_v5_irq_db_domain_free(domain, virq + i, 1);
+ return ret;
+ }
+ }
+
+ 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 vgic_v5_vm *vm)
+{
+ if (!gicv5_global_data.lpi_domain) {
+ pr_err("LPI domain uninitialized, can't set up KVM Doorbells");
+ return -ENODEV;
+ }
+
+ vm->fwnode = irq_domain_alloc_named_id_fwnode("GICv5-vpe-db",
+ task_pid_nr(current));
+
+ /*
+ * 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, vm->nr_vpes, vm->fwnode,
+ &vgic_v5_irq_db_domain_ops, vm);
+
+ if (WARN_ON(!vm->domain))
+ return -ENOMEM;
+
+ return 0;
+}
+
+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)
{
/*
@@ -181,10 +314,15 @@ int vgic_v5_init(struct kvm *kvm)
{
struct kvm_vcpu *vcpu;
unsigned long idx;
+ int ret;

if (vgic_initialized(kvm))
return 0;

+ ret = vgic_v5_create_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+ if (ret)
+ return ret;
+
kvm_for_each_vcpu(idx, vcpu, kvm) {
if (vcpu_has_nv(vcpu)) {
kvm_err("Nested GICv5 VMs are currently unsupported\n");
@@ -203,6 +341,11 @@ int vgic_v5_init(struct kvm *kvm)
return 0;
}

+void vgic_v5_teardown(struct kvm *kvm)
+{
+ vgic_v5_teardown_per_vm_domain(&kvm->arch.vgic.gicv5_vm);
+}
+
int vgic_v5_map_resources(struct kvm *kvm)
{
if (!vgic_initialized(kvm))
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 d14cf4771d606..05dbd01f6fd21 100644
--- a/include/kvm/arm_vgic.h
+++ b/include/kvm/arm_vgic.h
@@ -366,6 +366,12 @@ 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;
+ int nr_vpes;
+ u16 vm_id;
};

struct vgic_dist {
diff --git a/include/linux/irqchip/arm-gic-v5.h b/include/linux/irqchip/arm-gic-v5.h
index 76dcf414ffb20..087d94f739672 100644
--- a/include/linux/irqchip/arm-gic-v5.h
+++ b/include/linux/irqchip/arm-gic-v5.h
@@ -401,6 +401,8 @@ void gicv5_irs_syncr(void);

/* Embedded in kvm.arch */
struct gicv5_vpe {
+ int db;
+ bool db_fired;
bool resident;
};

--
2.34.1