All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] kvm: tests: Test that APICv is effective
@ 2019-05-22 16:07 Aaron Lewis
  0 siblings, 0 replies; only message in thread
From: Aaron Lewis @ 2019-05-22 16:07 UTC (permalink / raw)
  To: jmattson, pshier, marcorr, kvm; +Cc: Aaron Lewis

This test determines whether a read from the local APIC ID register in an L1 guest is significantly faster than a read of the local APIC current count register (implying that APICv is effective).

Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
 tools/testing/selftests/kvm/.gitignore        |   1 +
 tools/testing/selftests/kvm/Makefile          |   3 +-
 .../testing/selftests/kvm/include/kvm_util.h  |   2 +
 tools/testing/selftests/kvm/include/timing.h  |  53 +++++++
 tools/testing/selftests/kvm/lib/kvm_util.c    |  41 +++++
 .../testing/selftests/kvm/x86_64/apicv_test.c | 145 ++++++++++++++++++
 6 files changed, 244 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/kvm/include/timing.h
 create mode 100644 tools/testing/selftests/kvm/x86_64/apicv_test.c

diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore
index df1bf9230a74..25be14292031 100644
--- a/tools/testing/selftests/kvm/.gitignore
+++ b/tools/testing/selftests/kvm/.gitignore
@@ -1,3 +1,4 @@
+/x86_64/apicv_test
 /x86_64/cr4_cpuid_sync_test
 /x86_64/evmcs_test
 /x86_64/hyperv_cpuid
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 79c524395ebe..7f33b1d7fd56 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -10,7 +10,8 @@ LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/ucall.c lib/sparsebi
 LIBKVM_x86_64 = lib/x86_64/processor.c lib/x86_64/vmx.c
 LIBKVM_aarch64 = lib/aarch64/processor.c
 
-TEST_GEN_PROGS_x86_64 = x86_64/platform_info_test
+TEST_GEN_PROGS_x86_64 = x86_64/apicv_test
+TEST_GEN_PROGS_x86_64 += x86_64/platform_info_test
 TEST_GEN_PROGS_x86_64 += x86_64/set_sregs_test
 TEST_GEN_PROGS_x86_64 += x86_64/sync_regs_test
 TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 8c6b9619797d..94ceedfeb373 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -146,6 +146,8 @@ allocate_kvm_dirty_log(struct kvm_userspace_memory_region *region);
 
 int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd);
 
+bool vm_is_apicv_enabled(struct kvm_vm *vm);
+
 #define sync_global_to_guest(vm, g) ({				\
 	typeof(g) *_p = addr_gva2hva(vm, (vm_vaddr_t)&(g));	\
 	memcpy(_p, &(g), sizeof(g));				\
diff --git a/tools/testing/selftests/kvm/include/timing.h b/tools/testing/selftests/kvm/include/timing.h
new file mode 100644
index 000000000000..f97d5f4427dc
--- /dev/null
+++ b/tools/testing/selftests/kvm/include/timing.h
@@ -0,0 +1,53 @@
+/*
+ * tools/testing/selftests/kvm/include/timing.h
+ *
+ * Copyright (C) 2019, Google LLC.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ *
+ */
+
+#ifndef SELFTEST_TIMING_H
+#define SELFTEST_TIMING_H
+
+/*
+ * Time the execution of 'fn' over the indicated number of iterations, and
+ * return the minimum (in TSC cycles).
+ */
+static inline u64 min_time(unsigned int iter, void (*fn)(void *arg), void *arg)
+{
+	u64 min = ~0ull;
+	unsigned int i;
+
+	for (i = 0; i < iter; i++) {
+		u64 cycles;
+		u64 start;
+
+		/*
+		 * Ensure all prior reads and writes are complete before
+		 * calling rdtsc().  This is necessary so the loads and stores
+		 * that are in flight prior to the first call to rdtsc don't
+		 * cause the memory barriers to stall after the callback is
+		 * called.
+		 * Note: Only a write barrier is needed because rdtsc() calls
+		 * a read barrier before reading the tsc.
+		 */
+		asm volatile("sfence");
+		start = rdtsc();
+
+		fn(arg);
+
+		/*
+		 * Ensure all reads and writes from the callback are complete
+		 * before calling rdtsc().  Again, rdtsc() will call a read
+		 * barrier, so just a write barrier is needed.
+		 */
+		asm volatile("sfence");
+		cycles = rdtsc() - start;
+		if (cycles < min)
+			min = cycles;
+	}
+	return min;
+}
+
+#endif /* SELFTEST_TIMING_H */
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index e9113857f44e..b52e2201ab1c 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -1584,3 +1584,44 @@ void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva)
 {
 	return addr_gpa2hva(vm, addr_gva2gpa(vm, gva));
 }
