[RFC PATCH 05/13] KVM: arm64: Implement vLPI ENABLE ioctl for per-vCPU vLPI injection API
Maximilian Dittgen
mdittgen at amazon.de
Thu Nov 20 06:02:54 PST 2025
Implement kvm_vgic_enable_vcpu_vlpi, which handles the
KVM_ENABLE_VCPU_VLPI ioctl to enable direct vLPI injection on a
specific vCPU. The function has two components: a call to
vgic_v4_cpu_init and a call to upgrade_existing_lpis_to_vlpis:
- vgic_v4_vcpu_init() is the per-vCPU corrolary to vgic_cpu_init, and
initializes all of the GIC structures a vCPU needs to handle LPI
interrupts via direct injection. While IRQ domains are usually
allocated on a per-VM basis, vgic_v4_cpu_init() creates a per-vPE IRQ
domain and fwnode to decouple vLPI doorbell allocation across
separate vCPUs. The domain allocation routine in
its_vpe_irq_domain_alloc() also allocates a vPE table entry and
virtual pending table for the vCPU.
- upgrade_existing_lpis_to_vlpis() iterates through all of the LPIs
targeting the vCPU and initializes hardware forwarding to process
them as direct vLPIs. This includes updating the LPIs ITE to hold a
vPE's vPEID instead of a Collection table's collection ID. It also
toggles each interrupt's irq->hw flag to true to notify the ITS to
handle the interrupt via direct injection.
Signed-off-by: Maximilian Dittgen <mdittgen at amazon.com>
---
arch/arm64/kvm/arm.c | 13 ++-
arch/arm64/kvm/vgic/vgic-its.c | 4 +-
arch/arm64/kvm/vgic/vgic-v4.c | 157 ++++++++++++++++++++++++++++-
arch/arm64/kvm/vgic/vgic.h | 4 +
drivers/irqchip/irq-gic-v3-its.c | 48 ++++++++-
drivers/irqchip/irq-gic-v4.c | 56 ++++++++--
include/linux/irqchip/arm-gic-v3.h | 4 +
include/linux/irqchip/arm-gic-v4.h | 8 +-
8 files changed, 277 insertions(+), 17 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index afb04162e0cf..169860649bdd 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1951,8 +1951,17 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
return kvm_vm_ioctl_get_reg_writable_masks(kvm, &range);
}
case KVM_ENABLE_VCPU_VLPI: {
- /* TODO: create ioctl handler function */
- return -ENOSYS;
+ int vcpu_id;
+ struct kvm_vcpu *vcpu;
+
+ if (copy_from_user(&vcpu_id, argp, sizeof(vcpu_id)))
+ return -EFAULT;
+
+ vcpu = kvm_get_vcpu_by_id(kvm, vcpu_id);
+ if (!vcpu)
+ return -EINVAL;
+
+ return kvm_vgic_enable_vcpu_vlpi(vcpu);
}
case KVM_DISABLE_VCPU_VLPI: {
/* TODO: create ioctl handler function */
diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c
index ce3e3ed3f29f..5f3bbf24cc2f 100644
--- a/arch/arm64/kvm/vgic/vgic-its.c
+++ b/arch/arm64/kvm/vgic/vgic-its.c
@@ -23,7 +23,7 @@
#include "vgic.h"
#include "vgic-mmio.h"
-static struct kvm_device_ops kvm_arm_vgic_its_ops;
+struct kvm_device_ops kvm_arm_vgic_its_ops;
static int vgic_its_save_tables_v0(struct vgic_its *its);
static int vgic_its_restore_tables_v0(struct vgic_its *its);
@@ -2801,7 +2801,7 @@ static int vgic_its_get_attr(struct kvm_device *dev,
return 0;
}
-static struct kvm_device_ops kvm_arm_vgic_its_ops = {
+struct kvm_device_ops kvm_arm_vgic_its_ops = {
.name = "kvm-arm-vgic-its",
.create = vgic_its_create,
.destroy = vgic_its_destroy,
diff --git a/arch/arm64/kvm/vgic/vgic-v4.c b/arch/arm64/kvm/vgic/vgic-v4.c
index cebcb9175572..efb9ac9188e3 100644
--- a/arch/arm64/kvm/vgic/vgic-v4.c
+++ b/arch/arm64/kvm/vgic/vgic-v4.c
@@ -316,9 +316,15 @@ int vgic_v4_init(struct kvm *kvm)
}
#else
/*
- * TODO: Initialize the shared VM properties that remain necessary
- * in per-vCPU mode
+ * Initialize the shared VM properties that remain necessary in per-vCPU mode
*/
+
+ /* vPE properties table */
+ if (!dist->its_vm.vprop_page) {
+ dist->its_vm.vprop_page = its_allocate_prop_table(GFP_KERNEL);
+ if (!dist->its_vm.vprop_page)
+ ret = -ENOMEM;
+ }
#endif
if (ret)
vgic_v4_teardown(kvm);
@@ -326,6 +332,51 @@ int vgic_v4_init(struct kvm *kvm)
return ret;
}
+/**
+ * vgic_v4_vcpu_init - When per-vCPU vLPI injection is enabled,
+ * initialize the GICv4 data structures for a specific vCPU
+ * @vcpu: Pointer to the vcpu being initialized
+ *
+ * Called every time the KVM_ENABLE_VCPU_VLPI ioctl is called.
+ */
+int vgic_v4_vcpu_init(struct kvm_vcpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct vgic_dist *dist = &kvm->arch.vgic;
+ int i, ret, irq;
+ unsigned long irq_flags = DB_IRQ_FLAGS;
+
+ /* Validate vgic_v4_init() has been called to allocate the vpe array */
+ if (!dist->its_vm.vpes)
+ return -ENODEV;
+
+ /* Link KVM distributor to the newly-allocated vPE */
+ i = kvm_idx_from_vcpu(kvm, vcpu);
+ if (i == UINT_MAX)
+ return -EINVAL;
+ dist->its_vm.vpes[i] = &vcpu->arch.vgic_cpu.vgic_v3.its_vpe;
+
+ ret = its_alloc_vcpu_irq(vcpu);
+ if (ret)
+ return ret;
+
+ /* Same routine as the kvm_for_each_vcpu of vgic_v4_init */
+ irq = dist->its_vm.vpes[i]->irq;
+
+ if (kvm_vgic_global_state.has_gicv4_1)
+ irq_flags &= ~IRQ_NOAUTOEN;
+ irq_set_status_flags(irq, irq_flags);
+
+ ret = vgic_v4_request_vpe_irq(vcpu, irq);
+ if (ret)
+ kvm_err("failed to allocate vcpu IRQ%d\n", irq);
+
+ if (ret)
+ vgic_v4_teardown(kvm);
+
+ return ret;
+}
+
/**
* vgic_v4_teardown - Free the GICv4 data structures
* @kvm: Pointer to the VM being destroyed
@@ -357,6 +408,9 @@ void vgic_v4_teardown(struct kvm *kvm)
* in per-vCPU mode. Create separate teardown function
* that operates on a per-vCPU basis.
*/
+
+ /* vPE properties table */
+ its_free_prop_table(its_vm->vprop_page);
#else
its_free_vcpu_irqs(its_vm);
#endif
@@ -618,6 +672,105 @@ void kvm_vgic_v4_unset_forwarding(struct kvm *kvm, int host_irq)
vgic_put_irq(kvm, irq);
}
+static int upgrade_existing_lpis_to_vlpis(struct kvm_vcpu *vcpu)
+{
+ struct kvm *kvm = vcpu->kvm;
+ struct kvm_device *dev;
+ struct vgic_its *its, *its_from_entry;
+ struct its_device *device;
+ struct its_ite *ite;
+ struct kvm_kernel_irq_routing_entry entry;
+ int ret = 0;
+ int host_irq;
+
+ list_for_each_entry(dev, &kvm->devices, vm_node) {
+ /* Ensure we only look at ITS devices */
+ if (dev->ops != &kvm_arm_vgic_its_ops)
+ continue;
+
+ its = dev->private;
+ mutex_lock(&its->its_lock);
+
+ list_for_each_entry(device, &its->device_list, dev_list) {
+ list_for_each_entry(ite, &device->itt_head, ite_list) {
+ /* ite->irq->hw means entry already upgraded to vLPI */
+ if (ite->collection &&
+ ite->collection->target_addr == vcpu->vcpu_id &&
+ ite->irq && !ite->irq->hw) {
+
+ /*
+ * An existing IRQ would only have a null host_irq if it is
+ * completely defined in software, in which case it cannot
+ * be direct injected anyways. Thus, we skip interrupt
+ * upgrade for IRQs with null host_irqs.
+ */
+ if (ite->irq->host_irq > 0)
+ host_irq = ite->irq->host_irq;
+ else
+ continue;
+
+ /* Create routing entry */
+ memset(&entry, 0, sizeof(entry));
+ entry.gsi = host_irq;
+ entry.type = KVM_IRQ_ROUTING_MSI;
+ /* MSI address is system defined for ARM GICv3 */
+ entry.msi.address_lo =
+ (u32)(its->vgic_its_base + GITS_TRANSLATER);
+ entry.msi.address_hi =
+ (u32)((its->vgic_its_base + GITS_TRANSLATER) >> 32);
+ entry.msi.data = ite->event_id;
+ entry.msi.devid = device->device_id;
+ entry.msi.flags = KVM_MSI_VALID_DEVID;
+
+ /* Verify ITS consistency */
+ its_from_entry = vgic_get_its(kvm, &entry);
+ if (IS_ERR(its_from_entry) || its_from_entry != its)
+ continue;
+
+ /* Upgrade to vLPI */
+ ret = kvm_vgic_v4_set_forwarding_locked(kvm, host_irq,
+ &entry, its);
+ if (ret)
+ kvm_info("Failed to upgrade LPI %d: %d\n",
+ host_irq, ret);
+ }
+ }
+ }
+
+ mutex_unlock(&its->its_lock);
+ }
+
+ return 0;
+}
+
+/* Enable vLPI direct injection on a specific vCPU */
+int kvm_vgic_enable_vcpu_vlpi(struct kvm_vcpu *vcpu)
+{
+ int ret;
+ int vcpu_vlpi_status = kvm_vgic_query_vcpu_vlpi(vcpu);
+
+ /* vGIC not initialized for vCPU */
+ if (vcpu_vlpi_status < 0)
+ return vcpu_vlpi_status;
+ /* vLPI already enabled */
+ if (vcpu_vlpi_status > 0)
+ return 0;
+
+ /* Allocate the vPE struct and vPE table for the vCPU */
+ ret = vgic_v4_vcpu_init(vcpu);
+ if (ret)
+ return ret;
+
+ /*
+ * Upgrade existing LPIs to vLPIs. We
+ * do not need to error check since
+ * a failure in upgrading an LPI is non-breaking;
+ * those LPIs may continue to be processed by
+ * software.
+ */
+ return upgrade_existing_lpis_to_vlpis(vcpu);
+}
+
/* query whether vLPI direct injection is enabled on a specific vCPU.
* return 0 if disabled, 1 if enabled, -EINVAL if vCPU non-existant or GIC
* uninitialized
diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h
index 295088913c26..60ae0d1f044d 100644
--- a/arch/arm64/kvm/vgic/vgic.h
+++ b/arch/arm64/kvm/vgic/vgic.h
@@ -251,6 +251,8 @@ struct ap_list_summary {
#define irqs_active_outside_lrs(s) \
((s)->nr_act && irqs_outside_lrs(s))
+extern struct kvm_device_ops kvm_arm_vgic_its_ops;
+
int vgic_v3_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
struct vgic_reg_attr *reg_attr);
int vgic_v2_parse_attr(struct kvm_device *dev, struct kvm_device_attr *attr,
@@ -434,6 +436,7 @@ static inline bool vgic_supports_direct_irqs(struct kvm *kvm)
}
int vgic_v4_init(struct kvm *kvm);
+int vgic_v4_vcpu_init(struct kvm_vcpu *vcpu);
void vgic_v4_teardown(struct kvm *kvm);
void vgic_v4_configure_vsgis(struct kvm *kvm);
void vgic_v4_get_vlpi_state(struct vgic_irq *irq, bool *val);
@@ -468,6 +471,7 @@ int vgic_its_debug_init(struct kvm_device *dev);
void vgic_its_debug_destroy(struct kvm_device *dev);
bool kvm_per_vcpu_vlpi_supported(void);
+int kvm_vgic_enable_vcpu_vlpi(struct kvm_vcpu *vcpu);
int kvm_vgic_query_vcpu_vlpi(struct kvm_vcpu *vcpu);
#endif
diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c
index 467cb78435a9..67749578f973 100644
--- a/drivers/irqchip/irq-gic-v3-its.c
+++ b/drivers/irqchip/irq-gic-v3-its.c
@@ -2261,7 +2261,7 @@ static void gic_reset_prop_table(void *va)
gic_flush_dcache_to_poc(va, LPI_PROPBASE_SZ);
}
-static struct page *its_allocate_prop_table(gfp_t gfp_flags)
+struct page *its_allocate_prop_table(gfp_t gfp_flags)
{
struct page *prop_page;
@@ -2275,7 +2275,7 @@ static struct page *its_allocate_prop_table(gfp_t gfp_flags)
return prop_page;
}
-static void its_free_prop_table(struct page *prop_page)
+void its_free_prop_table(struct page *prop_page)
{
its_free_pages(page_address(prop_page), get_order(LPI_PROPBASE_SZ));
}
@@ -4612,25 +4612,65 @@ static void its_vpe_irq_domain_free(struct irq_domain *domain,
BUG_ON(vm != vpe->its_vm);
+#ifdef CONFIG_ARM_GIC_V3_PER_VCPU_VLPI
+ free_lpi_range(vpe->vpe_db_lpi, 1);
+#else
clear_bit(data->hwirq, vm->db_bitmap);
+#endif
its_vpe_teardown(vpe);
irq_domain_reset_irq_data(data);
}
+#ifndef CONFIG_ARM_GIC_V3_PER_VCPU_VLPI
if (bitmap_empty(vm->db_bitmap, vm->nr_db_lpis)) {
its_lpi_free(vm->db_bitmap, vm->db_lpi_base, vm->nr_db_lpis);
its_free_prop_table(vm->vprop_page);
}
+#endif
}
static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
unsigned int nr_irqs, void *args)
{
struct irq_chip *irqchip = &its_vpe_irq_chip;
+ int base, err;
+#ifdef CONFIG_ARM_GIC_V3_PER_VCPU_VLPI
+ struct its_vpe *vpe = args;
+
+ /* Per-vCPU mode: allocate domain on vPE––rather than VM––level */
+ WARN_ON(nr_irqs != 1);
+
+ /* Use VM's shared properties table */
+ if (!vpe->its_vm || !vpe->its_vm->vprop_page)
+ return -EINVAL;
+
+ if (gic_rdists->has_rvpeid)
+ irqchip = &its_vpe_4_1_irq_chip;
+
+ err = alloc_lpi_range(1, &base);
+ if (err)
+ return err;
+ vpe->vpe_db_lpi = base;
+ err = its_vpe_init(vpe);
+ if (err)
+ return err;
+
+ err = its_irq_gic_domain_alloc(domain, virq, vpe->vpe_db_lpi);
+ if (err)
+ goto err_teardown_vpe;
+
+ irq_domain_set_hwirq_and_chip(domain, virq, 0, irqchip, vpe);
+ irqd_set_resend_when_in_progress(irq_get_irq_data(virq));
+
+ return 0;
+
+err_teardown_vpe:
+ its_vpe_teardown(vpe);
+#else
struct its_vm *vm = args;
unsigned long *bitmap;
struct page *vprop_page;
- int base, nr_ids, i, err = 0;
+ int nr_ids, i;
bitmap = its_lpi_alloc(roundup_pow_of_two(nr_irqs), &base, &nr_ids);
if (!bitmap)
@@ -4673,7 +4713,7 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq
if (err)
its_vpe_irq_domain_free(domain, virq, i);
-
+#endif
return err;
}
diff --git a/drivers/irqchip/irq-gic-v4.c b/drivers/irqchip/irq-gic-v4.c
index 8455b4a5fbb0..c8e324cd8911 100644
--- a/drivers/irqchip/irq-gic-v4.c
+++ b/drivers/irqchip/irq-gic-v4.c
@@ -7,6 +7,7 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
+#include <linux/kvm_host.h>
#include <linux/msi.h>
#include <linux/pid.h>
#include <linux/sched.h>
@@ -128,14 +129,14 @@ static int its_alloc_vcpu_sgis(struct its_vpe *vpe, int idx)
if (!name)
goto err;
- vpe->fwnode = irq_domain_alloc_named_id_fwnode(name, idx);
- if (!vpe->fwnode)
+ vpe->sgi_fwnode = irq_domain_alloc_named_id_fwnode(name, idx);
+ if (!vpe->sgi_fwnode)
goto err;
kfree(name);
name = NULL;
- vpe->sgi_domain = irq_domain_create_linear(vpe->fwnode, 16,
+ vpe->sgi_domain = irq_domain_create_linear(vpe->sgi_fwnode, 16,
sgi_domain_ops, vpe);
if (!vpe->sgi_domain)
goto err;
@@ -149,8 +150,8 @@ static int its_alloc_vcpu_sgis(struct its_vpe *vpe, int idx)
err:
if (vpe->sgi_domain)
irq_domain_remove(vpe->sgi_domain);
- if (vpe->fwnode)
- irq_domain_free_fwnode(vpe->fwnode);
+ if (vpe->sgi_fwnode)
+ irq_domain_free_fwnode(vpe->sgi_fwnode);
kfree(name);
return -ENOMEM;
}
@@ -199,6 +200,49 @@ int its_alloc_vcpu_irqs(struct its_vm *vm)
return -ENOMEM;
}
+int its_alloc_vcpu_irq(struct kvm_vcpu *vcpu)
+{
+ struct its_vpe *vpe = &vcpu->arch.vgic_cpu.vgic_v3.its_vpe;
+ struct its_vm *vm = &vcpu->kvm->arch.vgic.its_vm;
+ int ret;
+
+ vpe->its_vm = vm; /* point all vPEs on a VM to the same shared dist its_vm*/
+ if (!has_v4_1_sgi()) /* idai bool shares memory with sgi_domain pointer */
+ vpe->idai = true;
+
+ /* create a per-vPE, rather than per-VM, fwnode */
+ if (!vpe->lpi_fwnode) {
+ /* add vcpu_id to fwnode naming to differentiate vcpus in same VM */
+ vpe->lpi_fwnode = irq_domain_alloc_named_id_fwnode("GICv4-vpe-lpi",
+ task_pid_nr(current) * 1000 + vcpu->vcpu_id);
+ if (!vpe->lpi_fwnode)
+ goto err;
+ }
+
+ /* create domain hierarchy for vPE */
+ vpe->lpi_domain = irq_domain_create_hierarchy(gic_domain, 0, 1,
+ vpe->lpi_fwnode, vpe_domain_ops, vpe);
+ if (!vpe->lpi_domain)
+ goto err;
+
+ /* allocate IRQs from vPE domain */
+ vpe->irq = irq_domain_alloc_irqs(vpe->lpi_domain, 1, NUMA_NO_NODE, vpe);
+ if (vpe->irq <= 0)
+ goto err;
+
+ ret = its_alloc_vcpu_sgis(vpe, vcpu->vcpu_id);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ if (vpe->lpi_domain)
+ irq_domain_remove(vpe->lpi_domain);
+ if (vpe->lpi_fwnode)
+ irq_domain_free_fwnode(vpe->lpi_fwnode);
+ return -ENOMEM;
+}
+
static void its_free_sgi_irqs(struct its_vm *vm)
{
int i;
@@ -214,7 +258,7 @@ static void its_free_sgi_irqs(struct its_vm *vm)
irq_domain_free_irqs(irq, 16);
irq_domain_remove(vm->vpes[i]->sgi_domain);
- irq_domain_free_fwnode(vm->vpes[i]->fwnode);
+ irq_domain_free_fwnode(vm->vpes[i]->sgi_fwnode);
}
}
diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h
index 70c0948f978e..5031a4c25543 100644
--- a/include/linux/irqchip/arm-gic-v3.h
+++ b/include/linux/irqchip/arm-gic-v3.h
@@ -641,6 +641,10 @@ int its_init(struct fwnode_handle *handle, struct rdists *rdists,
struct irq_domain *domain, u8 irq_prio);
int mbi_init(struct fwnode_handle *fwnode, struct irq_domain *parent);
+/* Enable prop table alloc/free on vGIC init/destroy when per-vCPU vLPI is enabled */
+struct page *its_allocate_prop_table(gfp_t gfp_flags);
+void its_free_prop_table(struct page *prop_page);
+
static inline bool gic_enable_sre(void)
{
u32 val;
diff --git a/include/linux/irqchip/arm-gic-v4.h b/include/linux/irqchip/arm-gic-v4.h
index 0b0887099fd7..bc493fed75ab 100644
--- a/include/linux/irqchip/arm-gic-v4.h
+++ b/include/linux/irqchip/arm-gic-v4.h
@@ -8,6 +8,7 @@
#define __LINUX_IRQCHIP_ARM_GIC_V4_H
struct its_vpe;
+struct kvm_vcpu;
/*
* Maximum number of ITTs when GITS_TYPER.VMOVP == 0, using the
@@ -42,6 +43,10 @@ struct its_vpe {
struct its_vm *its_vm;
/* per-vPE VLPI tracking */
atomic_t vlpi_count;
+ /* per-vPE domain for per-vCPU VLPI enablement */
+ struct irq_domain *lpi_domain;
+ /* enables per-vPE vLPI IRQ Domains during per-vCPU VLPI enablement */
+ struct fwnode_handle *lpi_fwnode;
/* Doorbell interrupt */
int irq;
irq_hw_number_t vpe_db_lpi;
@@ -59,7 +64,7 @@ struct its_vpe {
};
/* GICv4.1 implementations */
struct {
- struct fwnode_handle *fwnode;
+ struct fwnode_handle *sgi_fwnode;
struct irq_domain *sgi_domain;
struct {
u8 priority;
@@ -139,6 +144,7 @@ struct its_cmd_info {
};
int its_alloc_vcpu_irqs(struct its_vm *vm);
+int its_alloc_vcpu_irq(struct kvm_vcpu *vcpu);
void its_free_vcpu_irqs(struct its_vm *vm);
int its_make_vpe_resident(struct its_vpe *vpe, bool g0en, bool g1en);
int its_make_vpe_non_resident(struct its_vpe *vpe, bool db);
--
2.50.1 (Apple Git-155)
Amazon Web Services Development Center Germany GmbH
Tamara-Danz-Str. 13
10243 Berlin
Geschaeftsfuehrung: Christian Schlaeger, Christof Hellmis
Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B
Sitz: Berlin
Ust-ID: DE 365 538 597
More information about the linux-arm-kernel
mailing list