Commit afdb1960 authored by Peter Xu's avatar Peter Xu Committed by Paolo Bonzini
Browse files

KVM: selftests: Use a single binary for dirty/clear log test



Remove the clear_dirty_log test, instead merge it into the existing
dirty_log_test.  It should be cleaner to use this single binary to do
both tests, also it's a preparation for the upcoming dirty ring test.

The default behavior will run all the modes in sequence.

Reviewed-by: default avatarAndrew Jones <drjones@redhat.com>
Signed-off-by: default avatarPeter Xu <peterx@redhat.com>
Message-Id: <20201001012233.6013-1-peterx@redhat.com>
Signed-off-by: default avatarPaolo Bonzini <pbonzini@redhat.com>
parent 3031e028
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -59,7 +59,6 @@ TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test
TEST_GEN_PROGS_x86_64 += x86_64/debug_regs
TEST_GEN_PROGS_x86_64 += x86_64/tsc_msrs_test
TEST_GEN_PROGS_x86_64 += x86_64/user_msr_test
TEST_GEN_PROGS_x86_64 += clear_dirty_log_test
TEST_GEN_PROGS_x86_64 += demand_paging_test
TEST_GEN_PROGS_x86_64 += dirty_log_test
TEST_GEN_PROGS_x86_64 += kvm_create_max_vcpus
@@ -68,7 +67,6 @@ TEST_GEN_PROGS_x86_64 += steal_time

TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list
TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list-sve
TEST_GEN_PROGS_aarch64 += clear_dirty_log_test
TEST_GEN_PROGS_aarch64 += demand_paging_test
TEST_GEN_PROGS_aarch64 += dirty_log_test
TEST_GEN_PROGS_aarch64 += kvm_create_max_vcpus
+0 −6
Original line number Diff line number Diff line
#define USE_CLEAR_DIRTY_LOG
#define KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE (1 << 0)
#define KVM_DIRTY_LOG_INITIALLY_SET         (1 << 1)
#define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
		KVM_DIRTY_LOG_INITIALLY_SET)
#include "dirty_log_test.c"
+156 −31
Original line number Diff line number Diff line
@@ -128,6 +128,78 @@ static uint64_t host_dirty_count;
static uint64_t host_clear_count;
static uint64_t host_track_next_count;

enum log_mode_t {
	/* Only use KVM_GET_DIRTY_LOG for logging */
	LOG_MODE_DIRTY_LOG = 0,

	/* Use both KVM_[GET|CLEAR]_DIRTY_LOG for logging */
	LOG_MODE_CLEAR_LOG = 1,

	LOG_MODE_NUM,

	/* Run all supported modes */
	LOG_MODE_ALL = LOG_MODE_NUM,
};

/* Mode of logging to test.  Default is to run all supported modes */
static enum log_mode_t host_log_mode_option = LOG_MODE_ALL;
/* Logging mode for current run */
static enum log_mode_t host_log_mode;

static bool clear_log_supported(void)
{
	return kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
}

static void clear_log_create_vm_done(struct kvm_vm *vm)
{
	struct kvm_enable_cap cap = {};
	u64 manual_caps;

	manual_caps = kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
	TEST_ASSERT(manual_caps, "MANUAL_CAPS is zero!");
	manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
			KVM_DIRTY_LOG_INITIALLY_SET);
	cap.cap = KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2;
	cap.args[0] = manual_caps;
	vm_enable_cap(vm, &cap);
}

static void dirty_log_collect_dirty_pages(struct kvm_vm *vm, int slot,
					  void *bitmap, uint32_t num_pages)
{
	kvm_vm_get_dirty_log(vm, slot, bitmap);
}

static void clear_log_collect_dirty_pages(struct kvm_vm *vm, int slot,
					  void *bitmap, uint32_t num_pages)
{
	kvm_vm_get_dirty_log(vm, slot, bitmap);
	kvm_vm_clear_dirty_log(vm, slot, bitmap, 0, num_pages);
}

