// 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 <libvfio.h>
#include <linux/sizes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/eventfd.h>
#include <sys/sysinfo.h>
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 <segment:bus:device.function>] [-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;
}