Ackerley Tng <[email protected]> writes: > 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. > > Signed-off-by: Ackerley Tng <[email protected]> > Co-developed-by: Sean Christopherson <[email protected]> > Signed-off-by: Sean Christopherson <[email protected]> > --- > tools/testing/selftests/kvm/Makefile.kvm | 1 + > .../selftests/kvm/guest_memfd_conversions_test.c | 205 > +++++++++++++++++++++ > 2 files changed, 206 insertions(+) > > diff --git a/tools/testing/selftests/kvm/Makefile.kvm > b/tools/testing/selftests/kvm/Makefile.kvm > index dc68371f76a33..0e2a9adfca57e 100644 > --- a/tools/testing/selftests/kvm/Makefile.kvm > +++ b/tools/testing/selftests/kvm/Makefile.kvm > @@ -147,6 +147,7 @@ TEST_GEN_PROGS_x86 += access_tracking_perf_test > TEST_GEN_PROGS_x86 += coalesced_io_test > TEST_GEN_PROGS_x86 += dirty_log_perf_test > TEST_GEN_PROGS_x86 += guest_memfd_test > +TEST_GEN_PROGS_x86 += guest_memfd_conversions_test > TEST_GEN_PROGS_x86 += hardware_disable_test > TEST_GEN_PROGS_x86 += memslot_modification_stress_test > TEST_GEN_PROGS_x86 += memslot_perf_test > diff --git a/tools/testing/selftests/kvm/guest_memfd_conversions_test.c > b/tools/testing/selftests/kvm/guest_memfd_conversions_test.c > new file mode 100644 > index 0000000000000..841b2824ae996 > --- /dev/null > +++ b/tools/testing/selftests/kvm/guest_memfd_conversions_test.c > @@ -0,0 +1,205 @@ > +// 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 uint64_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 uint64_t gpa = SZ_4G; > + const uint32_t slot = 1; > + u64 supported_flags; > + struct kvm_vm *vm; > + > + vm = __vm_create_shape_with_one_vcpu(shape, &t->vcpu, nr_pages, > guest_do_rmw); > + > + supported_flags = vm_check_cap(vm, KVM_CAP_MEMORY_ATTRIBUTES2_FLAGS); > + TEST_REQUIRE(supported_flags & KVM_SET_MEMORY_ATTRIBUTES2_PRESERVE); > + > + 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, loff_t 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);
TEST_ASSERT_EQ() ends up calling exit() on failures, which skips FIXTURE_TEARDOWN(). Other than the explicit assertions not working with the kselftest_harness, kvm selftest library functions like vm_mem_add() also call TEST_ASSERT, which doesn't play nice with kselftest_harness. Any suggestions for this? Should we use the kselftest framework with these tests? (I ran into this issue while trying to test something else, where I needed FIXTURE_TEARDOWN() to clean up system state.) Or is it "okay" in this case since FIXTURE_TEARDOWN() only cleans up stuff that would happen if the program exits anyway? > > [...snip...] >
