From 4e18bccc2e5544f0be28fc1c4e6be47a469d6c60 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 22 Aug 2018 15:19:57 +0800 Subject: kvm: selftest: unify the guest port macros Most of the tests are using the same way to do guest to host sync but the code is mostly duplicated. Generalize the guest port macros into the common header file and use it in different tests. Meanwhile provide "struct guest_args" and a helper "guest_args_read()" to hide the register details when playing with these port operations on RDI and RSI. Signed-off-by: Peter Xu Signed-off-by: Paolo Bonzini --- tools/testing/selftests/kvm/cr4_cpuid_sync_test.c | 30 ++++------------- tools/testing/selftests/kvm/include/kvm_util.h | 39 +++++++++++++++++++++ tools/testing/selftests/kvm/lib/kvm_util.c | 14 ++++++++ tools/testing/selftests/kvm/state_test.c | 30 +++-------------- tools/testing/selftests/kvm/sync_regs_test.c | 19 +---------- tools/testing/selftests/kvm/vmx_tsc_adjust_test.c | 41 +++++++---------------- 6 files changed, 78 insertions(+), 95 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c index 8346b33c2073..d46b42274fd5 100644 --- a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c +++ b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c @@ -23,20 +23,6 @@ #define X86_FEATURE_OSXSAVE (1<<27) #define VCPU_ID 1 -enum { - GUEST_UPDATE_CR4 = 0x1000, - GUEST_FAILED, - GUEST_DONE, -}; - -static void exit_to_hv(uint16_t port) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port) - : "rax"); -} - static inline bool cr4_cpuid_is_sync(void) { int func, subfunc; @@ -64,17 +50,15 @@ static void guest_code(void) set_cr4(cr4); /* verify CR4.OSXSAVE == CPUID.OSXSAVE */ - if (!cr4_cpuid_is_sync()) - exit_to_hv(GUEST_FAILED); + GUEST_ASSERT(cr4_cpuid_is_sync()); /* notify hypervisor to change CR4 */ - exit_to_hv(GUEST_UPDATE_CR4); + GUEST_SYNC(0); /* check again */ - if (!cr4_cpuid_is_sync()) - exit_to_hv(GUEST_FAILED); + GUEST_ASSERT(cr4_cpuid_is_sync()); - exit_to_hv(GUEST_DONE); + GUEST_DONE(); } int main(int argc, char *argv[]) @@ -104,16 +88,16 @@ int main(int argc, char *argv[]) if (run->exit_reason == KVM_EXIT_IO) { switch (run->io.port) { - case GUEST_UPDATE_CR4: + case GUEST_PORT_SYNC: /* emulate hypervisor clearing CR4.OSXSAVE */ vcpu_sregs_get(vm, VCPU_ID, &sregs); sregs.cr4 &= ~X86_CR4_OSXSAVE; vcpu_sregs_set(vm, VCPU_ID, &sregs); break; - case GUEST_FAILED: + case GUEST_PORT_ABORT: TEST_ASSERT(false, "Guest CR4 bit (OSXSAVE) unsynchronized with CPUID bit."); break; - case GUEST_DONE: + case GUEST_PORT_DONE: goto done; default: TEST_ASSERT(false, "Unknown port 0x%x.", diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h index d32632f71ab8..d8ca48687e35 100644 --- a/tools/testing/selftests/kvm/include/kvm_util.h +++ b/tools/testing/selftests/kvm/include/kvm_util.h @@ -144,4 +144,43 @@ allocate_kvm_dirty_log(struct kvm_userspace_memory_region *region); int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd); +#define GUEST_PORT_SYNC 0x1000 +#define GUEST_PORT_ABORT 0x1001 +#define GUEST_PORT_DONE 0x1002 + +static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) +{ + __asm__ __volatile__("in %[port], %%al" + : + : [port]"d"(port), "D"(arg0), "S"(arg1) + : "rax"); +} + +/* + * Allows to pass three arguments to the host: port is 16bit wide, + * arg0 & arg1 are 64bit wide + */ +#define GUEST_SYNC_ARGS(_port, _arg0, _arg1) \ + __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) + +#define GUEST_ASSERT(_condition) do { \ + if (!(_condition)) \ + GUEST_SYNC_ARGS(GUEST_PORT_ABORT, \ + "Failed guest assert: " \ + #_condition, __LINE__); \ + } while (0) + +#define GUEST_SYNC(stage) GUEST_SYNC_ARGS(GUEST_PORT_SYNC, "hello", stage) + +#define GUEST_DONE() GUEST_SYNC_ARGS(GUEST_PORT_DONE, 0, 0) + +struct guest_args { + uint64_t arg0; + uint64_t arg1; + uint16_t port; +} __attribute__ ((packed)); + +void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id, + struct guest_args *args); + #endif /* SELFTEST_KVM_UTIL_H */ diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index 643309d6de74..97d344303c92 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -1536,3 +1536,17 @@ void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva) { return addr_gpa2hva(vm, addr_gva2gpa(vm, gva)); } + +void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id, + struct guest_args *args) +{ + struct kvm_run *run = vcpu_state(vm, vcpu_id); + struct kvm_regs regs; + + memset(®s, 0, sizeof(regs)); + vcpu_regs_get(vm, vcpu_id, ®s); + + args->port = run->io.port; + args->arg0 = regs.rdi; + args->arg1 = regs.rsi; +} diff --git a/tools/testing/selftests/kvm/state_test.c b/tools/testing/selftests/kvm/state_test.c index ecabf25b7077..438d7b828581 100644 --- a/tools/testing/selftests/kvm/state_test.c +++ b/tools/testing/selftests/kvm/state_test.c @@ -21,28 +21,6 @@ #include "vmx.h" #define VCPU_ID 5 -#define PORT_SYNC 0x1000 -#define PORT_ABORT 0x1001 -#define PORT_DONE 0x1002 - -static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg0), "S"(arg1) - : "rax"); -} - -#define exit_to_l0(_port, _arg0, _arg1) \ - __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition, __LINE__);\ -} while (0) - -#define GUEST_SYNC(stage) \ - exit_to_l0(PORT_SYNC, "hello", stage); static bool have_nested_state; @@ -137,7 +115,7 @@ void guest_code(struct vmx_pages *vmx_pages) if (vmx_pages) l1_guest_code(vmx_pages); - exit_to_l0(PORT_DONE, 0, 0); + GUEST_DONE(); } int main(int argc, char *argv[]) @@ -178,13 +156,13 @@ int main(int argc, char *argv[]) memset(®s1, 0, sizeof(regs1)); vcpu_regs_get(vm, VCPU_ID, ®s1); switch (run->io.port) { - case PORT_ABORT: + case GUEST_PORT_ABORT: TEST_ASSERT(false, "%s at %s:%d", (const char *) regs1.rdi, __FILE__, regs1.rsi); /* NOT REACHED */ - case PORT_SYNC: + case GUEST_PORT_SYNC: break; - case PORT_DONE: + case GUEST_PORT_DONE: goto done; default: TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port); diff --git a/tools/testing/selftests/kvm/sync_regs_test.c b/tools/testing/selftests/kvm/sync_regs_test.c index eae1ece3c31b..2dbf2e1af6c6 100644 --- a/tools/testing/selftests/kvm/sync_regs_test.c +++ b/tools/testing/selftests/kvm/sync_regs_test.c @@ -22,28 +22,11 @@ #include "x86.h" #define VCPU_ID 5 -#define PORT_HOST_SYNC 0x1000 - -static void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg0), "S"(arg1) - : "rax"); -} - -#define exit_to_l0(_port, _arg0, _arg1) \ - __exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1)) - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition, 0);\ -} while (0) void guest_code(void) { for (;;) { - exit_to_l0(PORT_HOST_SYNC, "hello", 0); + GUEST_SYNC(0); asm volatile ("inc %r11"); } } diff --git a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c index fc414c284368..4ddae120a9ea 100644 --- a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c +++ b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c @@ -62,27 +62,12 @@ struct kvm_single_msr { /* The virtual machine object. */ static struct kvm_vm *vm; -#define exit_to_l0(_port, _arg) do_exit_to_l0(_port, (unsigned long) (_arg)) -static void do_exit_to_l0(uint16_t port, unsigned long arg) -{ - __asm__ __volatile__("in %[port], %%al" - : - : [port]"d"(port), "D"(arg) - : "rax"); -} - - -#define GUEST_ASSERT(_condition) do { \ - if (!(_condition)) \ - exit_to_l0(PORT_ABORT, "Failed guest assert: " #_condition); \ -} while (0) - static void check_ia32_tsc_adjust(int64_t max) { int64_t adjust; adjust = rdmsr(MSR_IA32_TSC_ADJUST); - exit_to_l0(PORT_REPORT, adjust); + GUEST_SYNC(adjust); GUEST_ASSERT(adjust <= max); } @@ -132,7 +117,7 @@ static void l1_guest_code(struct vmx_pages *vmx_pages) check_ia32_tsc_adjust(-2 * TSC_ADJUST_VALUE); - exit_to_l0(PORT_DONE, 0); + GUEST_DONE(); } void report(int64_t val) @@ -161,26 +146,26 @@ int main(int argc, char *argv[]) for (;;) { volatile struct kvm_run *run = vcpu_state(vm, VCPU_ID); - struct kvm_regs regs; + struct guest_args args; vcpu_run(vm, VCPU_ID); - vcpu_regs_get(vm, VCPU_ID, ®s); + guest_args_read(vm, VCPU_ID, &args); TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, - "Got exit_reason other than KVM_EXIT_IO: %u (%s), rip=%lx\n", + "Got exit_reason other than KVM_EXIT_IO: %u (%s)\n", run->exit_reason, - exit_reason_str(run->exit_reason), regs.rip); + exit_reason_str(run->exit_reason)); - switch (run->io.port) { - case PORT_ABORT: - TEST_ASSERT(false, "%s", (const char *) regs.rdi); + switch (args.port) { + case GUEST_PORT_ABORT: + TEST_ASSERT(false, "%s", (const char *) args.arg0); /* NOT REACHED */ - case PORT_REPORT: - report(regs.rdi); + case GUEST_PORT_SYNC: + report(args.arg1); break; - case PORT_DONE: + case GUEST_PORT_DONE: goto done; default: - TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port); + TEST_ASSERT(false, "Unknown port 0x%x.", args.port); } } -- cgit v1.2.3 From bc8eb2fe2eefbcf66c947daebce7055a4110c66c Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 22 Aug 2018 15:19:58 +0800 Subject: kvm: selftest: include the tools headers Let the kvm selftest include the tools headers, then we can start to use things there like bitmap operations. Signed-off-by: Peter Xu Signed-off-by: Paolo Bonzini --- tools/testing/selftests/kvm/Makefile | 3 ++- tools/testing/selftests/kvm/include/test_util.h | 2 -- tools/testing/selftests/kvm/lib/kvm_util.c | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index dd0e5163f01f..c367bd06a5b3 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -17,7 +17,8 @@ LIBKVM += $(LIBKVM_$(UNAME_M)) INSTALL_HDR_PATH = $(top_srcdir)/usr LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/ -CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_HDR_PATH) -Iinclude -I$( #include #include +#include #define KVM_DEV_PATH "/dev/kvm" -- cgit v1.2.3 From aee41be5933fdd1cd6fbd80b31954585e3520d98 Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 22 Aug 2018 15:19:59 +0800 Subject: kvm: selftest: pass in extra memory when create vm This information can be used to decide the size of the default memory slot, which will need to cover the extra pages with page tables. Signed-off-by: Peter Xu Signed-off-by: Paolo Bonzini --- tools/testing/selftests/kvm/cr4_cpuid_sync_test.c | 2 +- tools/testing/selftests/kvm/include/kvm_util.h | 3 ++- tools/testing/selftests/kvm/lib/x86.c | 18 ++++++++++++++++-- tools/testing/selftests/kvm/set_sregs_test.c | 2 +- tools/testing/selftests/kvm/state_test.c | 2 +- tools/testing/selftests/kvm/sync_regs_test.c | 2 +- tools/testing/selftests/kvm/vmx_tsc_adjust_test.c | 2 +- 7 files changed, 23 insertions(+), 8 deletions(-) (limited to 'tools/testing') diff --git a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c index d46b42274fd5..11ec358bf969 100644 --- a/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c +++ b/tools/testing/selftests/kvm/cr4_cpuid_sync_test.c @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) setbuf(stdout, NULL); /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); run = vcpu_state(vm, VCPU_ID); diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h index d8ca48687e35..bd06d63a8fdf 100644 --- a/tools/testing/selftests/kvm/include/kvm_util.h +++ b/tools/testing/selftests/kvm/include/kvm_util.h @@ -127,7 +127,8 @@ kvm_get_supported_cpuid_entry(uint32_t function) return kvm_get_supported_cpuid_index(function, 0); } -struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code); +struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_size, + void *guest_code); void vm_vcpu_add_default(struct kvm_vm *vm, uint32_t vcpuid, void *guest_code); typedef void (*vmx_guest_code_t)(vm_vaddr_t vmxon_vaddr, diff --git a/tools/testing/selftests/kvm/lib/x86.c b/tools/testing/selftests/kvm/lib/x86.c index e38345252df5..a3122f1949a8 100644 --- a/tools/testing/selftests/kvm/lib/x86.c +++ b/tools/testing/selftests/kvm/lib/x86.c @@ -702,6 +702,9 @@ void vcpu_set_cpuid(struct kvm_vm *vm, * * Input Args: * vcpuid - The id of the single VCPU to add to the VM. + * extra_mem_pages - The size of extra memories to add (this will + * decide how much extra space we will need to + * setup the page tables using mem slot 0) * guest_code - The vCPU's entry point * * Output Args: None @@ -709,12 +712,23 @@ void vcpu_set_cpuid(struct kvm_vm *vm, * Return: * Pointer to opaque structure that describes the created VM. */ -struct kvm_vm *vm_create_default(uint32_t vcpuid, void *guest_code) +struct kvm_vm *vm_create_default(uint32_t vcpuid, uint64_t extra_mem_pages, + void *guest_code) { struct kvm_vm *vm; + /* + * For x86 the maximum page table size for a memory region + * will be when only 4K pages are used. In that case the + * total extra size for page tables (for extra N pages) will + * be: N/512+N/512^2+N/512^3+... which is definitely smaller + * than N/512*2. + */ + uint64_t extra_pg_pages = extra_mem_pages / 512 * 2; /* Create VM */ - vm = vm_create(VM_MODE_FLAT48PG, DEFAULT_GUEST_PHY_PAGES, O_RDWR); + vm = vm_create(VM_MODE_FLAT48PG, + DEFAULT_GUEST_PHY_PAGES + extra_pg_pages, + O_RDWR); /* Setup guest code */ kvm_vm_elf_load(vm, program_invocation_name, 0, 0); diff --git a/tools/testing/selftests/kvm/set_sregs_test.c b/tools/testing/selftests/kvm/set_sregs_test.c index 090fd3f19352..881419d5746e 100644 --- a/tools/testing/selftests/kvm/set_sregs_test.c +++ b/tools/testing/selftests/kvm/set_sregs_test.c @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) setbuf(stdout, NULL); /* Create VM */ - vm = vm_create_default(VCPU_ID, NULL); + vm = vm_create_default(VCPU_ID, 0, NULL); vcpu_sregs_get(vm, VCPU_ID, &sregs); sregs.apic_base = 1 << 10; diff --git a/tools/testing/selftests/kvm/state_test.c b/tools/testing/selftests/kvm/state_test.c index 438d7b828581..900e3e9dfb9f 100644 --- a/tools/testing/selftests/kvm/state_test.c +++ b/tools/testing/selftests/kvm/state_test.c @@ -132,7 +132,7 @@ int main(int argc, char *argv[]) struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1); /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); run = vcpu_state(vm, VCPU_ID); diff --git a/tools/testing/selftests/kvm/sync_regs_test.c b/tools/testing/selftests/kvm/sync_regs_test.c index 2dbf2e1af6c6..213343e5dff9 100644 --- a/tools/testing/selftests/kvm/sync_regs_test.c +++ b/tools/testing/selftests/kvm/sync_regs_test.c @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) } /* Create VM */ - vm = vm_create_default(VCPU_ID, guest_code); + vm = vm_create_default(VCPU_ID, 0, guest_code); run = vcpu_state(vm, VCPU_ID); diff --git a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c index 4ddae120a9ea..49bcc68b0235 100644 --- a/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c +++ b/tools/testing/selftests/kvm/vmx_tsc_adjust_test.c @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) exit(KSFT_SKIP); } - vm = vm_create_default(VCPU_ID, (void *) l1_guest_code); + vm = vm_create_default(VCPU_ID, 0, (void *) l1_guest_code); vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); /* Allocate VMX pages and shared descriptors (vmx_pages). */ -- cgit v1.2.3 From 3b4cd0ff5407c14900bcda7ea4aeb43a65620deb Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 22 Aug 2018 15:20:00 +0800 Subject: kvm: selftest: add dirty logging test Test KVM dirty logging functionality. The test creates a standalone memory slot to test tracking the dirty pages since we can't really write to the default memory slot which still contains the guest ELF image. We have two threads running during the test: (1) the vcpu thread continuously dirties random guest pages by writting a iteration number to the first 8 bytes of the page (2) the host thread continuously fetches dirty logs for the testing memory region and verify each single bit of the dirty bitmap by checking against the values written onto the page Note that since the guest cannot calls the general userspace APIs like random(), it depends on the host to provide random numbers for the page indexes to dirty. Signed-off-by: Peter Xu Signed-off-by: Paolo Bonzini --- tools/testing/selftests/kvm/Makefile | 2 + tools/testing/selftests/kvm/dirty_log_test.c | 308 +++++++++++++++++++++++++ tools/testing/selftests/kvm/include/kvm_util.h | 3 + tools/testing/selftests/kvm/lib/kvm_util.c | 43 ++++ 4 files changed, 356 insertions(+) create mode 100644 tools/testing/selftests/kvm/dirty_log_test.c (limited to 'tools/testing') diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile index c367bd06a5b3..03b0f551bedf 100644 --- a/tools/testing/selftests/kvm/Makefile +++ b/tools/testing/selftests/kvm/Makefile @@ -11,6 +11,7 @@ TEST_GEN_PROGS_x86_64 += sync_regs_test TEST_GEN_PROGS_x86_64 += vmx_tsc_adjust_test TEST_GEN_PROGS_x86_64 += cr4_cpuid_sync_test TEST_GEN_PROGS_x86_64 += state_test +TEST_GEN_PROGS_x86_64 += dirty_log_test TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) LIBKVM += $(LIBKVM_$(UNAME_M)) @@ -19,6 +20,7 @@ INSTALL_HDR_PATH = $(top_srcdir)/usr LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/ LINUX_TOOL_INCLUDE = $(top_srcdir)tools/include CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_TOOL_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude -I$( +#include +#include +#include +#include +#include +#include + +#include "test_util.h" +#include "kvm_util.h" + +#define DEBUG printf + +#define VCPU_ID 1 +/* The memory slot index to track dirty pages */ +#define TEST_MEM_SLOT_INDEX 1 +/* + * GPA offset of the testing memory slot. Must be bigger than the + * default vm mem slot, which is DEFAULT_GUEST_PHY_PAGES. + */ +#define TEST_MEM_OFFSET (1ULL << 30) /* 1G */ +/* Size of the testing memory slot */ +#define TEST_MEM_PAGES (1ULL << 18) /* 1G for 4K pages */ +/* How many pages to dirty for each guest loop */ +#define TEST_PAGES_PER_LOOP 1024 +/* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */ +#define TEST_HOST_LOOP_N 32 +/* Interval for each host loop (ms) */ +#define TEST_HOST_LOOP_INTERVAL 10 + +/* + * Guest variables. We use these variables to share data between host + * and guest. There are two copies of the variables, one in host memory + * (which is unused) and one in guest memory. When the host wants to + * access these variables, it needs to call addr_gva2hva() to access the + * guest copy. + */ +uint64_t guest_random_array[TEST_PAGES_PER_LOOP]; +uint64_t guest_iteration; +uint64_t guest_page_size; + +/* + * Writes to the first byte of a random page within the testing memory + * region continuously. + */ +void guest_code(void) +{ + int i = 0; + uint64_t volatile *array = guest_random_array; + uint64_t volatile *guest_addr; + + while (true) { + for (i = 0; i < TEST_PAGES_PER_LOOP; i++) { + /* + * Write to the first 8 bytes of a random page + * on the testing memory region. + */ + guest_addr = (uint64_t *) + (TEST_MEM_OFFSET + + (array[i] % TEST_MEM_PAGES) * guest_page_size); + *guest_addr = guest_iteration; + } + /* Tell the host that we need more random numbers */ + GUEST_SYNC(1); + } +} + +/* + * Host variables. These variables should only be used by the host + * rather than the guest. + */ +bool host_quit; + +/* Points to the test VM memory region on which we track dirty logs */ +void *host_test_mem; + +/* For statistics only */ +uint64_t host_dirty_count; +uint64_t host_clear_count; +uint64_t host_track_next_count; + +/* + * 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 + * page value changed to current iteration but at the same time the + * page bit is cleared in the latest bitmap, then the system must + * report that write in the next get dirty log call. + */ +unsigned long *host_bmap_track; + +void generate_random_array(uint64_t *guest_array, uint64_t size) +{ + uint64_t i; + + for (i = 0; i < size; i++) { + guest_array[i] = random(); + } +} + +void *vcpu_worker(void *data) +{ + int ret; + uint64_t loops, *guest_array, pages_count = 0; + struct kvm_vm *vm = data; + struct kvm_run *run; + struct guest_args args; + + run = vcpu_state(vm, VCPU_ID); + + /* Retrieve the guest random array pointer and cache it */ + guest_array = addr_gva2hva(vm, (vm_vaddr_t)guest_random_array); + + DEBUG("VCPU starts\n"); + + generate_random_array(guest_array, TEST_PAGES_PER_LOOP); + + while (!READ_ONCE(host_quit)) { + /* Let the guest to dirty these random pages */ + ret = _vcpu_run(vm, VCPU_ID); + guest_args_read(vm, VCPU_ID, &args); + if (run->exit_reason == KVM_EXIT_IO && + args.port == GUEST_PORT_SYNC) { + pages_count += TEST_PAGES_PER_LOOP; + generate_random_array(guest_array, TEST_PAGES_PER_LOOP); + } else { + TEST_ASSERT(false, + "Invalid guest sync status: " + "exit_reason=%s\n", + exit_reason_str(run->exit_reason)); + } + } + + DEBUG("VCPU exits, dirtied %"PRIu64" pages\n", pages_count); + + return NULL; +} + +void vm_dirty_log_verify(unsigned long *bmap, uint64_t iteration) +{ + uint64_t page; + uint64_t volatile *value_ptr; + + for (page = 0; page < TEST_MEM_PAGES; page++) { + value_ptr = host_test_mem + page * getpagesize(); + + /* If this is a special page that we were tracking... */ + if (test_and_clear_bit(page, host_bmap_track)) { + host_track_next_count++; + TEST_ASSERT(test_bit(page, bmap), + "Page %"PRIu64" should have its dirty bit " + "set in this iteration but it is missing", + page); + } + + if (test_bit(page, bmap)) { + host_dirty_count++; + /* + * If the bit is set, the value written onto + * the corresponding page should be either the + * previous iteration number or the current one. + */ + TEST_ASSERT(*value_ptr == iteration || + *value_ptr == iteration - 1, + "Set page %"PRIu64" value %"PRIu64 + " incorrect (iteration=%"PRIu64")", + page, *value_ptr, iteration); + } else { + host_clear_count++; + /* + * If cleared, the value written can be any + * value smaller or equals to the iteration + * number. Note that the value can be exactly + * (iteration-1) if that write can happen + * like this: + * + * (1) increase loop count to "iteration-1" + * (2) write to page P happens (with value + * "iteration-1") + * (3) get dirty log for "iteration-1"; we'll + * see that page P bit is set (dirtied), + * and not set the bit in host_bmap_track + * (4) increase loop count to "iteration" + * (which is current iteration) + * (5) get dirty log for current iteration, + * we'll see that page P is cleared, with + * value "iteration-1". + */ + TEST_ASSERT(*value_ptr <= iteration, + "Clear page %"PRIu64" value %"PRIu64 + " incorrect (iteration=%"PRIu64")", + page, *value_ptr, iteration); + if (*value_ptr == iteration) { + /* + * This page is _just_ modified; it + * should report its dirtyness in the + * next run + */ + set_bit(page, host_bmap_track); + } + } + } +} + +void help(char *name) +{ + puts(""); + printf("usage: %s [-i iterations] [-I interval] [-h]\n", name); + puts(""); + printf(" -i: specify iteration counts (default: %"PRIu64")\n", + TEST_HOST_LOOP_N); + printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n", + TEST_HOST_LOOP_INTERVAL); + puts(""); + exit(0); +} + +int main(int argc, char *argv[]) +{ + pthread_t vcpu_thread; + struct kvm_vm *vm; + uint64_t volatile *psize, *iteration; + unsigned long *bmap, iterations = TEST_HOST_LOOP_N, + interval = TEST_HOST_LOOP_INTERVAL; + int opt; + + while ((opt = getopt(argc, argv, "hi:I:")) != -1) { + switch (opt) { + case 'i': + iterations = strtol(optarg, NULL, 10); + break; + case 'I': + interval = strtol(optarg, NULL, 10); + break; + case 'h': + default: + help(argv[0]); + break; + } + } + + TEST_ASSERT(iterations > 2, "Iteration must be bigger than zero\n"); + TEST_ASSERT(interval > 0, "Interval must be bigger than zero"); + + DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n", + iterations, interval); + + srandom(time(0)); + + bmap = bitmap_alloc(TEST_MEM_PAGES); + host_bmap_track = bitmap_alloc(TEST_MEM_PAGES); + + vm = vm_create_default(VCPU_ID, TEST_MEM_PAGES, guest_code); + + /* Add an extra memory slot for testing dirty logging */ + vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, + TEST_MEM_OFFSET, + TEST_MEM_SLOT_INDEX, + TEST_MEM_PAGES, + KVM_MEM_LOG_DIRTY_PAGES); + /* Cache the HVA pointer of the region */ + host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)TEST_MEM_OFFSET); + + /* Do 1:1 mapping for the dirty track memory slot */ + virt_map(vm, TEST_MEM_OFFSET, TEST_MEM_OFFSET, + TEST_MEM_PAGES * getpagesize(), 0); + + vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); + + /* Tell the guest about the page size on the system */ + psize = addr_gva2hva(vm, (vm_vaddr_t)&guest_page_size); + *psize = getpagesize(); + + /* Start the iterations */ + iteration = addr_gva2hva(vm, (vm_vaddr_t)&guest_iteration); + *iteration = 1; + + /* Start dirtying pages */ + pthread_create(&vcpu_thread, NULL, vcpu_worker, vm); + + 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); + vm_dirty_log_verify(bmap, *iteration); + (*iteration)++; + } + + /* Tell the vcpu thread to quit */ + host_quit = true; + pthread_join(vcpu_thread, NULL); + + DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), " + "track_next (%"PRIu64")\n", host_dirty_count, host_clear_count, + host_track_next_count); + + free(bmap); + free(host_bmap_track); + kvm_vm_free(vm); + + return 0; +} diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h index bd06d63a8fdf..bb5a25fb82c6 100644 --- a/tools/testing/selftests/kvm/include/kvm_util.h +++ b/tools/testing/selftests/kvm/include/kvm_util.h @@ -55,6 +55,7 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm); void kvm_vm_free(struct kvm_vm *vmp); void kvm_vm_restart(struct kvm_vm *vmp, int perm); void kvm_vm_release(struct kvm_vm *vmp); +void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log); int kvm_memcmp_hva_gva(void *hva, struct kvm_vm *vm, const vm_vaddr_t gva, size_t len); @@ -80,6 +81,8 @@ void vm_mem_region_set_flags(struct kvm_vm *vm, uint32_t slot, uint32_t flags); void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot); vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, uint32_t data_memslot, uint32_t pgd_memslot); +void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, + size_t size, uint32_t pgd_memslot); void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa); void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva); vm_paddr_t addr_hva2gpa(struct kvm_vm *vm, void *hva); diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c index fa61afffcc8d..e9ba389c48db 100644 --- a/tools/testing/selftests/kvm/lib/kvm_util.c +++ b/tools/testing/selftests/kvm/lib/kvm_util.c @@ -169,6 +169,16 @@ void kvm_vm_restart(struct kvm_vm *vmp, int perm) } } +void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log) +{ + struct kvm_dirty_log args = { .dirty_bitmap = log, .slot = slot }; + int ret; + + ret = ioctl(vm->fd, KVM_GET_DIRTY_LOG, &args); + TEST_ASSERT(ret == 0, "%s: KVM_GET_DIRTY_LOG failed: %s", + strerror(-ret)); +} + /* Userspace Memory Region Find * * Input Args: @@ -924,6 +934,39 @@ vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, return vaddr_start; } +/* + * Map a range of VM virtual address to the VM's physical address + * + * Input Args: + * vm - Virtual Machine + * vaddr - Virtuall address to map + * paddr - VM Physical Address + * size - The size of the range to map + * pgd_memslot - Memory region slot for new virtual translation tables + * + * Output Args: None + * + * Return: None + * + * Within the VM given by vm, creates a virtual translation for the + * page range starting at vaddr to the page range starting at paddr. + */ +void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr, + size_t size, uint32_t pgd_memslot) +{ + size_t page_size = vm->page_size; + size_t npages = size / page_size; + + TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow"); + TEST_ASSERT(paddr + size > paddr, "Paddr overflow"); + + while (npages--) { + virt_pg_map(vm, vaddr, paddr, pgd_memslot); + vaddr += page_size; + paddr += page_size; + } +} + /* Address VM Physical to Host Virtual * * Input Args: -- cgit v1.2.3