From: Maximilian Dittgen At the moment, all MSIs injected from userspace using KVM_SIGNAL_MSI are processed as LPIs in software with a hypervisor trap and exit. To properly test GICv4 direct vLPI injection from KVM selftests, we write a KVM_DEBUG_GIC_MSI_SETUP ioctl that manually creates an IRQ routing table entry for the specified MSI, and populates ITS structures (device, collection, and interrupt translation table entries) to map the MSI to a vLPI. We then call GICv4 kvm_vgic_v4_set_forwarding to let the vLPI bypass hypervisor traps and inject directly to the vCPU. To demonstrate the use of this ioctl, we implement a -D flag to the vgic_lpi_stress.c selftest that runs the stress test using direct vLPI injection rather than software-emulated LPI handling. Signed-off-by: Maximilian Dittgen --- arch/arm64/kvm/arm.c | 37 +++++ arch/arm64/kvm/vgic/vgic-its.c | 133 ++++++++++++++++++ arch/arm64/kvm/vgic/vgic.h | 2 + include/linux/irqchip/arm-gic-v3.h | 1 + include/uapi/linux/kvm.h | 15 ++ .../selftests/kvm/arm64/vgic_lpi_stress.c | 52 ++++++- 6 files changed, 238 insertions(+), 2 deletions(-) diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 5bf101c869c9..e18f5ff68274 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -46,6 +46,8 @@ #include #include +#include + #include "sys_regs.h" static enum kvm_mode kvm_mode = KVM_MODE_DEFAULT; @@ -1927,6 +1929,41 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) return -EFAULT; return kvm_vm_ioctl_get_reg_writable_masks(kvm, &range); } + case KVM_DEBUG_GIC_MSI_SETUP: { + /* Define interrupt ID boundaries for input validation */ + #define GIC_LPI_OFFSET 8192 + #define GIC_LPI_MAX 65535 + #define SPI_INTID_MIN 32 + #define SPI_INTID_MAX 1019 + + struct kvm_debug_gic_msi_setup params; + struct kvm_vcpu *vcpu; + + if (copy_from_user(¶ms, argp, sizeof(params))) + return -EFAULT; + + /* validate vcpu_id is in range and exists */ + if (params.vcpu_id >= atomic_read(&kvm->online_vcpus)) + return -EINVAL; + + vcpu = kvm_get_vcpu(kvm, params.vcpu_id); + if (!vcpu) + return -EINVAL; + + /* validate vintid is in LPI range */ + if (params.vintid < GIC_LPI_OFFSET || params.vintid > GIC_LPI_MAX) + return -EINVAL; + + /* + * Validate host_irq is in safe range -- we use SPI range since + * selftests guests will have no shared peripheral devices + */ + if (params.host_irq < SPI_INTID_MIN || params.host_irq > SPI_INTID_MAX) + return -EINVAL; + + /* Mock single MSI for testing */ + return debug_gic_msi_setup_mock_msi(kvm, ¶ms); + } default: return -EINVAL; } diff --git a/arch/arm64/kvm/vgic/vgic-its.c b/arch/arm64/kvm/vgic/vgic-its.c index 7368c13f16b7..46153ef5efcb 100644 --- a/arch/arm64/kvm/vgic/vgic-its.c +++ b/arch/arm64/kvm/vgic/vgic-its.c @@ -2816,3 +2816,136 @@ int kvm_vgic_register_its_device(void) return kvm_register_device_ops(&kvm_arm_vgic_its_ops, KVM_DEV_TYPE_ARM_VGIC_ITS); } + +static struct vgic_its *vgic_get_its(struct kvm *kvm, + struct kvm_kernel_irq_routing_entry *irq_entry) +{ + struct kvm_msi msi = (struct kvm_msi) { + .address_lo = irq_entry->msi.address_lo, + .address_hi = irq_entry->msi.address_hi, + .data = irq_entry->msi.data, + .flags = irq_entry->msi.flags, + .devid = irq_entry->msi.devid, + }; + + return vgic_msi_to_its(kvm, &msi); +} + +/* + * debug_gic_msi_setup_mock_msi - manually set up vLPI direct injection infrastructure + * for an MSI upon userspace request. Used for testing vLPIs from selftests. + * + * Creates an IRQ routing entry mapping the specified MSI signature to a mock + * host IRQ, then populates ITS structures (device, collection, ITE) to establish + * the DevID/EventID to LPI translation. Finally enables GICv4 vLPI forwarding + * to bypass software emulation and inject interrupts directly to the vCPU. + * + * This function is intended solely for KVM selftests via KVM_DEBUG_GIC_MSI_SETUP. + * It uses mock host IRQs in the SPI range assuming no real hardware devices are + * present on a selftest guest. Using this interface in production will corrupt the + * IRQ routing table. + */ +int debug_gic_msi_setup_mock_msi(struct kvm *kvm, struct kvm_debug_gic_msi_setup *params) +{ + struct kvm_irq_routing_entry user_entry; + struct kvm_kernel_irq_routing_entry entry; + struct vgic_its *its; + struct its_device *device; + struct its_collection *collection; + struct its_ite *ite; + struct vgic_irq *irq; + struct kvm_vcpu *vcpu; + u64 doorbell_addr = GITS_BASE_GPA + GITS_TRANSLATER; + u32 device_id = params->device_id; + u32 event_id = params->event_id; + u32 coll_id = params->vcpu_id; + u32 lpi_nr = params->vintid; + gpa_t itt_addr = params->itt_addr; + int ret; + int host_irq = params->host_irq; + + // Unmap any existing vLPI on the mock host IRQ (remnants from prior mocks) + kvm_vgic_v4_unset_forwarding(kvm, host_irq); + + /* Create mock user IRQ routing entry using kvm_set_routing_entry function */ + memset(&user_entry, 0, sizeof(user_entry)); + user_entry.gsi = host_irq; + user_entry.type = KVM_IRQ_ROUTING_MSI; + user_entry.u.msi.address_lo = doorbell_addr & 0xFFFFFFFF; + user_entry.u.msi.address_hi = doorbell_addr >> 32; + user_entry.u.msi.data = event_id; + user_entry.u.msi.devid = device_id; + user_entry.flags = KVM_MSI_VALID_DEVID; + + /* Initialize kernel routing entry */ + memset(&entry, 0, sizeof(entry)); + + /* Use vgic-irqfd.c function to create entry */ + ret = kvm_set_routing_entry(kvm, &entry, &user_entry); + if (ret) + return ret; + + /* Now that we created an MSI -> ITS mapping, we can populate the ITS for this MSI */ + + /* Get ITS instance */ + its = vgic_get_its(kvm, &entry); + if (IS_ERR(its)) + return PTR_ERR(its); + + /* Enable ITS manually for testing, normally done by guest writing to GITS_CTLR register */ + its->enabled = true; + + /* Get target vCPU */ + vcpu = kvm_get_vcpu(kvm, params->vcpu_id); + if (!vcpu) + return -EINVAL; + + /* + * Enable this vLPIs for this vCPU manually for testing, normally + * done by guest writing GICR_CTLR + */ + atomic_set(&vcpu->arch.vgic_cpu.ctlr, GICR_CTLR_ENABLE_LPIS); + + mutex_lock(&its->its_lock); + + /* Create ITS device */ + device = vgic_its_alloc_device(its, device_id, itt_addr, 8); + if (IS_ERR(device)) { + ret = PTR_ERR(device); + goto unlock; + } + + /* Create collection mapped to inputted vcpu */ + ret = vgic_its_alloc_collection(its, &collection, coll_id); + if (ret) + goto unlock; + + collection->target_addr = params->vcpu_id; // Map to specified vcpu + + /* Create ITE */ + ite = vgic_its_alloc_ite(device, collection, event_id); + if (IS_ERR(ite)) { + ret = PTR_ERR(ite); + vgic_its_free_collection(its, coll_id); + goto unlock; + } + + /* Create LPI */ + irq = vgic_add_lpi(kvm, lpi_nr, vcpu); + if (IS_ERR(irq)) { + ret = PTR_ERR(irq); + its_free_ite(kvm, ite); + vgic_its_free_collection(its, coll_id); + goto unlock; + } + + ite->irq = irq; + mutex_unlock(&its->its_lock); + + /* Now that routing entry is initialized, call v4 forwarding setup */ + return kvm_vgic_v4_set_forwarding(kvm, host_irq, &entry); + +unlock: + mutex_unlock(&its->its_lock); + return ret; +} diff --git a/arch/arm64/kvm/vgic/vgic.h b/arch/arm64/kvm/vgic/vgic.h index de1c1d3261c3..8c8f1e963884 100644 --- a/arch/arm64/kvm/vgic/vgic.h +++ b/arch/arm64/kvm/vgic/vgic.h @@ -432,4 +432,6 @@ static inline bool vgic_is_v3(struct kvm *kvm) int vgic_its_debug_init(struct kvm_device *dev); void vgic_its_debug_destroy(struct kvm_device *dev); +int debug_gic_msi_setup_mock_msi(struct kvm *kvm, struct kvm_debug_gic_msi_setup *params); + #endif diff --git a/include/linux/irqchip/arm-gic-v3.h b/include/linux/irqchip/arm-gic-v3.h index 70c0948f978e..76beac55cb69 100644 --- a/include/linux/irqchip/arm-gic-v3.h +++ b/include/linux/irqchip/arm-gic-v3.h @@ -378,6 +378,7 @@ #define GITS_CIDR3 0xfffc #define GITS_TRANSLATER 0x10040 +#define GITS_BASE_GPA 0x8000000ULL #define GITS_SGIR 0x20020 diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index f0f0d49d2544..a655bbb70e99 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -1440,6 +1440,21 @@ struct kvm_enc_region { #define KVM_GET_SREGS2 _IOR(KVMIO, 0xcc, struct kvm_sregs2) #define KVM_SET_SREGS2 _IOW(KVMIO, 0xcd, struct kvm_sregs2) +/* + * Generate an IRQ routing entry and vLPI tables for userspace-sourced + * MSI, enabling direct vLPI injection testing from selftests + */ +#define KVM_DEBUG_GIC_MSI_SETUP _IOW(KVMIO, 0xf0, struct kvm_debug_gic_msi_setup) + +struct kvm_debug_gic_msi_setup { + __u32 device_id; + __u32 event_id; + __u32 vcpu_id; + __u32 vintid; + __u32 host_irq; + __u64 itt_addr; +}; + #define KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE (1 << 0) #define KVM_DIRTY_LOG_INITIALLY_SET (1 << 1) diff --git a/tools/testing/selftests/kvm/arm64/vgic_lpi_stress.c b/tools/testing/selftests/kvm/arm64/vgic_lpi_stress.c index fc4fe52fb6f8..8350665d9bdc 100644 --- a/tools/testing/selftests/kvm/arm64/vgic_lpi_stress.c +++ b/tools/testing/selftests/kvm/arm64/vgic_lpi_stress.c @@ -18,10 +18,14 @@ #include "ucall.h" #include "vgic.h" +#define KVM_DEBUG_GIC_MSI_SETUP _IOW(KVMIO, 0xf0, struct kvm_debug_gic_msi_setup) + #define TEST_MEMSLOT_INDEX 1 #define GIC_LPI_OFFSET 8192 +static bool vlpi_enabled; + static size_t nr_iterations = 1000; static vm_paddr_t gpa_base; @@ -220,6 +224,21 @@ static void setup_gic(void) its_fd = vgic_its_setup(vm); } +static int enable_msi_vlpi_injection(u32 device_id, u32 event_id, + u32 vcpu_id, u32 vintid, u32 host_irq) +{ + struct kvm_debug_gic_msi_setup params = { + .device_id = device_id, + .event_id = event_id, + .vcpu_id = vcpu_id, + .vintid = vintid, + .host_irq = host_irq, + .itt_addr = test_data.itt_tables + (device_id * SZ_64K) + }; + + return __vm_ioctl(vm, KVM_DEBUG_GIC_MSI_SETUP, ¶ms); +} + static void signal_lpi(u32 device_id, u32 event_id) { vm_paddr_t db_addr = GITS_BASE_GPA + GITS_TRANSLATER; @@ -267,6 +286,30 @@ static void *vcpu_worker_thread(void *data) switch (get_ucall(vcpu, &uc)) { case UCALL_SYNC: + /* if flag is set, set direct injection mappings for MSIs */ + if (vlpi_enabled) { + u32 intid = GIC_LPI_OFFSET; + + for (u32 device_id = 0; device_id < test_data.nr_devices; + device_id++) { + for (u32 event_id = 0; event_id < test_data.nr_event_ids; + event_id++) { + + /* we mock host_irqs in the SPI interrupt range of + * 100-1020 since selftest guests have no hardware + * devices + */ + int ret = enable_msi_vlpi_injection(device_id, + event_id, vcpu->id, intid, + intid - GIC_LPI_OFFSET + 100); + TEST_ASSERT(ret == 0, "KVM_DEBUG_GIC_MSI_SETUP failed: %d", + ret); + + intid++; + } + } + } + pthread_barrier_wait(&test_setup_barrier); continue; case UCALL_DONE: @@ -362,7 +405,9 @@ static void destroy_vm(void) static void pr_usage(const char *name) { - pr_info("%s [-v NR_VCPUS] [-d NR_DEVICES] [-e NR_EVENTS] [-i ITERS] -h\n", name); + pr_info("%s -D [-v NR_VCPUS] [-d NR_DEVICES] [-e NR_EVENTS] [-i ITERS] -h\n", name); + pr_info(" -D:\tenable direct vLPI injection (default: %s)\n", + vlpi_enabled ? "true" : "false"); pr_info(" -v:\tnumber of vCPUs (default: %u)\n", test_data.nr_cpus); pr_info(" -d:\tnumber of devices (default: %u)\n", test_data.nr_devices); pr_info(" -e:\tnumber of event IDs per device (default: %u)\n", test_data.nr_event_ids); @@ -374,8 +419,11 @@ int main(int argc, char **argv) u32 nr_threads; int c; - while ((c = getopt(argc, argv, "hv:d:e:i:")) != -1) { + while ((c = getopt(argc, argv, "hDv:d:e:i:")) != -1) { switch (c) { + case 'D': + vlpi_enabled = true; + break; case 'v': test_data.nr_cpus = atoi(optarg); break; -- 2.50.1 (Apple Git-155) Amazon Web Services Development Center Germany GmbH Tamara-Danz-Str. 13 10243 Berlin Geschaeftsfuehrung: Christian Schlaeger Eingetragen am Amtsgericht Charlottenburg unter HRB 257764 B Sitz: Berlin Ust-ID: DE 365 538 597