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.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
tools/testing/selftests/kvm/Makefile.kvm | 1 +
tools/testing/selftests/kvm/irqfd_test.c | 130 +++++++++++++++++++++++
2 files changed, 131 insertions(+)
create mode 100644 tools/testing/selftests/kvm/irqfd_test.c
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index f62b0a5aba35..318adf3ef6b6 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -54,6 +54,7 @@ TEST_PROGS_x86 += x86/nx_huge_pages_test.sh
TEST_GEN_PROGS_COMMON = demand_paging_test
TEST_GEN_PROGS_COMMON += dirty_log_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_create_max_vcpus
TEST_GEN_PROGS_COMMON += kvm_page_table_test
diff --git a/tools/testing/selftests/kvm/irqfd_test.c b/tools/testing/selftests/kvm/irqfd_test.c
new file mode 100644
index 000000000000..286f2b15fde6
--- /dev/null
+++ b/tools/testing/selftests/kvm/irqfd_test.c
@@ -0,0 +1,130 @@
+// 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);
+
+ 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);
+}
--
2.49.0.1151.ga128411c76-goog
On 5/23/2025 5:22 AM, Sean Christopherson wrote:
> +
> +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);
> +
> + 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);
Hi Sean,
I dont see any allocation for the GSI 13 and 14..
Is there any reason for the deassigning these two GSIs ?
Regards
Sairaj Kodilkar
> + 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);
> +}
On Fri, May 23, 2025, Sairaj Kodilkar wrote:
> On 5/23/2025 5:22 AM, Sean Christopherson wrote:
>
> > +
> > +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);
> > +
> > + 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);
>
> Hi Sean,
> I dont see any allocation for the GSI 13 and 14..
> Is there any reason for the deassigning these two GSIs ?
Yes, KVM's rather bizarre ABI is that DEASSIGN is allowed even if the VM doesn't
have a corresponding assigned irqfd. The reason I added these early DEASSIGN
calls is so that there will be an easier-to-debug failure if KVM's behavior
changes (the racing threads part of the test abuses KVM's ABI). I didn't add a
comment because the helpers already have comments, but looking at this again, I
agree that main() needs a better comment.
On 5/23/2025 8:03 PM, Sean Christopherson wrote:
> On Fri, May 23, 2025, Sairaj Kodilkar wrote:
>> On 5/23/2025 5:22 AM, Sean Christopherson wrote:
>>
>>> +
>>> +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);
>>> +
>>> + 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);
>>
>> Hi Sean,
>> I dont see any allocation for the GSI 13 and 14..
>> Is there any reason for the deassigning these two GSIs ?
>
> Yes, KVM's rather bizarre ABI is that DEASSIGN is allowed even if the VM doesn't
> have a corresponding assigned irqfd. The reason I added these early DEASSIGN
> calls is so that there will be an easier-to-debug failure if KVM's behavior
> changes (the racing threads part of the test abuses KVM's ABI). I didn't add a
> comment because the helpers already have comments, but looking at this again, I
> agree that main() needs a better comment.
Makes sense, thanks for the explanation.
Thanks
Sairaj Kodilkar
© 2016 - 2025 Red Hat, Inc.