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
>
>

Reply via email to