On Fri, 19 Jun 2026 at 01:32, Ackerley Tng via B4 Relay <[email protected]> wrote: > > From: Ackerley Tng <[email protected]> > > Add a selftest for the guest_memfd memory attribute conversion ioctls. > The test starts the guest_memfd as all-private (the default state), and > verifies the basic flow of converting a single page to shared and then back > to private. > > Add infrastructure that supports extensions to other conversion flow > tests. This infrastructure will be used in upcoming patches for other > conversion tests. > > Add test as an x86-specific test since guest_memfd's testing > vehicle (KVM_X86_SW_PROTECTED_VM) is x86-specific. > > Signed-off-by: Ackerley Tng <[email protected]> > Co-developed-by: Sean Christopherson <[email protected]> > Signed-off-by: Sean Christopherson <[email protected]>
Reviewed-by: Fuad Tabba <[email protected]> Cheers, /fuad > --- > tools/testing/selftests/kvm/Makefile.kvm | 1 + > .../kvm/x86/guest_memfd_conversions_test.c | 199 > +++++++++++++++++++++ > 2 files changed, 200 insertions(+) > > diff --git a/tools/testing/selftests/kvm/Makefile.kvm > b/tools/testing/selftests/kvm/Makefile.kvm > index 4ace12606e937..b0e64a6dde21a 100644 > --- a/tools/testing/selftests/kvm/Makefile.kvm > +++ b/tools/testing/selftests/kvm/Makefile.kvm > @@ -152,6 +152,7 @@ TEST_GEN_PROGS_x86 += x86/max_vcpuid_cap_test > TEST_GEN_PROGS_x86 += x86/triple_fault_event_test > TEST_GEN_PROGS_x86 += x86/recalc_apic_map_test > TEST_GEN_PROGS_x86 += x86/aperfmperf_test > +TEST_GEN_PROGS_x86 += x86/guest_memfd_conversions_test > TEST_GEN_PROGS_x86 += access_tracking_perf_test > TEST_GEN_PROGS_x86 += coalesced_io_test > TEST_GEN_PROGS_x86 += dirty_log_perf_test > diff --git a/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c > b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c > new file mode 100644 > index 0000000000000..8e09e241723e5 > --- /dev/null > +++ b/tools/testing/selftests/kvm/x86/guest_memfd_conversions_test.c > @@ -0,0 +1,199 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2024, Google LLC. > + */ > +#include <sys/mman.h> > +#include <unistd.h> > + > +#include <linux/align.h> > +#include <linux/kvm.h> > +#include <linux/sizes.h> > + > +#include "kvm_util.h" > +#include "kselftest_harness.h" > +#include "test_util.h" > +#include "ucall_common.h" > + > +FIXTURE(gmem_conversions) { > + struct kvm_vcpu *vcpu; > + int gmem_fd; > + /* HVA of the first byte of the memory mmap()-ed from gmem_fd. */ > + char *mem; > +}; > + > +typedef FIXTURE_DATA(gmem_conversions) test_data_t; > + > +FIXTURE_SETUP(gmem_conversions) { } > + > +static size_t page_size; > + > +static void guest_do_rmw(void); > +#define GUEST_MEMFD_SHARING_TEST_GVA 0x90000000ULL > + > +/* > + * Defer setup until the individual test is invoked so that tests can specify > + * the number of pages and flags for the guest_memfd instance. > + */ > +static void gmem_conversions_do_setup(test_data_t *t, int nr_pages, > + int gmem_flags) > +{ > + const struct vm_shape shape = { > + .mode = VM_MODE_DEFAULT, > + .type = KVM_X86_SW_PROTECTED_VM, > + }; > + /* > + * Use high GPA above APIC_DEFAULT_PHYS_BASE to avoid clashing with > + * APIC_DEFAULT_PHYS_BASE. > + */ > + const gpa_t gpa = SZ_4G; > + const u32 slot = 1; > + struct kvm_vm *vm; > + > + vm = __vm_create_shape_with_one_vcpu(shape, &t->vcpu, nr_pages, > guest_do_rmw); > + > + vm_mem_add(vm, VM_MEM_SRC_SHMEM, gpa, slot, nr_pages, > + KVM_MEM_GUEST_MEMFD, -1, 0, gmem_flags); > + > + t->gmem_fd = kvm_slot_to_fd(vm, slot); > + t->mem = addr_gpa2hva(vm, gpa); > + virt_map(vm, GUEST_MEMFD_SHARING_TEST_GVA, gpa, nr_pages); > +} > + > +static void gmem_conversions_do_teardown(test_data_t *t) > +{ > + /* No need to close gmem_fd, it's owned by the VM structure. */ > + kvm_vm_free(t->vcpu->vm); > +} > + > +FIXTURE_TEARDOWN(gmem_conversions) > +{ > + gmem_conversions_do_teardown(self); > +} > + > +/* > + * In these test definition macros, __nr_pages and nr_pages is used to set up > + * the total number of pages in the guest_memfd under test. This will be > + * available in the test definitions as nr_pages. > + */ > + > +#define __GMEM_CONVERSION_TEST(test, __nr_pages, flags) > \ > +static void __gmem_conversions_##test(test_data_t *t, int nr_pages); > \ > + > \ > +TEST_F(gmem_conversions, test) > \ > +{ > \ > + gmem_conversions_do_setup(self, __nr_pages, flags); > \ > + __gmem_conversions_##test(self, __nr_pages); > \ > +} > \ > +static void __gmem_conversions_##test(test_data_t *t, int nr_pages) > \ > + > +#define GMEM_CONVERSION_TEST(test, __nr_pages, flags) > \ > + __GMEM_CONVERSION_TEST(test, __nr_pages, (flags) | > GUEST_MEMFD_FLAG_MMAP) > + > +#define __GMEM_CONVERSION_TEST_INIT_PRIVATE(test, __nr_pages) > \ > + GMEM_CONVERSION_TEST(test, __nr_pages, 0) > + > +#define GMEM_CONVERSION_TEST_INIT_PRIVATE(test) > \ > + __GMEM_CONVERSION_TEST_INIT_PRIVATE(test, 1) > + > +struct guest_check_data { > + void *mem; > + char expected_val; > + char write_val; > +}; > +static struct guest_check_data guest_data; > + > +static void guest_do_rmw(void) > +{ > + for (;;) { > + char *mem = READ_ONCE(guest_data.mem); > + > + GUEST_ASSERT_EQ(READ_ONCE(*mem), > READ_ONCE(guest_data.expected_val)); > + WRITE_ONCE(*mem, READ_ONCE(guest_data.write_val)); > + > + GUEST_SYNC(0); > + } > +} > + > +static void run_guest_do_rmw(struct kvm_vcpu *vcpu, u64 pgoff, > + char expected_val, char write_val) > +{ > + struct ucall uc; > + int r; > + > + guest_data.mem = (void *)GUEST_MEMFD_SHARING_TEST_GVA + pgoff * > page_size; > + guest_data.expected_val = expected_val; > + guest_data.write_val = write_val; > + sync_global_to_guest(vcpu->vm, guest_data); > + > + do { > + r = __vcpu_run(vcpu); > + } while (r == -1 && errno == EINTR); > + > + TEST_ASSERT_EQ(r, 0); > + > + switch (get_ucall(vcpu, &uc)) { > + case UCALL_ABORT: > + REPORT_GUEST_ASSERT(uc); > + case UCALL_SYNC: > + break; > + default: > + TEST_FAIL("Unexpected ucall %lu", uc.cmd); > + } > +} > + > +static void host_do_rmw(char *mem, u64 pgoff, char expected_val, > + char write_val) > +{ > + TEST_ASSERT_EQ(READ_ONCE(mem[pgoff * page_size]), expected_val); > + WRITE_ONCE(mem[pgoff * page_size], write_val); > +} > + > +static void test_private(test_data_t *t, u64 pgoff, char starting_val, > + char write_val) > +{ > + TEST_EXPECT_SIGBUS(WRITE_ONCE(t->mem[pgoff * page_size], write_val)); > + run_guest_do_rmw(t->vcpu, pgoff, starting_val, write_val); > + TEST_EXPECT_SIGBUS(READ_ONCE(t->mem[pgoff * page_size])); > +} > + > +static void test_convert_to_private(test_data_t *t, u64 pgoff, > + char starting_val, char write_val) > +{ > + gmem_set_private(t->gmem_fd, pgoff * page_size, page_size); > + test_private(t, pgoff, starting_val, write_val); > +} > + > +static void test_shared(test_data_t *t, u64 pgoff, char starting_val, > + char host_write_val, char write_val) > +{ > + host_do_rmw(t->mem, pgoff, starting_val, host_write_val); > + run_guest_do_rmw(t->vcpu, pgoff, host_write_val, write_val); > + TEST_ASSERT_EQ(READ_ONCE(t->mem[pgoff * page_size]), write_val); > +} > + > +static void test_convert_to_shared(test_data_t *t, u64 pgoff, > + char starting_val, char host_write_val, > + char write_val) > +{ > + gmem_set_shared(t->gmem_fd, pgoff * page_size, page_size); > + test_shared(t, pgoff, starting_val, host_write_val, write_val); > +} > + > +GMEM_CONVERSION_TEST_INIT_PRIVATE(init_private) > +{ > + test_private(t, 0, 0, 'A'); > + test_convert_to_shared(t, 0, 'A', 'B', 'C'); > + test_convert_to_private(t, 0, 'C', 'E'); > +} > + > + > +int main(int argc, char *argv[]) > +{ > + TEST_REQUIRE(kvm_check_cap(KVM_CAP_VM_TYPES) & > BIT(KVM_X86_SW_PROTECTED_VM)); > + TEST_REQUIRE(kvm_check_cap(KVM_CAP_GUEST_MEMFD_MEMORY_ATTRIBUTES) & > + KVM_MEMORY_ATTRIBUTE_PRIVATE); > + > + page_size = getpagesize(); > + > + return test_harness_run(argc, argv); > +} > > -- > 2.55.0.rc0.738.g0c8ab3ebcc-goog > >