+
+/*
+ * Is APICv Enabled
+ *
+ * Input Args:
+ *   vm - Virtual Machine
+ *
+ * Output Args: None
+ *
+ * Return: True if the VM has APICv enabled, False if it is not enabled.
+ *
+ * Check if APICv is enabled for this vm.
+ */
+bool vm_is_apicv_enabled(struct kvm_vm *vm)
+{
+	char val;
+	size_t count;
+	FILE *f;
+
+	if (vm == NULL) {
+		/* Ensure that the KVM vendor-specific module is loaded. */
+		f = fopen(KVM_DEV_PATH, "r");
+		TEST_ASSERT(f != NULL, "Error in opening KVM dev file: %d",
+			    errno);
+		fclose(f);
+	}
+
+	f = fopen("/sys/module/kvm_intel/parameters/enable_apicv", "r");
+	if (f == NULL)
+		f = fopen("/sys/module/kvm_intel/parameters/enable_apicv", "r");
+
+	TEST_ASSERT(f != NULL, "Error in opening EPT/NPT param file: %d",
+		    errno);
+
+	count = fread(&val, sizeof(char), 1, f);
+	TEST_ASSERT(count == 1, "Unable to read from EPT param file.");
+
+	fclose(f);
+
+	return val == 'Y';
+}
diff --git a/tools/testing/selftests/kvm/x86_64/apicv_test.c b/tools/testing/selftests/kvm/x86_64/apicv_test.c
new file mode 100644
index 000000000000..d31f00db9ce6
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/apicv_test.c
@@ -0,0 +1,145 @@
+/*
+ * APICv Effectiveness Test --
+ *
+ *  This test determines whether a read from the local APIC ID register in an L1
+ *  guest is significantly faster than a read of the local APIC current count
+ *  register (implying that APICv is effective).
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <test_util.h>
+
+#include "kvm_util.h"
+#include "processor.h"
+#include "timing.h"
+#include "vmx.h"
+
+#define VCPU_ID		5
+#define APIC_ADDR	0xfee00000ULL
+#define APIC_ID		0x20
+#define APIC_TMCCT	0x390
+#define ITERATIONS	100
+
+#define PORT_INPUT	0
+#define PORT_OUTPUT	1
+
+#define SPEEDUP		3
+
+static inline uint64_t l1_vmexit(uint16_t port, uint64_t time)
+{
+	uint64_t input;
+
+	__asm__ __volatile__("in %%dx, %%al"
+			     :	"=S"(input) : "d"(port), "D"(time) : "eax");
+	return input;
+}
+
+void read_apic_register(void *offset)
+{
+	*(volatile uint32_t *)(APIC_ADDR + (uint64_t)offset);
+}
+
+void measure_apic_read(void)
+{
+	uint64_t offset;
+	uint64_t min;
+
+	offset = l1_vmexit(PORT_INPUT, 0);
+	min = min_time(ITERATIONS, read_apic_register, (void *)offset);
+	l1_vmexit(PORT_OUTPUT, min);
+}
+
+void guest_l1_entry(void)
+{
+	do {
+		measure_apic_read();
+	} while (true);
+}
+
+void verify_io_exit(struct kvm_vm *vm, uint16_t port)
+{
+	unsigned int exit_reason;
+	struct kvm_run *state;
+
+	state = vcpu_state(vm, VCPU_ID);
+	exit_reason = state->exit_reason;
+	TEST_ASSERT(
+		exit_reason == KVM_EXIT_IO,
+		"Unexpected exit reason: %u (%s)\n",
+		exit_reason, exit_reason_str(exit_reason));
+	TEST_ASSERT(
+		state->io.port == port,
+		"Unexpected I/O port: %u\n",
+		state->io.port);
+}
+
+uint64_t query_apic_read(struct kvm_vm *vm, uint64_t offset)
+{
+	struct kvm_regs regs;
+
+	vcpu_run(vm, VCPU_ID);
+	verify_io_exit(vm, PORT_INPUT);
+	vcpu_regs_get(vm, VCPU_ID, &regs);
+	regs.rsi = offset;
+	vcpu_regs_set(vm, VCPU_ID, &regs);
+
+	vcpu_run(vm, VCPU_ID);
+	verify_io_exit(vm, PORT_OUTPUT);
+	vcpu_regs_get(vm, VCPU_ID, &regs);
+
+	printf("APIC offset %#lx reads take %llu cycles\n", offset, regs.rdi);
+
+	return regs.rdi;
+}
+
+bool check_apicv(struct kvm_vm *vm)
+{
+	uint64_t fast = query_apic_read(vm, APIC_ID);
+	uint64_t slow = query_apic_read(vm, APIC_TMCCT);
+
+	return fast * SPEEDUP < slow;
+}
+
+int main(int argc, char *argv[])
+{
+	struct kvm_vm *vm;
+	struct kvm_cpuid2 *cpuid = kvm_get_supported_cpuid();
+	struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(0x1);
+
+	setbuf(stdout, NULL);
+
+	vm = vm_create_default(VCPU_ID, 0, guest_l1_entry);
+
+	if(!vm_is_apicv_enabled(vm)) {
+		printf("APICv not available, skipping test\n");
+		exit(KSFT_SKIP);
+	}
+
+	virt_pg_map(vm, APIC_ADDR, APIC_ADDR, 0);
+
+	/* Disable CPUID.01H:ECX.VMX */
+	entry->ecx |= CPUID_VMX;
+	vcpu_set_cpuid(vm, VCPU_ID, cpuid);
+
+	TEST_ASSERT(check_apicv(vm),
+		    "APICv is ineffective without CPUID.01H:ECX.VMX\n");
+
+	/* Enable CPUID.01H:ECX.VMX */
+	entry->ecx &= ~CPUID_VMX;
+	vcpu_set_cpuid(vm, VCPU_ID, cpuid);
+
+	printf("nVMX enabled.\n");
+	TEST_ASSERT(check_apicv(vm),
+		    "APICv is ineffective with CPUID.01H:ECX.VMX\n");
+
+	kvm_vm_free(vm);
+	free(cpuid);
+
+	return 0;
+}
-- 
2.21.0.1020.gf2820cf01a-goog


^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2019-05-22 16:07 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-05-22 16:07 [PATCH] kvm: tests: Test that APICv is effective Aaron Lewis

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.