* [PATCH] KVM: optionally post write on ioeventfd write
@ 2026-03-06 12:56 Thanos Makatos
2026-03-12 15:02 ` David Woodhouse
` (3 more replies)
0 siblings, 4 replies; 28+ messages in thread
From: Thanos Makatos @ 2026-03-06 12:56 UTC (permalink / raw)
To: seanjc@google.com
Cc: pbonzini@redhat.com, John Levon, kvm@vger.kernel.org,
Thanos Makatos
Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an
ioeventfd that results in the value written by the guest to be copied
to user-supplied memory instead of being discarded.
The goal of this new mechanism is to speed up doorbell writes on NVMe
controllers emulated outside of the VMM. Currently, a doorbell write to
an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the
VMM communicating the event, along with the doorbell value, to the NVMe
controller emulation task. With POST_WRITE, the NVMe emulation task is
directly notified of the doorbell write and can find the doorbell value
in a known location, without involving VMM.
Add tests for this new functionality.
LLM (claude-4.6-opus-high) was used mainly for the tests and to a
lesser extent for pre-reviewing this patch.
Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com>
---
Documentation/virt/kvm/api.rst | 13 +-
include/uapi/linux/kvm.h | 6 +-
tools/testing/selftests/kvm/Makefile.kvm | 1 +
tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++
virt/kvm/eventfd.c | 23 +
virt/kvm/kvm_main.c | 1 +
6 files changed, 666 insertions(+), 2 deletions(-)
create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 6f85e1b321dd..b8d030f03101 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -2109,7 +2109,8 @@ provided event instead of triggering an exit.
__u32 len; /* 0, 1, 2, 4, or 8 bytes */
__s32 fd;
__u32 flags;
- __u8 pad[36];
+ __aligned_u64 post_addr; /* address to write to if POST_WRITE is set */
+ __u8 pad[24];
};
For the special case of virtio-ccw devices on s390, the ioevent is matched
@@ -2122,6 +2123,7 @@ The following flags are defined::
#define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign)
#define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \
(1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify)
+ #define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write)
If datamatch flag is set, the event will be signaled only if the written value
to the registered address is equal to datamatch in struct kvm_ioeventfd.
@@ -2134,6 +2136,15 @@ the kernel will ignore the length of guest write and may get a faster vmexit.
The speedup may only apply to specific architectures, but the ioeventfd will
work anyway.
+With KVM_IOEVENTFD_FLAG_POST_WRITE, the value being written is copied to the
+userspace address specified by post_addr, and the eventfd is signaled. The
+copy is guaranteed to complete before the eventfd is signaled, so a userspace
+reader that wakes on the eventfd will observe the written value. When multiple
+vCPUs write to the same ioeventfd concurrently, the value at post_addr reflects
+one of the writes. If the copy to post_addr fails (e.g. the memory has been
+unmapped), the eventfd is not signaled and the write is reported to userspace
+as a regular MMIO/PIO exit.
+
4.60 KVM_DIRTY_TLB
------------------
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 65500f5db379..55b8683a856f 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -639,6 +639,7 @@ enum {
kvm_ioeventfd_flag_nr_deassign,
kvm_ioeventfd_flag_nr_virtio_ccw_notify,
kvm_ioeventfd_flag_nr_fast_mmio,
+ kvm_ioeventfd_flag_nr_post_write,
kvm_ioeventfd_flag_nr_max,
};
@@ -647,6 +648,7 @@ enum {
#define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign)
#define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \
(1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify)
+#define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write)
#define KVM_IOEVENTFD_VALID_FLAG_MASK ((1 << kvm_ioeventfd_flag_nr_max) - 1)
@@ -656,7 +658,8 @@ struct kvm_ioeventfd {
__u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */
__s32 fd;
__u32 flags;
- __u8 pad[36];
+ __aligned_u64 post_addr; /* address to write to if POST_WRITE is set */
+ __u8 pad[24];
};
#define KVM_X86_DISABLE_EXITS_MWAIT (1 << 0)
@@ -985,6 +988,7 @@ struct kvm_enable_cap {
#define KVM_CAP_ARM_SEA_TO_USER 245
#define KVM_CAP_S390_USER_OPEREXEC 246
#define KVM_CAP_S390_KEYOP 247
+#define KVM_CAP_IOEVENTFD_POST_WRITE 248
struct kvm_irq_routing_irqchip {
__u32 irqchip;
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index fdec90e85467..7ab470981c31 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -64,6 +64,7 @@ TEST_GEN_PROGS_COMMON += kvm_binary_stats_test
TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus
TEST_GEN_PROGS_COMMON += kvm_page_table_test
TEST_GEN_PROGS_COMMON += set_memory_region_test
+TEST_GEN_PROGS_COMMON += ioeventfd_test
# Compiled test targets
TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON)
diff --git a/tools/testing/selftests/kvm/ioeventfd_test.c b/tools/testing/selftests/kvm/ioeventfd_test.c
new file mode 100644
index 000000000000..24875a2562d4
--- /dev/null
+++ b/tools/testing/selftests/kvm/ioeventfd_test.c
@@ -0,0 +1,624 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ioeventfd_test.c - Tests for KVM_IOEVENTFD_FLAG_POST_WRITE.
+ *
+ * Tests that when KVM_IOEVENTFD_FLAG_POST_WRITE is set and the MMIO/PIO
+ * address is written to, the value is copied to the user-provided address
+ * and the eventfd is signaled. Also tests negative cases and interactions
+ * with DATAMATCH.
+ *
+ * Copyright Nutanix, 2026
+ *
+ * Author: Thanos Makatos <thanos.makatos@nutanix.com>
+ */
+
+#include <errno.h>
+#include <poll.h>
+#include <string.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "ucall_common.h"
+
+#define MMIO_GPA (1UL << 30)
+#define PIO_PORT 0xe000
+#define TEST_VAL 0xDEADBEEFCAFEBABEULL
+#define MATCH_VAL 0x42U
+#define NOMATCH_VAL (MATCH_VAL + 1)
+#define POISON_VAL 0xFFFFFFFFU
+
+/*
+ * Check that the most recent vCPU exit is a ucall (delivered as KVM_EXIT_IO
+ * on x86) matching @expected_cmd. The caller must have already called
+ * vcpu_run().
+ *
+ * @expected_cmd: UCALL_SYNC, UCALL_DONE, etc.
+ * @expected_stage: for UCALL_SYNC, the stage number passed by GUEST_SYNC().
+ * Ignored for other ucall types.
+ *
+ * Aborts the test on UCALL_ABORT (a guest-side assertion failure).
+ */
+static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_cmd,
+ uint64_t expected_stage)
+{
+ struct ucall uc;
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+ switch (get_ucall(vcpu, &uc)) {
+ case UCALL_ABORT:
+ REPORT_GUEST_ASSERT(uc);
+ break;
+ case UCALL_SYNC:
+ TEST_ASSERT(expected_cmd == UCALL_SYNC,
+ "got UCALL_SYNC, expected %lu",
+ expected_cmd);
+ TEST_ASSERT(uc.args[1] == expected_stage,
+ "expected stage %lu, got %lu",
+ expected_stage, uc.args[1]);
+ break;
+ case UCALL_DONE:
+ TEST_ASSERT(expected_cmd == UCALL_DONE,
+ "got UCALL_DONE, expected %lu",
+ expected_cmd);
+ break;
+ default:
+ TEST_FAIL("unexpected ucall %lu", uc.cmd);
+ }
+}
+
+/*
+ * Verify that KVM_IOEVENTFD rejects invalid POST_WRITE configurations:
+ * - len=0: the kernel needs a non-zero length to know how many bytes to copy.
+ * - post_addr=NULL: there is no destination for the copy.
+ * - post_addr outside the process address space: access_ok() rejects it.
+ * All three must fail with EINVAL.
+ */
+static void test_post_write_negative(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vm *vm;
+ uint64_t dummy;
+ int ret;
+ int fd;
+
+ vm = vm_create_barebones();
+ fd = kvm_new_eventfd();
+
+ /* length cannot be zero */
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = MMIO_GPA,
+ .len = 0,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE,
+ .post_addr = (u64)&dummy,
+ };
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "len=0: expected EINVAL, got ret=%d errno=%d", ret, errno);
+
+ /* post_addr cannot be NULL */
+ ioeventfd.len = 4;
+ ioeventfd.post_addr = 0ULL;
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "NULL post_addr: expected EINVAL, got ret=%d errno=%d",
+ ret, errno);
+
+ /* bogus post_addr */
+ ioeventfd.post_addr = (u64)0xdeaddeaddeaddeadULL;
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(ret && errno == EINVAL,
+ "bad post_addr: expected EINVAL, got ret=%d errno=%d",
+ ret, errno);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+#define DEFINE_GUEST_WRITE_FN(suffix, type) \
+static void guest_code_w##suffix(void) { \
+ *(volatile type *)MMIO_GPA = (type)TEST_VAL; \
+ GUEST_DONE(); \
+}
+
+DEFINE_GUEST_WRITE_FN(1, uint8_t)
+DEFINE_GUEST_WRITE_FN(2, uint16_t)
+DEFINE_GUEST_WRITE_FN(4, uint32_t)
+DEFINE_GUEST_WRITE_FN(8, uint64_t)
+
+/*
+ * Verify that ioeventfd_write copies exactly @width bytes to post_addr for
+ * each supported MMIO write width (1, 2, 4, 8). The guest writes the low
+ * @width bytes of TEST_VAL; the host checks that exactly those bytes land
+ * at post_addr and the eventfd is signaled.
+ */
+static void test_post_write_width(int width, void (*guest_fn)(void))
+{
+ uint64_t actual, expected, count;
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ int fd, ret;
+
+ /* need to initialize to 0 because the guest writes the low @width bytes */
+ actual = 0;
+ expected = 0;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_fn);
+ virt_map(vm, MMIO_GPA, MMIO_GPA, 1);
+
+ fd = kvm_new_eventfd();
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = MMIO_GPA,
+ .len = width,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ ret = read(fd, &count, sizeof(count));
+ TEST_ASSERT(ret == sizeof(count),
+ "eventfd read failed: ret=%d errno=%d", ret, errno);
+
+ memcpy(&expected, &(uint64_t){TEST_VAL}, width);
+ TEST_ASSERT_EQ(actual, expected);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+static void guest_code_datamatch(void)
+{
+ *(volatile uint32_t *)MMIO_GPA = MATCH_VAL;
+ GUEST_SYNC(1);
+ *(volatile uint32_t *)MMIO_GPA = NOMATCH_VAL;
+ GUEST_SYNC(2);
+ GUEST_DONE();
+}
+
+/*
+ * Test the interaction between DATAMATCH and POST_WRITE. When both flags are
+ * set, ioeventfd_write should only fire (signal eventfd + copy value) when the
+ * written value matches datamatch. A non-matching write must leave the eventfd
+ * unsignaled and post_addr untouched, and fall through to KVM_EXIT_MMIO.
+ */
+static void test_post_write_datamatch(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
+ struct kvm_vm *vm;
+ struct pollfd pfd;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_datamatch);
+ virt_map(vm, MMIO_GPA, MMIO_GPA, 1);
+ run = vcpu->run;
+
+ fd = kvm_new_eventfd();
+ pfd = (struct pollfd){ .fd = fd, .events = POLLIN };
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .datamatch = MATCH_VAL,
+ .addr = MMIO_GPA,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE |
+ KVM_IOEVENTFD_FLAG_DATAMATCH,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ /*
+ * Guest writes MATCH_VAL → ioeventfd fires (value copied, eventfd
+ * signaled), vCPU continues, then GUEST_SYNC(1).
+ */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, 1);
+ TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count),
+ "eventfd read failed: errno=%d", errno);
+ TEST_ASSERT_EQ(actual, MATCH_VAL);
+
+ actual = POISON_VAL;
+
+ /*
+ * Guest writes NOMATCH_VAL → ioeventfd_in_range() returns false, bus
+ * returns -EOPNOTSUPP → KVM_EXIT_MMIO to userspace.
+ */
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ TEST_ASSERT(run->mmio.is_write, "expected MMIO write");
+ TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA,
+ "expected MMIO at 0x%lx, got 0x%llx",
+ MMIO_GPA, run->mmio.phys_addr);
+
+ /* Re-enter: KVM completes the MMIO, guest runs to GUEST_SYNC(2). */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, 2);
+
+ TEST_ASSERT(poll(&pfd, 1, 0) == 0,
+ "eventfd should not be signaled after non-matching write");
+ TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL);
+
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+static void guest_code_multi(void)
+{
+ *(volatile uint32_t *)MMIO_GPA = 0x11111111;
+ GUEST_SYNC(1);
+ *(volatile uint32_t *)MMIO_GPA = 0x22222222;
+ GUEST_SYNC(2);
+ *(volatile uint32_t *)MMIO_GPA = 0x33333333;
+ GUEST_SYNC(3);
+ GUEST_DONE();
+}
+
+/*
+ * Verify that post_addr is updated on every MMIO write, not just the first.
+ * The guest writes three distinct values in sequence; the host checks after
+ * each one that post_addr holds the latest value and the eventfd is signaled
+ * each time.
+ */
+static void test_post_write_multi(void)
+{
+ static const uint32_t expected[] = {
+ 0x11111111, 0x22222222, 0x33333333,
+ };
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret, i;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi);
+ virt_map(vm, MMIO_GPA, MMIO_GPA, 1);
+
+ fd = kvm_new_eventfd();
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = MMIO_GPA,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ for (i = 0; i < ARRAY_SIZE(expected); i++) {
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, i + 1);
+ TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count),
+ "eventfd read failed: errno=%d", errno);
+ TEST_ASSERT_EQ(actual, expected[i]);
+ }
+
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+static void guest_code_multi_nosync(void)
+{
+ *(volatile uint32_t *)MMIO_GPA = 0x11111111;
+ *(volatile uint32_t *)MMIO_GPA = 0x22222222;
+ *(volatile uint32_t *)MMIO_GPA = 0x33333333;
+ GUEST_DONE();
+}
+
+/*
+ * Variant of the multi-write test where the guest performs three consecutive
+ * MMIO writes with no GUEST_SYNC in between. All three are handled in-kernel
+ * by ioeventfd before the vCPU exits at GUEST_DONE. Verify that:
+ * - post_addr reflects the last written value (0x33333333).
+ * - A single eventfd read() returns a counter of 3 (one signal per write).
+ */
+static void test_post_write_multi_nosync(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi_nosync);
+ virt_map(vm, MMIO_GPA, MMIO_GPA, 1);
+
+ fd = kvm_new_eventfd();
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = MMIO_GPA,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ ret = read(fd, &count, sizeof(count));
+ TEST_ASSERT(ret == sizeof(count),
+ "eventfd read failed: ret=%d errno=%d", ret, errno);
+ TEST_ASSERT_EQ(count, (uint64_t)3);
+ TEST_ASSERT_EQ(actual, (uint32_t)0x33333333);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+static void guest_code_deassign(void)
+{
+ *(volatile uint32_t *)MMIO_GPA = MATCH_VAL;
+ GUEST_SYNC(1);
+ *(volatile uint32_t *)MMIO_GPA = MATCH_VAL;
+ GUEST_DONE();
+}
+
+/*
+ * Verify that deassigning an ioeventfd with POST_WRITE fully removes it from
+ * the I/O bus.
+ */
+static void test_post_write_deassign(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
+ struct kvm_vm *vm;
+ struct pollfd pfd;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_deassign);
+ virt_map(vm, MMIO_GPA, MMIO_GPA, 1);
+ run = vcpu->run;
+
+ fd = kvm_new_eventfd();
+ pfd = (struct pollfd){ .fd = fd, .events = POLLIN };
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = MMIO_GPA,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD assign failed: %s", strerror(errno));
+
+ /*
+ * Guest writes MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1).
+ */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, 1);
+ TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count),
+ "eventfd read failed: errno=%d", errno);
+ TEST_ASSERT_EQ(actual, MATCH_VAL);
+
+ /* Deassign the ioeventfd. */
+ ioeventfd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN;
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD deassign failed: %s", strerror(errno));
+
+ actual = POISON_VAL;
+
+ /*
+ * Guest writes MATCH_VAL again → no handler on the bus →
+ * KVM_EXIT_MMIO to userspace.
+ */
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
+ TEST_ASSERT(run->mmio.is_write, "expected MMIO write");
+ TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA,
+ "expected MMIO at 0x%lx, got 0x%llx",
+ MMIO_GPA, run->mmio.phys_addr);
+
+ /* Re-enter: KVM completes MMIO, guest runs to GUEST_DONE. */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ TEST_ASSERT(poll(&pfd, 1, 0) == 0,
+ "eventfd should not be signaled after deassign");
+ TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+#ifdef __x86_64__
+static void guest_code_pio(void)
+{
+ outl(PIO_PORT, (uint32_t)TEST_VAL);
+ GUEST_DONE();
+}
+
+/*
+ * Verify that POST_WRITE works on the PIO bus (KVM_PIO_BUS), not just MMIO.
+ * The guest does an outl to PIO_PORT; the host checks that the written value
+ * is copied to post_addr and the eventfd is signaled.
+ */
+static void test_post_write_pio(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_vm *vm;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio);
+
+ fd = kvm_new_eventfd();
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .addr = PIO_PORT,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE |
+ KVM_IOEVENTFD_FLAG_PIO,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ ret = read(fd, &count, sizeof(count));
+ TEST_ASSERT(ret == sizeof(count),
+ "eventfd read failed: ret=%d errno=%d", ret, errno);
+
+ TEST_ASSERT_EQ(actual, (uint32_t)TEST_VAL);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+
+static void guest_code_pio_datamatch(void)
+{
+ outl(PIO_PORT, MATCH_VAL);
+ GUEST_SYNC(1);
+ outl(PIO_PORT, NOMATCH_VAL);
+ GUEST_SYNC(2);
+ GUEST_DONE();
+}
+
+/*
+ * Test POST_WRITE + PIO + DATAMATCH together. When all three flags are set,
+ * the ioeventfd should only fire when the outl value matches datamatch.
+ * A non-matching outl must fall through to KVM_EXIT_IO (port I/O exit),
+ * leaving the eventfd unsignaled and post_addr untouched.
+ */
+static void test_post_write_pio_datamatch(void)
+{
+ struct kvm_ioeventfd ioeventfd;
+ struct kvm_vcpu *vcpu;
+ struct kvm_run *run;
+ struct kvm_vm *vm;
+ struct pollfd pfd;
+ uint64_t count;
+ uint32_t actual;
+ int fd, ret;
+
+ actual = POISON_VAL;
+
+ vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio_datamatch);
+ run = vcpu->run;
+
+ fd = kvm_new_eventfd();
+ pfd = (struct pollfd){ .fd = fd, .events = POLLIN };
+
+ ioeventfd = (struct kvm_ioeventfd) {
+ .datamatch = MATCH_VAL,
+ .addr = PIO_PORT,
+ .len = 4,
+ .fd = fd,
+ .flags = KVM_IOEVENTFD_FLAG_POST_WRITE |
+ KVM_IOEVENTFD_FLAG_PIO |
+ KVM_IOEVENTFD_FLAG_DATAMATCH,
+ .post_addr = (u64)&actual,
+ };
+
+ ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd);
+ TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno));
+
+ /*
+ * Guest does outl MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1).
+ */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, 1);
+ TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count),
+ "eventfd read failed: errno=%d", errno);
+ TEST_ASSERT_EQ(actual, MATCH_VAL);
+
+ actual = POISON_VAL;
+
+ /*
+ * Guest does outl NOMATCH_VAL → no match → KVM_EXIT_IO.
+ */
+ vcpu_run(vcpu);
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+ TEST_ASSERT(run->io.direction == KVM_EXIT_IO_OUT,
+ "expected PIO write");
+ TEST_ASSERT(run->io.port == PIO_PORT,
+ "expected PIO at 0x%x, got 0x%x",
+ PIO_PORT, run->io.port);
+
+ /* Re-enter: guest continues to GUEST_SYNC(2). */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_SYNC, 2);
+
+ TEST_ASSERT(poll(&pfd, 1, 0) == 0,
+ "eventfd should not be signaled after non-matching PIO write");
+ TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL);
+
+ /* GUEST_DONE */
+ vcpu_run(vcpu);
+ assert_ucall(vcpu, UCALL_DONE, 0);
+
+ close(fd);
+ kvm_vm_free(vm);
+}
+#endif
+
+int main(void)
+{
+ TEST_REQUIRE(kvm_check_cap(KVM_CAP_IOEVENTFD_POST_WRITE));
+
+ test_post_write_negative();
+
+ test_post_write_width(1, guest_code_w1);
+ test_post_write_width(2, guest_code_w2);
+ test_post_write_width(4, guest_code_w4);
+ test_post_write_width(8, guest_code_w8);
+
+ test_post_write_datamatch();
+ test_post_write_multi();
+ test_post_write_multi_nosync();
+ test_post_write_deassign();
+
+#ifdef __x86_64__
+ test_post_write_pio();
+ test_post_write_pio_datamatch();
+#endif
+
+ return 0;
+}
diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c
index 0e8b8a2c5b79..22bc49a41503 100644
--- a/virt/kvm/eventfd.c
+++ b/virt/kvm/eventfd.c
@@ -741,6 +741,7 @@ struct _ioeventfd {
struct kvm_io_device dev;
u8 bus_idx;
bool wildcard;
+ void __user *post_addr;
};
static inline struct _ioeventfd *
@@ -812,6 +813,9 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr,
if (!ioeventfd_in_range(p, addr, len, val))
return -EOPNOTSUPP;
+ if (p->post_addr && len > 0 && __copy_to_user(p->post_addr, val, len))
+ return -EFAULT;
+
eventfd_signal(p->eventfd);
return 0;
}
@@ -866,6 +870,7 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
{
struct eventfd_ctx *eventfd;
+ void __user *post_addr;
struct _ioeventfd *p;
int ret;
@@ -873,6 +878,16 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
if (IS_ERR(eventfd))
return PTR_ERR(eventfd);
+ post_addr = u64_to_user_ptr(args->post_addr);
+ if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) &&
+ (!args->len || !post_addr ||
+ args->post_addr != untagged_addr(args->post_addr) ||
+ !access_ok(post_addr, args->len))) {
+ /* In KVM’s ABI, post_addr must be non‑NULL. */
+ ret = -EINVAL;
+ goto fail;
+ }
+
p = kzalloc_obj(*p, GFP_KERNEL_ACCOUNT);
if (!p) {
ret = -ENOMEM;
@@ -891,6 +906,9 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
else
p->wildcard = true;
+ if (args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE)
+ p->post_addr = post_addr;
+
mutex_lock(&kvm->slots_lock);
/* Verify that there isn't a match already */
@@ -942,6 +960,11 @@ kvm_deassign_ioeventfd_idx(struct kvm *kvm, enum kvm_bus bus_idx,
mutex_lock(&kvm->slots_lock);
list_for_each_entry(p, &kvm->ioeventfds, list) {
+ /*
+ * No need to match post_addr, ioeventfd_check_collision
+ * prevents duplicate registrations that only differ by
+ * post_addr.
+ */
if (p->bus_idx != bus_idx ||
p->eventfd != eventfd ||
p->addr != args->addr ||
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 1bc1da66b4b0..02abca5c49df 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -4883,6 +4883,7 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg)
case KVM_CAP_IRQFD:
#endif
case KVM_CAP_IOEVENTFD_ANY_LENGTH:
+ case KVM_CAP_IOEVENTFD_POST_WRITE:
case KVM_CAP_CHECK_EXTENSION_VM:
case KVM_CAP_ENABLE_CAP_VM:
case KVM_CAP_HALT_POLL:
--
2.47.3
^ permalink raw reply related [flat|nested] 28+ messages in thread* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-06 12:56 [PATCH] KVM: optionally post write on ioeventfd write Thanos Makatos @ 2026-03-12 15:02 ` David Woodhouse 2026-03-12 16:12 ` Thanos Makatos 2026-03-23 15:01 ` Thanos Makatos ` (2 subsequent siblings) 3 siblings, 1 reply; 28+ messages in thread From: David Woodhouse @ 2026-03-12 15:02 UTC (permalink / raw) To: Thanos Makatos, seanjc@google.com, Stamatis, Ilias, Paul Durrant, graf Cc: pbonzini@redhat.com, John Levon, kvm@vger.kernel.org [-- Attachment #1: Type: text/plain, Size: 1685 bytes --] On Fri, 2026-03-06 at 12:56 +0000, Thanos Makatos wrote: > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > ioeventfd that results in the value written by the guest to be copied > to user-supplied memory instead of being discarded. > > The goal of this new mechanism is to speed up doorbell writes on NVMe > controllers emulated outside of the VMM. Currently, a doorbell write to > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > VMM communicating the event, along with the doorbell value, to the NVMe > controller emulation task. With POST_WRITE, the NVMe emulation task is > directly notified of the doorbell write and can find the doorbell value > in a known location, without involving VMM. > > Add tests for this new functionality. > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > lesser extent for pre-reviewing this patch. > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> Interesting. Ilias posted a coalesced MMIO patch series¹ a couple of years ago which addressed similar requirements. IIRC his primary focus back then was emulating an Intel i82559 NIC, and he identified NVMe doorbells as another potential use case. More recently, I think Alex is looking at ways to enable doorbells directly between guests with as little interaction from the L0 hypervisor as possible. I'd love to see if this KVM_IOEVENTFD_FLAG_POST_WRITE works for the i82559 emulation use case. I think back-to-back writes are discarded with this model, while Ilias's patches would convey each one? ¹ https://lore.kernel.org/all/20240820133333.1724191-1-ilstam@amazon.com/ [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 5069 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-12 15:02 ` David Woodhouse @ 2026-03-12 16:12 ` Thanos Makatos 2026-04-21 14:44 ` Sean Christopherson 0 siblings, 1 reply; 28+ messages in thread From: Thanos Makatos @ 2026-03-12 16:12 UTC (permalink / raw) To: David Woodhouse, seanjc@google.com, Stamatis, Ilias, Paul Durrant, graf@amazon.de Cc: pbonzini@redhat.com, John Levon, kvm@vger.kernel.org > -----Original Message----- > From: David Woodhouse <dwmw2@infradead.org> > Sent: 12 March 2026 15:02 > To: Thanos Makatos <thanos.makatos@nutanix.com>; seanjc@google.com; > Stamatis, Ilias <ilstam@amazon.com>; Paul Durrant <paul@xen.org>; > graf@amazon.de > Cc: pbonzini@redhat.com; John Levon <john.levon@nutanix.com>; > kvm@vger.kernel.org > Subject: Re: [PATCH] KVM: optionally post write on ioeventfd write > > On Fri, 2026-03-06 at 12:56 +0000, Thanos Makatos wrote: > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > ioeventfd that results in the value written by the guest to be copied > > to user-supplied memory instead of being discarded. > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > controllers emulated outside of the VMM. Currently, a doorbell write to > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > VMM communicating the event, along with the doorbell value, to the NVMe > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > directly notified of the doorbell write and can find the doorbell value > > in a known location, without involving VMM. > > > > Add tests for this new functionality. > > > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > > lesser extent for pre-reviewing this patch. > > > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > > Interesting. Ilias posted a coalesced MMIO patch series¹ a couple of > years ago which addressed similar requirements. IIRC his primary focus > back then was emulating an Intel i82559 NIC, and he identified NVMe > doorbells as another potential use case. > > More recently, I think Alex is looking at ways to enable doorbells > directly between guests with as little interaction from the L0 > hypervisor as possible. Interesting, are there any patches / pointers to this work? > > I'd love to see if this KVM_IOEVENTFD_FLAG_POST_WRITE works for the > i82559 emulation use case. I think back-to-back writes are discarded > with this model, while Ilias's patches would convey each one? Yes, they're discarded, only the last write is visible, and this is by design to fit the NVMe doorbell use case. ioregionfd, https://lore.kernel.org/all/88ca79d2e378dcbfb3988b562ad2c16c4f929ac7.camel@gmail.com, was a similar proposal for forwarding MMIO writes without discarding back-to-back writes. > > > ¹ https://lore.kernel.org/all/20240820133333.1724191-1- > ilstam@amazon.com/ ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-12 16:12 ` Thanos Makatos @ 2026-04-21 14:44 ` Sean Christopherson 2026-09-19 15:59 ` David Woodhouse 0 siblings, 1 reply; 28+ messages in thread From: Sean Christopherson @ 2026-04-21 14:44 UTC (permalink / raw) To: Thanos Makatos Cc: David Woodhouse, Ilias Stamatis, Paul Durrant, graf@amazon.de, pbonzini@redhat.com, John Levon, kvm@vger.kernel.org On Thu, Mar 12, 2026, Thanos Makatos wrote: > > From: David Woodhouse <dwmw2@infradead.org> > > On Fri, 2026-03-06 at 12:56 +0000, Thanos Makatos wrote: > > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > > ioeventfd that results in the value written by the guest to be copied > > > to user-supplied memory instead of being discarded. > > > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > > controllers emulated outside of the VMM. Currently, a doorbell write to > > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > > VMM communicating the event, along with the doorbell value, to the NVMe > > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > > directly notified of the doorbell write and can find the doorbell value > > > in a known location, without involving VMM. ... > > I'd love to see if this KVM_IOEVENTFD_FLAG_POST_WRITE works for the > > i82559 emulation use case. I think back-to-back writes are discarded > > with this model, while Ilias's patches would convey each one? Do you happen to know the requirements for i82559 emulation? I tried reading the spec and QEMU's code, and that was just a waste of ~20 minutes :-) > Yes, they're discarded, only the last write is visible, and this is by design > to fit the NVMe doorbell use case. I wouldn't say the design is specifically to fit the NVMe doorbell use case, rather that KVM doesn't need to convey each write to support NVMe doorbells, and forwarding only the most recent value is a massive "win" for complexity. Which, for me, is also the argument for accepting KVM_IOEVENTFD_FLAG_POST_WRITE even if there are a limited number of use cases: it's simple (and performant) enough that it's probably worth supporting even if similar functionality can be implemented via polling on coalesced I/O buffers. I.e. maintaing both doesn't seem too onerous, if that's where we wend up. > ioregionfd, > https://lore.kernel.org/all/88ca79d2e378dcbfb3988b562ad2c16c4f929ac7.camel@gmail.com, > was a similar proposal for forwarding MMIO writes without discarding > back-to-back writes. And if you were curious what the code looked like: https://lore.kernel.org/kvm/cover.1613828726.git.eafanasova@gmail.com ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-04-21 14:44 ` Sean Christopherson @ 2026-09-19 15:59 ` David Woodhouse 0 siblings, 0 replies; 28+ messages in thread From: David Woodhouse @ 2026-09-19 15:59 UTC (permalink / raw) To: Sean Christopherson, Thanos Makatos Cc: Ilias Stamatis, Paul Durrant, graf@amazon.de, pbonzini@redhat.com, John Levon, kvm@vger.kernel.org [-- Attachment #1: Type: text/plain, Size: 636 bytes --] On Tue, 2026-04-21 at 07:44 -0700, Sean Christopherson wrote: > > > I'd love to see if this KVM_IOEVENTFD_FLAG_POST_WRITE works for the > > > i82559 emulation use case. I think back-to-back writes are discarded > > > with this model, while Ilias's patches would convey each one? > > Do you happen to know the requirements for i82559 emulation? I tried reading the > spec and QEMU's code, and that was just a waste of ~20 minutes :-) I think the last-write-wins mode is actually fine for the ring registers of both i82559 and ENAv1. I'm playing with a prototype which uses Thanos's patch, which seems to be working out... [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-06 12:56 [PATCH] KVM: optionally post write on ioeventfd write Thanos Makatos 2026-03-12 15:02 ` David Woodhouse @ 2026-03-23 15:01 ` Thanos Makatos 2026-04-10 19:11 ` Thanos Makatos 2026-09-22 13:51 ` Stefan Hajnoczi 2026-09-22 17:03 ` Sean Christopherson 3 siblings, 1 reply; 28+ messages in thread From: Thanos Makatos @ 2026-03-23 15:01 UTC (permalink / raw) To: seanjc@google.com, Paolo Bonzini; +Cc: John Levon, kvm@vger.kernel.org Hi Paolo, Sean, Just a gentle ping on this, happy to follow up if needed. > -----Original Message----- > From: Thanos Makatos <thanos.makatos@nutanix.com> > Sent: 06 March 2026 12:57 > To: seanjc@google.com > Cc: pbonzini@redhat.com; John Levon <john.levon@nutanix.com>; > kvm@vger.kernel.org; Thanos Makatos <thanos.makatos@nutanix.com> > Subject: [PATCH] KVM: optionally post write on ioeventfd write > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > ioeventfd that results in the value written by the guest to be copied > to user-supplied memory instead of being discarded. > > The goal of this new mechanism is to speed up doorbell writes on NVMe > controllers emulated outside of the VMM. Currently, a doorbell write to > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > VMM communicating the event, along with the doorbell value, to the NVMe > controller emulation task. With POST_WRITE, the NVMe emulation task is > directly notified of the doorbell write and can find the doorbell value > in a known location, without involving VMM. > > Add tests for this new functionality. > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > lesser extent for pre-reviewing this patch. > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > --- > Documentation/virt/kvm/api.rst | 13 +- > include/uapi/linux/kvm.h | 6 +- > tools/testing/selftests/kvm/Makefile.kvm | 1 + > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > virt/kvm/eventfd.c | 23 + > virt/kvm/kvm_main.c | 1 + > 6 files changed, 666 insertions(+), 2 deletions(-) > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c > > diff --git a/Documentation/virt/kvm/api.rst > b/Documentation/virt/kvm/api.rst > index 6f85e1b321dd..b8d030f03101 100644 > --- a/Documentation/virt/kvm/api.rst > +++ b/Documentation/virt/kvm/api.rst > @@ -2109,7 +2109,8 @@ provided event instead of triggering an exit. > __u32 len; /* 0, 1, 2, 4, or 8 bytes */ > __s32 fd; > __u32 flags; > - __u8 pad[36]; > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is set > */ > + __u8 pad[24]; > }; > > For the special case of virtio-ccw devices on s390, the ioevent is matched > @@ -2122,6 +2123,7 @@ The following flags are defined:: > #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << > kvm_ioeventfd_flag_nr_deassign) > #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ > (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) > + #define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << > kvm_ioeventfd_flag_nr_post_write) > > If datamatch flag is set, the event will be signaled only if the written value > to the registered address is equal to datamatch in struct kvm_ioeventfd. > @@ -2134,6 +2136,15 @@ the kernel will ignore the length of guest write > and may get a faster vmexit. > The speedup may only apply to specific architectures, but the ioeventfd will > work anyway. > > +With KVM_IOEVENTFD_FLAG_POST_WRITE, the value being written is copied > to the > +userspace address specified by post_addr, and the eventfd is signaled. The > +copy is guaranteed to complete before the eventfd is signaled, so a userspace > +reader that wakes on the eventfd will observe the written value. When > multiple > +vCPUs write to the same ioeventfd concurrently, the value at post_addr > reflects > +one of the writes. If the copy to post_addr fails (e.g. the memory has been > +unmapped), the eventfd is not signaled and the write is reported to > userspace > +as a regular MMIO/PIO exit. > + > 4.60 KVM_DIRTY_TLB > ------------------ > > diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h > index 65500f5db379..55b8683a856f 100644 > --- a/include/uapi/linux/kvm.h > +++ b/include/uapi/linux/kvm.h > @@ -639,6 +639,7 @@ enum { > kvm_ioeventfd_flag_nr_deassign, > kvm_ioeventfd_flag_nr_virtio_ccw_notify, > kvm_ioeventfd_flag_nr_fast_mmio, > + kvm_ioeventfd_flag_nr_post_write, > kvm_ioeventfd_flag_nr_max, > }; > > @@ -647,6 +648,7 @@ enum { > #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << > kvm_ioeventfd_flag_nr_deassign) > #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ > (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) > +#define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << > kvm_ioeventfd_flag_nr_post_write) > > #define KVM_IOEVENTFD_VALID_FLAG_MASK ((1 << > kvm_ioeventfd_flag_nr_max) - 1) > > @@ -656,7 +658,8 @@ struct kvm_ioeventfd { > __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ > __s32 fd; > __u32 flags; > - __u8 pad[36]; > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is set > */ > + __u8 pad[24]; > }; > > #define KVM_X86_DISABLE_EXITS_MWAIT (1 << 0) > @@ -985,6 +988,7 @@ struct kvm_enable_cap { > #define KVM_CAP_ARM_SEA_TO_USER 245 > #define KVM_CAP_S390_USER_OPEREXEC 246 > #define KVM_CAP_S390_KEYOP 247 > +#define KVM_CAP_IOEVENTFD_POST_WRITE 248 > > struct kvm_irq_routing_irqchip { > __u32 irqchip; > diff --git a/tools/testing/selftests/kvm/Makefile.kvm > b/tools/testing/selftests/kvm/Makefile.kvm > index fdec90e85467..7ab470981c31 100644 > --- a/tools/testing/selftests/kvm/Makefile.kvm > +++ b/tools/testing/selftests/kvm/Makefile.kvm > @@ -64,6 +64,7 @@ TEST_GEN_PROGS_COMMON += kvm_binary_stats_test > TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus > TEST_GEN_PROGS_COMMON += kvm_page_table_test > TEST_GEN_PROGS_COMMON += set_memory_region_test > +TEST_GEN_PROGS_COMMON += ioeventfd_test > > # Compiled test targets > TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON) > diff --git a/tools/testing/selftests/kvm/ioeventfd_test.c > b/tools/testing/selftests/kvm/ioeventfd_test.c > new file mode 100644 > index 000000000000..24875a2562d4 > --- /dev/null > +++ b/tools/testing/selftests/kvm/ioeventfd_test.c > @@ -0,0 +1,624 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * ioeventfd_test.c - Tests for KVM_IOEVENTFD_FLAG_POST_WRITE. > + * > + * Tests that when KVM_IOEVENTFD_FLAG_POST_WRITE is set and the > MMIO/PIO > + * address is written to, the value is copied to the user-provided address > + * and the eventfd is signaled. Also tests negative cases and interactions > + * with DATAMATCH. > + * > + * Copyright Nutanix, 2026 > + * > + * Author: Thanos Makatos <thanos.makatos@nutanix.com> > + */ > + > +#include <errno.h> > +#include <poll.h> > +#include <string.h> > + > +#include "kvm_util.h" > +#include "processor.h" > +#include "ucall_common.h" > + > +#define MMIO_GPA (1UL << 30) > +#define PIO_PORT 0xe000 > +#define TEST_VAL 0xDEADBEEFCAFEBABEULL > +#define MATCH_VAL 0x42U > +#define NOMATCH_VAL (MATCH_VAL + 1) > +#define POISON_VAL 0xFFFFFFFFU > + > +/* > + * Check that the most recent vCPU exit is a ucall (delivered as KVM_EXIT_IO > + * on x86) matching @expected_cmd. The caller must have already called > + * vcpu_run(). > + * > + * @expected_cmd: UCALL_SYNC, UCALL_DONE, etc. > + * @expected_stage: for UCALL_SYNC, the stage number passed by > GUEST_SYNC(). > + * Ignored for other ucall types. > + * > + * Aborts the test on UCALL_ABORT (a guest-side assertion failure). > + */ > +static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_cmd, > + uint64_t expected_stage) > +{ > + struct ucall uc; > + > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); > + > + switch (get_ucall(vcpu, &uc)) { > + case UCALL_ABORT: > + REPORT_GUEST_ASSERT(uc); > + break; > + case UCALL_SYNC: > + TEST_ASSERT(expected_cmd == UCALL_SYNC, > + "got UCALL_SYNC, expected %lu", > + expected_cmd); > + TEST_ASSERT(uc.args[1] == expected_stage, > + "expected stage %lu, got %lu", > + expected_stage, uc.args[1]); > + break; > + case UCALL_DONE: > + TEST_ASSERT(expected_cmd == UCALL_DONE, > + "got UCALL_DONE, expected %lu", > + expected_cmd); > + break; > + default: > + TEST_FAIL("unexpected ucall %lu", uc.cmd); > + } > +} > + > +/* > + * Verify that KVM_IOEVENTFD rejects invalid POST_WRITE configurations: > + * - len=0: the kernel needs a non-zero length to know how many bytes to > copy. > + * - post_addr=NULL: there is no destination for the copy. > + * - post_addr outside the process address space: access_ok() rejects it. > + * All three must fail with EINVAL. > + */ > +static void test_post_write_negative(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vm *vm; > + uint64_t dummy; > + int ret; > + int fd; > + > + vm = vm_create_barebones(); > + fd = kvm_new_eventfd(); > + > + /* length cannot be zero */ > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = MMIO_GPA, > + .len = 0, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > + .post_addr = (u64)&dummy, > + }; > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(ret && errno == EINVAL, > + "len=0: expected EINVAL, got ret=%d errno=%d", ret, errno); > + > + /* post_addr cannot be NULL */ > + ioeventfd.len = 4; > + ioeventfd.post_addr = 0ULL; > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(ret && errno == EINVAL, > + "NULL post_addr: expected EINVAL, got ret=%d errno=%d", > + ret, errno); > + > + /* bogus post_addr */ > + ioeventfd.post_addr = (u64)0xdeaddeaddeaddeadULL; > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(ret && errno == EINVAL, > + "bad post_addr: expected EINVAL, got ret=%d errno=%d", > + ret, errno); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +#define DEFINE_GUEST_WRITE_FN(suffix, type) \ > +static void guest_code_w##suffix(void) { \ > + *(volatile type *)MMIO_GPA = (type)TEST_VAL; \ > + GUEST_DONE(); \ > +} > + > +DEFINE_GUEST_WRITE_FN(1, uint8_t) > +DEFINE_GUEST_WRITE_FN(2, uint16_t) > +DEFINE_GUEST_WRITE_FN(4, uint32_t) > +DEFINE_GUEST_WRITE_FN(8, uint64_t) > + > +/* > + * Verify that ioeventfd_write copies exactly @width bytes to post_addr for > + * each supported MMIO write width (1, 2, 4, 8). The guest writes the low > + * @width bytes of TEST_VAL; the host checks that exactly those bytes land > + * at post_addr and the eventfd is signaled. > + */ > +static void test_post_write_width(int width, void (*guest_fn)(void)) > +{ > + uint64_t actual, expected, count; > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_vm *vm; > + int fd, ret; > + > + /* need to initialize to 0 because the guest writes the low @width > bytes */ > + actual = 0; > + expected = 0; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_fn); > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > + > + fd = kvm_new_eventfd(); > + > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = MMIO_GPA, > + .len = width, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + ret = read(fd, &count, sizeof(count)); > + TEST_ASSERT(ret == sizeof(count), > + "eventfd read failed: ret=%d errno=%d", ret, errno); > + > + memcpy(&expected, &(uint64_t){TEST_VAL}, width); > + TEST_ASSERT_EQ(actual, expected); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +static void guest_code_datamatch(void) > +{ > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > + GUEST_SYNC(1); > + *(volatile uint32_t *)MMIO_GPA = NOMATCH_VAL; > + GUEST_SYNC(2); > + GUEST_DONE(); > +} > + > +/* > + * Test the interaction between DATAMATCH and POST_WRITE. When both > flags are > + * set, ioeventfd_write should only fire (signal eventfd + copy value) when the > + * written value matches datamatch. A non-matching write must leave the > eventfd > + * unsignaled and post_addr untouched, and fall through to > KVM_EXIT_MMIO. > + */ > +static void test_post_write_datamatch(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_run *run; > + struct kvm_vm *vm; > + struct pollfd pfd; > + uint64_t count; > + uint32_t actual; > + int fd, ret; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_datamatch); > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > + run = vcpu->run; > + > + fd = kvm_new_eventfd(); > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > + > + ioeventfd = (struct kvm_ioeventfd) { > + .datamatch = MATCH_VAL, > + .addr = MMIO_GPA, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > + KVM_IOEVENTFD_FLAG_DATAMATCH, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + /* > + * Guest writes MATCH_VAL → ioeventfd fires (value copied, eventfd > + * signaled), vCPU continues, then GUEST_SYNC(1). > + */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, 1); > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > + "eventfd read failed: errno=%d", errno); > + TEST_ASSERT_EQ(actual, MATCH_VAL); > + > + actual = POISON_VAL; > + > + /* > + * Guest writes NOMATCH_VAL → ioeventfd_in_range() returns false, > bus > + * returns -EOPNOTSUPP → KVM_EXIT_MMIO to userspace. > + */ > + vcpu_run(vcpu); > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); > + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); > + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, > + "expected MMIO at 0x%lx, got 0x%llx", > + MMIO_GPA, run->mmio.phys_addr); > + > + /* Re-enter: KVM completes the MMIO, guest runs to > GUEST_SYNC(2). */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, 2); > + > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > + "eventfd should not be signaled after non-matching write"); > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > + > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +static void guest_code_multi(void) > +{ > + *(volatile uint32_t *)MMIO_GPA = 0x11111111; > + GUEST_SYNC(1); > + *(volatile uint32_t *)MMIO_GPA = 0x22222222; > + GUEST_SYNC(2); > + *(volatile uint32_t *)MMIO_GPA = 0x33333333; > + GUEST_SYNC(3); > + GUEST_DONE(); > +} > + > +/* > + * Verify that post_addr is updated on every MMIO write, not just the first. > + * The guest writes three distinct values in sequence; the host checks after > + * each one that post_addr holds the latest value and the eventfd is signaled > + * each time. > + */ > +static void test_post_write_multi(void) > +{ > + static const uint32_t expected[] = { > + 0x11111111, 0x22222222, 0x33333333, > + }; > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_vm *vm; > + uint64_t count; > + uint32_t actual; > + int fd, ret, i; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi); > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > + > + fd = kvm_new_eventfd(); > + > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = MMIO_GPA, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + for (i = 0; i < ARRAY_SIZE(expected); i++) { > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, i + 1); > + TEST_ASSERT(read(fd, &count, sizeof(count)) == > sizeof(count), > + "eventfd read failed: errno=%d", errno); > + TEST_ASSERT_EQ(actual, expected[i]); > + } > + > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +static void guest_code_multi_nosync(void) > +{ > + *(volatile uint32_t *)MMIO_GPA = 0x11111111; > + *(volatile uint32_t *)MMIO_GPA = 0x22222222; > + *(volatile uint32_t *)MMIO_GPA = 0x33333333; > + GUEST_DONE(); > +} > + > +/* > + * Variant of the multi-write test where the guest performs three consecutive > + * MMIO writes with no GUEST_SYNC in between. All three are handled in- > kernel > + * by ioeventfd before the vCPU exits at GUEST_DONE. Verify that: > + * - post_addr reflects the last written value (0x33333333). > + * - A single eventfd read() returns a counter of 3 (one signal per write). > + */ > +static void test_post_write_multi_nosync(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_vm *vm; > + uint64_t count; > + uint32_t actual; > + int fd, ret; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi_nosync); > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > + > + fd = kvm_new_eventfd(); > + > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = MMIO_GPA, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + ret = read(fd, &count, sizeof(count)); > + TEST_ASSERT(ret == sizeof(count), > + "eventfd read failed: ret=%d errno=%d", ret, errno); > + TEST_ASSERT_EQ(count, (uint64_t)3); > + TEST_ASSERT_EQ(actual, (uint32_t)0x33333333); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +static void guest_code_deassign(void) > +{ > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > + GUEST_SYNC(1); > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > + GUEST_DONE(); > +} > + > +/* > + * Verify that deassigning an ioeventfd with POST_WRITE fully removes it > from > + * the I/O bus. > + */ > +static void test_post_write_deassign(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_run *run; > + struct kvm_vm *vm; > + struct pollfd pfd; > + uint64_t count; > + uint32_t actual; > + int fd, ret; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_deassign); > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > + run = vcpu->run; > + > + fd = kvm_new_eventfd(); > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > + > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = MMIO_GPA, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD assign failed: %s", > strerror(errno)); > + > + /* > + * Guest writes MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). > + */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, 1); > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > + "eventfd read failed: errno=%d", errno); > + TEST_ASSERT_EQ(actual, MATCH_VAL); > + > + /* Deassign the ioeventfd. */ > + ioeventfd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD deassign failed: %s", > strerror(errno)); > + > + actual = POISON_VAL; > + > + /* > + * Guest writes MATCH_VAL again → no handler on the bus → > + * KVM_EXIT_MMIO to userspace. > + */ > + vcpu_run(vcpu); > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); > + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); > + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, > + "expected MMIO at 0x%lx, got 0x%llx", > + MMIO_GPA, run->mmio.phys_addr); > + > + /* Re-enter: KVM completes MMIO, guest runs to GUEST_DONE. */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > + "eventfd should not be signaled after deassign"); > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +#ifdef __x86_64__ > +static void guest_code_pio(void) > +{ > + outl(PIO_PORT, (uint32_t)TEST_VAL); > + GUEST_DONE(); > +} > + > +/* > + * Verify that POST_WRITE works on the PIO bus (KVM_PIO_BUS), not just > MMIO. > + * The guest does an outl to PIO_PORT; the host checks that the written value > + * is copied to post_addr and the eventfd is signaled. > + */ > +static void test_post_write_pio(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_vm *vm; > + uint64_t count; > + uint32_t actual; > + int fd, ret; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio); > + > + fd = kvm_new_eventfd(); > + > + ioeventfd = (struct kvm_ioeventfd) { > + .addr = PIO_PORT, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > + KVM_IOEVENTFD_FLAG_PIO, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + ret = read(fd, &count, sizeof(count)); > + TEST_ASSERT(ret == sizeof(count), > + "eventfd read failed: ret=%d errno=%d", ret, errno); > + > + TEST_ASSERT_EQ(actual, (uint32_t)TEST_VAL); > + > + close(fd); > + kvm_vm_free(vm); > +} > + > +static void guest_code_pio_datamatch(void) > +{ > + outl(PIO_PORT, MATCH_VAL); > + GUEST_SYNC(1); > + outl(PIO_PORT, NOMATCH_VAL); > + GUEST_SYNC(2); > + GUEST_DONE(); > +} > + > +/* > + * Test POST_WRITE + PIO + DATAMATCH together. When all three flags are > set, > + * the ioeventfd should only fire when the outl value matches datamatch. > + * A non-matching outl must fall through to KVM_EXIT_IO (port I/O exit), > + * leaving the eventfd unsignaled and post_addr untouched. > + */ > +static void test_post_write_pio_datamatch(void) > +{ > + struct kvm_ioeventfd ioeventfd; > + struct kvm_vcpu *vcpu; > + struct kvm_run *run; > + struct kvm_vm *vm; > + struct pollfd pfd; > + uint64_t count; > + uint32_t actual; > + int fd, ret; > + > + actual = POISON_VAL; > + > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio_datamatch); > + run = vcpu->run; > + > + fd = kvm_new_eventfd(); > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > + > + ioeventfd = (struct kvm_ioeventfd) { > + .datamatch = MATCH_VAL, > + .addr = PIO_PORT, > + .len = 4, > + .fd = fd, > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > + KVM_IOEVENTFD_FLAG_PIO | > + KVM_IOEVENTFD_FLAG_DATAMATCH, > + .post_addr = (u64)&actual, > + }; > + > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > + > + /* > + * Guest does outl MATCH_VAL → ioeventfd fires, then > GUEST_SYNC(1). > + */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, 1); > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > + "eventfd read failed: errno=%d", errno); > + TEST_ASSERT_EQ(actual, MATCH_VAL); > + > + actual = POISON_VAL; > + > + /* > + * Guest does outl NOMATCH_VAL → no match → KVM_EXIT_IO. > + */ > + vcpu_run(vcpu); > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); > + TEST_ASSERT(run->io.direction == KVM_EXIT_IO_OUT, > + "expected PIO write"); > + TEST_ASSERT(run->io.port == PIO_PORT, > + "expected PIO at 0x%x, got 0x%x", > + PIO_PORT, run->io.port); > + > + /* Re-enter: guest continues to GUEST_SYNC(2). */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_SYNC, 2); > + > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > + "eventfd should not be signaled after non-matching PIO > write"); > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > + > + /* GUEST_DONE */ > + vcpu_run(vcpu); > + assert_ucall(vcpu, UCALL_DONE, 0); > + > + close(fd); > + kvm_vm_free(vm); > +} > +#endif > + > +int main(void) > +{ > + > TEST_REQUIRE(kvm_check_cap(KVM_CAP_IOEVENTFD_POST_WRITE > )); > + > + test_post_write_negative(); > + > + test_post_write_width(1, guest_code_w1); > + test_post_write_width(2, guest_code_w2); > + test_post_write_width(4, guest_code_w4); > + test_post_write_width(8, guest_code_w8); > + > + test_post_write_datamatch(); > + test_post_write_multi(); > + test_post_write_multi_nosync(); > + test_post_write_deassign(); > + > +#ifdef __x86_64__ > + test_post_write_pio(); > + test_post_write_pio_datamatch(); > +#endif > + > + return 0; > +} > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > index 0e8b8a2c5b79..22bc49a41503 100644 > --- a/virt/kvm/eventfd.c > +++ b/virt/kvm/eventfd.c > @@ -741,6 +741,7 @@ struct _ioeventfd { > struct kvm_io_device dev; > u8 bus_idx; > bool wildcard; > + void __user *post_addr; > }; > > static inline struct _ioeventfd * > @@ -812,6 +813,9 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct > kvm_io_device *this, gpa_t addr, > if (!ioeventfd_in_range(p, addr, len, val)) > return -EOPNOTSUPP; > > + if (p->post_addr && len > 0 && __copy_to_user(p->post_addr, val, > len)) > + return -EFAULT; > + > eventfd_signal(p->eventfd); > return 0; > } > @@ -866,6 +870,7 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, > { > > struct eventfd_ctx *eventfd; > + void __user *post_addr; > struct _ioeventfd *p; > int ret; > > @@ -873,6 +878,16 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > if (IS_ERR(eventfd)) > return PTR_ERR(eventfd); > > + post_addr = u64_to_user_ptr(args->post_addr); > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > + (!args->len || !post_addr || > + args->post_addr != untagged_addr(args->post_addr) || > + !access_ok(post_addr, args->len))) { > + /* In KVM’s ABI, post_addr must be non‑NULL. */ > + ret = -EINVAL; > + goto fail; > + } > + > p = kzalloc_obj(*p, GFP_KERNEL_ACCOUNT); > if (!p) { > ret = -ENOMEM; > @@ -891,6 +906,9 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, > else > p->wildcard = true; > > + if (args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) > + p->post_addr = post_addr; > + > mutex_lock(&kvm->slots_lock); > > /* Verify that there isn't a match already */ > @@ -942,6 +960,11 @@ kvm_deassign_ioeventfd_idx(struct kvm *kvm, > enum kvm_bus bus_idx, > mutex_lock(&kvm->slots_lock); > > list_for_each_entry(p, &kvm->ioeventfds, list) { > + /* > + * No need to match post_addr, ioeventfd_check_collision > + * prevents duplicate registrations that only differ by > + * post_addr. > + */ > if (p->bus_idx != bus_idx || > p->eventfd != eventfd || > p->addr != args->addr || > diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c > index 1bc1da66b4b0..02abca5c49df 100644 > --- a/virt/kvm/kvm_main.c > +++ b/virt/kvm/kvm_main.c > @@ -4883,6 +4883,7 @@ static int > kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) > case KVM_CAP_IRQFD: > #endif > case KVM_CAP_IOEVENTFD_ANY_LENGTH: > + case KVM_CAP_IOEVENTFD_POST_WRITE: > case KVM_CAP_CHECK_EXTENSION_VM: > case KVM_CAP_ENABLE_CAP_VM: > case KVM_CAP_HALT_POLL: > -- > 2.47.3 ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-23 15:01 ` Thanos Makatos @ 2026-04-10 19:11 ` Thanos Makatos 2026-04-21 14:45 ` Sean Christopherson 0 siblings, 1 reply; 28+ messages in thread From: Thanos Makatos @ 2026-04-10 19:11 UTC (permalink / raw) To: Thanos Makatos, seanjc@google.com, Paolo Bonzini Cc: John Levon, kvm@vger.kernel.org Hi Paolo, Sean, Following up on this, please let me know whether this is something I should revise/resend. > -----Original Message----- > From: Thanos Makatos <thanos.makatos@nutanix.com> > Sent: 23 March 2026 17:02 > To: seanjc@google.com; Paolo Bonzini <pbonzini@redhat.com> > Cc: John Levon <john.levon@nutanix.com>; kvm@vger.kernel.org > Subject: RE: [PATCH] KVM: optionally post write on ioeventfd write > > Hi Paolo, Sean, > > Just a gentle ping on this, happy to follow up if needed. > > > -----Original Message----- > > From: Thanos Makatos <thanos.makatos@nutanix.com> > > Sent: 06 March 2026 12:57 > > To: seanjc@google.com > > Cc: pbonzini@redhat.com; John Levon <john.levon@nutanix.com>; > > kvm@vger.kernel.org; Thanos Makatos <thanos.makatos@nutanix.com> > > Subject: [PATCH] KVM: optionally post write on ioeventfd write > > > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > ioeventfd that results in the value written by the guest to be copied > > to user-supplied memory instead of being discarded. > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > controllers emulated outside of the VMM. Currently, a doorbell write to > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > VMM communicating the event, along with the doorbell value, to the NVMe > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > directly notified of the doorbell write and can find the doorbell value > > in a known location, without involving VMM. > > > > Add tests for this new functionality. > > > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > > lesser extent for pre-reviewing this patch. > > > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > > --- > > Documentation/virt/kvm/api.rst | 13 +- > > include/uapi/linux/kvm.h | 6 +- > > tools/testing/selftests/kvm/Makefile.kvm | 1 + > > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > > virt/kvm/eventfd.c | 23 + > > virt/kvm/kvm_main.c | 1 + > > 6 files changed, 666 insertions(+), 2 deletions(-) > > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c > > > > diff --git a/Documentation/virt/kvm/api.rst > > b/Documentation/virt/kvm/api.rst > > index 6f85e1b321dd..b8d030f03101 100644 > > --- a/Documentation/virt/kvm/api.rst > > +++ b/Documentation/virt/kvm/api.rst > > @@ -2109,7 +2109,8 @@ provided event instead of triggering an exit. > > __u32 len; /* 0, 1, 2, 4, or 8 bytes */ > > __s32 fd; > > __u32 flags; > > - __u8 pad[36]; > > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is set > > */ > > + __u8 pad[24]; > > }; > > > > For the special case of virtio-ccw devices on s390, the ioevent is matched > > @@ -2122,6 +2123,7 @@ The following flags are defined:: > > #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << > > kvm_ioeventfd_flag_nr_deassign) > > #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ > > (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) > > + #define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << > > kvm_ioeventfd_flag_nr_post_write) > > > > If datamatch flag is set, the event will be signaled only if the written value > > to the registered address is equal to datamatch in struct kvm_ioeventfd. > > @@ -2134,6 +2136,15 @@ the kernel will ignore the length of guest write > > and may get a faster vmexit. > > The speedup may only apply to specific architectures, but the ioeventfd will > > work anyway. > > > > +With KVM_IOEVENTFD_FLAG_POST_WRITE, the value being written is > copied > > to the > > +userspace address specified by post_addr, and the eventfd is signaled. The > > +copy is guaranteed to complete before the eventfd is signaled, so a > userspace > > +reader that wakes on the eventfd will observe the written value. When > > multiple > > +vCPUs write to the same ioeventfd concurrently, the value at post_addr > > reflects > > +one of the writes. If the copy to post_addr fails (e.g. the memory has been > > +unmapped), the eventfd is not signaled and the write is reported to > > userspace > > +as a regular MMIO/PIO exit. > > + > > 4.60 KVM_DIRTY_TLB > > ------------------ > > > > diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h > > index 65500f5db379..55b8683a856f 100644 > > --- a/include/uapi/linux/kvm.h > > +++ b/include/uapi/linux/kvm.h > > @@ -639,6 +639,7 @@ enum { > > kvm_ioeventfd_flag_nr_deassign, > > kvm_ioeventfd_flag_nr_virtio_ccw_notify, > > kvm_ioeventfd_flag_nr_fast_mmio, > > + kvm_ioeventfd_flag_nr_post_write, > > kvm_ioeventfd_flag_nr_max, > > }; > > > > @@ -647,6 +648,7 @@ enum { > > #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << > > kvm_ioeventfd_flag_nr_deassign) > > #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ > > (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) > > +#define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << > > kvm_ioeventfd_flag_nr_post_write) > > > > #define KVM_IOEVENTFD_VALID_FLAG_MASK ((1 << > > kvm_ioeventfd_flag_nr_max) - 1) > > > > @@ -656,7 +658,8 @@ struct kvm_ioeventfd { > > __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ > > __s32 fd; > > __u32 flags; > > - __u8 pad[36]; > > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is set > > */ > > + __u8 pad[24]; > > }; > > > > #define KVM_X86_DISABLE_EXITS_MWAIT (1 << 0) > > @@ -985,6 +988,7 @@ struct kvm_enable_cap { > > #define KVM_CAP_ARM_SEA_TO_USER 245 > > #define KVM_CAP_S390_USER_OPEREXEC 246 > > #define KVM_CAP_S390_KEYOP 247 > > +#define KVM_CAP_IOEVENTFD_POST_WRITE 248 > > > > struct kvm_irq_routing_irqchip { > > __u32 irqchip; > > diff --git a/tools/testing/selftests/kvm/Makefile.kvm > > b/tools/testing/selftests/kvm/Makefile.kvm > > index fdec90e85467..7ab470981c31 100644 > > --- a/tools/testing/selftests/kvm/Makefile.kvm > > +++ b/tools/testing/selftests/kvm/Makefile.kvm > > @@ -64,6 +64,7 @@ TEST_GEN_PROGS_COMMON += > kvm_binary_stats_test > > TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus > > TEST_GEN_PROGS_COMMON += kvm_page_table_test > > TEST_GEN_PROGS_COMMON += set_memory_region_test > > +TEST_GEN_PROGS_COMMON += ioeventfd_test > > > > # Compiled test targets > > TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON) > > diff --git a/tools/testing/selftests/kvm/ioeventfd_test.c > > b/tools/testing/selftests/kvm/ioeventfd_test.c > > new file mode 100644 > > index 000000000000..24875a2562d4 > > --- /dev/null > > +++ b/tools/testing/selftests/kvm/ioeventfd_test.c > > @@ -0,0 +1,624 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * ioeventfd_test.c - Tests for KVM_IOEVENTFD_FLAG_POST_WRITE. > > + * > > + * Tests that when KVM_IOEVENTFD_FLAG_POST_WRITE is set and the > > MMIO/PIO > > + * address is written to, the value is copied to the user-provided address > > + * and the eventfd is signaled. Also tests negative cases and interactions > > + * with DATAMATCH. > > + * > > + * Copyright Nutanix, 2026 > > + * > > + * Author: Thanos Makatos <thanos.makatos@nutanix.com> > > + */ > > + > > +#include <errno.h> > > +#include <poll.h> > > +#include <string.h> > > + > > +#include "kvm_util.h" > > +#include "processor.h" > > +#include "ucall_common.h" > > + > > +#define MMIO_GPA (1UL << 30) > > +#define PIO_PORT 0xe000 > > +#define TEST_VAL 0xDEADBEEFCAFEBABEULL > > +#define MATCH_VAL 0x42U > > +#define NOMATCH_VAL (MATCH_VAL + 1) > > +#define POISON_VAL 0xFFFFFFFFU > > + > > +/* > > + * Check that the most recent vCPU exit is a ucall (delivered as > KVM_EXIT_IO > > + * on x86) matching @expected_cmd. The caller must have already called > > + * vcpu_run(). > > + * > > + * @expected_cmd: UCALL_SYNC, UCALL_DONE, etc. > > + * @expected_stage: for UCALL_SYNC, the stage number passed by > > GUEST_SYNC(). > > + * Ignored for other ucall types. > > + * > > + * Aborts the test on UCALL_ABORT (a guest-side assertion failure). > > + */ > > +static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_cmd, > > + uint64_t expected_stage) > > +{ > > + struct ucall uc; > > + > > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); > > + > > + switch (get_ucall(vcpu, &uc)) { > > + case UCALL_ABORT: > > + REPORT_GUEST_ASSERT(uc); > > + break; > > + case UCALL_SYNC: > > + TEST_ASSERT(expected_cmd == UCALL_SYNC, > > + "got UCALL_SYNC, expected %lu", > > + expected_cmd); > > + TEST_ASSERT(uc.args[1] == expected_stage, > > + "expected stage %lu, got %lu", > > + expected_stage, uc.args[1]); > > + break; > > + case UCALL_DONE: > > + TEST_ASSERT(expected_cmd == UCALL_DONE, > > + "got UCALL_DONE, expected %lu", > > + expected_cmd); > > + break; > > + default: > > + TEST_FAIL("unexpected ucall %lu", uc.cmd); > > + } > > +} > > + > > +/* > > + * Verify that KVM_IOEVENTFD rejects invalid POST_WRITE configurations: > > + * - len=0: the kernel needs a non-zero length to know how many bytes to > > copy. > > + * - post_addr=NULL: there is no destination for the copy. > > + * - post_addr outside the process address space: access_ok() rejects it. > > + * All three must fail with EINVAL. > > + */ > > +static void test_post_write_negative(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vm *vm; > > + uint64_t dummy; > > + int ret; > > + int fd; > > + > > + vm = vm_create_barebones(); > > + fd = kvm_new_eventfd(); > > + > > + /* length cannot be zero */ > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = MMIO_GPA, > > + .len = 0, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > > + .post_addr = (u64)&dummy, > > + }; > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(ret && errno == EINVAL, > > + "len=0: expected EINVAL, got ret=%d errno=%d", ret, > errno); > > + > > + /* post_addr cannot be NULL */ > > + ioeventfd.len = 4; > > + ioeventfd.post_addr = 0ULL; > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(ret && errno == EINVAL, > > + "NULL post_addr: expected EINVAL, got ret=%d errno=%d", > > + ret, errno); > > + > > + /* bogus post_addr */ > > + ioeventfd.post_addr = (u64)0xdeaddeaddeaddeadULL; > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(ret && errno == EINVAL, > > + "bad post_addr: expected EINVAL, got ret=%d errno=%d", > > + ret, errno); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +#define DEFINE_GUEST_WRITE_FN(suffix, type) \ > > +static void guest_code_w##suffix(void) { \ > > + *(volatile type *)MMIO_GPA = (type)TEST_VAL; \ > > + GUEST_DONE(); \ > > +} > > + > > +DEFINE_GUEST_WRITE_FN(1, uint8_t) > > +DEFINE_GUEST_WRITE_FN(2, uint16_t) > > +DEFINE_GUEST_WRITE_FN(4, uint32_t) > > +DEFINE_GUEST_WRITE_FN(8, uint64_t) > > + > > +/* > > + * Verify that ioeventfd_write copies exactly @width bytes to post_addr for > > + * each supported MMIO write width (1, 2, 4, 8). The guest writes the low > > + * @width bytes of TEST_VAL; the host checks that exactly those bytes land > > + * at post_addr and the eventfd is signaled. > > + */ > > +static void test_post_write_width(int width, void (*guest_fn)(void)) > > +{ > > + uint64_t actual, expected, count; > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_vm *vm; > > + int fd, ret; > > + > > + /* need to initialize to 0 because the guest writes the low @width > > bytes */ > > + actual = 0; > > + expected = 0; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_fn); > > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > > + > > + fd = kvm_new_eventfd(); > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = MMIO_GPA, > > + .len = width, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + ret = read(fd, &count, sizeof(count)); > > + TEST_ASSERT(ret == sizeof(count), > > + "eventfd read failed: ret=%d errno=%d", ret, errno); > > + > > + memcpy(&expected, &(uint64_t){TEST_VAL}, width); > > + TEST_ASSERT_EQ(actual, expected); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +static void guest_code_datamatch(void) > > +{ > > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > > + GUEST_SYNC(1); > > + *(volatile uint32_t *)MMIO_GPA = NOMATCH_VAL; > > + GUEST_SYNC(2); > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Test the interaction between DATAMATCH and POST_WRITE. When both > > flags are > > + * set, ioeventfd_write should only fire (signal eventfd + copy value) when > the > > + * written value matches datamatch. A non-matching write must leave the > > eventfd > > + * unsignaled and post_addr untouched, and fall through to > > KVM_EXIT_MMIO. > > + */ > > +static void test_post_write_datamatch(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_run *run; > > + struct kvm_vm *vm; > > + struct pollfd pfd; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_datamatch); > > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > > + run = vcpu->run; > > + > > + fd = kvm_new_eventfd(); > > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .datamatch = MATCH_VAL, > > + .addr = MMIO_GPA, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > > + KVM_IOEVENTFD_FLAG_DATAMATCH, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + /* > > + * Guest writes MATCH_VAL → ioeventfd fires (value copied, eventfd > > + * signaled), vCPU continues, then GUEST_SYNC(1). > > + */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, 1); > > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > > + "eventfd read failed: errno=%d", errno); > > + TEST_ASSERT_EQ(actual, MATCH_VAL); > > + > > + actual = POISON_VAL; > > + > > + /* > > + * Guest writes NOMATCH_VAL → ioeventfd_in_range() returns false, > > bus > > + * returns -EOPNOTSUPP → KVM_EXIT_MMIO to userspace. > > + */ > > + vcpu_run(vcpu); > > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); > > + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); > > + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, > > + "expected MMIO at 0x%lx, got 0x%llx", > > + MMIO_GPA, run->mmio.phys_addr); > > + > > + /* Re-enter: KVM completes the MMIO, guest runs to > > GUEST_SYNC(2). */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, 2); > > + > > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > > + "eventfd should not be signaled after non-matching write"); > > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > > + > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +static void guest_code_multi(void) > > +{ > > + *(volatile uint32_t *)MMIO_GPA = 0x11111111; > > + GUEST_SYNC(1); > > + *(volatile uint32_t *)MMIO_GPA = 0x22222222; > > + GUEST_SYNC(2); > > + *(volatile uint32_t *)MMIO_GPA = 0x33333333; > > + GUEST_SYNC(3); > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Verify that post_addr is updated on every MMIO write, not just the first. > > + * The guest writes three distinct values in sequence; the host checks after > > + * each one that post_addr holds the latest value and the eventfd is > signaled > > + * each time. > > + */ > > +static void test_post_write_multi(void) > > +{ > > + static const uint32_t expected[] = { > > + 0x11111111, 0x22222222, 0x33333333, > > + }; > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_vm *vm; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret, i; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi); > > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > > + > > + fd = kvm_new_eventfd(); > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = MMIO_GPA, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + for (i = 0; i < ARRAY_SIZE(expected); i++) { > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, i + 1); > > + TEST_ASSERT(read(fd, &count, sizeof(count)) == > > sizeof(count), > > + "eventfd read failed: errno=%d", errno); > > + TEST_ASSERT_EQ(actual, expected[i]); > > + } > > + > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +static void guest_code_multi_nosync(void) > > +{ > > + *(volatile uint32_t *)MMIO_GPA = 0x11111111; > > + *(volatile uint32_t *)MMIO_GPA = 0x22222222; > > + *(volatile uint32_t *)MMIO_GPA = 0x33333333; > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Variant of the multi-write test where the guest performs three > consecutive > > + * MMIO writes with no GUEST_SYNC in between. All three are handled > in- > > kernel > > + * by ioeventfd before the vCPU exits at GUEST_DONE. Verify that: > > + * - post_addr reflects the last written value (0x33333333). > > + * - A single eventfd read() returns a counter of 3 (one signal per write). > > + */ > > +static void test_post_write_multi_nosync(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_vm *vm; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi_nosync); > > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > > + > > + fd = kvm_new_eventfd(); > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = MMIO_GPA, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + ret = read(fd, &count, sizeof(count)); > > + TEST_ASSERT(ret == sizeof(count), > > + "eventfd read failed: ret=%d errno=%d", ret, errno); > > + TEST_ASSERT_EQ(count, (uint64_t)3); > > + TEST_ASSERT_EQ(actual, (uint32_t)0x33333333); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +static void guest_code_deassign(void) > > +{ > > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > > + GUEST_SYNC(1); > > + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Verify that deassigning an ioeventfd with POST_WRITE fully removes it > > from > > + * the I/O bus. > > + */ > > +static void test_post_write_deassign(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_run *run; > > + struct kvm_vm *vm; > > + struct pollfd pfd; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_deassign); > > + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); > > + run = vcpu->run; > > + > > + fd = kvm_new_eventfd(); > > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = MMIO_GPA, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD assign failed: %s", > > strerror(errno)); > > + > > + /* > > + * Guest writes MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). > > + */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, 1); > > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > > + "eventfd read failed: errno=%d", errno); > > + TEST_ASSERT_EQ(actual, MATCH_VAL); > > + > > + /* Deassign the ioeventfd. */ > > + ioeventfd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD deassign failed: %s", > > strerror(errno)); > > + > > + actual = POISON_VAL; > > + > > + /* > > + * Guest writes MATCH_VAL again → no handler on the bus → > > + * KVM_EXIT_MMIO to userspace. > > + */ > > + vcpu_run(vcpu); > > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); > > + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); > > + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, > > + "expected MMIO at 0x%lx, got 0x%llx", > > + MMIO_GPA, run->mmio.phys_addr); > > + > > + /* Re-enter: KVM completes MMIO, guest runs to GUEST_DONE. */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > > + "eventfd should not be signaled after deassign"); > > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +#ifdef __x86_64__ > > +static void guest_code_pio(void) > > +{ > > + outl(PIO_PORT, (uint32_t)TEST_VAL); > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Verify that POST_WRITE works on the PIO bus (KVM_PIO_BUS), not just > > MMIO. > > + * The guest does an outl to PIO_PORT; the host checks that the written > value > > + * is copied to post_addr and the eventfd is signaled. > > + */ > > +static void test_post_write_pio(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_vm *vm; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio); > > + > > + fd = kvm_new_eventfd(); > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .addr = PIO_PORT, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > > + KVM_IOEVENTFD_FLAG_PIO, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + ret = read(fd, &count, sizeof(count)); > > + TEST_ASSERT(ret == sizeof(count), > > + "eventfd read failed: ret=%d errno=%d", ret, errno); > > + > > + TEST_ASSERT_EQ(actual, (uint32_t)TEST_VAL); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > + > > +static void guest_code_pio_datamatch(void) > > +{ > > + outl(PIO_PORT, MATCH_VAL); > > + GUEST_SYNC(1); > > + outl(PIO_PORT, NOMATCH_VAL); > > + GUEST_SYNC(2); > > + GUEST_DONE(); > > +} > > + > > +/* > > + * Test POST_WRITE + PIO + DATAMATCH together. When all three flags are > > set, > > + * the ioeventfd should only fire when the outl value matches datamatch. > > + * A non-matching outl must fall through to KVM_EXIT_IO (port I/O exit), > > + * leaving the eventfd unsignaled and post_addr untouched. > > + */ > > +static void test_post_write_pio_datamatch(void) > > +{ > > + struct kvm_ioeventfd ioeventfd; > > + struct kvm_vcpu *vcpu; > > + struct kvm_run *run; > > + struct kvm_vm *vm; > > + struct pollfd pfd; > > + uint64_t count; > > + uint32_t actual; > > + int fd, ret; > > + > > + actual = POISON_VAL; > > + > > + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio_datamatch); > > + run = vcpu->run; > > + > > + fd = kvm_new_eventfd(); > > + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; > > + > > + ioeventfd = (struct kvm_ioeventfd) { > > + .datamatch = MATCH_VAL, > > + .addr = PIO_PORT, > > + .len = 4, > > + .fd = fd, > > + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | > > + KVM_IOEVENTFD_FLAG_PIO | > > + KVM_IOEVENTFD_FLAG_DATAMATCH, > > + .post_addr = (u64)&actual, > > + }; > > + > > + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); > > + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); > > + > > + /* > > + * Guest does outl MATCH_VAL → ioeventfd fires, then > > GUEST_SYNC(1). > > + */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, 1); > > + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), > > + "eventfd read failed: errno=%d", errno); > > + TEST_ASSERT_EQ(actual, MATCH_VAL); > > + > > + actual = POISON_VAL; > > + > > + /* > > + * Guest does outl NOMATCH_VAL → no match → KVM_EXIT_IO. > > + */ > > + vcpu_run(vcpu); > > + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); > > + TEST_ASSERT(run->io.direction == KVM_EXIT_IO_OUT, > > + "expected PIO write"); > > + TEST_ASSERT(run->io.port == PIO_PORT, > > + "expected PIO at 0x%x, got 0x%x", > > + PIO_PORT, run->io.port); > > + > > + /* Re-enter: guest continues to GUEST_SYNC(2). */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_SYNC, 2); > > + > > + TEST_ASSERT(poll(&pfd, 1, 0) == 0, > > + "eventfd should not be signaled after non-matching PIO > > write"); > > + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); > > + > > + /* GUEST_DONE */ > > + vcpu_run(vcpu); > > + assert_ucall(vcpu, UCALL_DONE, 0); > > + > > + close(fd); > > + kvm_vm_free(vm); > > +} > > +#endif > > + > > +int main(void) > > +{ > > + > > TEST_REQUIRE(kvm_check_cap(KVM_CAP_IOEVENTFD_POST_WRITE > > )); > > + > > + test_post_write_negative(); > > + > > + test_post_write_width(1, guest_code_w1); > > + test_post_write_width(2, guest_code_w2); > > + test_post_write_width(4, guest_code_w4); > > + test_post_write_width(8, guest_code_w8); > > + > > + test_post_write_datamatch(); > > + test_post_write_multi(); > > + test_post_write_multi_nosync(); > > + test_post_write_deassign(); > > + > > +#ifdef __x86_64__ > > + test_post_write_pio(); > > + test_post_write_pio_datamatch(); > > +#endif > > + > > + return 0; > > +} > > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > > index 0e8b8a2c5b79..22bc49a41503 100644 > > --- a/virt/kvm/eventfd.c > > +++ b/virt/kvm/eventfd.c > > @@ -741,6 +741,7 @@ struct _ioeventfd { > > struct kvm_io_device dev; > > u8 bus_idx; > > bool wildcard; > > + void __user *post_addr; > > }; > > > > static inline struct _ioeventfd * > > @@ -812,6 +813,9 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct > > kvm_io_device *this, gpa_t addr, > > if (!ioeventfd_in_range(p, addr, len, val)) > > return -EOPNOTSUPP; > > > > + if (p->post_addr && len > 0 && __copy_to_user(p->post_addr, val, > > len)) > > + return -EFAULT; > > + > > eventfd_signal(p->eventfd); > > return 0; > > } > > @@ -866,6 +870,7 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > > { > > > > struct eventfd_ctx *eventfd; > > + void __user *post_addr; > > struct _ioeventfd *p; > > int ret; > > > > @@ -873,6 +878,16 @@ static int kvm_assign_ioeventfd_idx(struct kvm > > *kvm, > > if (IS_ERR(eventfd)) > > return PTR_ERR(eventfd); > > > > + post_addr = u64_to_user_ptr(args->post_addr); > > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > > + (!args->len || !post_addr || > > + args->post_addr != untagged_addr(args->post_addr) || > > + !access_ok(post_addr, args->len))) { > > + /* In KVM’s ABI, post_addr must be non‑NULL. */ > > + ret = -EINVAL; > > + goto fail; > > + } > > + > > p = kzalloc_obj(*p, GFP_KERNEL_ACCOUNT); > > if (!p) { > > ret = -ENOMEM; > > @@ -891,6 +906,9 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > > else > > p->wildcard = true; > > > > + if (args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) > > + p->post_addr = post_addr; > > + > > mutex_lock(&kvm->slots_lock); > > > > /* Verify that there isn't a match already */ > > @@ -942,6 +960,11 @@ kvm_deassign_ioeventfd_idx(struct kvm *kvm, > > enum kvm_bus bus_idx, > > mutex_lock(&kvm->slots_lock); > > > > list_for_each_entry(p, &kvm->ioeventfds, list) { > > + /* > > + * No need to match post_addr, ioeventfd_check_collision > > + * prevents duplicate registrations that only differ by > > + * post_addr. > > + */ > > if (p->bus_idx != bus_idx || > > p->eventfd != eventfd || > > p->addr != args->addr || > > diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c > > index 1bc1da66b4b0..02abca5c49df 100644 > > --- a/virt/kvm/kvm_main.c > > +++ b/virt/kvm/kvm_main.c > > @@ -4883,6 +4883,7 @@ static int > > kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) > > case KVM_CAP_IRQFD: > > #endif > > case KVM_CAP_IOEVENTFD_ANY_LENGTH: > > + case KVM_CAP_IOEVENTFD_POST_WRITE: > > case KVM_CAP_CHECK_EXTENSION_VM: > > case KVM_CAP_ENABLE_CAP_VM: > > case KVM_CAP_HALT_POLL: > > -- > > 2.47.3 ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-04-10 19:11 ` Thanos Makatos @ 2026-04-21 14:45 ` Sean Christopherson 2026-06-10 7:31 ` John Levon 0 siblings, 1 reply; 28+ messages in thread From: Sean Christopherson @ 2026-04-21 14:45 UTC (permalink / raw) To: Thanos Makatos; +Cc: Paolo Bonzini, John Levon, kvm@vger.kernel.org On Fri, Apr 10, 2026, Thanos Makatos wrote: > Hi Paolo, Sean, > > Following up on this, please let me know whether this is something I > should revise/resend. No action needed on your end. More discussion is needed to make sure this is uAPI that we want to support long term, but we're not waiting on you for anything. ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-04-21 14:45 ` Sean Christopherson @ 2026-06-10 7:31 ` John Levon 0 siblings, 0 replies; 28+ messages in thread From: John Levon @ 2026-06-10 7:31 UTC (permalink / raw) To: Sean Christopherson; +Cc: Thanos Makatos, Paolo Bonzini, kvm@vger.kernel.org On Tue, Apr 21, 2026 at 07:45:29AM -0700, Sean Christopherson wrote: > On Fri, Apr 10, 2026, Thanos Makatos wrote: > > Hi Paolo, Sean, > > > > Following up on this, please let me know whether this is something I > > should revise/resend. > > No action needed on your end. More discussion is needed to make sure this is > uAPI that we want to support long term, but we're not waiting on you for anything. I appreciate everyone is busy, but I haven't seen any further discussion on this since this email, and I have people asking about this feature - is it possible to get an update? thanks, john ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-06 12:56 [PATCH] KVM: optionally post write on ioeventfd write Thanos Makatos 2026-03-12 15:02 ` David Woodhouse 2026-03-23 15:01 ` Thanos Makatos @ 2026-09-22 13:51 ` Stefan Hajnoczi 2026-09-22 15:28 ` David Woodhouse 2026-10-04 16:16 ` David Woodhouse 2026-09-22 17:03 ` Sean Christopherson 3 siblings, 2 replies; 28+ messages in thread From: Stefan Hajnoczi @ 2026-09-22 13:51 UTC (permalink / raw) To: seanjc, pbonzini Cc: John Levon, kvm@vger.kernel.org, Thanos Makatos, David Woodhouse [-- Attachment #1: Type: text/plain, Size: 2079 bytes --] On Fri, Mar 06, 2026 at 12:56:54PM +0000, Thanos Makatos wrote: > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > ioeventfd that results in the value written by the guest to be copied > to user-supplied memory instead of being discarded. > > The goal of this new mechanism is to speed up doorbell writes on NVMe > controllers emulated outside of the VMM. Currently, a doorbell write to > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > VMM communicating the event, along with the doorbell value, to the NVMe > controller emulation task. With POST_WRITE, the NVMe emulation task is > directly notified of the doorbell write and can find the doorbell value > in a known location, without involving VMM. > > Add tests for this new functionality. > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > lesser extent for pre-reviewing this patch. > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > --- > Documentation/virt/kvm/api.rst | 13 +- > include/uapi/linux/kvm.h | 6 +- > tools/testing/selftests/kvm/Makefile.kvm | 1 + > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > virt/kvm/eventfd.c | 23 + > virt/kvm/kvm_main.c | 1 + > 6 files changed, 666 insertions(+), 2 deletions(-) > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c Sean, Paolo: Ping I would like to enable ioeventfd support in QEMU's NVMe device emulation and this requires POST_WRITE for Windows guests (they don't support NVMe's Doorbell Buffer Config feature so it's necessary to capture the latest value written to the doorbell somehow). POST_WRITE applies to any device that has a doorbell register where the latest value needs to be captured. This looks like a reasonable extension to ioeventfd. I also looked into alternatives, like using userfaultfd, and didn't find anything better. Please consider merging this. Thanks! Stefan [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-09-22 13:51 ` Stefan Hajnoczi @ 2026-09-22 15:28 ` David Woodhouse 2026-09-22 16:47 ` Sean Christopherson 2026-10-04 16:16 ` David Woodhouse 1 sibling, 1 reply; 28+ messages in thread From: David Woodhouse @ 2026-09-22 15:28 UTC (permalink / raw) To: Stefan Hajnoczi, seanjc, pbonzini Cc: John Levon, kvm@vger.kernel.org, Thanos Makatos [-- Attachment #1: Type: text/plain, Size: 2675 bytes --] On Tue, 2026-09-22 at 09:51 -0400, Stefan Hajnoczi wrote: > On Fri, Mar 06, 2026 at 12:56:54PM +0000, Thanos Makatos wrote: > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > ioeventfd that results in the value written by the guest to be copied > > to user-supplied memory instead of being discarded. > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > controllers emulated outside of the VMM. Currently, a doorbell write to > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > VMM communicating the event, along with the doorbell value, to the NVMe > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > directly notified of the doorbell write and can find the doorbell value > > in a known location, without involving VMM. > > > > Add tests for this new functionality. > > > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > > lesser extent for pre-reviewing this patch. > > > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > > --- > > Documentation/virt/kvm/api.rst | 13 +- > > include/uapi/linux/kvm.h | 6 +- > > tools/testing/selftests/kvm/Makefile.kvm | 1 + > > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > > virt/kvm/eventfd.c | 23 + > > virt/kvm/kvm_main.c | 1 + > > 6 files changed, 666 insertions(+), 2 deletions(-) > > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c > > Sean, Paolo: Ping > > I would like to enable ioeventfd support in QEMU's NVMe device emulation > and this requires POST_WRITE for Windows guests (they don't support > NVMe's Doorbell Buffer Config feature so it's necessary to capture the > latest value written to the doorbell somehow). > > POST_WRITE applies to any device that has a doorbell register where the > latest value needs to be captured. This looks like a reasonable > extension to ioeventfd. > > I also looked into alternatives, like using userfaultfd, and didn't find > anything better. > > Please consider merging this. Thanks! I said I was working on using this for i82559 emulation; I have that working now, and POST_WRITE is perfectly sufficient. I think I'd prefer to see it use a WRITE_ONCE to the userspace address rather than a __copy_to_user() which could theoretically tear, but in generally I'm happy with it. Reviewed-by: David Woodhouse <dwmw@amazon.co.uk> [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-09-22 15:28 ` David Woodhouse @ 2026-09-22 16:47 ` Sean Christopherson 0 siblings, 0 replies; 28+ messages in thread From: Sean Christopherson @ 2026-09-22 16:47 UTC (permalink / raw) To: David Woodhouse Cc: Stefan Hajnoczi, pbonzini, John Levon, kvm@vger.kernel.org, Thanos Makatos On Tue, Sep 22, 2026, David Woodhouse wrote: > On Tue, 2026-09-22 at 09:51 -0400, Stefan Hajnoczi wrote: > > On Fri, Mar 06, 2026 at 12:56:54PM +0000, Thanos Makatos wrote: > > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > > ioeventfd that results in the value written by the guest to be copied > > > to user-supplied memory instead of being discarded. > > > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > > controllers emulated outside of the VMM. Currently, a doorbell write to > > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > > VMM communicating the event, along with the doorbell value, to the NVMe > > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > > directly notified of the doorbell write and can find the doorbell value > > > in a known location, without involving VMM. > > > > > > Add tests for this new functionality. > > > > > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > > > lesser extent for pre-reviewing this patch. > > > > > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > > > --- > > > Documentation/virt/kvm/api.rst | 13 +- > > > include/uapi/linux/kvm.h | 6 +- > > > tools/testing/selftests/kvm/Makefile.kvm | 1 + > > > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > > > virt/kvm/eventfd.c | 23 + > > > virt/kvm/kvm_main.c | 1 + > > > 6 files changed, 666 insertions(+), 2 deletions(-) > > > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c > > > > Sean, Paolo: Ping > > > > I would like to enable ioeventfd support in QEMU's NVMe device emulation > > and this requires POST_WRITE for Windows guests (they don't support > > NVMe's Doorbell Buffer Config feature so it's necessary to capture the > > latest value written to the doorbell somehow). > > > > POST_WRITE applies to any device that has a doorbell register where the > > latest value needs to be captured. This looks like a reasonable > > extension to ioeventfd. > > > > I also looked into alternatives, like using userfaultfd, and didn't find > > anything better. > > > > Please consider merging this. Thanks! > > I said I was working on using this for i82559 emulation; I have that > working now, and POST_WRITE is perfectly sufficient. > > I think I'd prefer to see it use a WRITE_ONCE to the userspace address > rather than a __copy_to_user() which could theoretically tear, Hrm, I was going to say that's a "feature" of sorts, not a bug, as eventfd_signal() provides ordering by way of its spinlock, but I'm guessing that the userspace side wants to read the tail pointer multiple times per wakeup, i.e. wants to eagerly process all transactions. I don't see any explicit documentation with respect to put_user() providing atomicity guarantees, but unsafe_atomic_store_release_user() uses unsafe_put_user(), so presumably that's an undocumented requirement? Ah, not a requirement so much as a "this will work so long as userspace isn't being stupid". E.g. a 64-bit write on a 32-bit x86 host is doomed unless KVM goes to extreme lengths. So, if we want a best effort approach something end up with something like the (compile-tested-only) below? As ugly as it is, I think it has my vote, because it should Just Work for the majority of use cases. #define ioeventfd_get_val(__ptr, __len, __unsupported_len) \ ({ \ u64 __val; \ \ switch (len) { \ case 1: \ __val = get_unaligned((u8 *)__ptr); \ break; \ case 2: \ __val = get_unaligned((u16 *)__ptr); \ break; \ case 4: \ __val = get_unaligned((u32 *)__ptr); \ break; \ case 8: \ __val = get_unaligned((u64 *)__ptr); \ break; \ default: \ goto __unsupported_len; \ } \ __val; \ }) static bool ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val) { if (addr != p->addr) /* address must be precise for a hit */ return false; if (!p->length) /* length = 0 means only look at the address, so always a hit */ return true; if (len != p->length) /* address-range must be precise for a hit */ return false; if (p->wildcard) /* all else equal, wildcard is always a hit */ return true; /* otherwise, we have to actually compare the data */ return ioeventfd_get_val(val, len, unsupported_len) == p->datamatch; unsupported_len: return false; } /* MMIO/PIO writes trigger an event if the addr/val match */ static int ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, int len, const void *val) { struct _ioeventfd *p = to_ioeventfd(this); if (!ioeventfd_in_range(p, addr, len, val)) return -EOPNOTSUPP; if (p->post_addr) { u64 _val = ioeventfd_get_val(val, len, unsupported_len); int r; switch (len) { case 1: r = put_user(_val, (u8 __user *)p->post_addr); break; case 2: r = put_user(_val, (u16 __user *)p->post_addr); break; case 4: r = put_user(_val, (u32 __user *)p->post_addr); break; case 8: r = put_user(_val, (u64 __user *)p->post_addr); break; default: WARN_ON_ONCE(1); r = 0; break; } if (r) return r; } unsupported_len: eventfd_signal(p->eventfd); return 0; } ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-09-22 13:51 ` Stefan Hajnoczi 2026-09-22 15:28 ` David Woodhouse @ 2026-10-04 16:16 ` David Woodhouse 2026-10-05 5:11 ` Sean Christopherson 2026-10-15 8:44 ` David Woodhouse 1 sibling, 2 replies; 28+ messages in thread From: David Woodhouse @ 2026-10-04 16:16 UTC (permalink / raw) To: Stefan Hajnoczi, seanjc, pbonzini Cc: John Levon, kvm@vger.kernel.org, Thanos Makatos [-- Attachment #1: Type: text/plain, Size: 2750 bytes --] On Tue, 2026-09-22 at 09:51 -0400, Stefan Hajnoczi wrote: > On Fri, Mar 06, 2026 at 12:56:54PM +0000, Thanos Makatos wrote: > > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > > ioeventfd that results in the value written by the guest to be copied > > to user-supplied memory instead of being discarded. > > > > The goal of this new mechanism is to speed up doorbell writes on NVMe > > controllers emulated outside of the VMM. Currently, a doorbell write to > > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > > VMM communicating the event, along with the doorbell value, to the NVMe > > controller emulation task. With POST_WRITE, the NVMe emulation task is > > directly notified of the doorbell write and can find the doorbell value > > in a known location, without involving VMM. > > > > Add tests for this new functionality. > > > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > > lesser extent for pre-reviewing this patch. > > > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > > --- > > Documentation/virt/kvm/api.rst | 13 +- > > include/uapi/linux/kvm.h | 6 +- > > tools/testing/selftests/kvm/Makefile.kvm | 1 + > > tools/testing/selftests/kvm/ioeventfd_test.c | 624 +++++++++++++++++++ > > virt/kvm/eventfd.c | 23 + > > virt/kvm/kvm_main.c | 1 + > > 6 files changed, 666 insertions(+), 2 deletions(-) > > create mode 100644 tools/testing/selftests/kvm/ioeventfd_test.c > > Sean, Paolo: Ping > > I would like to enable ioeventfd support in QEMU's NVMe device emulation > and this requires POST_WRITE for Windows guests (they don't support > NVMe's Doorbell Buffer Config feature so it's necessary to capture the > latest value written to the doorbell somehow). > > POST_WRITE applies to any device that has a doorbell register where the > latest value needs to be captured. This looks like a reasonable > extension to ioeventfd. > > I also looked into alternatives, like using userfaultfd, and didn't find > anything better. > > Please consider merging this. Thanks! FWIW while this is sufficient for implementing i82599 VF emulation, to get line-rate TCP *performance* I also needed a directed yield_to() to the consumer worker thread when it gets triggered. And to get line-rate *UDP* performance I needed the consumer to be able to arm/disarm it, to avoid getting throttled into lockstep with the guest. I'll clean up my proof of concept hacks and post them after LPC. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-04 16:16 ` David Woodhouse @ 2026-10-05 5:11 ` Sean Christopherson 2026-10-15 8:44 ` David Woodhouse 1 sibling, 0 replies; 28+ messages in thread From: Sean Christopherson @ 2026-10-05 5:11 UTC (permalink / raw) To: David Woodhouse Cc: Stefan Hajnoczi, pbonzini, John Levon, kvm@vger.kernel.org, Thanos Makatos On Sun, Oct 04, 2026, David Woodhouse wrote: > FWIW while this is sufficient for implementing i82599 VF emulation, to > get line-rate TCP *performance* I also needed a directed yield_to() to > the consumer worker thread when it gets triggered. > > And to get line-rate *UDP* performance I needed the consumer to be able > to arm/disarm it, to avoid getting throttled into lockstep with the > guest. By "it", do you mean (a) the eventfd, (b) the yield_to() behavior, (c) both, or (d) something else entirely? > I'll clean up my proof of concept hacks and post them after LPC. ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-04 16:16 ` David Woodhouse 2026-10-05 5:11 ` Sean Christopherson @ 2026-10-15 8:44 ` David Woodhouse 2026-10-15 18:23 ` Stefan Hajnoczi 1 sibling, 1 reply; 28+ messages in thread From: David Woodhouse @ 2026-10-15 8:44 UTC (permalink / raw) To: Stefan Hajnoczi, seanjc, pbonzini Cc: John Levon, kvm@vger.kernel.org, Thanos Makatos [-- Attachment #1: Type: text/plain, Size: 724 bytes --] On Sun, 2026-10-04 at 18:16 +0200, David Woodhouse wrote: > > FWIW while this is sufficient for implementing i82599 VF emulation, to > get line-rate TCP *performance* I also needed a directed yield_to() to > the consumer worker thread when it gets triggered. > > And to get line-rate *UDP* performance I needed the consumer to be able > to arm/disarm it, to avoid getting throttled into lockstep with the > guest. > > I'll clean up my proof of concept hacks and post them after LPC. FWIW after a bunch of other performance work on the emulation it turns out I can live without this. As I was preparing to send a version of it upstream, I did a final retest with/without it, and it isn't needed any more. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-15 8:44 ` David Woodhouse @ 2026-10-15 18:23 ` Stefan Hajnoczi 2026-10-15 18:43 ` David Woodhouse 2026-10-26 19:29 ` Thanos Makatos 0 siblings, 2 replies; 28+ messages in thread From: Stefan Hajnoczi @ 2026-10-15 18:23 UTC (permalink / raw) To: David Woodhouse Cc: seanjc, pbonzini, John Levon, kvm@vger.kernel.org, Thanos Makatos On Thu, Oct 15, 2026 at 09:44:54AM +0100, David Woodhouse wrote: > On Sun, 2026-10-04 at 18:16 +0200, David Woodhouse wrote: > > > > FWIW while this is sufficient for implementing i82599 VF emulation, to > > get line-rate TCP *performance* I also needed a directed yield_to() to > > the consumer worker thread when it gets triggered. > > > > And to get line-rate *UDP* performance I needed the consumer to be able > > to arm/disarm it, to avoid getting throttled into lockstep with the > > guest. > > > > I'll clean up my proof of concept hacks and post them after LPC. > > FWIW after a bunch of other performance work on the emulation it turns > out I can live without this. As I was preparing to send a version of it > upstream, I did a final retest with/without it, and it isn't needed any > more. Neat, were you able to still use ioeventfd without POST_WRITE or did you take a different approach? We ran NVMe emulation benchmarks with QEMU recently and found Windows guests need ioeventfd POST_WRITE to get into the ballpark of virtio-blk. I'm still keen for POST_WRITE to be merged. Thanos & John: do you want to give the patch series another spin based on the feedback? Stefan ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-15 18:23 ` Stefan Hajnoczi @ 2026-10-15 18:43 ` David Woodhouse 2026-10-19 19:48 ` Stefan Hajnoczi 2026-10-26 19:29 ` Thanos Makatos 1 sibling, 1 reply; 28+ messages in thread From: David Woodhouse @ 2026-10-15 18:43 UTC (permalink / raw) To: Stefan Hajnoczi Cc: seanjc, pbonzini, John Levon, kvm@vger.kernel.org, Thanos Makatos [-- Attachment #1: Type: text/plain, Size: 2116 bytes --] On Thu, 2026-10-15 at 14:23 -0400, Stefan Hajnoczi wrote: > On Thu, Oct 15, 2026 at 09:44:54AM +0100, David Woodhouse wrote: > > On Sun, 2026-10-04 at 18:16 +0200, David Woodhouse wrote: > > > > > > FWIW while this is sufficient for implementing i82599 VF emulation, to > > > get line-rate TCP *performance* I also needed a directed yield_to() to > > > the consumer worker thread when it gets triggered. > > > > > > And to get line-rate *UDP* performance I needed the consumer to be able > > > to arm/disarm it, to avoid getting throttled into lockstep with the > > > guest. > > > > > > I'll clean up my proof of concept hacks and post them after LPC. > > > > FWIW after a bunch of other performance work on the emulation it turns > > out I can live without this. As I was preparing to send a version of it > > upstream, I did a final retest with/without it, and it isn't needed any > > more. > > Neat, were you able to still use ioeventfd without POST_WRITE or did you > take a different approach? > > We ran NVMe emulation benchmarks with QEMU recently and found Windows > guests need ioeventfd POST_WRITE to get into the ballpark of virtio-blk. > I'm still keen for POST_WRITE to be merged. > > Thanos & John: do you want to give the patch series another spin based > on the feedback? Apologies, I was again being lax with my pronouns, and expecting my choice of citation to carry the information about what I meant by "it". I am using the ioeventfd POST_WRITE (with Sean's put_user cleanup). As cited above, I *had* implemented a directed yield too, and further made it possible for the consumer to arm/disarm that directed yield (becomes sometimes latency matters, and sometimes throughput matters). Since then, I've done a lot more optimisation of wakeups and batch processing on the emulation side. So when I was preparing the directed yield for upstream submission and did a last-minute A/B test with and without the directed yield, I found that the directed yield didn't actually give me a significant win at all — neither for TCP nor UDP traffic. [-- Attachment #2: smime.p7s --] [-- Type: application/pkcs7-signature, Size: 6179 bytes --] ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-15 18:43 ` David Woodhouse @ 2026-10-19 19:48 ` Stefan Hajnoczi 0 siblings, 0 replies; 28+ messages in thread From: Stefan Hajnoczi @ 2026-10-19 19:48 UTC (permalink / raw) To: David Woodhouse Cc: seanjc, pbonzini, John Levon, kvm@vger.kernel.org, Thanos Makatos On Thu, Oct 15, 2026 at 07:43:34PM +0100, David Woodhouse wrote: > On Thu, 2026-10-15 at 14:23 -0400, Stefan Hajnoczi wrote: > > On Thu, Oct 15, 2026 at 09:44:54AM +0100, David Woodhouse wrote: > > > On Sun, 2026-10-04 at 18:16 +0200, David Woodhouse wrote: > > > > > > > > FWIW while this is sufficient for implementing i82599 VF emulation, to > > > > get line-rate TCP *performance* I also needed a directed yield_to() to > > > > the consumer worker thread when it gets triggered. > > > > > > > > And to get line-rate *UDP* performance I needed the consumer to be able > > > > to arm/disarm it, to avoid getting throttled into lockstep with the > > > > guest. > > > > > > > > I'll clean up my proof of concept hacks and post them after LPC. > > > > > > FWIW after a bunch of other performance work on the emulation it turns > > > out I can live without this. As I was preparing to send a version of it > > > upstream, I did a final retest with/without it, and it isn't needed any > > > more. > > > > Neat, were you able to still use ioeventfd without POST_WRITE or did you > > take a different approach? > > > > We ran NVMe emulation benchmarks with QEMU recently and found Windows > > guests need ioeventfd POST_WRITE to get into the ballpark of virtio-blk. > > I'm still keen for POST_WRITE to be merged. > > > > Thanos & John: do you want to give the patch series another spin based > > on the feedback? > > Apologies, I was again being lax with my pronouns, and expecting my > choice of citation to carry the information about what I meant by "it". > > I am using the ioeventfd POST_WRITE (with Sean's put_user cleanup). > > As cited above, I *had* implemented a directed yield too, and further > made it possible for the consumer to arm/disarm that directed yield > (becomes sometimes latency matters, and sometimes throughput matters). > > Since then, I've done a lot more optimisation of wakeups and batch > processing on the emulation side. So when I was preparing the directed > yield for upstream submission and did a last-minute A/B test with and > without the directed yield, I found that the directed yield didn't > actually give me a significant win at all — neither for TCP nor UDP > traffic. I see, thanks for clarifying. Stefan ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH] KVM: optionally post write on ioeventfd write 2026-10-15 18:23 ` Stefan Hajnoczi 2026-10-15 18:43 ` David Woodhouse @ 2026-10-26 19:29 ` Thanos Makatos 1 sibling, 0 replies; 28+ messages in thread From: Thanos Makatos @ 2026-10-26 19:29 UTC (permalink / raw) To: Stefan Hajnoczi, David Woodhouse Cc: seanjc@google.com, pbonzini@redhat.com, John Levon, kvm@vger.kernel.org > -----Original Message----- > From: Stefan Hajnoczi <stefanha@redhat.com> > Sent: 15 October 2026 19:23 > To: David Woodhouse <dwmw2@infradead.org> > Cc: seanjc@google.com; pbonzini@redhat.com; John Levon > <john.levon@nutanix.com>; kvm@vger.kernel.org; Thanos Makatos > <thanos.makatos@nutanix.com> > Subject: Re: [PATCH] KVM: optionally post write on ioeventfd write > > !-------------------------------------------------------------------| > CAUTION: External Email > > |-------------------------------------------------------------------! > > On Thu, Oct 15, 2026 at 09:44:54AM +0100, David Woodhouse wrote: > > On Sun, 2026-10-04 at 18:16 +0200, David Woodhouse wrote: > > > > > > FWIW while this is sufficient for implementing i82599 VF > emulation, to > > > get line-rate TCP *performance* I also needed a directed > yield_to() to > > > the consumer worker thread when it gets triggered. > > > > > > And to get line-rate *UDP* performance I needed the consumer to be > able > > > to arm/disarm it, to avoid getting throttled into lockstep with > the > > > guest. > > > > > > I'll clean up my proof of concept hacks and post them after LPC. > > > > FWIW after a bunch of other performance work on the emulation it > turns > > out I can live without this. As I was preparing to send a version of > it > > upstream, I did a final retest with/without it, and it isn't needed > any > > more. > > Neat, were you able to still use ioeventfd without POST_WRITE or did > you > take a different approach? > > We ran NVMe emulation benchmarks with QEMU recently and found Windows > guests need ioeventfd POST_WRITE to get into the ballpark of virtio- > blk. > I'm still keen for POST_WRITE to be merged. > > Thanos & John: do you want to give the patch series another spin based > on the feedback? I'll respin in the next few days. > > Stefan ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH] KVM: optionally post write on ioeventfd write 2026-03-06 12:56 [PATCH] KVM: optionally post write on ioeventfd write Thanos Makatos ` (2 preceding siblings ...) 2026-09-22 13:51 ` Stefan Hajnoczi @ 2026-09-22 17:03 ` Sean Christopherson 2026-10-27 17:50 ` Thanos Makatos 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos 3 siblings, 2 replies; 28+ messages in thread From: Sean Christopherson @ 2026-09-22 17:03 UTC (permalink / raw) To: Thanos Makatos; +Cc: pbonzini@redhat.com, John Levon, kvm@vger.kernel.org On Fri, Mar 06, 2026, Thanos Makatos wrote: > @@ -656,7 +658,8 @@ struct kvm_ioeventfd { > __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ > __s32 fd; > __u32 flags; > - __u8 pad[36]; > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is set */ > + __u8 pad[24]; > }; I would prefer to have explicit padding: struct kvm_ioeventfd { __u64 datamatch; __u64 addr; /* legal pio/mmio address */ __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ __s32 fd; __u32 flags; __u32 pad0; __u64 post_addr; /* address to write to if POST_WRITE is set */ __u8 pad[24]; }; that way it's more obvious there's a hole we can use in the future. > diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm > index fdec90e85467..7ab470981c31 100644 > --- a/tools/testing/selftests/kvm/Makefile.kvm > +++ b/tools/testing/selftests/kvm/Makefile.kvm > @@ -64,6 +64,7 @@ TEST_GEN_PROGS_COMMON += kvm_binary_stats_test > TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus > TEST_GEN_PROGS_COMMON += kvm_page_table_test > TEST_GEN_PROGS_COMMON += set_memory_region_test > +TEST_GEN_PROGS_COMMON += ioeventfd_test Please name this ioeventfd_posted_write_test, as it isn't a generic ioeventfd test. And maintain the alphabetical sort. Does this actually work on s390? Which doesn't have MMIO... > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > index 0e8b8a2c5b79..22bc49a41503 100644 > --- a/virt/kvm/eventfd.c > +++ b/virt/kvm/eventfd.c > @@ -741,6 +741,7 @@ struct _ioeventfd { > struct kvm_io_device dev; > u8 bus_idx; > bool wildcard; > + void __user *post_addr; > }; > > static inline struct _ioeventfd * > @@ -812,6 +813,9 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, > if (!ioeventfd_in_range(p, addr, len, val)) > return -EOPNOTSUPP; > > + if (p->post_addr && len > 0 && __copy_to_user(p->post_addr, val, len)) > + return -EFAULT; > + > eventfd_signal(p->eventfd); > return 0; > } > @@ -866,6 +870,7 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, > { > > struct eventfd_ctx *eventfd; > + void __user *post_addr; > struct _ioeventfd *p; > int ret; > > @@ -873,6 +878,16 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, > if (IS_ERR(eventfd)) > return PTR_ERR(eventfd); > > + post_addr = u64_to_user_ptr(args->post_addr); > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > + (!args->len || !post_addr || > + args->post_addr != untagged_addr(args->post_addr) || > + !access_ok(post_addr, args->len))) { > + /* In KVM’s ABI, post_addr must be non‑NULL. */ Maybe elaborate just a bit? And I vote to drop the local "post_addr", even though it's more "work", because it's super easy to overlook that KVM *must* do the cast before checking for a NULL pointer. E.g. /* * Disallow posting to virtual address 0 so that KVM can treat * a NULL user pointer as "no posted write". */ if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && (!args->len || !u64_to_user_ptr(args->post_addr) || args->post_addr != untagged_addr(args->post_addr) || !access_ok(u64_to_user_ptr(args->post_addr), args->len))) { ret = -EINVAL; goto fail; } ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH] KVM: optionally post write on ioeventfd write 2026-09-22 17:03 ` Sean Christopherson @ 2026-10-27 17:50 ` Thanos Makatos 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos 1 sibling, 0 replies; 28+ messages in thread From: Thanos Makatos @ 2026-10-27 17:50 UTC (permalink / raw) To: Sean Christopherson Cc: pbonzini@redhat.com, John Levon, kvm@vger.kernel.org, stefanha@redhat.com, David Woodhouse, Mark Cave-Ayland > -----Original Message----- > From: Sean Christopherson <seanjc@google.com> > Sent: 22 September 2026 18:04 > To: Thanos Makatos <thanos.makatos@nutanix.com> > Cc: pbonzini@redhat.com; John Levon <john.levon@nutanix.com>; > kvm@vger.kernel.org > Subject: Re: [PATCH] KVM: optionally post write on ioeventfd write > > !-------------------------------------------------------------------| > CAUTION: External Email > > |-------------------------------------------------------------------! > > On Fri, Mar 06, 2026, Thanos Makatos wrote: > > @@ -656,7 +658,8 @@ struct kvm_ioeventfd { > > __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length > */ > > __s32 fd; > > __u32 flags; > > - __u8 pad[36]; > > + __aligned_u64 post_addr; /* address to write to if POST_WRITE is > set */ > > + __u8 pad[24]; > > }; > > I would prefer to have explicit padding: > > struct kvm_ioeventfd { > __u64 datamatch; > __u64 addr; /* legal pio/mmio address */ > __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length > */ > __s32 fd; > __u32 flags; > __u32 pad0; > __u64 post_addr; /* address to write to if POST_WRITE is set */ > __u8 pad[24]; > }; > > that way it's more obvious there's a hole we can use in the future. Ack. > > > > diff --git a/tools/testing/selftests/kvm/Makefile.kvm > b/tools/testing/selftests/kvm/Makefile.kvm > > index fdec90e85467..7ab470981c31 100644 > > --- a/tools/testing/selftests/kvm/Makefile.kvm > > +++ b/tools/testing/selftests/kvm/Makefile.kvm > > @@ -64,6 +64,7 @@ TEST_GEN_PROGS_COMMON += kvm_binary_stats_test > > TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus > > TEST_GEN_PROGS_COMMON += kvm_page_table_test > > TEST_GEN_PROGS_COMMON += set_memory_region_test > > +TEST_GEN_PROGS_COMMON += ioeventfd_test > > Please name this ioeventfd_posted_write_test, as it isn't a generic > ioeventfd > test. And maintain the alphabetical sort. Ack. > > Does this actually work on s390? Which doesn't have MMIO... I haven't tested on anything else other than x86, moving to x86-specific tests. > > > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > > index 0e8b8a2c5b79..22bc49a41503 100644 > > --- a/virt/kvm/eventfd.c > > +++ b/virt/kvm/eventfd.c > > @@ -741,6 +741,7 @@ struct _ioeventfd { > > struct kvm_io_device dev; > > u8 bus_idx; > > bool wildcard; > > + void __user *post_addr; > > }; > > > > static inline struct _ioeventfd * > > @@ -812,6 +813,9 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct > kvm_io_device *this, gpa_t addr, > > if (!ioeventfd_in_range(p, addr, len, val)) > > return -EOPNOTSUPP; > > > > + if (p->post_addr && len > 0 && __copy_to_user(p->post_addr, val, > len)) > > + return -EFAULT; > > + > > eventfd_signal(p->eventfd); > > return 0; > > } > > @@ -866,6 +870,7 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > > { > > > > struct eventfd_ctx *eventfd; > > + void __user *post_addr; > > struct _ioeventfd *p; > > int ret; > > > > @@ -873,6 +878,16 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > > if (IS_ERR(eventfd)) > > return PTR_ERR(eventfd); > > > > + post_addr = u64_to_user_ptr(args->post_addr); > > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > > + (!args->len || !post_addr || > > + args->post_addr != untagged_addr(args->post_addr) || > > + !access_ok(post_addr, args->len))) { > > + /* In KVM’s ABI, post_addr must be non‑NULL. */ > > Maybe elaborate just a bit? And I vote to drop the local "post_addr", > even though > it's more "work", because it's super easy to overlook that KVM *must* > do the cast > before checking for a NULL pointer. > > E.g. > > /* > * Disallow posting to virtual address 0 so that KVM can treat > * a NULL user pointer as "no posted write". > */ > if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > (!args->len || !u64_to_user_ptr(args->post_addr) || > args->post_addr != untagged_addr(args->post_addr) || > !access_ok(u64_to_user_ptr(args->post_addr), args->len))) { > ret = -EINVAL; > goto fail; > } Ack. ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v2] KVM: optionally post write on ioeventfd write 2026-09-22 17:03 ` Sean Christopherson 2026-10-27 17:50 ` Thanos Makatos @ 2026-10-27 18:09 ` Thanos Makatos 2026-10-27 18:34 ` sashiko-bot ` (2 more replies) 1 sibling, 3 replies; 28+ messages in thread From: Thanos Makatos @ 2026-10-27 18:09 UTC (permalink / raw) To: seanjc@google.com Cc: John Levon, kvm@vger.kernel.org, pbonzini@redhat.com, Thanos Makatos, dwmw2@infradead.org, stefanha@redhat.com, Mark Cave-Ayland Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an ioeventfd that results in the value written by the guest to be copied to user-supplied memory instead of being discarded. The goal of this new mechanism is to speed up doorbell writes on NVMe controllers emulated outside of the VMM. Currently, a doorbell write to an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the VMM communicating the event, along with the doorbell value, to the NVMe controller emulation task. With POST_WRITE, the NVMe emulation task is directly notified of the doorbell write and can find the doorbell value in a known location, without involving VMM. Add tests for this new functionality. LLM (claude-4.6-opus-high) was used mainly for the tests and to a lesser extent for pre-reviewing this patch. Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> --- Changes since v1 (Sean): - Made test x86-only. - Added explicit padding to struct kvm_ioeventfd. - Renamed ioeventfd_test to ioeventfd_posted_write_test and moved to maintain alphabetical sort. - Dropped post_addr from kvm_assign_ioeventfd_idx. - Expanded comment in kvm_assign_ioeventfd_idx. - Refactored part of ioeventfd_in_range() into ioeventfd_get_val() and reused it. Documentation/virt/kvm/api.rst | 14 +- include/uapi/linux/kvm.h | 7 +- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../kvm/x86/ioeventfd_posted_write_test.c | 623 ++++++++++++++++++ virt/kvm/eventfd.c | 94 ++- virt/kvm/kvm_main.c | 1 + 6 files changed, 719 insertions(+), 21 deletions(-) create mode 100644 tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index e95c9cda085d..a5e61ba7eaf5 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -2132,7 +2132,9 @@ provided event instead of triggering an exit. __u32 len; /* 0, 1, 2, 4, or 8 bytes */ __s32 fd; __u32 flags; - __u8 pad[36]; + __u32 pad0; + __u64 post_addr; /* address to write to if POST_WRITE is set */ + __u8 pad[24]; }; For the special case of virtio-ccw devices on s390, the ioevent is matched @@ -2145,6 +2147,7 @@ The following flags are defined:: #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign) #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) + #define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write) If datamatch flag is set, the event will be signaled only if the written value to the registered address is equal to datamatch in struct kvm_ioeventfd. @@ -2157,6 +2160,15 @@ the kernel will ignore the length of guest write and may get a faster vmexit. The speedup may only apply to specific architectures, but the ioeventfd will work anyway. +With KVM_IOEVENTFD_FLAG_POST_WRITE, the value being written is copied to the +userspace address specified by post_addr, and the eventfd is signaled. The +copy is guaranteed to complete before the eventfd is signaled, so a userspace +reader that wakes on the eventfd will observe the written value. When multiple +vCPUs write to the same ioeventfd concurrently, the value at post_addr reflects +one of the writes. If the copy to post_addr fails (e.g. the memory has been +unmapped), the eventfd is not signaled and the write is reported to userspace +as a regular MMIO/PIO exit. + 4.60 KVM_DIRTY_TLB ------------------ diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 8dff2fc1972e..07ac3142ce17 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -644,6 +644,7 @@ enum { kvm_ioeventfd_flag_nr_deassign, kvm_ioeventfd_flag_nr_virtio_ccw_notify, kvm_ioeventfd_flag_nr_fast_mmio, + kvm_ioeventfd_flag_nr_post_write, kvm_ioeventfd_flag_nr_max, }; @@ -652,6 +653,7 @@ enum { #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign) #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) +#define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write) #define KVM_IOEVENTFD_VALID_FLAG_MASK ((1 << kvm_ioeventfd_flag_nr_max) - 1) @@ -661,7 +663,9 @@ struct kvm_ioeventfd { __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ __s32 fd; __u32 flags; - __u8 pad[36]; + __u32 pad0; + __u64 post_addr; /* address to write to if POST_WRITE is set */ + __u8 pad[24]; }; #define KVM_X86_DISABLE_EXITS_MWAIT (1 << 0) @@ -1000,6 +1004,7 @@ struct kvm_enable_cap { #define KVM_CAP_PPC_COMPAT_CAPS 250 #define KVM_CAP_ARM_PMU_V3_STRICT 251 #define KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES 252 +#define KVM_CAP_IOEVENTFD_POST_WRITE 253 struct kvm_irq_routing_irqchip { __u32 irqchip; diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 3cb35a6963d5..38860f2f08b0 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -92,6 +92,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_features TEST_GEN_PROGS_x86 += x86/hyperv_ipi TEST_GEN_PROGS_x86 += x86/hyperv_svm_test TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush +TEST_GEN_PROGS_x86 += x86/ioeventfd_posted_write_test TEST_GEN_PROGS_x86 += x86/kvm_clock_test TEST_GEN_PROGS_x86 += x86/kvm_pv_test TEST_GEN_PROGS_x86 += x86/kvm_buslock_test diff --git a/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c b/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c new file mode 100644 index 000000000000..0c7072e9d377 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ioeventfd_posted_write_test.c - Tests for KVM_IOEVENTFD_FLAG_POST_WRITE. + * + * Tests that when KVM_IOEVENTFD_FLAG_POST_WRITE is set and the MMIO/PIO + * address is written to, the value is copied to the user-provided address + * and the eventfd is signaled. Also tests negative cases and interactions + * with DATAMATCH. + * + * Copyright Nutanix, 2026 + * + * Author: Thanos Makatos <thanos.makatos@nutanix.com> + */ + +#include <errno.h> +#include <poll.h> +#include <string.h> + +#include "kvm_util.h" +#include "processor.h" +#include "ucall_common.h" + +#define MMIO_GPA (1UL << 30) +#define PIO_PORT 0xe000 +#define TEST_VAL 0xDEADBEEFCAFEBABEULL +#define MATCH_VAL 0x42U +#define NOMATCH_VAL (MATCH_VAL + 1) +#define POISON_VAL 0xFFFFFFFFU + +/* + * Check that the most recent vCPU exit is a ucall (delivered as KVM_EXIT_IO + * on x86) matching @expected_cmd. The caller must have already called + * vcpu_run(). + * + * @expected_cmd: UCALL_SYNC, UCALL_DONE, etc. + * @expected_stage: for UCALL_SYNC, the stage number passed by GUEST_SYNC(). + * Ignored for other ucall types. + * + * Aborts the test on UCALL_ABORT (a guest-side assertion failure). + */ +static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_cmd, + uint64_t expected_stage) +{ + struct ucall uc; + + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + case UCALL_SYNC: + TEST_ASSERT(expected_cmd == UCALL_SYNC, + "got UCALL_SYNC, expected %lu", + expected_cmd); + TEST_ASSERT(uc.args[1] == expected_stage, + "expected stage %lu, got %lu", + expected_stage, uc.args[1]); + break; + case UCALL_DONE: + TEST_ASSERT(expected_cmd == UCALL_DONE, + "got UCALL_DONE, expected %lu", + expected_cmd); + break; + default: + TEST_FAIL("unexpected ucall %lu", uc.cmd); + } +} + +/* + * Verify that KVM_IOEVENTFD rejects invalid POST_WRITE configurations: + * - len=0: the kernel needs a non-zero length to know how many bytes to copy. + * - post_addr=NULL: there is no destination for the copy. + * - post_addr outside the process address space: access_ok() rejects it. + * All three must fail with EINVAL. + */ +static void test_post_write_negative(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vm *vm; + uint64_t dummy; + int ret; + int fd; + + vm = vm_create_barebones(); + fd = kvm_new_eventfd(); + + /* length cannot be zero */ + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 0, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&dummy, + }; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "len=0: expected EINVAL, got ret=%d errno=%d", ret, errno); + + /* post_addr cannot be NULL */ + ioeventfd.len = 4; + ioeventfd.post_addr = 0ULL; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "NULL post_addr: expected EINVAL, got ret=%d errno=%d", + ret, errno); + + /* bogus post_addr */ + ioeventfd.post_addr = (u64)0xdeaddeaddeaddeadULL; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "bad post_addr: expected EINVAL, got ret=%d errno=%d", + ret, errno); + + close(fd); + kvm_vm_free(vm); +} + +#define DEFINE_GUEST_WRITE_FN(suffix, type) \ +static void guest_code_w##suffix(void) { \ + *(volatile type *)MMIO_GPA = (type)TEST_VAL; \ + GUEST_DONE(); \ +} + +DEFINE_GUEST_WRITE_FN(1, uint8_t) +DEFINE_GUEST_WRITE_FN(2, uint16_t) +DEFINE_GUEST_WRITE_FN(4, uint32_t) +DEFINE_GUEST_WRITE_FN(8, uint64_t) + +/* + * Verify that ioeventfd_write copies exactly @width bytes to post_addr for + * each supported MMIO write width (1, 2, 4, 8). The guest writes the low + * @width bytes of TEST_VAL; the host checks that exactly those bytes land + * at post_addr and the eventfd is signaled. + */ +static void test_post_write_width(int width, void (*guest_fn)(void)) +{ + uint64_t actual, expected, count; + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int fd, ret; + + /* need to initialize to 0 because the guest writes the low @width bytes */ + actual = 0; + expected = 0; + + vm = vm_create_with_one_vcpu(&vcpu, guest_fn); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = width, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + + memcpy(&expected, &(uint64_t){TEST_VAL}, width); + TEST_ASSERT_EQ(actual, expected); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_datamatch(void) +{ + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = NOMATCH_VAL; + GUEST_SYNC(2); + GUEST_DONE(); +} + +/* + * Test the interaction between DATAMATCH and POST_WRITE. When both flags are + * set, ioeventfd_write should only fire (signal eventfd + copy value) when the + * written value matches datamatch. A non-matching write must leave the eventfd + * unsignaled and post_addr untouched, and fall through to KVM_EXIT_MMIO. + */ +static void test_post_write_datamatch(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_datamatch); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .datamatch = MATCH_VAL, + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_DATAMATCH, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + /* + * Guest writes MATCH_VAL → ioeventfd fires (value copied, eventfd + * signaled), vCPU continues, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + actual = POISON_VAL; + + /* + * Guest writes NOMATCH_VAL → ioeventfd_in_range() returns false, bus + * returns -EOPNOTSUPP → KVM_EXIT_MMIO to userspace. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, + "expected MMIO at 0x%lx, got 0x%llx", + MMIO_GPA, run->mmio.phys_addr); + + /* Re-enter: KVM completes the MMIO, guest runs to GUEST_SYNC(2). */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 2); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after non-matching write"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_multi(void) +{ + *(volatile uint32_t *)MMIO_GPA = 0x11111111; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = 0x22222222; + GUEST_SYNC(2); + *(volatile uint32_t *)MMIO_GPA = 0x33333333; + GUEST_SYNC(3); + GUEST_DONE(); +} + +/* + * Verify that post_addr is updated on every MMIO write, not just the first. + * The guest writes three distinct values in sequence; the host checks after + * each one that post_addr holds the latest value and the eventfd is signaled + * each time. + */ +static void test_post_write_multi(void) +{ + static const uint32_t expected[] = { + 0x11111111, 0x22222222, 0x33333333, + }; + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret, i; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + for (i = 0; i < ARRAY_SIZE(expected); i++) { + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, i + 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, expected[i]); + } + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_multi_nosync(void) +{ + *(volatile uint32_t *)MMIO_GPA = 0x11111111; + *(volatile uint32_t *)MMIO_GPA = 0x22222222; + *(volatile uint32_t *)MMIO_GPA = 0x33333333; + GUEST_DONE(); +} + +/* + * Variant of the multi-write test where the guest performs three consecutive + * MMIO writes with no GUEST_SYNC in between. All three are handled in-kernel + * by ioeventfd before the vCPU exits at GUEST_DONE. Verify that: + * - post_addr reflects the last written value (0x33333333). + * - A single eventfd read() returns a counter of 3 (one signal per write). + */ +static void test_post_write_multi_nosync(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi_nosync); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + TEST_ASSERT_EQ(count, (uint64_t)3); + TEST_ASSERT_EQ(actual, (uint32_t)0x33333333); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_deassign(void) +{ + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_DONE(); +} + +/* + * Verify that deassigning an ioeventfd with POST_WRITE fully removes it from + * the I/O bus. + */ +static void test_post_write_deassign(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_deassign); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD assign failed: %s", strerror(errno)); + + /* + * Guest writes MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + /* Deassign the ioeventfd. */ + ioeventfd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD deassign failed: %s", strerror(errno)); + + actual = POISON_VAL; + + /* + * Guest writes MATCH_VAL again → no handler on the bus → + * KVM_EXIT_MMIO to userspace. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, + "expected MMIO at 0x%lx, got 0x%llx", + MMIO_GPA, run->mmio.phys_addr); + + /* Re-enter: KVM completes MMIO, guest runs to GUEST_DONE. */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after deassign"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_pio(void) +{ + outl(PIO_PORT, (uint32_t)TEST_VAL); + GUEST_DONE(); +} + +/* + * Verify that POST_WRITE works on the PIO bus (KVM_PIO_BUS), not just MMIO. + * The guest does an outl to PIO_PORT; the host checks that the written value + * is copied to post_addr and the eventfd is signaled. + */ +static void test_post_write_pio(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = PIO_PORT, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_PIO, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + + TEST_ASSERT_EQ(actual, (uint32_t)TEST_VAL); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_pio_datamatch(void) +{ + outl(PIO_PORT, MATCH_VAL); + GUEST_SYNC(1); + outl(PIO_PORT, NOMATCH_VAL); + GUEST_SYNC(2); + GUEST_DONE(); +} + +/* + * Test POST_WRITE + PIO + DATAMATCH together. When all three flags are set, + * the ioeventfd should only fire when the outl value matches datamatch. + * A non-matching outl must fall through to KVM_EXIT_IO (port I/O exit), + * leaving the eventfd unsignaled and post_addr untouched. + */ +static void test_post_write_pio_datamatch(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio_datamatch); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .datamatch = MATCH_VAL, + .addr = PIO_PORT, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_PIO | + KVM_IOEVENTFD_FLAG_DATAMATCH, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + /* + * Guest does outl MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + actual = POISON_VAL; + + /* + * Guest does outl NOMATCH_VAL → no match → KVM_EXIT_IO. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + TEST_ASSERT(run->io.direction == KVM_EXIT_IO_OUT, + "expected PIO write"); + TEST_ASSERT(run->io.port == PIO_PORT, + "expected PIO at 0x%x, got 0x%x", + PIO_PORT, run->io.port); + + /* Re-enter: guest continues to GUEST_SYNC(2). */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 2); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after non-matching PIO write"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + /* GUEST_DONE */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +int main(void) +{ + TEST_REQUIRE(kvm_check_cap(KVM_CAP_IOEVENTFD_POST_WRITE)); + + test_post_write_negative(); + + test_post_write_width(1, guest_code_w1); + test_post_write_width(2, guest_code_w2); + test_post_write_width(4, guest_code_w4); + test_post_write_width(8, guest_code_w8); + + test_post_write_datamatch(); + + test_post_write_multi(); + + test_post_write_multi_nosync(); + + test_post_write_deassign(); + + test_post_write_pio(); + test_post_write_pio_datamatch(); + + return 0; +} diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c index f43b8b25bc85..b0f2a6b43e35 100644 --- a/virt/kvm/eventfd.c +++ b/virt/kvm/eventfd.c @@ -743,6 +743,7 @@ struct _ioeventfd { struct kvm_io_device dev; u8 bus_idx; bool wildcard; + void __user *post_addr; }; static inline struct _ioeventfd * @@ -759,11 +760,32 @@ ioeventfd_release(struct _ioeventfd *p) kfree(p); } +#define ioeventfd_get_val(__ptr, __len, __unsupported_len) \ +({ \ + u64 __val; \ + \ + switch (__len) { \ + case 1: \ + __val = get_unaligned((u8 *)__ptr); \ + break; \ + case 2: \ + __val = get_unaligned((u16 *)__ptr); \ + break; \ + case 4: \ + __val = get_unaligned((u32 *)__ptr); \ + break; \ + case 8: \ + __val = get_unaligned((u64 *)__ptr); \ + break; \ + default: \ + goto __unsupported_len; \ + } \ + __val; \ +}) + static bool ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val) { - u64 _val; - if (addr != p->addr) /* address must be precise for a hit */ return false; @@ -781,24 +803,10 @@ ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val) return true; /* otherwise, we have to actually compare the data */ - switch (len) { - case 1: - _val = get_unaligned((u8 *)val); - break; - case 2: - _val = get_unaligned((u16 *)val); - break; - case 4: - _val = get_unaligned((u32 *)val); - break; - case 8: - _val = get_unaligned((u64 *)val); - break; - default: - return false; - } + return ioeventfd_get_val(val, len, unsupported_len) == p->datamatch; - return _val == p->datamatch; +unsupported_len: + return false; } /* MMIO/PIO writes trigger an event if the addr/val match */ @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, if (!ioeventfd_in_range(p, addr, len, val)) return -EOPNOTSUPP; + if (p->post_addr) { + u64 _val = ioeventfd_get_val(val, len, unsupported_len); + int r; + + switch (len) { + case 1: + r = put_user(_val, (u8 __user *)p->post_addr); + break; + case 2: + r = put_user(_val, (u16 __user *)p->post_addr); + break; + case 4: + r = put_user(_val, (u32 __user *)p->post_addr); + break; + case 8: + r = put_user(_val, (u64 __user *)p->post_addr); + break; + default: + WARN_ON_ONCE(1); + r = 0; + break; + } + if (r) + return r; + } + eventfd_signal(p->eventfd); return 0; +unsupported_len: + return -EOPNOTSUPP; } /* @@ -872,6 +908,18 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, if (IS_ERR(eventfd)) return PTR_ERR(eventfd); + /* + * Disallow posting to virtual address 0 so that KVM can treat + * a NULL user pointer as "no posted write". + */ + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && + (!args->len || !u64_to_user_ptr(args->post_addr) || + args->post_addr != untagged_addr(args->post_addr) || + !access_ok(u64_to_user_ptr(args->post_addr), args->len))) { + ret = -EINVAL; + goto fail; + } + p = kzalloc_obj(*p, GFP_KERNEL_ACCOUNT); if (!p) { ret = -ENOMEM; @@ -890,6 +938,9 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, else p->wildcard = true; + if (args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) + p->post_addr = u64_to_user_ptr(args->post_addr); + mutex_lock(&kvm->slots_lock); /* Verify that there isn't a match already */ @@ -941,6 +992,11 @@ kvm_deassign_ioeventfd_idx(struct kvm *kvm, enum kvm_bus bus_idx, mutex_lock(&kvm->slots_lock); list_for_each_entry(p, &kvm->ioeventfds, list) { + /* + * No need to match post_addr, ioeventfd_check_collision + * prevents duplicate registrations that only differ by + * post_addr. + */ if (p->bus_idx != bus_idx || p->eventfd != eventfd || p->addr != args->addr || diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index cf4cb586f140..7c8b013fad77 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4982,6 +4982,7 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) case KVM_CAP_IRQFD: #endif case KVM_CAP_IOEVENTFD_ANY_LENGTH: + case KVM_CAP_IOEVENTFD_POST_WRITE: case KVM_CAP_CHECK_EXTENSION_VM: case KVM_CAP_ENABLE_CAP_VM: case KVM_CAP_HALT_POLL: -- 2.47.3 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v2] KVM: optionally post write on ioeventfd write 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos @ 2026-10-27 18:34 ` sashiko-bot 2026-10-28 8:32 ` Thanos Makatos 2026-10-28 13:09 ` Stefan Hajnoczi 2026-10-28 16:47 ` [PATCH v3] " Thanos Makatos 2 siblings, 1 reply; 28+ messages in thread From: sashiko-bot @ 2026-10-27 18:34 UTC (permalink / raw) To: Thanos Makatos; +Cc: kvm@vger.kernel.org Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] KVM: eventfd: require natural alignment for post_addr in kvm_assign_ioeventfd_idx() -- commit c6cd7519e7f3bd3af324596a345f4b346eac3929 Author: Thanos Makatos <thanos.makatos@nutanix.com> KVM: optionally post write on ioeventfd write This commit introduces KVM_IOEVENTFD_FLAG_POST_WRITE to copy the value written by a guest on an ioeventfd MMIO or PIO access to a user-provided address before signaling the eventfd, along with documentation and x86 selftests. > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > index f43b8b25bc854..b0f2a6b43e354 100644 > --- a/virt/kvm/eventfd.c > +++ b/virt/kvm/eventfd.c [ ... ] > @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, > if (!ioeventfd_in_range(p, addr, len, val)) > return -EOPNOTSUPP; > > + if (p->post_addr) { > + u64 _val = ioeventfd_get_val(val, len, unsupported_len); > + int r; > + > + switch (len) { > + case 1: > + r = put_user(_val, (u8 __user *)p->post_addr); > + break; > + case 2: > + r = put_user(_val, (u16 __user *)p->post_addr); > + break; > + case 4: > + r = put_user(_val, (u32 __user *)p->post_addr); > + break; > + case 8: > + r = put_user(_val, (u64 __user *)p->post_addr); > + break; > + default: > + WARN_ON_ONCE(1); > + r = 0; > + break; > + } > + if (r) > + return r; > + } > + > eventfd_signal(p->eventfd); > return 0; > +unsupported_len: > + return -EOPNOTSUPP; > } > > /* > @@ -872,6 +908,18 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, > if (IS_ERR(eventfd)) > return PTR_ERR(eventfd); > > + /* > + * Disallow posting to virtual address 0 so that KVM can treat > + * a NULL user pointer as "no posted write". > + */ > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > + (!args->len || !u64_to_user_ptr(args->post_addr) || > + args->post_addr != untagged_addr(args->post_addr) || > + !access_ok(u64_to_user_ptr(args->post_addr), args->len))) { > + ret = -EINVAL; > + goto fail; > + } [Severity: Medium] Should kvm_assign_ioeventfd_idx() in virt/kvm/eventfd.c also require args->post_addr to be naturally aligned to args->len via IS_ALIGNED(args->post_addr, args->len)? In Documentation/virt/kvm/api.rst, KVM_IOEVENTFD_FLAG_POST_WRITE documents that when multiple vCPUs write to the same ioeventfd concurrently, the value at post_addr reflects one of the writes, and that if the copy to post_addr fails, the eventfd is not signaled and the write is reported to userspace as a regular MMIO/PIO exit. When userspace registers an ioeventfd with KVM_IOEVENTFD_FLAG_POST_WRITE, args->len in {2, 4, 8}, and an unaligned args->post_addr, subsequent guest writes invoke ioeventfd_write() -> put_user() on p->post_addr: 1. On x86 and arm64, an unaligned 2-, 4-, or 8-byte put_user() store that crosses a cache-line (or 16-byte on arm64) or page boundary is split into multiple memory transactions and is not single-copy atomic, so concurrent vCPU writes can tear. 2. On RISC-V without CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS, __put_user_nocheck() in arch/riscv/include/asm/uaccess.h checks !IS_ALIGNED((uintptr_t)__gu_ptr, sizeof(*__gu_ptr)) and falls back to __asm_copy_to_user_sum_enabled(), which copies byte-by-byte in arch/riscv/lib/uaccess.S and tears on any unaligned post_addr. 3. If an unaligned post_addr straddles a mapped page and an unmapped page, put_user() partially overwrites the first page before faulting on the second page and returning -EFAULT. 4. On architectures such as MIPS (do_ade() in arch/mips/kernel/unaligned.c when unaligned_action == UNALIGNED_ACTION_SIGNAL) and LoongArch (do_ale() in arch/loongarch/kernel/traps.c when unaligned emulation is disabled or absent), an unaligned put_user() traps in kernel mode and calls die_if_kernel() without running fixup_exception(). -- Sashiko AI review · https://sashiko.dev/#/patchset/20261027180903.1008-1-thanos.makatos@nutanix.com?part=1 ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH v2] KVM: optionally post write on ioeventfd write 2026-10-27 18:34 ` sashiko-bot @ 2026-10-28 8:32 ` Thanos Makatos 0 siblings, 0 replies; 28+ messages in thread From: Thanos Makatos @ 2026-10-28 8:32 UTC (permalink / raw) To: sashiko-reviews@lists.linux.dev; +Cc: kvm@vger.kernel.org > -----Original Message----- > From: sashiko-bot@kernel.org <sashiko-bot@kernel.org> > Sent: 27 October 2026 18:34 > To: Thanos Makatos <thanos.makatos@nutanix.com> > Cc: kvm@vger.kernel.org > Subject: Re: [PATCH v2] KVM: optionally post write on ioeventfd write > > Thank you for your contribution! Sashiko AI review found 1 potential > issue(s) to consider: > - [Medium] KVM: eventfd: require natural alignment for post_addr in > kvm_assign_ioeventfd_idx() > -- > > commit c6cd7519e7f3bd3af324596a345f4b346eac3929 > Author: Thanos Makatos <thanos.makatos@nutanix.com> > > KVM: optionally post write on ioeventfd write > > This commit introduces KVM_IOEVENTFD_FLAG_POST_WRITE to copy the value > written by a guest on an ioeventfd MMIO or PIO access to a user- > provided > address before signaling the eventfd, along with documentation and x86 > selftests. > > > diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c > > index f43b8b25bc854..b0f2a6b43e354 100644 > > --- a/virt/kvm/eventfd.c > > +++ b/virt/kvm/eventfd.c > > [ ... ] > > > @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct > kvm_io_device *this, gpa_t addr, > > if (!ioeventfd_in_range(p, addr, len, val)) > > return -EOPNOTSUPP; > > > > + if (p->post_addr) { > > + u64 _val = ioeventfd_get_val(val, len, unsupported_len); > > + int r; > > + > > + switch (len) { > > + case 1: > > + r = put_user(_val, (u8 __user *)p->post_addr); > > + break; > > + case 2: > > + r = put_user(_val, (u16 __user *)p->post_addr); > > + break; > > + case 4: > > + r = put_user(_val, (u32 __user *)p->post_addr); > > + break; > > + case 8: > > + r = put_user(_val, (u64 __user *)p->post_addr); > > + break; > > + default: > > + WARN_ON_ONCE(1); > > + r = 0; > > + break; > > + } > > + if (r) > > + return r; > > + } > > + > > eventfd_signal(p->eventfd); > > return 0; > > +unsupported_len: > > + return -EOPNOTSUPP; > > } > > > > /* > > @@ -872,6 +908,18 @@ static int kvm_assign_ioeventfd_idx(struct kvm > *kvm, > > if (IS_ERR(eventfd)) > > return PTR_ERR(eventfd); > > > > + /* > > + * Disallow posting to virtual address 0 so that KVM can treat > > + * a NULL user pointer as "no posted write". > > + */ > > + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && > > + (!args->len || !u64_to_user_ptr(args->post_addr) || > > + args->post_addr != untagged_addr(args->post_addr) || > > + !access_ok(u64_to_user_ptr(args->post_addr), args->len))) { > > + ret = -EINVAL; > > + goto fail; > > + } > > [Severity: Medium] > Should kvm_assign_ioeventfd_idx() in virt/kvm/eventfd.c also require > args->post_addr to be naturally aligned to args->len via > IS_ALIGNED(args->post_addr, args->len)? > > In Documentation/virt/kvm/api.rst, KVM_IOEVENTFD_FLAG_POST_WRITE > documents > that when multiple vCPUs write to the same ioeventfd concurrently, the > value > at post_addr reflects one of the writes, and that if the copy to > post_addr > fails, the eventfd is not signaled and the write is reported to > userspace as > a regular MMIO/PIO exit. > > When userspace registers an ioeventfd with > KVM_IOEVENTFD_FLAG_POST_WRITE, > args->len in {2, 4, 8}, and an unaligned args->post_addr, subsequent > guest > writes invoke ioeventfd_write() -> put_user() on p->post_addr: > > 1. On x86 and arm64, an unaligned 2-, 4-, or 8-byte put_user() store > that > crosses a cache-line (or 16-byte on arm64) or page boundary is > split into > multiple memory transactions and is not single-copy atomic, so > concurrent > vCPU writes can tear. > > 2. On RISC-V without CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS, > __put_user_nocheck() in arch/riscv/include/asm/uaccess.h checks > !IS_ALIGNED((uintptr_t)__gu_ptr, sizeof(*__gu_ptr)) and falls back > to > __asm_copy_to_user_sum_enabled(), which copies byte-by-byte in > arch/riscv/lib/uaccess.S and tears on any unaligned post_addr. > > 3. If an unaligned post_addr straddles a mapped page and an unmapped > page, > put_user() partially overwrites the first page before faulting on > the > second page and returning -EFAULT. > > 4. On architectures such as MIPS (do_ade() in > arch/mips/kernel/unaligned.c > when unaligned_action == UNALIGNED_ACTION_SIGNAL) and LoongArch > (do_ale() > in arch/loongarch/kernel/traps.c when unaligned emulation is > disabled or > absent), an unaligned put_user() traps in kernel mode and calls > die_if_kernel() without running fixup_exception(). > > -- > Sashiko AI review · https://urldefense.proofpoint.com/v2/url?u=https- > 3A__sashiko.dev_-23_patchset_20261027180903.1008-2D1-2Dthanos.makatos- > 40nutanix.com-3Fpart- > 3D1&d=DwIFaQ&c=s883GpUCOChKOHiocYtGcg&r=XTpYsh5Ps2zJvtw6ogtti46atk736S > I4vgsJiUKIyDE&m=u0u3Lrtn0M4YhzW_4n6b_OMxna8eHNPKsNE6- > OiUx11fIRf7DBJEchwV3dKm5_Hh&s=fdILcMGx65wsOjMAcD6ojBuqZwEs7flW5nREqLc- > NLs&e= Requiring the address to be aligned makes sense, will send a v3 shortly. ^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v2] KVM: optionally post write on ioeventfd write 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos 2026-10-27 18:34 ` sashiko-bot @ 2026-10-28 13:09 ` Stefan Hajnoczi 2026-10-28 16:22 ` Thanos Makatos 2026-10-28 16:47 ` [PATCH v3] " Thanos Makatos 2 siblings, 1 reply; 28+ messages in thread From: Stefan Hajnoczi @ 2026-10-28 13:09 UTC (permalink / raw) To: Thanos Makatos Cc: seanjc@google.com, John Levon, kvm@vger.kernel.org, pbonzini@redhat.com, dwmw2@infradead.org, Mark Cave-Ayland On Tue, Oct 27, 2026 at 06:09:30PM +0000, Thanos Makatos wrote: > @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, > if (!ioeventfd_in_range(p, addr, len, val)) > return -EOPNOTSUPP; > > + if (p->post_addr) { > + u64 _val = ioeventfd_get_val(val, len, unsupported_len); > + int r; > + > + switch (len) { > + case 1: > + r = put_user(_val, (u8 __user *)p->post_addr); > + break; > + case 2: > + r = put_user(_val, (u16 __user *)p->post_addr); > + break; > + case 4: > + r = put_user(_val, (u32 __user *)p->post_addr); > + break; > + case 8: > + r = put_user(_val, (u64 __user *)p->post_addr); > + break; > + default: > + WARN_ON_ONCE(1); > + r = 0; > + break; goto unsupported_len instead of handling an unsupported length differently here? ^ permalink raw reply [flat|nested] 28+ messages in thread
* RE: [PATCH v2] KVM: optionally post write on ioeventfd write 2026-10-28 13:09 ` Stefan Hajnoczi @ 2026-10-28 16:22 ` Thanos Makatos 0 siblings, 0 replies; 28+ messages in thread From: Thanos Makatos @ 2026-10-28 16:22 UTC (permalink / raw) To: Stefan Hajnoczi Cc: seanjc@google.com, John Levon, kvm@vger.kernel.org, pbonzini@redhat.com, dwmw2@infradead.org, Mark Cave-Ayland > -----Original Message----- > From: Stefan Hajnoczi <stefanha@redhat.com> > Sent: 28 October 2026 13:09 > To: Thanos Makatos <thanos.makatos@nutanix.com> > Cc: seanjc@google.com; John Levon <john.levon@nutanix.com>; > kvm@vger.kernel.org; pbonzini@redhat.com; dwmw2@infradead.org; Mark > Cave-Ayland <mark.caveayland@nutanix.com> > Subject: Re: [PATCH v2] KVM: optionally post write on ioeventfd write > > !-------------------------------------------------------------------| > CAUTION: External Email > > |-------------------------------------------------------------------! > > On Tue, Oct 27, 2026 at 06:09:30PM +0000, Thanos Makatos wrote: > > @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct > kvm_io_device *this, gpa_t addr, > > if (!ioeventfd_in_range(p, addr, len, val)) > > return -EOPNOTSUPP; > > > > + if (p->post_addr) { > > + u64 _val = ioeventfd_get_val(val, len, unsupported_len); > > + int r; > > + > > + switch (len) { > > + case 1: > > + r = put_user(_val, (u8 __user *)p->post_addr); > > + break; > > + case 2: > > + r = put_user(_val, (u16 __user *)p->post_addr); > > + break; > > + case 4: > > + r = put_user(_val, (u32 __user *)p->post_addr); > > + break; > > + case 8: > > + r = put_user(_val, (u64 __user *)p->post_addr); > > + break; > > + default: > > + WARN_ON_ONCE(1); > > + r = 0; > > + break; > > goto unsupported_len instead of handling an unsupported length > differently here? Ack. ^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v3] KVM: optionally post write on ioeventfd write 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos 2026-10-27 18:34 ` sashiko-bot 2026-10-28 13:09 ` Stefan Hajnoczi @ 2026-10-28 16:47 ` Thanos Makatos 2026-10-28 18:39 ` Stefan Hajnoczi 2 siblings, 1 reply; 28+ messages in thread From: Thanos Makatos @ 2026-10-28 16:47 UTC (permalink / raw) To: seanjc@google.com Cc: John Levon, kvm@vger.kernel.org, pbonzini@redhat.com, Thanos Makatos, dwmw2@infradead.org, stefanha@redhat.com, Mark Cave-Ayland Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an ioeventfd that results in the value written by the guest to be copied to user-supplied memory instead of being discarded. The goal of this new mechanism is to speed up doorbell writes on NVMe controllers emulated outside of the VMM. Currently, a doorbell write to an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the VMM communicating the event, along with the doorbell value, to the NVMe controller emulation task. With POST_WRITE, the NVMe emulation task is directly notified of the doorbell write and can find the doorbell value in a known location, without involving VMM. Add tests for this new functionality. LLM (claude-4.6-opus-high) was used mainly for the tests and to a lesser extent for pre-reviewing this patch. Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> --- Changes since v2: - Require post_addr to be naturally aligned (Sashiko bot). - Goto unsupported_len instead of break in ioeventfd_write for consistency (Stefan). Changes since v1 (Sean): - Made test x86-only. - Added explicit padding to struct kvm_ioeventfd. - Renamed ioeventfd_test to ioeventfd_posted_write_test and moved to maintain alphabetical sort. - Dropped post_addr from kvm_assign_ioeventfd_idx. - Expanded comment in kvm_assign_ioeventfd_idx. - Refactored part of ioeventfd_in_range() into ioeventfd_get_val() and reused it. Documentation/virt/kvm/api.rst | 14 +- include/uapi/linux/kvm.h | 7 +- tools/testing/selftests/kvm/Makefile.kvm | 1 + .../kvm/x86/ioeventfd_posted_write_test.c | 632 ++++++++++++++++++ virt/kvm/eventfd.c | 95 ++- virt/kvm/kvm_main.c | 1 + 6 files changed, 729 insertions(+), 21 deletions(-) create mode 100644 tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index e95c9cda085d..a5e61ba7eaf5 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -2132,7 +2132,9 @@ provided event instead of triggering an exit. __u32 len; /* 0, 1, 2, 4, or 8 bytes */ __s32 fd; __u32 flags; - __u8 pad[36]; + __u32 pad0; + __u64 post_addr; /* address to write to if POST_WRITE is set */ + __u8 pad[24]; }; For the special case of virtio-ccw devices on s390, the ioevent is matched @@ -2145,6 +2147,7 @@ The following flags are defined:: #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign) #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) + #define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write) If datamatch flag is set, the event will be signaled only if the written value to the registered address is equal to datamatch in struct kvm_ioeventfd. @@ -2157,6 +2160,15 @@ the kernel will ignore the length of guest write and may get a faster vmexit. The speedup may only apply to specific architectures, but the ioeventfd will work anyway. +With KVM_IOEVENTFD_FLAG_POST_WRITE, the value being written is copied to the +userspace address specified by post_addr, and the eventfd is signaled. The +copy is guaranteed to complete before the eventfd is signaled, so a userspace +reader that wakes on the eventfd will observe the written value. When multiple +vCPUs write to the same ioeventfd concurrently, the value at post_addr reflects +one of the writes. If the copy to post_addr fails (e.g. the memory has been +unmapped), the eventfd is not signaled and the write is reported to userspace +as a regular MMIO/PIO exit. + 4.60 KVM_DIRTY_TLB ------------------ diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 8dff2fc1972e..07ac3142ce17 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -644,6 +644,7 @@ enum { kvm_ioeventfd_flag_nr_deassign, kvm_ioeventfd_flag_nr_virtio_ccw_notify, kvm_ioeventfd_flag_nr_fast_mmio, + kvm_ioeventfd_flag_nr_post_write, kvm_ioeventfd_flag_nr_max, }; @@ -652,6 +653,7 @@ enum { #define KVM_IOEVENTFD_FLAG_DEASSIGN (1 << kvm_ioeventfd_flag_nr_deassign) #define KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY \ (1 << kvm_ioeventfd_flag_nr_virtio_ccw_notify) +#define KVM_IOEVENTFD_FLAG_POST_WRITE (1 << kvm_ioeventfd_flag_nr_post_write) #define KVM_IOEVENTFD_VALID_FLAG_MASK ((1 << kvm_ioeventfd_flag_nr_max) - 1) @@ -661,7 +663,9 @@ struct kvm_ioeventfd { __u32 len; /* 1, 2, 4, or 8 bytes; or 0 to ignore length */ __s32 fd; __u32 flags; - __u8 pad[36]; + __u32 pad0; + __u64 post_addr; /* address to write to if POST_WRITE is set */ + __u8 pad[24]; }; #define KVM_X86_DISABLE_EXITS_MWAIT (1 << 0) @@ -1000,6 +1004,7 @@ struct kvm_enable_cap { #define KVM_CAP_PPC_COMPAT_CAPS 250 #define KVM_CAP_ARM_PMU_V3_STRICT 251 #define KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES 252 +#define KVM_CAP_IOEVENTFD_POST_WRITE 253 struct kvm_irq_routing_irqchip { __u32 irqchip; diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index 3cb35a6963d5..38860f2f08b0 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -92,6 +92,7 @@ TEST_GEN_PROGS_x86 += x86/hyperv_features TEST_GEN_PROGS_x86 += x86/hyperv_ipi TEST_GEN_PROGS_x86 += x86/hyperv_svm_test TEST_GEN_PROGS_x86 += x86/hyperv_tlb_flush +TEST_GEN_PROGS_x86 += x86/ioeventfd_posted_write_test TEST_GEN_PROGS_x86 += x86/kvm_clock_test TEST_GEN_PROGS_x86 += x86/kvm_pv_test TEST_GEN_PROGS_x86 += x86/kvm_buslock_test diff --git a/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c b/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c new file mode 100644 index 000000000000..f429f875c023 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ioeventfd_posted_write_test.c - Tests for KVM_IOEVENTFD_FLAG_POST_WRITE. + * + * Tests that when KVM_IOEVENTFD_FLAG_POST_WRITE is set and the MMIO/PIO + * address is written to, the value is copied to the user-provided address + * and the eventfd is signaled. Also tests negative cases and interactions + * with DATAMATCH. + * + * Copyright Nutanix, 2026 + * + * Author: Thanos Makatos <thanos.makatos@nutanix.com> + */ + +#include <errno.h> +#include <poll.h> +#include <string.h> + +#include "kvm_util.h" +#include "processor.h" +#include "ucall_common.h" + +#define MMIO_GPA (1UL << 30) +#define PIO_PORT 0xe000 +#define TEST_VAL 0xDEADBEEFCAFEBABEULL +#define MATCH_VAL 0x42U +#define NOMATCH_VAL (MATCH_VAL + 1) +#define POISON_VAL 0xFFFFFFFFU + +/* + * Check that the most recent vCPU exit is a ucall (delivered as KVM_EXIT_IO + * on x86) matching @expected_cmd. The caller must have already called + * vcpu_run(). + * + * @expected_cmd: UCALL_SYNC, UCALL_DONE, etc. + * @expected_stage: for UCALL_SYNC, the stage number passed by GUEST_SYNC(). + * Ignored for other ucall types. + * + * Aborts the test on UCALL_ABORT (a guest-side assertion failure). + */ +static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_cmd, + uint64_t expected_stage) +{ + struct ucall uc; + + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + + switch (get_ucall(vcpu, &uc)) { + case UCALL_ABORT: + REPORT_GUEST_ASSERT(uc); + break; + case UCALL_SYNC: + TEST_ASSERT(expected_cmd == UCALL_SYNC, + "got UCALL_SYNC, expected %lu", + expected_cmd); + TEST_ASSERT(uc.args[1] == expected_stage, + "expected stage %lu, got %lu", + expected_stage, uc.args[1]); + break; + case UCALL_DONE: + TEST_ASSERT(expected_cmd == UCALL_DONE, + "got UCALL_DONE, expected %lu", + expected_cmd); + break; + default: + TEST_FAIL("unexpected ucall %lu", uc.cmd); + } +} + +/* + * Verify that KVM_IOEVENTFD rejects invalid POST_WRITE configurations: + * - len=0: the kernel needs a non-zero length to know how many bytes to copy. + * - post_addr=NULL: there is no destination for the copy. + * - post_addr outside the process address space: access_ok() rejects it. + * - post_addr unaligned: IS_ALIGNED() rejects it + * All three must fail with EINVAL. + */ +static void test_post_write_negative(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vm *vm; + uint64_t dummy; + int ret; + int fd; + + vm = vm_create_barebones(); + fd = kvm_new_eventfd(); + + /* length cannot be zero */ + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 0, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&dummy, + }; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "len=0: expected EINVAL, got ret=%d errno=%d", ret, errno); + + /* post_addr cannot be NULL */ + ioeventfd.len = 4; + ioeventfd.post_addr = 0ULL; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "NULL post_addr: expected EINVAL, got ret=%d errno=%d", + ret, errno); + + /* bogus post_addr */ + ioeventfd.post_addr = (u64)0xdeaddeaddeaddeadULL; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "bad post_addr: expected EINVAL, got ret=%d errno=%d", + ret, errno); + + /* unaligned post_addr */ + ioeventfd.post_addr = (uintptr_t)((char *)&dummy + 1); + ioeventfd.len = sizeof dummy; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(ret && errno == EINVAL, + "bad post_addr: expected EINVAL, got ret=%d errno=%d", + ret, errno); + + close(fd); + kvm_vm_free(vm); +} + +#define DEFINE_GUEST_WRITE_FN(suffix, type) \ +static void guest_code_w##suffix(void) { \ + *(volatile type *)MMIO_GPA = (type)TEST_VAL; \ + GUEST_DONE(); \ +} + +DEFINE_GUEST_WRITE_FN(1, uint8_t) +DEFINE_GUEST_WRITE_FN(2, uint16_t) +DEFINE_GUEST_WRITE_FN(4, uint32_t) +DEFINE_GUEST_WRITE_FN(8, uint64_t) + +/* + * Verify that ioeventfd_write copies exactly @width bytes to post_addr for + * each supported MMIO write width (1, 2, 4, 8). The guest writes the low + * @width bytes of TEST_VAL; the host checks that exactly those bytes land + * at post_addr and the eventfd is signaled. + */ +static void test_post_write_width(int width, void (*guest_fn)(void)) +{ + uint64_t actual, expected, count; + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + int fd, ret; + + /* need to initialize to 0 because the guest writes the low @width bytes */ + actual = 0; + expected = 0; + + vm = vm_create_with_one_vcpu(&vcpu, guest_fn); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = width, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + + memcpy(&expected, &(uint64_t){TEST_VAL}, width); + TEST_ASSERT_EQ(actual, expected); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_datamatch(void) +{ + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = NOMATCH_VAL; + GUEST_SYNC(2); + GUEST_DONE(); +} + +/* + * Test the interaction between DATAMATCH and POST_WRITE. When both flags are + * set, ioeventfd_write should only fire (signal eventfd + copy value) when the + * written value matches datamatch. A non-matching write must leave the eventfd + * unsignaled and post_addr untouched, and fall through to KVM_EXIT_MMIO. + */ +static void test_post_write_datamatch(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_datamatch); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .datamatch = MATCH_VAL, + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_DATAMATCH, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + /* + * Guest writes MATCH_VAL → ioeventfd fires (value copied, eventfd + * signaled), vCPU continues, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + actual = POISON_VAL; + + /* + * Guest writes NOMATCH_VAL → ioeventfd_in_range() returns false, bus + * returns -EOPNOTSUPP → KVM_EXIT_MMIO to userspace. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, + "expected MMIO at 0x%lx, got 0x%llx", + MMIO_GPA, run->mmio.phys_addr); + + /* Re-enter: KVM completes the MMIO, guest runs to GUEST_SYNC(2). */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 2); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after non-matching write"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_multi(void) +{ + *(volatile uint32_t *)MMIO_GPA = 0x11111111; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = 0x22222222; + GUEST_SYNC(2); + *(volatile uint32_t *)MMIO_GPA = 0x33333333; + GUEST_SYNC(3); + GUEST_DONE(); +} + +/* + * Verify that post_addr is updated on every MMIO write, not just the first. + * The guest writes three distinct values in sequence; the host checks after + * each one that post_addr holds the latest value and the eventfd is signaled + * each time. + */ +static void test_post_write_multi(void) +{ + static const uint32_t expected[] = { + 0x11111111, 0x22222222, 0x33333333, + }; + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret, i; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + for (i = 0; i < ARRAY_SIZE(expected); i++) { + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, i + 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, expected[i]); + } + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_multi_nosync(void) +{ + *(volatile uint32_t *)MMIO_GPA = 0x11111111; + *(volatile uint32_t *)MMIO_GPA = 0x22222222; + *(volatile uint32_t *)MMIO_GPA = 0x33333333; + GUEST_DONE(); +} + +/* + * Variant of the multi-write test where the guest performs three consecutive + * MMIO writes with no GUEST_SYNC in between. All three are handled in-kernel + * by ioeventfd before the vCPU exits at GUEST_DONE. Verify that: + * - post_addr reflects the last written value (0x33333333). + * - A single eventfd read() returns a counter of 3 (one signal per write). + */ +static void test_post_write_multi_nosync(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_multi_nosync); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + TEST_ASSERT_EQ(count, (uint64_t)3); + TEST_ASSERT_EQ(actual, (uint32_t)0x33333333); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_deassign(void) +{ + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_SYNC(1); + *(volatile uint32_t *)MMIO_GPA = MATCH_VAL; + GUEST_DONE(); +} + +/* + * Verify that deassigning an ioeventfd with POST_WRITE fully removes it from + * the I/O bus. + */ +static void test_post_write_deassign(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_deassign); + virt_map(vm, MMIO_GPA, MMIO_GPA, 1); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .addr = MMIO_GPA, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD assign failed: %s", strerror(errno)); + + /* + * Guest writes MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + /* Deassign the ioeventfd. */ + ioeventfd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD deassign failed: %s", strerror(errno)); + + actual = POISON_VAL; + + /* + * Guest writes MATCH_VAL again → no handler on the bus → + * KVM_EXIT_MMIO to userspace. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO); + TEST_ASSERT(run->mmio.is_write, "expected MMIO write"); + TEST_ASSERT(run->mmio.phys_addr == MMIO_GPA, + "expected MMIO at 0x%lx, got 0x%llx", + MMIO_GPA, run->mmio.phys_addr); + + /* Re-enter: KVM completes MMIO, guest runs to GUEST_DONE. */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after deassign"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_pio(void) +{ + outl(PIO_PORT, (uint32_t)TEST_VAL); + GUEST_DONE(); +} + +/* + * Verify that POST_WRITE works on the PIO bus (KVM_PIO_BUS), not just MMIO. + * The guest does an outl to PIO_PORT; the host checks that the written value + * is copied to post_addr and the eventfd is signaled. + */ +static void test_post_write_pio(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_vm *vm; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio); + + fd = kvm_new_eventfd(); + + ioeventfd = (struct kvm_ioeventfd) { + .addr = PIO_PORT, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_PIO, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + ret = read(fd, &count, sizeof(count)); + TEST_ASSERT(ret == sizeof(count), + "eventfd read failed: ret=%d errno=%d", ret, errno); + + TEST_ASSERT_EQ(actual, (uint32_t)TEST_VAL); + + close(fd); + kvm_vm_free(vm); +} + +static void guest_code_pio_datamatch(void) +{ + outl(PIO_PORT, MATCH_VAL); + GUEST_SYNC(1); + outl(PIO_PORT, NOMATCH_VAL); + GUEST_SYNC(2); + GUEST_DONE(); +} + +/* + * Test POST_WRITE + PIO + DATAMATCH together. When all three flags are set, + * the ioeventfd should only fire when the outl value matches datamatch. + * A non-matching outl must fall through to KVM_EXIT_IO (port I/O exit), + * leaving the eventfd unsignaled and post_addr untouched. + */ +static void test_post_write_pio_datamatch(void) +{ + struct kvm_ioeventfd ioeventfd; + struct kvm_vcpu *vcpu; + struct kvm_run *run; + struct kvm_vm *vm; + struct pollfd pfd; + uint64_t count; + uint32_t actual; + int fd, ret; + + actual = POISON_VAL; + + vm = vm_create_with_one_vcpu(&vcpu, guest_code_pio_datamatch); + run = vcpu->run; + + fd = kvm_new_eventfd(); + pfd = (struct pollfd){ .fd = fd, .events = POLLIN }; + + ioeventfd = (struct kvm_ioeventfd) { + .datamatch = MATCH_VAL, + .addr = PIO_PORT, + .len = 4, + .fd = fd, + .flags = KVM_IOEVENTFD_FLAG_POST_WRITE | + KVM_IOEVENTFD_FLAG_PIO | + KVM_IOEVENTFD_FLAG_DATAMATCH, + .post_addr = (u64)&actual, + }; + + ret = __vm_ioctl(vm, KVM_IOEVENTFD, &ioeventfd); + TEST_ASSERT(!ret, "KVM_IOEVENTFD failed: %s", strerror(errno)); + + /* + * Guest does outl MATCH_VAL → ioeventfd fires, then GUEST_SYNC(1). + */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 1); + TEST_ASSERT(read(fd, &count, sizeof(count)) == sizeof(count), + "eventfd read failed: errno=%d", errno); + TEST_ASSERT_EQ(actual, MATCH_VAL); + + actual = POISON_VAL; + + /* + * Guest does outl NOMATCH_VAL → no match → KVM_EXIT_IO. + */ + vcpu_run(vcpu); + TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); + TEST_ASSERT(run->io.direction == KVM_EXIT_IO_OUT, + "expected PIO write"); + TEST_ASSERT(run->io.port == PIO_PORT, + "expected PIO at 0x%x, got 0x%x", + PIO_PORT, run->io.port); + + /* Re-enter: guest continues to GUEST_SYNC(2). */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_SYNC, 2); + + TEST_ASSERT(poll(&pfd, 1, 0) == 0, + "eventfd should not be signaled after non-matching PIO write"); + TEST_ASSERT_EQ(actual, (uint32_t)POISON_VAL); + + /* GUEST_DONE */ + vcpu_run(vcpu); + assert_ucall(vcpu, UCALL_DONE, 0); + + close(fd); + kvm_vm_free(vm); +} + +int main(void) +{ + TEST_REQUIRE(kvm_check_cap(KVM_CAP_IOEVENTFD_POST_WRITE)); + + test_post_write_negative(); + + test_post_write_width(1, guest_code_w1); + test_post_write_width(2, guest_code_w2); + test_post_write_width(4, guest_code_w4); + test_post_write_width(8, guest_code_w8); + + test_post_write_datamatch(); + + test_post_write_multi(); + + test_post_write_multi_nosync(); + + test_post_write_deassign(); + + test_post_write_pio(); + test_post_write_pio_datamatch(); + + return 0; +} diff --git a/virt/kvm/eventfd.c b/virt/kvm/eventfd.c index f43b8b25bc85..6a221184b54a 100644 --- a/virt/kvm/eventfd.c +++ b/virt/kvm/eventfd.c @@ -743,6 +743,7 @@ struct _ioeventfd { struct kvm_io_device dev; u8 bus_idx; bool wildcard; + void __user *post_addr; }; static inline struct _ioeventfd * @@ -759,11 +760,32 @@ ioeventfd_release(struct _ioeventfd *p) kfree(p); } +#define ioeventfd_get_val(__ptr, __len, __unsupported_len) \ +({ \ + u64 __val; \ + \ + switch (__len) { \ + case 1: \ + __val = get_unaligned((u8 *)__ptr); \ + break; \ + case 2: \ + __val = get_unaligned((u16 *)__ptr); \ + break; \ + case 4: \ + __val = get_unaligned((u32 *)__ptr); \ + break; \ + case 8: \ + __val = get_unaligned((u64 *)__ptr); \ + break; \ + default: \ + goto __unsupported_len; \ + } \ + __val; \ +}) + static bool ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val) { - u64 _val; - if (addr != p->addr) /* address must be precise for a hit */ return false; @@ -781,24 +803,10 @@ ioeventfd_in_range(struct _ioeventfd *p, gpa_t addr, int len, const void *val) return true; /* otherwise, we have to actually compare the data */ - switch (len) { - case 1: - _val = get_unaligned((u8 *)val); - break; - case 2: - _val = get_unaligned((u16 *)val); - break; - case 4: - _val = get_unaligned((u32 *)val); - break; - case 8: - _val = get_unaligned((u64 *)val); - break; - default: - return false; - } + return ioeventfd_get_val(val, len, unsupported_len) == p->datamatch; - return _val == p->datamatch; +unsupported_len: + return false; } /* MMIO/PIO writes trigger an event if the addr/val match */ @@ -811,8 +819,36 @@ ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this, gpa_t addr, if (!ioeventfd_in_range(p, addr, len, val)) return -EOPNOTSUPP; + if (p->post_addr) { + u64 _val = ioeventfd_get_val(val, len, unsupported_len); + int r; + + switch (len) { + case 1: + r = put_user(_val, (u8 __user *)p->post_addr); + break; + case 2: + r = put_user(_val, (u16 __user *)p->post_addr); + break; + case 4: + r = put_user(_val, (u32 __user *)p->post_addr); + break; + case 8: + r = put_user(_val, (u64 __user *)p->post_addr); + break; + default: + WARN_ON_ONCE(1); + r = 0; + goto unsupported_len; + } + if (r) + return r; + } + eventfd_signal(p->eventfd); return 0; +unsupported_len: + return -EOPNOTSUPP; } /* @@ -872,6 +908,19 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, if (IS_ERR(eventfd)) return PTR_ERR(eventfd); + /* + * Disallow posting to virtual address 0 so that KVM can treat + * a NULL user pointer as "no posted write". + */ + if ((args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) && + (!args->len || !u64_to_user_ptr(args->post_addr) || + args->post_addr != untagged_addr(args->post_addr) || + !access_ok(u64_to_user_ptr(args->post_addr), args->len) || + !IS_ALIGNED(args->post_addr, args->len))) { + ret = -EINVAL; + goto fail; + } + p = kzalloc_obj(*p, GFP_KERNEL_ACCOUNT); if (!p) { ret = -ENOMEM; @@ -890,6 +939,9 @@ static int kvm_assign_ioeventfd_idx(struct kvm *kvm, else p->wildcard = true; + if (args->flags & KVM_IOEVENTFD_FLAG_POST_WRITE) + p->post_addr = u64_to_user_ptr(args->post_addr); + mutex_lock(&kvm->slots_lock); /* Verify that there isn't a match already */ @@ -941,6 +993,11 @@ kvm_deassign_ioeventfd_idx(struct kvm *kvm, enum kvm_bus bus_idx, mutex_lock(&kvm->slots_lock); list_for_each_entry(p, &kvm->ioeventfds, list) { + /* + * No need to match post_addr, ioeventfd_check_collision + * prevents duplicate registrations that only differ by + * post_addr. + */ if (p->bus_idx != bus_idx || p->eventfd != eventfd || p->addr != args->addr || diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index cf4cb586f140..7c8b013fad77 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -4982,6 +4982,7 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg) case KVM_CAP_IRQFD: #endif case KVM_CAP_IOEVENTFD_ANY_LENGTH: + case KVM_CAP_IOEVENTFD_POST_WRITE: case KVM_CAP_CHECK_EXTENSION_VM: case KVM_CAP_ENABLE_CAP_VM: case KVM_CAP_HALT_POLL: -- 2.47.3 ^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v3] KVM: optionally post write on ioeventfd write 2026-10-28 16:47 ` [PATCH v3] " Thanos Makatos @ 2026-10-28 18:39 ` Stefan Hajnoczi 0 siblings, 0 replies; 28+ messages in thread From: Stefan Hajnoczi @ 2026-10-28 18:39 UTC (permalink / raw) To: Thanos Makatos Cc: seanjc@google.com, John Levon, kvm@vger.kernel.org, pbonzini@redhat.com, dwmw2@infradead.org, Mark Cave-Ayland On Wed, Oct 28, 2026 at 04:47:30PM +0000, Thanos Makatos wrote: > Add a new flag, KVM_IOEVENTFD_FLAG_POST_WRITE, when assigning an > ioeventfd that results in the value written by the guest to be copied > to user-supplied memory instead of being discarded. > > The goal of this new mechanism is to speed up doorbell writes on NVMe > controllers emulated outside of the VMM. Currently, a doorbell write to > an NVMe SQ tail doorbell requires returning from ioctl(KVM_RUN) and the > VMM communicating the event, along with the doorbell value, to the NVMe > controller emulation task. With POST_WRITE, the NVMe emulation task is > directly notified of the doorbell write and can find the doorbell value > in a known location, without involving VMM. > > Add tests for this new functionality. > > LLM (claude-4.6-opus-high) was used mainly for the tests and to a > lesser extent for pre-reviewing this patch. > > Signed-off-by: Thanos Makatos <thanos.makatos@nutanix.com> > --- > Changes since v2: > - Require post_addr to be naturally aligned (Sashiko bot). > - Goto unsupported_len instead of break in ioeventfd_write for > consistency (Stefan). > > Changes since v1 (Sean): > - Made test x86-only. > - Added explicit padding to struct kvm_ioeventfd. > - Renamed ioeventfd_test to ioeventfd_posted_write_test and moved to > maintain alphabetical sort. > - Dropped post_addr from kvm_assign_ioeventfd_idx. > - Expanded comment in kvm_assign_ioeventfd_idx. > - Refactored part of ioeventfd_in_range() into ioeventfd_get_val() > and reused it. > > Documentation/virt/kvm/api.rst | 14 +- > include/uapi/linux/kvm.h | 7 +- > tools/testing/selftests/kvm/Makefile.kvm | 1 + > .../kvm/x86/ioeventfd_posted_write_test.c | 632 ++++++++++++++++++ > virt/kvm/eventfd.c | 95 ++- > virt/kvm/kvm_main.c | 1 + > 6 files changed, 729 insertions(+), 21 deletions(-) > create mode 100644 tools/testing/selftests/kvm/x86/ioeventfd_posted_write_test.c Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> ^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-10-28 18:39 UTC | newest] Thread overview: 28+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-06 12:56 [PATCH] KVM: optionally post write on ioeventfd write Thanos Makatos 2026-03-12 15:02 ` David Woodhouse 2026-03-12 16:12 ` Thanos Makatos 2026-04-21 14:44 ` Sean Christopherson 2026-09-19 15:59 ` David Woodhouse 2026-03-23 15:01 ` Thanos Makatos 2026-04-10 19:11 ` Thanos Makatos 2026-04-21 14:45 ` Sean Christopherson 2026-06-10 7:31 ` John Levon 2026-09-22 13:51 ` Stefan Hajnoczi 2026-09-22 15:28 ` David Woodhouse 2026-09-22 16:47 ` Sean Christopherson 2026-10-04 16:16 ` David Woodhouse 2026-10-05 5:11 ` Sean Christopherson 2026-10-15 8:44 ` David Woodhouse 2026-10-15 18:23 ` Stefan Hajnoczi 2026-10-15 18:43 ` David Woodhouse 2026-10-19 19:48 ` Stefan Hajnoczi 2026-10-26 19:29 ` Thanos Makatos 2026-09-22 17:03 ` Sean Christopherson 2026-10-27 17:50 ` Thanos Makatos 2026-10-27 18:09 ` [PATCH v2] " Thanos Makatos 2026-10-27 18:34 ` sashiko-bot 2026-10-28 8:32 ` Thanos Makatos 2026-10-28 13:09 ` Stefan Hajnoczi 2026-10-28 16:22 ` Thanos Makatos 2026-10-28 16:47 ` [PATCH v3] " Thanos Makatos 2026-10-28 18:39 ` Stefan Hajnoczi
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.