All of lore.kernel.org
 help / color / mirror / Atom feed
From: Takahiro Itazuri <itazur@amazon.com>
To: <kvm@vger.kernel.org>, Sean Christopherson <seanjc@google.com>,
	"Paolo Bonzini" <pbonzini@redhat.com>
Cc: Vitaly Kuznetsov <vkuznets@redhat.com>,
	Fuad Tabba <tabba@google.com>,
	Brendan Jackman <jackmanb@google.com>,
	David Hildenbrand <david@kernel.org>,
	David Woodhouse <dwmw2@infradead.org>,
	Paul Durrant <pdurrant@amazon.com>,
	Nikita Kalyazin <nikita.kalyazin@linux.dev>,
	Patrick Roy <patrick.roy@campus.lmu.de>,
	Patrick Roy <patrick.roy@linux.dev>,
	"Derek Manwaring" <derekmn@amazon.com>,
	Alina Cernea <acernea@amazon.com>,
	"Michael Zoumboulakis" <zoumboul@amazon.com>,
	Takahiro Itazuri <zulinx86@gmail.com>,
	Takahiro Itazuri <itazur@amazon.com>
Subject: [RFC PATCH v4 6/7] KVM: selftests: Test pfncache with gmem-backed memory
Date: Mon, 20 Apr 2026 15:46:07 +0000	[thread overview]
Message-ID: <20260420154720.29012-7-itazur@amazon.com> (raw)
In-Reply-To: <20260420154720.29012-1-itazur@amazon.com>

Add a selftest that exercises pfncache (gfn_to_pfn_cache) with
guest_memfd-backed memory by using kvm-clock as the test vehicle.

The test creates two VM configurations:

 - NO_DIRECT_MAP VM: All memory is gmem-backed (MMAP | INIT_SHARED |
   NO_DIRECT_MAP). KVM_MEMSLOT_GMEM_ONLY is set, so pfncache resolves
   PFNs via kvm_gmem_get_pfn() and maps KHVAs via vmap().

 - SW_PROTECTED_VM: Memory starts private. pfncache resolves PFNs via
   kvm_gmem_get_pfn() for private pages. This validates the private
   memory pfncache path for future extensibility (e.g. pKVM-like VMs).

The guest writes MSR_KVM_SYSTEM_TIME_NEW (triggering kvm_gpc_activate()
internally), reads the pvclock structure to verify KVM wrote through the
pfncache KHVA correctly, and reports the kvm-clock value to the host for
bounds checking against KVM_GET_CLOCK.

Signed-off-by: Takahiro Itazuri <itazur@amazon.com>
---
 tools/testing/selftests/kvm/Makefile.kvm      |   1 +
 .../selftests/kvm/x86/pfncache_gmem_test.c    | 193 ++++++++++++++++++
 2 files changed, 194 insertions(+)
 create mode 100644 tools/testing/selftests/kvm/x86/pfncache_gmem_test.c

diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 148d427ff24b..faf454d64e4e 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -92,6 +92,7 @@ TEST_GEN_PROGS_x86 += x86/nested_emulation_test
 TEST_GEN_PROGS_x86 += x86/nested_exceptions_test
 TEST_GEN_PROGS_x86 += x86/platform_info_test
 TEST_GEN_PROGS_x86 += x86/pmu_counters_test
+TEST_GEN_PROGS_x86 += x86/pfncache_gmem_test
 TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
 TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
 TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