struct log_mode {
	const char *name;
	/* Return true if this mode is supported, otherwise false */
	bool (*supported)(void);
	/* Hook when the vm creation is done (before vcpu creation) */
	void (*create_vm_done)(struct kvm_vm *vm);
	/* Hook to collect the dirty pages into the bitmap provided */
	void (*collect_dirty_pages) (struct kvm_vm *vm, int slot,
				     void *bitmap, uint32_t num_pages);
} log_modes[LOG_MODE_NUM] = {
	{
		.name = "dirty-log",
		.collect_dirty_pages = dirty_log_collect_dirty_pages,
	},
	{
		.name = "clear-log",
		.supported = clear_log_supported,
		.create_vm_done = clear_log_create_vm_done,
		.collect_dirty_pages = clear_log_collect_dirty_pages,
	},
};

/*
 * We use this bitmap to track some pages that should have its dirty
 * bit set in the _next_ iteration.  For example, if we detected the
@@ -137,6 +209,44 @@ static uint64_t host_track_next_count;
 */
static unsigned long *host_bmap_track;

static void log_modes_dump(void)
{
	int i;

	printf("all");
	for (i = 0; i < LOG_MODE_NUM; i++)
		printf(", %s", log_modes[i].name);
	printf("\n");
}

static bool log_mode_supported(void)
{
	struct log_mode *mode = &log_modes[host_log_mode];

	if (mode->supported)
		return mode->supported();

	return true;
}

static void log_mode_create_vm_done(struct kvm_vm *vm)
{
	struct log_mode *mode = &log_modes[host_log_mode];

	if (mode->create_vm_done)
		mode->create_vm_done(vm);
}

static void log_mode_collect_dirty_pages(struct kvm_vm *vm, int slot,
					 void *bitmap, uint32_t num_pages)
{
	struct log_mode *mode = &log_modes[host_log_mode];

	TEST_ASSERT(mode->collect_dirty_pages != NULL,
		    "collect_dirty_pages() is required for any log mode!");
	mode->collect_dirty_pages(vm, slot, bitmap, num_pages);
}

