mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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

  reply	other threads:[~2026-11-10 14:38 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 ` [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-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=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 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®