// SPDX-License-Identifier: GPL-2.0 #include "kvm_util.h" #include "test_util.h" #include "apic.h" #include "processor.h" #include "proc_util.h" #include #include #include #include #include #include #include #include static u64 timeout_ns = 2ULL * 1000 * 1000 * 1000; static bool guest_ready_for_irqs[KVM_MAX_VCPUS]; static bool guest_received_irq[KVM_MAX_VCPUS]; static bool guest_received_nmi[KVM_MAX_VCPUS]; static bool x2apic = true; static bool irq_affinity; static bool done; #define GUEST_RECEIVED_IRQ(__vcpu) \ SYNC_FROM_GUEST_AND_READ((__vcpu)->vm, guest_received_irq[(__vcpu)->id]) #define GUEST_RECEIVED_NMI(__vcpu) \ SYNC_FROM_GUEST_AND_READ((__vcpu)->vm, guest_received_nmi[(__vcpu)->id]) #define GUEST_RECEIVED_INTERRUPT(__vcpu, __nmi) \ ((__nmi) ? GUEST_RECEIVED_NMI(__vcpu) : GUEST_RECEIVED_IRQ(__vcpu)) static u32 guest_get_vcpu_id(void) { if (x2apic) return x2apic_read_reg(APIC_ID); else return xapic_read_reg(APIC_ID) >> 24; } static void guest_irq_handler(struct ex_regs *regs) { WRITE_ONCE(guest_received_irq[guest_get_vcpu_id()], true); if (x2apic) x2apic_write_reg(APIC_EOI, 0); else xapic_write_reg(APIC_EOI, 0); } static void guest_nmi_handler(struct ex_regs *regs) { WRITE_ONCE(guest_received_nmi[guest_get_vcpu_id()], true); } static void guest_code(void) { if (x2apic) x2apic_enable(); else xapic_enable(); sti_nop(); WRITE_ONCE(guest_ready_for_irqs[guest_get_vcpu_id()], true); while (!READ_ONCE(done)) cpu_relax(); GUEST_DONE(); } static void *vcpu_thread_main(void *arg) { struct kvm_vcpu *vcpu = arg; struct ucall uc; vcpu_run(vcpu); TEST_ASSERT_EQ(UCALL_DONE, get_ucall(vcpu, &uc)); return NULL; } static int vfio_setup_msi(struct vfio_pci_device *device) { const int flags = MAP_SHARED | MAP_ANONYMOUS; const int prot = PROT_READ | PROT_WRITE; struct iova_allocator *allocator; struct dma_region *region; /* Sanity check that the device+driver can actually send MSIs. */ TEST_REQUIRE(device->driver.ops); TEST_REQUIRE(device->driver.ops->send_msi); /* * Set up a DMA-able region for the driver to use. Very few devices * provide a way to arbitrarily send interrupts (MSIs), e.g. by writing * an MMIO register. Instead, most devices send MSIs when an action is * completed, and practically all actions involve DMA of some form. */ allocator = iova_allocator_init(device->iommu); region = &device->driver.region; region->size = SZ_2M; region->iova = iova_allocator_alloc(allocator, region->size); region->vaddr = kvm_mmap(region->size, prot, flags, -1); TEST_ASSERT(region->vaddr != MAP_FAILED, "mmap() failed\n"); iommu_map(device->iommu, region); iova_allocator_cleanup(allocator); vfio_pci_driver_init(device); return device->driver.msi; } static void trigger_interrupt(struct vfio_pci_device *device, int eventfd) { if (device) vfio_pci_driver_send_msi(device); else eventfd_write(eventfd, 1); } static void kvm_route_msi(struct kvm_vm *vm, u32 gsi, struct kvm_vcpu *vcpu, u8 vector, bool use_nmi) { struct { struct kvm_irq_routing header; struct kvm_irq_routing_entry entry; } routing = { .header.nr = 1, .entry = { .gsi = gsi, .type = KVM_IRQ_ROUTING_MSI, .u.msi.address_lo = 0xFEE00000 | (vcpu->id & GENMASK(7, 0)) << 12, .u.msi.address_hi = vcpu->id & GENMASK(31, 8), .u.msi.data = use_nmi ? NMI_VECTOR | (4 << 8) : vector, }, }; vm_ioctl(vm, KVM_SET_GSI_ROUTING, &routing.header); } static void kvm_set_empty_gsi_routing(struct kvm_vm *vm) { struct kvm_irq_routing routing = {}; vm_ioctl(vm, KVM_SET_GSI_ROUTING, &routing); } static const char *probe_iommu_type(void) { int io_fd; io_fd = open("/dev/iommu", O_RDONLY); if (io_fd >= 0) { close(io_fd); return MODE_IOMMUFD; } io_fd = __open_path_or_exit("/dev/vfio/vfio", O_RDONLY, "Is VFIO (or IOMMUFD) loaded and enabled?"); close(io_fd); return MODE_VFIO_TYPE1_IOMMU; } static void help(const char *name) { printf("Usage: %s [-a] [-d ] [-e] [-h] [-i nr_irqs] [-m] [-n] [-t iommu_type] [-v nr_vcpus] [-x]\n", name); printf("\n"); printf("Tests KVM interrupt routing and delivery via irqfd.\n"); printf("-a Affine the device's host IRQ to a random physical CPU\n"); printf("-d Use a VFIO device to send MSI-X interrupts instead of manually signaling the eventfd\n"); printf("-e Set empty GSI routing in-between some interrupts\n"); printf("-i The number of IRQs to generate during the test\n"); printf("-m Pin target vCPU to random physical CPU before triggering interrupt\n"); printf("-n Deliver 50 percent of IRQs as non-maskable interrupts\n"); printf("-t Override the IOMMU type to use (vfio_type1_iommu or iommufd)\n"); printf("-v Number of vCPUS to run\n"); printf("-x Use xAPIC mode instead of x2APIC mode in the guest\n"); printf("\n"); exit(KSFT_FAIL); } int main(int argc, char **argv) { /* * Pick a random vector and a random GSI to use for device IRQ. * * Pick an IRQ vector in range [32, UINT8_MAX]. Min value is 32 because * Linux/x86 reserves vectors 0-31 for exceptions and architecture * defined NMIs and interrupts. * * Pick a GSI in range [24, KVM_MAX_IRQ_ROUTES - 1]. The min value is 24 * because KVM reserves GSIs 0-15 for legacy ISA IRQs and 16-23 only go * to the IOAPIC. The max is KVM_MAX_IRQ_ROUTES - 1, because * KVM_MAX_IRQ_ROUTES is exclusive. */ u32 gsi = kvm_random_u64_in_range(&kvm_rng, 24, KVM_MAX_IRQ_ROUTES - 1); u8 vector = kvm_random_u64_in_range(&kvm_rng, 32, UINT8_MAX); pthread_t vcpu_threads[KVM_MAX_VCPUS]; struct kvm_vcpu *vcpus[KVM_MAX_VCPUS]; struct vfio_pci_device *device = NULL; int nr_irqs = 1000, nr_vcpus = 1; bool set_empty_routing = false; const char *device_bdf = NULL; const char *iommu_type = NULL; int i, j, c, msix, eventfd; bool migrate_vcpus = false; cpu_set_t available_cpus; bool use_nmi = false; struct iommu *iommu; struct kvm_vm *vm; int irq, irq_cpu; while ((c = getopt(argc, argv, "ad:ehi:mnt:v:x")) != -1) { switch (c) { case 'a': irq_affinity = true; break; case 'd': device_bdf = optarg; break; case 'e': set_empty_routing = true; break; case 'i': nr_irqs = atoi_positive("Number of IRQs", optarg); break; case 'm': migrate_vcpus = true; break; case 'n': use_nmi = true; break; case 't': iommu_type = optarg; break; case 'v': nr_vcpus = atoi_positive("Number of vCPUS", optarg); TEST_ASSERT(nr_vcpus <= KVM_MAX_VCPUS, "KVM selftests support at most %u vCPUs", KVM_MAX_VCPUS); break; case 'x': x2apic = false; break; case 'h': default: help(argv[0]); } } TEST_REQUIRE(kvm_arch_has_default_irqchip()); vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus); vm_enable_cap(vm, KVM_CAP_X2APIC_API, KVM_X2APIC_API_USE_32BIT_IDS | KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK); vm_install_exception_handler(vm, vector, guest_irq_handler); vm_install_exception_handler(vm, NMI_VECTOR, guest_nmi_handler); if (!x2apic) { TEST_ASSERT(nr_vcpus < 256, "xAPIC can only target IDs [0-254] (255 vCPUs)"); virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA); } if (device_bdf) { if (!iommu_type) iommu_type = probe_iommu_type(); iommu = iommu_init(iommu_type); device = vfio_pci_device_init(device_bdf, iommu); msix = vfio_setup_msi(device); irq = vfio_msix_to_host_irq(device_bdf, msix); eventfd = device->msi_eventfds[msix]; printf("Using device %s MSI-X[%d] (IRQ-%u)\n", device_bdf, msix, irq); } else { TEST_ASSERT(!irq_affinity, "Setting IRQ affinity (-a) requires a backing device (-d)"); eventfd = kvm_new_eventfd(); irq = -1; } pr_info("Injecting interrupts for GSI %d (guest vector 0x%x) %d times\n", gsi, vector, nr_irqs); kvm_assign_irqfd(vm, gsi, eventfd); sync_global_to_guest(vm, x2apic); if (migrate_vcpus) kvm_sched_getaffinity(0, sizeof(available_cpus), &available_cpus); for (i = 0; i < nr_vcpus; i++) kvm_pthread_create(&vcpu_threads[i], NULL, vcpu_thread_main, vcpus[i]); for (i = 0; i < nr_vcpus; i++) { struct kvm_vcpu *vcpu = vcpus[i]; while (!SYNC_FROM_GUEST_AND_READ(vm, guest_ready_for_irqs[vcpu->id])) continue; } irq_cpu = -1; for (i = 0; i < nr_irqs; i++) { const bool do_set_empty_routing = set_empty_routing && (i & BIT(3)); const bool do_use_nmi = use_nmi && (i & BIT(2)); struct kvm_vcpu *vcpu = vcpus[i % nr_vcpus]; struct timespec start; if (do_set_empty_routing) kvm_set_empty_gsi_routing(vm); kvm_route_msi(vm, gsi, vcpu, vector, do_use_nmi); if (irq_affinity) { irq_cpu = kvm_random_u64(&kvm_rng) % get_nprocs(); proc_irq_set_smp_affinity(irq, irq_cpu); } if (migrate_vcpus) pin_task_to_random_cpu(vcpu_threads[i % nr_vcpus], &available_cpus); for (j = 0; j < nr_vcpus; j++) { TEST_ASSERT(!GUEST_RECEIVED_IRQ(vcpus[j]), "IRQ flag for vCPU %d not clear prior to test", vcpus[j]->id); TEST_ASSERT(!GUEST_RECEIVED_NMI(vcpus[j]), "NMI flag for vCPU %d not clear prior to test", vcpus[j]->id); } trigger_interrupt(device, eventfd); clock_gettime(CLOCK_MONOTONIC, &start); while (!GUEST_RECEIVED_INTERRUPT(vcpu, do_use_nmi) && timespec_to_ns(timespec_elapsed(start)) <= timeout_ns) cpu_relax(); TEST_ASSERT(GUEST_RECEIVED_INTERRUPT(vcpu, do_use_nmi), "vCPU %d timed out waiting for %s (vector 0x%x) from GSI %d (via CPU %d)\n", vcpu->id, do_use_nmi ? "NMI" : "IRQ", do_use_nmi ? NMI_VECTOR : vector, gsi, irq_cpu); if (do_use_nmi) WRITE_AND_SYNC_TO_GUEST(vm, guest_received_nmi[vcpu->id], false); else WRITE_AND_SYNC_TO_GUEST(vm, guest_received_irq[vcpu->id], false); } WRITE_AND_SYNC_TO_GUEST(vm, done, true); for (i = 0; i < nr_vcpus; i++) kvm_pthread_join(vcpu_threads[i], NULL); return 0; }