diff --git a/tools/testing/selftests/kvm/x86/pfncache_gmem_test.c b/tools/testing/selftests/kvm/x86/pfncache_gmem_test.c
new file mode 100644
index 000000000000..c61b161f3e0c
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86/pfncache_gmem_test.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025, Amazon.com, Inc. or its affiliates.
+ *
+ * Test pfncache (gfn_to_pfn_cache) with guest_memfd-backed memory.
+ *
+ * Exercises pfncache indirectly through kvm-clock: the guest writes
+ * MSR_KVM_SYSTEM_TIME_NEW (triggering kvm_gpc_activate() in KVM), KVM writes
+ * pvclock data through the pfncache's KHVA, and the guest reads the
+ * pvclock_vcpu_time_info structure to verify correctness.
+ *
+ * Two VM configurations exercise distinct pfncache code paths:
+ *
+ *  - NO_DIRECT_MAP VM: All memory is gmem-backed and shared (MMAP |
+ *    INIT_SHARED | NO_DIRECT_MAP).  KVM_MEMSLOT_GMEM_ONLY is set, so
+ *    gpc_is_gmem_backed() always returns true.  PFN resolution goes through
+ *    kvm_gmem_get_pfn() and KHVA mapping uses vmap().
+ *
+ *  - SW_PROTECTED_VM: Memory starts private.  pfncache uses
+ *    kvm_gmem_get_pfn() for private pages.  This validates the private
+ *    memory pfncache path for future extensibility (e.g. pKVM-like VMs).
+ */
+#include <asm/kvm_para.h>
+#include <asm/pvclock.h>
+#include <asm/pvclock-abi.h>
+#include <stdint.h>
+
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#define GUEST_SYNC_CLOCK(__stage, __val)	\
+	GUEST_SYNC_ARGS(__stage, __val, 0, 0, 0)
+
+static void guest_main(vm_paddr_t pvti_pa, struct pvclock_vcpu_time_info *pvti)
+{
+	int stage = 0;
+
+	wrmsr(MSR_KVM_SYSTEM_TIME_NEW, pvti_pa | KVM_MSR_ENABLED);
+
+	for (;;) {
+		uint64_t clock;
+
+		GUEST_ASSERT(pvti->system_time != 0);
+		clock = __pvclock_read_cycles(pvti, rdtsc());
+		GUEST_SYNC_CLOCK(stage++, clock);
+	}
+}
+
+static uint64_t run_and_verify_kvm_clock(struct kvm_vcpu *vcpu,
+					 uint64_t prev_clock)
+{
+	struct kvm_clock_data start, end;
+	struct ucall uc;
+	uint64_t guest_clock;
+
+	vm_ioctl(vcpu->vm, KVM_GET_CLOCK, &start);
+	vcpu_run(vcpu);
+	vm_ioctl(vcpu->vm, KVM_GET_CLOCK, &end);
+
+	TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+
+	switch (get_ucall(vcpu, &uc)) {
+	case UCALL_SYNC:
+		break;
+	case UCALL_ABORT:
+		REPORT_GUEST_ASSERT(uc);
+		/* unreachable */
+		return 0;
+	default:
+		TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
+	}
+
+	guest_clock = uc.args[2];
+
+	TEST_ASSERT(start.clock <= guest_clock && guest_clock <= end.clock,
+		    "guest clock %llu ns not in expected range [%llu, %llu] ns",
+		    (unsigned long long)guest_clock,
+		    (unsigned long long)start.clock,
+		    (unsigned long long)end.clock);
+
+	if (prev_clock)
+		TEST_ASSERT(guest_clock > prev_clock,
+			    "guest clock %llu ns not monotonic (prev %llu ns)",
+			    (unsigned long long)guest_clock,
+			    (unsigned long long)prev_clock);
+
+	pr_info("  guest clock %llu ns, expected range [%llu, %llu] ns\n",
+		(unsigned long long)guest_clock,
+		(unsigned long long)start.clock,
+		(unsigned long long)end.clock);
+
+	return guest_clock;
+}
+
+#define PVCLOCK_SLOT		10
+#define PVCLOCK_GPA		(1ULL << 32)
+
+static struct kvm_vm *setup_vm(struct vm_shape shape,
+			       struct kvm_vcpu **vcpu_out)
+{
+	struct kvm_vm *vm;
+
+	vm = __vm_create_shape_with_one_vcpu(shape, vcpu_out, 0, guest_main);
+
+	/*
+	 * For SW_PROTECTED_VM, the primary memslot doesn't have guest_memfd.
+	 * Place the pvclock page in a separate memslot with both anonymous
+	 * memory (for shared) and guest_memfd (for private), and mark it
+	 * private so that pfncache exercises the kvm_gmem_get_pfn() path.
+	 */
+	if (shape.type == KVM_X86_SW_PROTECTED_VM) {
+		int memfd = vm_create_guest_memfd(vm, getpagesize(), 0);
+
+		vm_mem_add(vm, VM_MEM_SRC_ANONYMOUS, PVCLOCK_GPA,
+			   PVCLOCK_SLOT, 1, KVM_MEM_GUEST_MEMFD, memfd, 0);
+		virt_map(vm, PVCLOCK_GPA, PVCLOCK_GPA, 1);
+		vcpu_args_set(*vcpu_out, 2, (vm_paddr_t)PVCLOCK_GPA,
+			      (struct pvclock_vcpu_time_info *)PVCLOCK_GPA);
+		vm_mem_set_private(vm, PVCLOCK_GPA, getpagesize());
+	} else {
+		vm_vaddr_t pvti_gva;
+		vm_paddr_t pvti_gpa;
+
+		pvti_gva = vm_vaddr_alloc(vm, getpagesize(), 0x10000);
+		pvti_gpa = addr_gva2gpa(vm, pvti_gva);
+		vcpu_args_set(*vcpu_out, 2, pvti_gpa, pvti_gva);
+	}
+
+	return vm;
+}
+
+static void test_no_direct_map(void)
+{
+	struct vm_shape shape = {
+		.mode = VM_MODE_DEFAULT,
+		.type = VM_TYPE_DEFAULT,
+		.src_type = VM_MEM_SRC_GUEST_MEMFD_NO_DIRECT_MAP,
+	};
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	uint64_t clock = 0;
+
+	pr_info("Testing pfncache with NO_DIRECT_MAP guest_memfd\n");
+
+	vm = setup_vm(shape, &vcpu);
+
+	/* Verify kvm-clock works with gmem-backed pfncache (vmap KHVA) */
+	clock = run_and_verify_kvm_clock(vcpu, clock);
+	clock = run_and_verify_kvm_clock(vcpu, clock);
+
+	kvm_vm_free(vm);
+}
+
+static void test_sw_protected_vm(void)
+{
+	struct vm_shape shape = {
+		.mode = VM_MODE_DEFAULT,
+		.type = KVM_X86_SW_PROTECTED_VM,
+	};
+	struct kvm_vcpu *vcpu;
+	struct kvm_vm *vm;
+	uint64_t clock = 0;
+
+	pr_info("Testing pfncache with SW_PROTECTED_VM (guest_memfd-backed private memory)\n");
+
+	vm = setup_vm(shape, &vcpu);
+
+	/* Verify kvm-clock works with gmem-backed private memory */
+	clock = run_and_verify_kvm_clock(vcpu, clock);
+	clock = run_and_verify_kvm_clock(vcpu, clock);
+
+	kvm_vm_free(vm);
+}
+
+int main(int argc, char *argv[])
+{
+	TEST_REQUIRE(kvm_has_cap(KVM_CAP_GUEST_MEMFD));
+	TEST_REQUIRE(sys_clocksource_is_based_on_tsc());
+
+	if (kvm_check_cap(KVM_CAP_GUEST_MEMFD_FLAGS) &
+	    GUEST_MEMFD_FLAG_NO_DIRECT_MAP)
+		test_no_direct_map();
+	else
+		print_skip("GUEST_MEMFD_FLAG_NO_DIRECT_MAP not supported");
+
+	if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM))
+		test_sw_protected_vm();
+	else
+		print_skip("KVM_X86_SW_PROTECTED_VM not supported");
+
+	return 0;
+}
-- 
2.50.1


  parent reply	other threads:[~2026-04-20 15:48 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-20 15:46 [RFC PATCH v4 0/7] KVM: pfncache: Add guest_memfd support to pfncache Takahiro Itazuri
2026-04-20 15:46 ` [RFC PATCH v4 1/7] KVM: pfncache: Resolve PFNs via kvm_gmem_get_pfn() for gmem-backed GPAs Takahiro Itazuri
2026-04-20 15:46 ` [RFC PATCH v4 2/7] KVM: pfncache: Obtain KHVA via vmap() for gmem with NO_DIRECT_MAP Takahiro Itazuri
2026-04-20 15:46 ` [RFC PATCH v4 3/7] KVM: Rename invalidate_begin to invalidate_start for consistency Takahiro Itazuri
2026-04-20 15:46 ` [RFC PATCH v4 4/7] KVM: pfncache: Rename invalidate_start() helper Takahiro Itazuri
2026-05-01 21:38   ` Ackerley Tng
2026-05-12 22:14     ` Sean Christopherson
2026-04-20 15:46 ` [RFC PATCH v4 5/7] KVM: pfncache: Invalidate on gmem invalidation and memattr updates Takahiro Itazuri
2026-05-01 21:43   ` Ackerley Tng
2026-04-20 15:46 ` Takahiro Itazuri [this message]
2026-05-01 21:16   ` [RFC PATCH v4 6/7] KVM: selftests: Test pfncache with gmem-backed memory Ackerley Tng
2026-04-20 15:46 ` [RFC PATCH v4 7/7] KVM: selftests: Test pfncache invalidation for " Takahiro Itazuri
2026-05-12 22:12 ` [RFC PATCH v4 0/7] KVM: pfncache: Add guest_memfd support to pfncache Sean Christopherson

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=20260420154720.29012-7-itazur@amazon.com \
    --to=itazur@amazon.com \
    --cc=acernea@amazon.com \
    --cc=david@kernel.org \
    --cc=derekmn@amazon.com \
    --cc=dwmw2@infradead.org \
    --cc=jackmanb@google.com \
    --cc=kvm@vger.kernel.org \
    --cc=nikita.kalyazin@linux.dev \
    --cc=patrick.roy@campus.lmu.de \
    --cc=patrick.roy@linux.dev \
    --cc=pbonzini@redhat.com \
    --cc=pdurrant@amazon.com \
    --cc=seanjc@google.com \
    --cc=tabba@google.com \
    --cc=vkuznets@redhat.com \
    --cc=zoumboul@amazon.com \
    --cc=zulinx86@gmail.com \
    /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.