From: "Lorenzo Stoakes (ARM)" <ljs@kernel.org>
To: Fuad Tabba <fuad.tabba@linux.dev>
Cc: Paolo Bonzini <pbonzini@redhat.com>,
Sean Christopherson <seanjc@google.com>,
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>,
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: Re: [PATCH 2/2] KVM: selftests: Test KVM_PRE_FAULT_MEMORY against a full dirty ring
Date: Tue, 10 Nov 2026 14:38:27 +0000 [thread overview]
Message-ID: <avMoubwqgXbpflwM@gremlin> (raw)
In-Reply-To: <20261110110724.3614412-3-fuad.tabba@linux.dev>
On Tue, Nov 10, 2026 at 11:07:24AM +0000, Fuad Tabba wrote:
> 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
Are we asserting an invariant that prefaulting happens in address order
here? Is that explicitly presumed in the prefault API, or just how it
happens to pan out?
I wonder if we might decide for whatever reason in future to do things
differently there. Bit nitty I know, but wonder if it's asserting an
implementation detail?
> 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. */
Reviewed below, but I wonder how x86 could then read fault these?
> + memset(addr_gpa2hva(vm, gpa), 1, size);
> +
> + range = (struct kvm_pre_fault_memory) { .gpa = gpa, .size = size };
> + while (range.size) {
NIT: n could be scoped to this block instead.
> + 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++) {
Could this loop forever if cur->flags always == KVM_DIRTY_GFN_F_DIRTY? Or
is that guaranteed not to be the case?
> + cur = &gfns[fetch % entries];
> + if (smp_load_acquire(&cur->flags) != KVM_DIRTY_GFN_F_DIRTY)
Is this and the below release paired with equivalent stuff in the dirty ring code?
> + 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. */
Hmm under what circumstances would that happen? Is there no way to ensure pages
are dirtied by prefaulting? I imagine not though!
> + if (!n && !harvested && !range.size) {
Hmm, so !n && (harvested || range_size) will pass this, but then you assert
n immediately below, so it seems like n is an unconditional requirement.
So I wonder whether this should just be !n in this conditional or maybe the
assert afterwards should be dropped?
> + 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
>
--
Cheers, Lorenzo
next prev parent reply other threads:[~2026-11-10 14:38 UTC|newest]
Thread overview: 16+ 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 dirty ring 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 17:18 ` Fuad Tabba
2026-11-10 18:35 ` Sean Christopherson
2026-11-10 11:07 ` [PATCH 2/2] KVM: selftests: Test KVM_PRE_FAULT_MEMORY against a full dirty ring Fuad Tabba
2026-11-10 14:38 ` Lorenzo Stoakes (ARM) [this message]
2026-11-10 15:37 ` Fuad Tabba
2026-11-10 15:53 ` Sean Christopherson
2026-11-10 16:02 ` Lorenzo Stoakes (ARM)
2026-11-11 10:02 ` Alexandru Elisei
2026-11-11 14:57 ` Sean Christopherson
2026-11-10 17:29 ` Fuad Tabba
2026-11-10 15:57 ` Lorenzo Stoakes (ARM)
2026-11-10 17:31 ` Fuad Tabba
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=avMoubwqgXbpflwM@gremlin \
--to=ljs@kernel.org \
--cc=corbet@lwn.net \
--cc=fuad.tabba@linux.dev \
--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=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 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.