static void generate_random_array(uint64_t *guest_array, uint64_t size)
{
	uint64_t i;
@@ -257,6 +367,7 @@ static struct kvm_vm *create_vm(enum vm_guest_mode mode, uint32_t vcpuid,
#ifdef __x86_64__
	vm_create_irqchip(vm);
#endif
	log_mode_create_vm_done(vm);
	vm_vcpu_add_default(vm, vcpuid, guest_code);
	return vm;
}
@@ -264,10 +375,6 @@ static struct kvm_vm *create_vm(enum vm_guest_mode mode, uint32_t vcpuid,
#define DIRTY_MEM_BITS 30 /* 1G */
#define PAGE_SHIFT_4K  12

#ifdef USE_CLEAR_DIRTY_LOG
static u64 dirty_log_manual_caps;
#endif

static void run_test(enum vm_guest_mode mode, unsigned long iterations,
		     unsigned long interval, uint64_t phys_offset)
{
@@ -275,6 +382,12 @@ static void run_test(enum vm_guest_mode mode, unsigned long iterations,
	struct kvm_vm *vm;
	unsigned long *bmap;

	if (!log_mode_supported()) {
		print_skip("Log mode '%s' not supported",
			   log_modes[host_log_mode].name);
		return;
	}

	/*
	 * We reserve page table for 2 times of extra dirty mem which
	 * will definitely cover the original (1G+) test range.  Here
@@ -317,14 +430,6 @@ static void run_test(enum vm_guest_mode mode, unsigned long iterations,
	bmap = bitmap_alloc(host_num_pages);
	host_bmap_track = bitmap_alloc(host_num_pages);

#ifdef USE_CLEAR_DIRTY_LOG
	struct kvm_enable_cap cap = {};

	cap.cap = KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2;
	cap.args[0] = dirty_log_manual_caps;
	vm_enable_cap(vm, &cap);
#endif

	/* Add an extra memory slot for testing dirty logging */
	vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
				    guest_test_phys_mem,
@@ -362,11 +467,8 @@ static void run_test(enum vm_guest_mode mode, unsigned long iterations,
	while (iteration < iterations) {
		/* Give the vcpu thread some time to dirty some pages */
		usleep(interval * 1000);
		kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
#ifdef USE_CLEAR_DIRTY_LOG
		kvm_vm_clear_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap, 0,
				       host_num_pages);
#endif
		log_mode_collect_dirty_pages(vm, TEST_MEM_SLOT_INDEX,
					     bmap, host_num_pages);
		vm_dirty_log_verify(mode, bmap);
		iteration++;
		sync_global_to_guest(vm, iteration);
@@ -410,6 +512,9 @@ static void help(char *name)
	       TEST_HOST_LOOP_INTERVAL);
	printf(" -p: specify guest physical test memory offset\n"
	       "     Warning: a low offset can conflict with the loaded test code.\n");
	printf(" -M: specify the host logging mode "
	       "(default: run all log modes).  Supported modes: \n\t");
	log_modes_dump();
	printf(" -m: specify the guest mode ID to test "
	       "(default: test all supported modes)\n"
	       "     This option may be used multiple times.\n"
@@ -429,18 +534,7 @@ int main(int argc, char *argv[])
	bool mode_selected = false;
	uint64_t phys_offset = 0;
	unsigned int mode;
	int opt, i;

#ifdef USE_CLEAR_DIRTY_LOG
	dirty_log_manual_caps =
		kvm_check_cap(KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2);
	if (!dirty_log_manual_caps) {
		print_skip("KVM_CLEAR_DIRTY_LOG not available");
		exit(KSFT_SKIP);
	}
	dirty_log_manual_caps &= (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
				  KVM_DIRTY_LOG_INITIALLY_SET);
#endif
	int opt, i, j;

#ifdef __x86_64__
	guest_mode_init(VM_MODE_PXXV48_4K, true, true);
@@ -464,7 +558,7 @@ int main(int argc, char *argv[])
	guest_mode_init(VM_MODE_P40V48_4K, true, true);
#endif

	while ((opt = getopt(argc, argv, "hi:I:p:m:")) != -1) {
	while ((opt = getopt(argc, argv, "hi:I:p:m:M:")) != -1) {
		switch (opt) {
		case 'i':
			iterations = strtol(optarg, NULL, 10);
@@ -486,6 +580,26 @@ int main(int argc, char *argv[])
				    "Guest mode ID %d too big", mode);
			guest_modes[mode].enabled = true;
			break;
		case 'M':
			if (!strcmp(optarg, "all")) {
				host_log_mode_option = LOG_MODE_ALL;
				break;
			}
			for (i = 0; i < LOG_MODE_NUM; i++) {
				if (!strcmp(optarg, log_modes[i].name)) {
					pr_info("Setting log mode to: '%s'\n",
						optarg);
					host_log_mode_option = i;
					break;
				}
			}
			if (i == LOG_MODE_NUM) {
				printf("Log mode '%s' invalid. Please choose "
				       "from: ", optarg);
				log_modes_dump();
				exit(1);
			}
			break;
		case 'h':
		default:
			help(argv[0]);
@@ -507,8 +621,19 @@ int main(int argc, char *argv[])
		TEST_ASSERT(guest_modes[i].supported,
			    "Guest mode ID %d (%s) not supported.",
			    i, vm_guest_mode_string(i));
		if (host_log_mode_option == LOG_MODE_ALL) {
			/* Run each log mode */
			for (j = 0; j < LOG_MODE_NUM; j++) {
				pr_info("Testing Log Mode '%s'\n",
					log_modes[j].name);
				host_log_mode = j;
				run_test(i, iterations, interval, phys_offset);
			}
		} else {
			host_log_mode = host_log_mode_option;
			run_test(i, iterations, interval, phys_offset);
		}
	}

	return 0;
}