From: Fuad Tabba <fuad.tabba@linux.dev>
To: Paolo Bonzini <pbonzini@redhat.com>,
Sean Christopherson <seanjc@google.com>
Cc: kvm@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Isaku Yamahata <isaku.yamahata@intel.com>,
Rick Edgecombe <rick.p.edgecombe@intel.com>,
Lorenzo Stoakes <ljs@kernel.org>, Marc Zyngier <maz@kernel.org>,
Oliver Upton <oliver.upton@linux.dev>,
kvmarm@lists.linux.dev, Wei-Lin Chang <weilin.chang@arm.com>,
Will Deacon <will@kernel.org>, Fuad Tabba <tabba@google.com>
Subject: [PATCH 2/2] KVM: selftests: Test KVM_PRE_FAULT_MEMORY against a full dirty ring
Date: Tue, 10 Nov 2026 11:07:24 +0000 [thread overview]
Message-ID: <20261110110724.3614412-3-fuad.tabba@linux.dev> (raw)
In-Reply-To: <20261110110724.3614412-1-fuad.tabba@linux.dev>
Pre-fault more populated, dirty-logged pages than the vCPU's dirty ring
holds, and check that each call stops before the ring overflows, that a
retry returns ENOSPC without progress, and that the harvested entries
are the pages in order. A read pre-fault can map a page read-only, or
access-tracked on x86 without EPT A/D bits, so skip the case if
pre-faulting dirties nothing.
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
.../selftests/kvm/pre_fault_memory_test.c | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/tools/testing/selftests/kvm/pre_fault_memory_test.c b/tools/testing/selftests/kvm/pre_fault_memory_test.c
index 3082fe09b95fa..18fc1d69acbf0 100644
--- a/tools/testing/selftests/kvm/pre_fault_memory_test.c
+++ b/tools/testing/selftests/kvm/pre_fault_memory_test.c
@@ -5,6 +5,7 @@
* Author:
* Isaku Yamahata <isaku.yamahata at gmail.com>
*/
+#include <asm/barrier.h>
#include <linux/sizes.h>
#include <test_util.h>
@@ -17,6 +18,8 @@
/* Arbitrarily chosen values */
#define TEST_BASE_SIZE SZ_2M
#define TEST_SLOT 10
+/* Large enough for the entries every architecture reserves. */
+#define TEST_RING_SIZE SZ_64K
/* Storage of test info to share with guest code */
struct test_config {
@@ -281,6 +284,87 @@ static void test_pre_fault_memory(unsigned long vm_type, enum vm_mem_backing_src
for_each_guest_mode(__test_pre_fault_memory, &p);
}
+/*
+ * Pre-fault more dirty-logged pages than the ring holds: each call has to stop
+ * before the ring overflows, and no entry may be lost.
+ */
+static void test_pre_fault_dirty_ring(void)
+{
+ const u64 entries = TEST_RING_SIZE / sizeof(struct kvm_dirty_gfn);
+ const u64 npages = entries + entries / 4;
+ struct kvm_pre_fault_memory range, left;
+ struct kvm_dirty_gfn *gfns, *cur;
+ u64 harvested = 0, n, size;
+ struct kvm_vcpu *vcpu;
+ int ret, stops = 0;
+ struct kvm_vm *vm;
+ u32 fetch = 0;
+ gpa_t gpa;
+
+ if (!kvm_has_cap(KVM_CAP_DIRTY_LOG_RING) && !kvm_has_cap(KVM_CAP_DIRTY_LOG_RING_ACQ_REL)) {
+ pr_info("Skipping the dirty ring test: no dirty ring\n");
+ return;
+ }
+
+ vm = vm_create(1);
+ vm_enable_dirty_ring(vm, TEST_RING_SIZE);
+ vcpu = vm_vcpu_add(vm, 0, NULL);
+ gfns = vcpu_map_dirty_ring(vcpu);
+
+ size = npages * vm->page_size;
+ gpa = align_down(vm->max_gfn * vm->page_size - size, SZ_2M);
+ vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa, TEST_SLOT, npages,
+ KVM_MEM_LOG_DIRTY_PAGES);
+
+ /* Populate the pages, so that pre-faulting can map them writable. */
+ memset(addr_gpa2hva(vm, gpa), 1, size);
+
+ range = (struct kvm_pre_fault_memory) { .gpa = gpa, .size = size };
+ while (range.size) {
+ vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, &range);
+
+ if (range.size) {
+ left = range;
+ ret = __vcpu_ioctl(vcpu, KVM_PRE_FAULT_MEMORY, &range);
+ TEST_ASSERT_VM_VCPU_IOCTL(ret == -1 && errno == ENOSPC,
+ KVM_PRE_FAULT_MEMORY, ret, vm);
+ TEST_ASSERT(range.gpa == left.gpa && range.size == left.size,
+ "KVM_PRE_FAULT_MEMORY made progress on a full ring");
+ stops++;
+ }
+
+ for (n = 0; ; n++) {
+ cur = &gfns[fetch % entries];
+ if (smp_load_acquire(&cur->flags) != KVM_DIRTY_GFN_F_DIRTY)
+ break;
+
+ TEST_ASSERT(cur->slot == TEST_SLOT && cur->offset == harvested + n,
+ "Ring entry %u: slot %u offset 0x%llx, expected slot %u offset 0x%lx",
+ fetch, cur->slot, cur->offset, TEST_SLOT, harvested + n);
+ smp_store_release(&cur->flags, KVM_DIRTY_GFN_F_RESET);
+ fetch++;
+ }
+
+ /* A read pre-fault can map a page read-only, or access-tracked on x86. */
+ if (!n && !harvested && !range.size) {
+ pr_info("Skipping the dirty ring test: pre-faulting dirtied no pages\n");
+ kvm_vm_free(vm);
+ return;
+ }
+ TEST_ASSERT(n, "KVM_PRE_FAULT_MEMORY pushed nothing to the ring");
+ TEST_ASSERT(kvm_vm_reset_dirty_ring(vm) == n,
+ "KVM_RESET_DIRTY_RINGS didn't reset %lu entries", n);
+ harvested += n;
+ }
+
+ TEST_ASSERT(harvested == npages, "%lu ring entries for %lu pages", harvested, npages);
+ TEST_ASSERT(stops, "KVM_PRE_FAULT_MEMORY never stopped for a %lu-entry ring", entries);
+ pr_info("Pre-faulted %lu pages through a %lu-entry ring, stopping %d times\n",
+ npages, entries, stops);
+
+ kvm_vm_free(vm);
+}
+
static void help(char *name)
{
puts("");
@@ -320,5 +404,6 @@ int main(int argc, char *argv[])
test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, false);
test_pre_fault_memory(KVM_X86_SW_PROTECTED_VM, backing, true);
#endif
+ test_pre_fault_dirty_ring();
return 0;
}
--
2.47.3
next prev parent reply other threads:[~2026-11-10 11:07 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 11:07 [PATCH 0/2] KVM: Bound KVM_PRE_FAULT_MEMORY by the vCPU's " Fuad Tabba
2026-11-10 11:07 ` [PATCH 1/2] KVM: Stop KVM_PRE_FAULT_MEMORY when the vCPU's dirty ring is full Fuad Tabba
2026-11-10 14:22 ` Lorenzo Stoakes (ARM)
2026-11-10 15:48 ` Sean Christopherson
2026-11-10 11:07 ` Fuad Tabba [this message]
2026-11-10 14:38 ` [PATCH 2/2] KVM: selftests: Test KVM_PRE_FAULT_MEMORY against a full dirty ring Lorenzo Stoakes (ARM)
2026-11-10 15:37 ` Fuad Tabba
2026-11-10 15:53 ` Sean Christopherson
2026-11-10 16:02 ` Lorenzo Stoakes (ARM)
2026-11-10 15:57 ` Lorenzo Stoakes (ARM)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261110110724.3614412-3-fuad.tabba@linux.dev \
--to=fuad.tabba@linux.dev \
--cc=corbet@lwn.net \
--cc=isaku.yamahata@intel.com \
--cc=kvm@vger.kernel.org \
--cc=kvmarm@lists.linux.dev \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=maz@kernel.org \
--cc=oliver.upton@linux.dev \
--cc=pbonzini@redhat.com \
--cc=rick.p.edgecombe@intel.com \
--cc=seanjc@google.com \
--cc=skhan@linuxfoundation.org \
--cc=tabba@google.com \
--cc=weilin.chang@arm.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®