mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
KVM: selftests: Add a KVM_IRQFD test to verify uniqueness requirements
Add a selftest to verify that eventfd+irqfd bindings are globally unique, i.e. that KVM doesn't allow multiple irqfds to bind to a single eventfd, even across VMs. Tested-by: K Prateek Nayak <kprateek.nayak@amd.com> Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://lore.kernel.org/r/20250522235223.3178519-14-seanjc@google.com Signed-off-by: Sean Christopherson <seanjc@google.com>
This commit is contained in:
parent
74e5e3fb0d
commit
7e9b231c40
2 changed files with 136 additions and 0 deletions
|
@ -59,6 +59,7 @@ TEST_PROGS_x86 += x86/nx_huge_pages_test.sh
|
||||||
TEST_GEN_PROGS_COMMON = demand_paging_test
|
TEST_GEN_PROGS_COMMON = demand_paging_test
|
||||||
TEST_GEN_PROGS_COMMON += dirty_log_test
|
TEST_GEN_PROGS_COMMON += dirty_log_test
|
||||||
TEST_GEN_PROGS_COMMON += guest_print_test
|
TEST_GEN_PROGS_COMMON += guest_print_test
|
||||||
|
TEST_GEN_PROGS_COMMON += irqfd_test
|
||||||
TEST_GEN_PROGS_COMMON += kvm_binary_stats_test
|
TEST_GEN_PROGS_COMMON += kvm_binary_stats_test
|
||||||
TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus
|
TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus
|
||||||
TEST_GEN_PROGS_COMMON += kvm_page_table_test
|
TEST_GEN_PROGS_COMMON += kvm_page_table_test
|
||||||
|
|
135
tools/testing/selftests/kvm/irqfd_test.c
Normal file
135
tools/testing/selftests/kvm/irqfd_test.c
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/sysinfo.h>
|
||||||
|
|
||||||
|
#include "kvm_util.h"
|
||||||
|
|
||||||
|
static struct kvm_vm *vm1;
|
||||||
|
static struct kvm_vm *vm2;
|
||||||
|
static int __eventfd;
|
||||||
|
static bool done;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* KVM de-assigns based on eventfd *and* GSI, but requires unique eventfds when
|
||||||
|
* assigning (the API isn't symmetrical). Abuse the oddity and use a per-task
|
||||||
|
* GSI base to avoid false failures due to cross-task de-assign, i.e. so that
|
||||||
|
* the secondary doesn't de-assign the primary's eventfd and cause assign to
|
||||||
|
* unexpectedly succeed on the primary.
|
||||||
|
*/
|
||||||
|
#define GSI_BASE_PRIMARY 0x20
|
||||||
|
#define GSI_BASE_SECONDARY 0x30
|
||||||
|
|
||||||
|
static void juggle_eventfd_secondary(struct kvm_vm *vm, int eventfd)
|
||||||
|
{
|
||||||
|
int r, i;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The secondary task can encounter EBADF since the primary can close
|
||||||
|
* the eventfd at any time. And because the primary can recreate the
|
||||||
|
* eventfd, at the safe fd in the file table, the secondary can also
|
||||||
|
* encounter "unexpected" success, e.g. if the close+recreate happens
|
||||||
|
* between the first and second assignments. The secondary's role is
|
||||||
|
* mostly to antagonize KVM, not to detect bugs.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 2; i++) {
|
||||||
|
r = __kvm_irqfd(vm, GSI_BASE_SECONDARY, eventfd, 0);
|
||||||
|
TEST_ASSERT(!r || errno == EBUSY || errno == EBADF,
|
||||||
|
"Wanted success, EBUSY, or EBADF, r = %d, errno = %d",
|
||||||
|
r, errno);
|
||||||
|
|
||||||
|
/* De-assign should succeed unless the eventfd was closed. */
|
||||||
|
r = __kvm_irqfd(vm, GSI_BASE_SECONDARY + i, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
TEST_ASSERT(!r || errno == EBADF,
|
||||||
|
"De-assign should succeed unless the fd was closed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *secondary_irqfd_juggler(void *ign)
|
||||||
|
{
|
||||||
|
while (!READ_ONCE(done)) {
|
||||||
|
juggle_eventfd_secondary(vm1, READ_ONCE(__eventfd));
|
||||||
|
juggle_eventfd_secondary(vm2, READ_ONCE(__eventfd));
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void juggle_eventfd_primary(struct kvm_vm *vm, int eventfd)
|
||||||
|
{
|
||||||
|
int r1, r2;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At least one of the assigns should fail. KVM disallows assigning a
|
||||||
|
* single eventfd to multiple GSIs (or VMs), so it's possible that both
|
||||||
|
* assignments can fail, too.
|
||||||
|
*/
|
||||||
|
r1 = __kvm_irqfd(vm, GSI_BASE_PRIMARY, eventfd, 0);
|
||||||
|
TEST_ASSERT(!r1 || errno == EBUSY,
|
||||||
|
"Wanted success or EBUSY, r = %d, errno = %d", r1, errno);
|
||||||
|
|
||||||
|
r2 = __kvm_irqfd(vm, GSI_BASE_PRIMARY + 1, eventfd, 0);
|
||||||
|
TEST_ASSERT(r1 || (r2 && errno == EBUSY),
|
||||||
|
"Wanted failure (EBUSY), r1 = %d, r2 = %d, errno = %d",
|
||||||
|
r1, r2, errno);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* De-assign should always succeed, even if the corresponding assign
|
||||||
|
* failed.
|
||||||
|
*/
|
||||||
|
kvm_irqfd(vm, GSI_BASE_PRIMARY, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
kvm_irqfd(vm, GSI_BASE_PRIMARY + 1, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
pthread_t racing_thread;
|
||||||
|
int r, i;
|
||||||
|
|
||||||
|
/* Create "full" VMs, as KVM_IRQFD requires an in-kernel IRQ chip. */
|
||||||
|
vm1 = vm_create(1);
|
||||||
|
vm2 = vm_create(1);
|
||||||
|
|
||||||
|
WRITE_ONCE(__eventfd, kvm_new_eventfd());
|
||||||
|
|
||||||
|
kvm_irqfd(vm1, 10, __eventfd, 0);
|
||||||
|
|
||||||
|
r = __kvm_irqfd(vm1, 11, __eventfd, 0);
|
||||||
|
TEST_ASSERT(r && errno == EBUSY,
|
||||||
|
"Wanted EBUSY, r = %d, errno = %d", r, errno);
|
||||||
|
|
||||||
|
r = __kvm_irqfd(vm2, 12, __eventfd, 0);
|
||||||
|
TEST_ASSERT(r && errno == EBUSY,
|
||||||
|
"Wanted EBUSY, r = %d, errno = %d", r, errno);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* De-assign all eventfds, along with multiple eventfds that were never
|
||||||
|
* assigned. KVM's ABI is that de-assign is allowed so long as the
|
||||||
|
* eventfd itself is valid.
|
||||||
|
*/
|
||||||
|
kvm_irqfd(vm1, 11, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
kvm_irqfd(vm1, 12, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
kvm_irqfd(vm1, 13, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
kvm_irqfd(vm1, 14, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
kvm_irqfd(vm1, 10, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
|
||||||
|
|
||||||
|
close(__eventfd);
|
||||||
|
|
||||||
|
pthread_create(&racing_thread, NULL, secondary_irqfd_juggler, vm2);
|
||||||
|
|
||||||
|
for (i = 0; i < 10000; i++) {
|
||||||
|
WRITE_ONCE(__eventfd, kvm_new_eventfd());
|
||||||
|
|
||||||
|
juggle_eventfd_primary(vm1, __eventfd);
|
||||||
|
juggle_eventfd_primary(vm2, __eventfd);
|
||||||
|
close(__eventfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
WRITE_ONCE(done, true);
|
||||||
|
pthread_join(racing_thread, NULL);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue