Merge tag 'kvm-x86-selftests-6.5' of https://github.com/kvm-x86/linux into HEAD
authorPaolo Bonzini <pbonzini@redhat.com>
Sat, 1 Jul 2023 11:19:09 +0000 (07:19 -0400)
committerPaolo Bonzini <pbonzini@redhat.com>
Sat, 1 Jul 2023 11:19:09 +0000 (07:19 -0400)
KVM selftests changes for 6.5:

 - Add a test for splitting and reconstituting hugepages during and after
   dirty logging

 - Add support for CPU pinning in demand paging test

 - Generate dependency files so that partial rebuilds work as expected

 - Misc cleanups and fixes

tools/testing/selftests/kvm/Makefile
tools/testing/selftests/kvm/demand_paging_test.c
tools/testing/selftests/kvm/dirty_log_perf_test.c
tools/testing/selftests/kvm/include/kvm_util_base.h
tools/testing/selftests/kvm/include/memstress.h
tools/testing/selftests/kvm/lib/kvm_util.c
tools/testing/selftests/kvm/lib/memstress.c
tools/testing/selftests/kvm/lib/userfaultfd_util.c
tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c [new file with mode: 0644]
tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c
tools/testing/selftests/kvm/x86_64/vmx_nested_tsc_scaling_test.c

index 74e0a44f921649191e6828e7f1b35c9079ff52d0..c692cc86e7da83f92f19efa48b9833701d06bc91 100644 (file)
@@ -61,6 +61,7 @@ TEST_PROGS_x86_64 += x86_64/nx_huge_pages_test.sh
 # Compiled test targets
 TEST_GEN_PROGS_x86_64 = x86_64/cpuid_test
 TEST_GEN_PROGS_x86_64 += x86_64/cr4_cpuid_sync_test
+TEST_GEN_PROGS_x86_64 += x86_64/dirty_log_page_splitting_test
 TEST_GEN_PROGS_x86_64 += x86_64/get_msr_index_features
 TEST_GEN_PROGS_x86_64 += x86_64/exit_on_emulation_failure_test
 TEST_GEN_PROGS_x86_64 += x86_64/fix_hypercall_test
@@ -185,6 +186,8 @@ TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(ARCH_DIR))
 TEST_GEN_PROGS_EXTENDED += $(TEST_GEN_PROGS_EXTENDED_$(ARCH_DIR))
 LIBKVM += $(LIBKVM_$(ARCH_DIR))
 
+OVERRIDE_TARGETS = 1
+
 # lib.mak defines $(OUTPUT), prepends $(OUTPUT)/ to $(TEST_GEN_PROGS), and most
 # importantly defines, i.e. overwrites, $(CC) (unless `make -e` or `make CC=`,
 # which causes the environment variable to override the makefile).
@@ -199,7 +202,7 @@ else
 LINUX_TOOL_ARCH_INCLUDE = $(top_srcdir)/tools/arch/$(ARCH)/include
 endif
 CFLAGS += -Wall -Wstrict-prototypes -Wuninitialized -O2 -g -std=gnu99 \
-       -Wno-gnu-variable-sized-type-not-at-end \
+       -Wno-gnu-variable-sized-type-not-at-end -MD\
        -fno-builtin-memcmp -fno-builtin-memcpy -fno-builtin-memset \
        -fno-stack-protector -fno-PIE -I$(LINUX_TOOL_INCLUDE) \
        -I$(LINUX_TOOL_ARCH_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude \
@@ -226,7 +229,18 @@ LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
 LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING))
 LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ)
 
-EXTRA_CLEAN += $(LIBKVM_OBJS) cscope.*
+TEST_GEN_OBJ = $(patsubst %, %.o, $(TEST_GEN_PROGS))
+TEST_GEN_OBJ += $(patsubst %, %.o, $(TEST_GEN_PROGS_EXTENDED))
+TEST_DEP_FILES = $(patsubst %.o, %.d, $(TEST_GEN_OBJ))
+TEST_DEP_FILES += $(patsubst %.o, %.d, $(LIBKVM_OBJS))
+-include $(TEST_DEP_FILES)
+
+$(TEST_GEN_PROGS) $(TEST_GEN_PROGS_EXTENDED): %: %.o
+       $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) $< $(LIBKVM_OBJS) $(LDLIBS) -o $@
+$(TEST_GEN_OBJ): $(OUTPUT)/%.o: %.c
+       $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
+
+EXTRA_CLEAN += $(LIBKVM_OBJS) $(TEST_DEP_FILES) $(TEST_GEN_OBJ) cscope.*
 
 x := $(shell mkdir -p $(sort $(dir $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ))))
 $(LIBKVM_C_OBJ): $(OUTPUT)/%.o: %.c
index 2439c4043fed62af05b0c027a6db9c6f2f4a52cd..09c116a82a8499d7b14dc60bdcb088dd6c6914c7 100644 (file)
@@ -128,6 +128,7 @@ static void prefault_mem(void *alias, uint64_t len)
 
 static void run_test(enum vm_guest_mode mode, void *arg)
 {
+       struct memstress_vcpu_args *vcpu_args;
        struct test_params *p = arg;
        struct uffd_desc **uffd_descs = NULL;
        struct timespec start;
@@ -145,24 +146,24 @@ static void run_test(enum vm_guest_mode mode, void *arg)
                    "Failed to allocate buffer for guest data pattern");
        memset(guest_data_prototype, 0xAB, demand_paging_size);
 
+       if (p->uffd_mode == UFFDIO_REGISTER_MODE_MINOR) {
+               for (i = 0; i < nr_vcpus; i++) {
+                       vcpu_args = &memstress_args.vcpu_args[i];
+                       prefault_mem(addr_gpa2alias(vm, vcpu_args->gpa),
+                                    vcpu_args->pages * memstress_args.guest_page_size);
+               }
+       }
+
        if (p->uffd_mode) {
                uffd_descs = malloc(nr_vcpus * sizeof(struct uffd_desc *));
                TEST_ASSERT(uffd_descs, "Memory allocation failed");
-
                for (i = 0; i < nr_vcpus; i++) {
-                       struct memstress_vcpu_args *vcpu_args;
                        void *vcpu_hva;
-                       void *vcpu_alias;
 
                        vcpu_args = &memstress_args.vcpu_args[i];
 
                        /* Cache the host addresses of the region */
                        vcpu_hva = addr_gpa2hva(vm, vcpu_args->gpa);
-                       vcpu_alias = addr_gpa2alias(vm, vcpu_args->gpa);
-
-                       prefault_mem(vcpu_alias,
-                               vcpu_args->pages * memstress_args.guest_page_size);
-
                        /*
                         * Set up user fault fd to handle demand paging
                         * requests.
@@ -207,10 +208,11 @@ static void help(char *name)
 {
        puts("");
        printf("usage: %s [-h] [-m vm_mode] [-u uffd_mode] [-d uffd_delay_usec]\n"
-              "          [-b memory] [-s type] [-v vcpus] [-o]\n", name);
+              "          [-b memory] [-s type] [-v vcpus] [-c cpu_list] [-o]\n", name);
        guest_modes_help();
        printf(" -u: use userfaultfd to handle vCPU page faults. Mode is a\n"
               "     UFFD registration mode: 'MISSING' or 'MINOR'.\n");
+       kvm_print_vcpu_pinning_help();
        printf(" -d: add a delay in usec to the User Fault\n"
               "     FD handler to simulate demand paging\n"
               "     overheads. Ignored without -u.\n");
@@ -228,6 +230,7 @@ static void help(char *name)
 int main(int argc, char *argv[])
 {
        int max_vcpus = kvm_check_cap(KVM_CAP_MAX_VCPUS);
+       const char *cpulist = NULL;
        struct test_params p = {
                .src_type = DEFAULT_VM_MEM_SRC,
                .partition_vcpu_memory_access = true,
@@ -236,7 +239,7 @@ int main(int argc, char *argv[])
 
        guest_modes_append_default();
 
-       while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:o")) != -1) {
+       while ((opt = getopt(argc, argv, "hm:u:d:b:s:v:c:o")) != -1) {
                switch (opt) {
                case 'm':
                        guest_modes_cmdline(optarg);
@@ -263,6 +266,9 @@ int main(int argc, char *argv[])
                        TEST_ASSERT(nr_vcpus <= max_vcpus,
                                    "Invalid number of vcpus, must be between 1 and %d", max_vcpus);
                        break;
+               case 'c':
+                       cpulist = optarg;
+                       break;
                case 'o':
                        p.partition_vcpu_memory_access = false;
                        break;
@@ -278,6 +284,12 @@ int main(int argc, char *argv[])
                TEST_FAIL("userfaultfd MINOR mode requires shared memory; pick a different -s");
        }
 
+       if (cpulist) {
+               kvm_parse_vcpu_pinning(cpulist, memstress_args.vcpu_to_pcpu,
+                                      nr_vcpus);
+               memstress_args.pin_vcpus = true;
+       }
+
        for_each_guest_mode(run_test, &p);
 
        return 0;
index e9d6d1aecf89cb346070184115c982325d307960..d374dbcf9a535dbd9efc7316e9c63c4152010e7a 100644 (file)
@@ -136,77 +136,6 @@ struct test_params {
        bool random_access;
 };
 
-static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable)
-{
-       int i;
-
-       for (i = 0; i < slots; i++) {
-               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
-               int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0;
-
-               vm_mem_region_set_flags(vm, slot, flags);
-       }
-}
-
-static inline void enable_dirty_logging(struct kvm_vm *vm, int slots)
-{
-       toggle_dirty_logging(vm, slots, true);
-}
-
-static inline void disable_dirty_logging(struct kvm_vm *vm, int slots)
-{
-       toggle_dirty_logging(vm, slots, false);
-}
-
-static void get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots)
-{
-       int i;
-
-       for (i = 0; i < slots; i++) {
-               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
-
-               kvm_vm_get_dirty_log(vm, slot, bitmaps[i]);
-       }
-}
-
-static void clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
-                           int slots, uint64_t pages_per_slot)
-{
-       int i;
-
-       for (i = 0; i < slots; i++) {
-               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
-
-               kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot);
-       }
-}
-
-static unsigned long **alloc_bitmaps(int slots, uint64_t pages_per_slot)
-{
-       unsigned long **bitmaps;
-       int i;
-
-       bitmaps = malloc(slots * sizeof(bitmaps[0]));
-       TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array.");
-
-       for (i = 0; i < slots; i++) {
-               bitmaps[i] = bitmap_zalloc(pages_per_slot);
-               TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap.");
-       }
-
-       return bitmaps;
-}
-
-static void free_bitmaps(unsigned long *bitmaps[], int slots)
-{
-       int i;
-
-       for (i = 0; i < slots; i++)
-               free(bitmaps[i]);
-
-       free(bitmaps);
-}
-
 static void run_test(enum vm_guest_mode mode, void *arg)
 {
        struct test_params *p = arg;
@@ -236,7 +165,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
        host_num_pages = vm_num_host_pages(mode, guest_num_pages);
        pages_per_slot = host_num_pages / p->slots;
 
-       bitmaps = alloc_bitmaps(p->slots, pages_per_slot);
+       bitmaps = memstress_alloc_bitmaps(p->slots, pages_per_slot);
 
        if (dirty_log_manual_caps)
                vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2,
@@ -277,7 +206,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
 
        /* Enable dirty logging */
        clock_gettime(CLOCK_MONOTONIC, &start);
-       enable_dirty_logging(vm, p->slots);
+       memstress_enable_dirty_logging(vm, p->slots);
        ts_diff = timespec_elapsed(start);
        pr_info("Enabling dirty logging time: %ld.%.9lds\n\n",
                ts_diff.tv_sec, ts_diff.tv_nsec);
@@ -306,7 +235,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
                        iteration, ts_diff.tv_sec, ts_diff.tv_nsec);
 
                clock_gettime(CLOCK_MONOTONIC, &start);
-               get_dirty_log(vm, bitmaps, p->slots);
+               memstress_get_dirty_log(vm, bitmaps, p->slots);
                ts_diff = timespec_elapsed(start);
                get_dirty_log_total = timespec_add(get_dirty_log_total,
                                                   ts_diff);
@@ -315,7 +244,8 @@ static void run_test(enum vm_guest_mode mode, void *arg)
 
                if (dirty_log_manual_caps) {
                        clock_gettime(CLOCK_MONOTONIC, &start);
-                       clear_dirty_log(vm, bitmaps, p->slots, pages_per_slot);
+                       memstress_clear_dirty_log(vm, bitmaps, p->slots,
+                                                 pages_per_slot);
                        ts_diff = timespec_elapsed(start);
                        clear_dirty_log_total = timespec_add(clear_dirty_log_total,
                                                             ts_diff);
@@ -334,7 +264,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
 
        /* Disable dirty logging */
        clock_gettime(CLOCK_MONOTONIC, &start);
-       disable_dirty_logging(vm, p->slots);
+       memstress_disable_dirty_logging(vm, p->slots);
        ts_diff = timespec_elapsed(start);
        pr_info("Disabling dirty logging time: %ld.%.9lds\n",
                ts_diff.tv_sec, ts_diff.tv_nsec);
@@ -359,7 +289,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
                        clear_dirty_log_total.tv_nsec, avg.tv_sec, avg.tv_nsec);
        }
 
-       free_bitmaps(bitmaps, p->slots);
+       memstress_free_bitmaps(bitmaps, p->slots);
        arch_cleanup_vm(vm);
        memstress_destroy_vm(vm);
 }
@@ -402,17 +332,7 @@ static void help(char *name)
               "     so -w X means each page has an X%% chance of writing\n"
               "     and a (100-X)%% chance of reading.\n"
               "     (default: 100 i.e. all pages are written to.)\n");
-       printf(" -c: Pin tasks to physical CPUs.  Takes a list of comma separated\n"
-              "     values (target pCPU), one for each vCPU, plus an optional\n"
-              "     entry for the main application task (specified via entry\n"
-              "     <nr_vcpus + 1>).  If used, entries must be provided for all\n"
-              "     vCPUs, i.e. pinning vCPUs is all or nothing.\n\n"
-              "     E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n"
-              "     vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n"
-              "         ./dirty_log_perf_test -v 3 -c 22,23,24,50\n\n"
-              "     To leave the application task unpinned, drop the final entry:\n\n"
-              "         ./dirty_log_perf_test -v 3 -c 22,23,24\n\n"
-              "     (default: no pinning)\n");
+       kvm_print_vcpu_pinning_help();
        puts("");
        exit(0);
 }
index a089c356f354e39d3fac2ef42164fd37c309b637..07732a157ccd640ccccdc73477f98ce796c33b46 100644 (file)
@@ -733,6 +733,7 @@ static inline struct kvm_vm *vm_create_with_one_vcpu(struct kvm_vcpu **vcpu,
 struct kvm_vcpu *vm_recreate_with_one_vcpu(struct kvm_vm *vm);
 
 void kvm_pin_this_task_to_pcpu(uint32_t pcpu);
+void kvm_print_vcpu_pinning_help(void);
 void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[],
                            int nr_vcpus);
 
index 72e3e358ef7bd35d8c4ce910961090d593a30934..ce4e603050eaaa292d6980e9939f4677d8a75b68 100644 (file)
@@ -72,4 +72,12 @@ void memstress_guest_code(uint32_t vcpu_id);
 uint64_t memstress_nested_pages(int nr_vcpus);
 void memstress_setup_nested(struct kvm_vm *vm, int nr_vcpus, struct kvm_vcpu *vcpus[]);
 
+void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots);
+void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots);
+void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots);
+void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
+                              int slots, uint64_t pages_per_slot);
+unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot);
+void memstress_free_bitmaps(unsigned long *bitmaps[], int slots);
+
 #endif /* SELFTEST_KVM_MEMSTRESS_H */
index 298c4372fb1ad7af99eba186957b27fd0e348f40..9741a7ff6380fe4caf4b6545cb522bed4c18620c 100644 (file)
@@ -494,6 +494,23 @@ static uint32_t parse_pcpu(const char *cpu_str, const cpu_set_t *allowed_mask)
        return pcpu;
 }
 
+void kvm_print_vcpu_pinning_help(void)
+{
+       const char *name = program_invocation_name;
+
+       printf(" -c: Pin tasks to physical CPUs.  Takes a list of comma separated\n"
+              "     values (target pCPU), one for each vCPU, plus an optional\n"
+              "     entry for the main application task (specified via entry\n"
+              "     <nr_vcpus + 1>).  If used, entries must be provided for all\n"
+              "     vCPUs, i.e. pinning vCPUs is all or nothing.\n\n"
+              "     E.g. to create 3 vCPUs, pin vCPU0=>pCPU22, vCPU1=>pCPU23,\n"
+              "     vCPU2=>pCPU24, and pin the application task to pCPU50:\n\n"
+              "         %s -v 3 -c 22,23,24,50\n\n"
+              "     To leave the application task unpinned, drop the final entry:\n\n"
+              "         %s -v 3 -c 22,23,24\n\n"
+              "     (default: no pinning)\n", name, name);
+}
+
 void kvm_parse_vcpu_pinning(const char *pcpus_string, uint32_t vcpu_to_pcpu[],
                            int nr_vcpus)
 {
index 5f1d3173c238cb7d570b3c391945276a316b8336..df457452d1464f3fa659d38202e8d8e7dc89de0b 100644 (file)
@@ -5,6 +5,7 @@
 #define _GNU_SOURCE
 
 #include <inttypes.h>
+#include <linux/bitmap.h>
 
 #include "kvm_util.h"
 #include "memstress.h"
@@ -64,6 +65,9 @@ void memstress_guest_code(uint32_t vcpu_idx)
        GUEST_ASSERT(vcpu_args->vcpu_idx == vcpu_idx);
 
        while (true) {
+               for (i = 0; i < sizeof(memstress_args); i += args->guest_page_size)
+                       (void) *((volatile char *)args + i);
+
                for (i = 0; i < pages; i++) {
                        if (args->random_access)
                                page = guest_random_u32(&rand_state) % pages;
@@ -320,3 +324,74 @@ void memstress_join_vcpu_threads(int nr_vcpus)
        for (i = 0; i < nr_vcpus; i++)
                pthread_join(vcpu_threads[i].thread, NULL);
 }
+
+static void toggle_dirty_logging(struct kvm_vm *vm, int slots, bool enable)
+{
+       int i;
+
+       for (i = 0; i < slots; i++) {
+               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
+               int flags = enable ? KVM_MEM_LOG_DIRTY_PAGES : 0;
+
+               vm_mem_region_set_flags(vm, slot, flags);
+       }
+}
+
+void memstress_enable_dirty_logging(struct kvm_vm *vm, int slots)
+{
+       toggle_dirty_logging(vm, slots, true);
+}
+
+void memstress_disable_dirty_logging(struct kvm_vm *vm, int slots)
+{
+       toggle_dirty_logging(vm, slots, false);
+}
+
+void memstress_get_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[], int slots)
+{
+       int i;
+
+       for (i = 0; i < slots; i++) {
+               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
+
+               kvm_vm_get_dirty_log(vm, slot, bitmaps[i]);
+       }
+}
+
+void memstress_clear_dirty_log(struct kvm_vm *vm, unsigned long *bitmaps[],
+                              int slots, uint64_t pages_per_slot)
+{
+       int i;
+
+       for (i = 0; i < slots; i++) {
+               int slot = MEMSTRESS_MEM_SLOT_INDEX + i;
+
+               kvm_vm_clear_dirty_log(vm, slot, bitmaps[i], 0, pages_per_slot);
+       }
+}
+
+unsigned long **memstress_alloc_bitmaps(int slots, uint64_t pages_per_slot)
+{
+       unsigned long **bitmaps;
+       int i;
+
+       bitmaps = malloc(slots * sizeof(bitmaps[0]));
+       TEST_ASSERT(bitmaps, "Failed to allocate bitmaps array.");
+
+       for (i = 0; i < slots; i++) {
+               bitmaps[i] = bitmap_zalloc(pages_per_slot);
+               TEST_ASSERT(bitmaps[i], "Failed to allocate slot bitmap.");
+       }
+
+       return bitmaps;
+}
+
+void memstress_free_bitmaps(unsigned long *bitmaps[], int slots)
+{
+       int i;
+
+       for (i = 0; i < slots; i++)
+               free(bitmaps[i]);
+
+       free(bitmaps);
+}
index 92cef20902f1f901abba2947de27e315694a6bd7..271f6389158122a973fa597d68ee147f7169374c 100644 (file)
@@ -70,7 +70,7 @@ static void *uffd_handler_thread_fn(void *arg)
                        r = read(pollfd[1].fd, &tmp_chr, 1);
                        TEST_ASSERT(r == 1,
                                    "Error reading pipefd in UFFD thread\n");
-                       return NULL;
+                       break;
                }
 
                if (!(pollfd[0].revents & POLLIN))
@@ -103,7 +103,7 @@ static void *uffd_handler_thread_fn(void *arg)
        ts_diff = timespec_elapsed(start);
        PER_VCPU_DEBUG("userfaulted %ld pages over %ld.%.9lds. (%f/sec)\n",
                       pages, ts_diff.tv_sec, ts_diff.tv_nsec,
-                      pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / 100000000.0));
+                      pages / ((double)ts_diff.tv_sec + (double)ts_diff.tv_nsec / NSEC_PER_SEC));
 
        return NULL;
 }
diff --git a/tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c b/tools/testing/selftests/kvm/x86_64/dirty_log_page_splitting_test.c
new file mode 100644 (file)
index 0000000..beb7e2c
--- /dev/null
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * KVM dirty logging page splitting test
+ *
+ * Based on dirty_log_perf.c
+ *
+ * Copyright (C) 2018, Red Hat, Inc.
+ * Copyright (C) 2023, Google, Inc.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <linux/bitmap.h>
+
+#include "kvm_util.h"
+#include "test_util.h"
+#include "memstress.h"
+#include "guest_modes.h"
+
+#define VCPUS          2
+#define SLOTS          2
+#define ITERATIONS     2
+
+static uint64_t guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE;
+
+static enum vm_mem_backing_src_type backing_src = VM_MEM_SRC_ANONYMOUS_HUGETLB;
+
+static u64 dirty_log_manual_caps;
+static bool host_quit;
+static int iteration;
+static int vcpu_last_completed_iteration[KVM_MAX_VCPUS];
+
+struct kvm_page_stats {
+       uint64_t pages_4k;
+       uint64_t pages_2m;
+       uint64_t pages_1g;
+       uint64_t hugepages;
+};
+
+static void get_page_stats(struct kvm_vm *vm, struct kvm_page_stats *stats, const char *stage)
+{
+       stats->pages_4k = vm_get_stat(vm, "pages_4k");
+       stats->pages_2m = vm_get_stat(vm, "pages_2m");
+       stats->pages_1g = vm_get_stat(vm, "pages_1g");
+       stats->hugepages = stats->pages_2m + stats->pages_1g;
+
+       pr_debug("\nPage stats after %s: 4K: %ld 2M: %ld 1G: %ld huge: %ld\n",
+                stage, stats->pages_4k, stats->pages_2m, stats->pages_1g,
+                stats->hugepages);
+}
+
+static void run_vcpu_iteration(struct kvm_vm *vm)
+{
+       int i;
+
+       iteration++;
+       for (i = 0; i < VCPUS; i++) {
+               while (READ_ONCE(vcpu_last_completed_iteration[i]) !=
+                      iteration)
+                       ;
+       }
+}
+
+static void vcpu_worker(struct memstress_vcpu_args *vcpu_args)
+{
+       struct kvm_vcpu *vcpu = vcpu_args->vcpu;
+       int vcpu_idx = vcpu_args->vcpu_idx;
+
+       while (!READ_ONCE(host_quit)) {
+               int current_iteration = READ_ONCE(iteration);
+
+               vcpu_run(vcpu);
+
+               ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_SYNC);
+
+               vcpu_last_completed_iteration[vcpu_idx] = current_iteration;
+
+               /* Wait for the start of the next iteration to be signaled. */
+               while (current_iteration == READ_ONCE(iteration) &&
+                      READ_ONCE(iteration) >= 0 &&
+                      !READ_ONCE(host_quit))
+                       ;
+       }
+}
+
+static void run_test(enum vm_guest_mode mode, void *unused)
+{
+       struct kvm_vm *vm;
+       unsigned long **bitmaps;
+       uint64_t guest_num_pages;
+       uint64_t host_num_pages;
+       uint64_t pages_per_slot;
+       int i;
+       uint64_t total_4k_pages;
+       struct kvm_page_stats stats_populated;
+       struct kvm_page_stats stats_dirty_logging_enabled;
+       struct kvm_page_stats stats_dirty_pass[ITERATIONS];
+       struct kvm_page_stats stats_clear_pass[ITERATIONS];
+       struct kvm_page_stats stats_dirty_logging_disabled;
+       struct kvm_page_stats stats_repopulated;
+
+       vm = memstress_create_vm(mode, VCPUS, guest_percpu_mem_size,
+                                SLOTS, backing_src, false);
+
+       guest_num_pages = (VCPUS * guest_percpu_mem_size) >> vm->page_shift;
+       guest_num_pages = vm_adjust_num_guest_pages(mode, guest_num_pages);
+       host_num_pages = vm_num_host_pages(mode, guest_num_pages);
+       pages_per_slot = host_num_pages / SLOTS;
+
+       bitmaps = memstress_alloc_bitmaps(SLOTS, pages_per_slot);
+
+       if (dirty_log_manual_caps)
+               vm_enable_cap(vm, KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2,
+                             dirty_log_manual_caps);
+
+       /* Start the iterations */
+       iteration = -1;
+       host_quit = false;
+
+       for (i = 0; i < VCPUS; i++)
+               vcpu_last_completed_iteration[i] = -1;
+
+       memstress_start_vcpu_threads(VCPUS, vcpu_worker);
+
+       run_vcpu_iteration(vm);
+       get_page_stats(vm, &stats_populated, "populating memory");
+
+       /* Enable dirty logging */
+       memstress_enable_dirty_logging(vm, SLOTS);
+
+       get_page_stats(vm, &stats_dirty_logging_enabled, "enabling dirty logging");
+
+       while (iteration < ITERATIONS) {
+               run_vcpu_iteration(vm);
+               get_page_stats(vm, &stats_dirty_pass[iteration - 1],
+                              "dirtying memory");
+
+               memstress_get_dirty_log(vm, bitmaps, SLOTS);
+
+               if (dirty_log_manual_caps) {
+                       memstress_clear_dirty_log(vm, bitmaps, SLOTS, pages_per_slot);
+
+                       get_page_stats(vm, &stats_clear_pass[iteration - 1], "clearing dirty log");
+               }
+       }
+
+       /* Disable dirty logging */
+       memstress_disable_dirty_logging(vm, SLOTS);
+
+       get_page_stats(vm, &stats_dirty_logging_disabled, "disabling dirty logging");
+
+       /* Run vCPUs again to fault pages back in. */
+       run_vcpu_iteration(vm);
+       get_page_stats(vm, &stats_repopulated, "repopulating memory");
+
+       /*
+        * Tell the vCPU threads to quit.  No need to manually check that vCPUs
+        * have stopped running after disabling dirty logging, the join will
+        * wait for them to exit.
+        */
+       host_quit = true;
+       memstress_join_vcpu_threads(VCPUS);
+
+       memstress_free_bitmaps(bitmaps, SLOTS);
+       memstress_destroy_vm(vm);
+
+       /* Make assertions about the page counts. */
+       total_4k_pages = stats_populated.pages_4k;
+       total_4k_pages += stats_populated.pages_2m * 512;
+       total_4k_pages += stats_populated.pages_1g * 512 * 512;
+
+       /*
+        * Check that all huge pages were split. Since large pages can only
+        * exist in the data slot, and the vCPUs should have dirtied all pages
+        * in the data slot, there should be no huge pages left after splitting.
+        * Splitting happens at dirty log enable time without
+        * KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 and after the first clear pass
+        * with that capability.
+        */
+       if (dirty_log_manual_caps) {
+               ASSERT_EQ(stats_clear_pass[0].hugepages, 0);
+               ASSERT_EQ(stats_clear_pass[0].pages_4k, total_4k_pages);
+               ASSERT_EQ(stats_dirty_logging_enabled.hugepages, stats_populated.hugepages);
+       } else {
+               ASSERT_EQ(stats_dirty_logging_enabled.hugepages, 0);
+               ASSERT_EQ(stats_dirty_logging_enabled.pages_4k, total_4k_pages);
+       }
+
+       /*
+        * Once dirty logging is disabled and the vCPUs have touched all their
+        * memory again, the page counts should be the same as they were
+        * right after initial population of memory.
+        */
+       ASSERT_EQ(stats_populated.pages_4k, stats_repopulated.pages_4k);
+       ASSERT_EQ(stats_populated.pages_2m, stats_repopulated.pages_2m);
+       ASSERT_EQ(stats_populated.pages_1g, stats_repopulated.pages_1g);
+}
+
+static void help(char *name)
+{
+       puts("");
+       printf("usage: %s [-h] [-b vcpu bytes] [-s mem type]\n",
+              name);
+       puts("");
+       printf(" -b: specify the size of the memory region which should be\n"
+              "     dirtied by each vCPU. e.g. 10M or 3G.\n"
+              "     (default: 1G)\n");
+       backing_src_help("-s");
+       puts("");
+}
+
+int main(int argc, char *argv[])
+{
+       int opt;
+
+       TEST_REQUIRE(get_kvm_param_bool("eager_page_split"));
+       TEST_REQUIRE(get_kvm_param_bool("tdp_mmu"));
+
+       while ((opt = getopt(argc, argv, "b:hs:")) != -1) {
+               switch (opt) {
+               case 'b':
+                       guest_percpu_mem_size = parse_size(optarg);
+                       break;
+               case 'h':
+                       help(argv[0]);
+                       exit(0);
+               case 's':
+                       backing_src = parse_backing_src_type(optarg);
+                       break;
+               default:
+                       help(argv[0]);
+                       exit(1);
+               }
+       }
+
+       if (!is_backing_src_hugetlb(backing_src)) {
+               pr_info("This test will only work reliably with HugeTLB memory. "
+                       "It can work with THP, but that is best effort.\n");
+       }
+
+       guest_modes_append_default();
+
+       dirty_log_manual_caps = 0;
+       for_each_guest_mode(run_test, NULL);
+
+       dirty_log_manual_caps =
+               kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
+
+       if (dirty_log_manual_caps) {
+               dirty_log_manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
+                                         KVM_DIRTY_LOG_INITIALLY_SET);
+               for_each_guest_mode(run_test, NULL);
+       } else {
+               pr_info("Skipping testing with MANUAL_PROTECT as it is not supported");
+       }
+
+       return 0;
+}
index 251794f8371939e7b33f15fda448b7b341fd2ab0..7f36c32fa76093a6affe275e237edda24f7020c6 100644 (file)
@@ -226,7 +226,7 @@ static void help(char *name)
        puts("");
        printf("usage: %s [-h] [-p period_ms] [-t token]\n", name);
        puts("");
-       printf(" -p: The NX reclaim period in miliseconds.\n");
+       printf(" -p: The NX reclaim period in milliseconds.\n");
        printf(" -t: The magic token to indicate environment setup is done.\n");
        printf(" -r: The test has reboot permissions and can disable NX huge pages.\n");
        puts("");
index fa03c8d1ce4edb1d38a9be4aa4a7e60ea68f65a5..e710b6e7fb384aac124ad0c7f646a872b347ad51 100644 (file)
@@ -116,29 +116,21 @@ static void l1_guest_code(struct vmx_pages *vmx_pages)
        GUEST_DONE();
 }
 
-static void stable_tsc_check_supported(void)
+static bool system_has_stable_tsc(void)
 {
+       bool tsc_is_stable;
        FILE *fp;
        char buf[4];
 
        fp = fopen("/sys/devices/system/clocksource/clocksource0/current_clocksource", "r");
        if (fp == NULL)
-               goto skip_test;
+               return false;
 
-       if (fgets(buf, sizeof(buf), fp) == NULL)
-               goto close_fp;
+       tsc_is_stable = fgets(buf, sizeof(buf), fp) &&
+                       !strncmp(buf, "tsc", sizeof(buf));
 
-       if (strncmp(buf, "tsc", sizeof(buf)))
-               goto close_fp;
-
-       fclose(fp);
-       return;
-
-close_fp:
        fclose(fp);
-skip_test:
-       print_skip("Kernel does not use TSC clocksource - assuming that host TSC is not stable");
-       exit(KSFT_SKIP);
+       return tsc_is_stable;
 }
 
 int main(int argc, char *argv[])
@@ -156,7 +148,7 @@ int main(int argc, char *argv[])
 
        TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_VMX));
        TEST_REQUIRE(kvm_has_cap(KVM_CAP_TSC_CONTROL));
-       stable_tsc_check_supported();
+       TEST_REQUIRE(system_has_stable_tsc());
 
        /*
         * We set L1's scale factor to be a random number from 2 to 10.