aboutsummaryrefslogtreecommitdiffstats
path: root/tools/testing
diff options
context:
space:
mode:
authorPeter Xu <peterx@redhat.com>2018-08-22 03:20:00 -0400
committerPaolo Bonzini <pbonzini@redhat.com>2018-08-22 10:48:39 -0400
commit3b4cd0ff5407c14900bcda7ea4aeb43a65620deb (patch)
tree5d7368ac31741a2c026ba5e17cea582630795507 /tools/testing
parentaee41be5933fdd1cd6fbd80b31954585e3520d98 (diff)
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 <peterx@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Diffstat (limited to 'tools/testing')
-rw-r--r--tools/testing/selftests/kvm/Makefile2
-rw-r--r--tools/testing/selftests/kvm/dirty_log_test.c308
-rw-r--r--tools/testing/selftests/kvm/include/kvm_util.h3
-rw-r--r--tools/testing/selftests/kvm/lib/kvm_util.c43
4 files changed, 356 insertions, 0 deletions
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
11TEST_GEN_PROGS_x86_64 += vmx_tsc_adjust_test 11TEST_GEN_PROGS_x86_64 += vmx_tsc_adjust_test
12TEST_GEN_PROGS_x86_64 += cr4_cpuid_sync_test 12TEST_GEN_PROGS_x86_64 += cr4_cpuid_sync_test
13TEST_GEN_PROGS_x86_64 += state_test 13TEST_GEN_PROGS_x86_64 += state_test
14TEST_GEN_PROGS_x86_64 += dirty_log_test
14 15
15TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M)) 16TEST_GEN_PROGS += $(TEST_GEN_PROGS_$(UNAME_M))
16LIBKVM += $(LIBKVM_$(UNAME_M)) 17LIBKVM += $(LIBKVM_$(UNAME_M))
@@ -19,6 +20,7 @@ INSTALL_HDR_PATH = $(top_srcdir)/usr
19LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/ 20LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/
20LINUX_TOOL_INCLUDE = $(top_srcdir)tools/include 21LINUX_TOOL_INCLUDE = $(top_srcdir)tools/include
21CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_TOOL_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I.. 22CFLAGS += -O2 -g -std=gnu99 -I$(LINUX_TOOL_INCLUDE) -I$(LINUX_HDR_PATH) -Iinclude -I$(<D) -I..
23LDFLAGS += -lpthread
22 24
23# After inclusion, $(OUTPUT) is defined and 25# After inclusion, $(OUTPUT) is defined and
24# $(TEST_GEN_PROGS) starts with $(OUTPUT)/ 26# $(TEST_GEN_PROGS) starts with $(OUTPUT)/
diff --git a/tools/testing/selftests/kvm/dirty_log_test.c b/tools/testing/selftests/kvm/dirty_log_test.c
new file mode 100644
index 000000000000..0c2cdc105f96
--- /dev/null
+++ b/tools/testing/selftests/kvm/dirty_log_test.c
@@ -0,0 +1,308 @@
1// SPDX-License-Identifier: GPL-2.0
2/*
3 * KVM dirty page logging test
4 *
5 * Copyright (C) 2018, Red Hat, Inc.
6 */
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <unistd.h>
11#include <time.h>
12#include <pthread.h>
13#include <linux/bitmap.h>
14#include <linux/bitops.h>
15
16#include "test_util.h"
17#include "kvm_util.h"
18
19#define DEBUG printf
20
21#define VCPU_ID 1
22/* The memory slot index to track dirty pages */
23#define TEST_MEM_SLOT_INDEX 1
24/*
25 * GPA offset of the testing memory slot. Must be bigger than the
26 * default vm mem slot, which is DEFAULT_GUEST_PHY_PAGES.
27 */
28#define TEST_MEM_OFFSET (1ULL << 30) /* 1G */
29/* Size of the testing memory slot */
30#define TEST_MEM_PAGES (1ULL << 18) /* 1G for 4K pages */
31/* How many pages to dirty for each guest loop */
32#define TEST_PAGES_PER_LOOP 1024
33/* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */
34#define TEST_HOST_LOOP_N 32
35/* Interval for each host loop (ms) */
36#define TEST_HOST_LOOP_INTERVAL 10
37
38/*
39 * Guest variables. We use these variables to share data between host
40 * and guest. There are two copies of the variables, one in host memory
41 * (which is unused) and one in guest memory. When the host wants to
42 * access these variables, it needs to call addr_gva2hva() to access the
43 * guest copy.
44 */
45uint64_t guest_random_array[TEST_PAGES_PER_LOOP];
46uint64_t guest_iteration;
47uint64_t guest_page_size;
48
49/*
50 * Writes to the first byte of a random page within the testing memory
51 * region continuously.
52 */
53void guest_code(void)
54{
55 int i = 0;
56 uint64_t volatile *array = guest_random_array;
57 uint64_t volatile *guest_addr;
58
59 while (true) {
60 for (i = 0; i < TEST_PAGES_PER_LOOP; i++) {
61 /*
62 * Write to the first 8 bytes of a random page
63 * on the testing memory region.
64 */
65 guest_addr = (uint64_t *)
66 (TEST_MEM_OFFSET +
67 (array[i] % TEST_MEM_PAGES) * guest_page_size);
68 *guest_addr = guest_iteration;
69 }
70 /* Tell the host that we need more random numbers */
71 GUEST_SYNC(1);
72 }
73}
74
75/*
76 * Host variables. These variables should only be used by the host
77 * rather than the guest.
78 */
79bool host_quit;
80
81/* Points to the test VM memory region on which we track dirty logs */
82void *host_test_mem;
83
84/* For statistics only */
85uint64_t host_dirty_count;
86uint64_t host_clear_count;
87uint64_t host_track_next_count;
88
89/*
90 * We use this bitmap to track some pages that should have its dirty
91 * bit set in the _next_ iteration. For example, if we detected the
92 * page value changed to current iteration but at the same time the
93 * page bit is cleared in the latest bitmap, then the system must
94 * report that write in the next get dirty log call.
95 */
96unsigned long *host_bmap_track;
97
98void generate_random_array(uint64_t *guest_array, uint64_t size)
99{
100 uint64_t i;
101
102 for (i = 0; i < size; i++) {
103 guest_array[i] = random();
104 }
105}
106
107void *vcpu_worker(void *data)
108{
109 int ret;
110 uint64_t loops, *guest_array, pages_count = 0;
111 struct kvm_vm *vm = data;
112 struct kvm_run *run;
113 struct guest_args args;
114
115 run = vcpu_state(vm, VCPU_ID);
116
117 /* Retrieve the guest random array pointer and cache it */
118 guest_array = addr_gva2hva(vm, (vm_vaddr_t)guest_random_array);
119
120 DEBUG("VCPU starts\n");
121
122 generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
123
124 while (!READ_ONCE(host_quit)) {
125 /* Let the guest to dirty these random pages */
126 ret = _vcpu_run(vm, VCPU_ID);
127 guest_args_read(vm, VCPU_ID, &args);
128 if (run->exit_reason == KVM_EXIT_IO &&
129 args.port == GUEST_PORT_SYNC) {
130 pages_count += TEST_PAGES_PER_LOOP;
131 generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
132 } else {
133 TEST_ASSERT(false,
134 "Invalid guest sync status: "
135 "exit_reason=%s\n",
136 exit_reason_str(run->exit_reason));
137 }
138 }
139
140 DEBUG("VCPU exits, dirtied %"PRIu64" pages\n", pages_count);
141
142 return NULL;
143}
144
145void vm_dirty_log_verify(unsigned long *bmap, uint64_t iteration)
146{
147 uint64_t page;
148 uint64_t volatile *value_ptr;
149
150 for (page = 0; page < TEST_MEM_PAGES; page++) {
151 value_ptr = host_test_mem + page * getpagesize();
152
153 /* If this is a special page that we were tracking... */
154 if (test_and_clear_bit(page, host_bmap_track)) {
155 host_track_next_count++;
156 TEST_ASSERT(test_bit(page, bmap),
157 "Page %"PRIu64" should have its dirty bit "
158 "set in this iteration but it is missing",
159 page);
160 }
161
162 if (test_bit(page, bmap)) {
163 host_dirty_count++;
164 /*
165 * If the bit is set, the value written onto
166 * the corresponding page should be either the
167 * previous iteration number or the current one.
168 */
169 TEST_ASSERT(*value_ptr == iteration ||
170 *value_ptr == iteration - 1,
171 "Set page %"PRIu64" value %"PRIu64
172 " incorrect (iteration=%"PRIu64")",
173 page, *value_ptr, iteration);
174 } else {
175 host_clear_count++;
176 /*
177 * If cleared, the value written can be any
178 * value smaller or equals to the iteration
179 * number. Note that the value can be exactly
180 * (iteration-1) if that write can happen
181 * like this:
182 *
183 * (1) increase loop count to "iteration-1"
184 * (2) write to page P happens (with value
185 * "iteration-1")
186 * (3) get dirty log for "iteration-1"; we'll
187 * see that page P bit is set (dirtied),
188 * and not set the bit in host_bmap_track
189 * (4) increase loop count to "iteration"
190 * (which is current iteration)
191 * (5) get dirty log for current iteration,
192 * we'll see that page P is cleared, with
193 * value "iteration-1".
194 */
195 TEST_ASSERT(*value_ptr <= iteration,
196 "Clear page %"PRIu64" value %"PRIu64
197 " incorrect (iteration=%"PRIu64")",
198 page, *value_ptr, iteration);
199 if (*value_ptr == iteration) {
200 /*
201 * This page is _just_ modified; it
202 * should report its dirtyness in the
203 * next run
204 */
205 set_bit(page, host_bmap_track);
206 }
207 }
208 }
209}
210
211void help(char *name)
212{
213 puts("");
214 printf("usage: %s [-i iterations] [-I interval] [-h]\n", name);
215 puts("");
216 printf(" -i: specify iteration counts (default: %"PRIu64")\n",
217 TEST_HOST_LOOP_N);
218 printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n",
219 TEST_HOST_LOOP_INTERVAL);
220 puts("");
221 exit(0);
222}
223
224int main(int argc, char *argv[])
225{
226 pthread_t vcpu_thread;
227 struct kvm_vm *vm;
228 uint64_t volatile *psize, *iteration;
229 unsigned long *bmap, iterations = TEST_HOST_LOOP_N,
230 interval = TEST_HOST_LOOP_INTERVAL;
231 int opt;
232
233 while ((opt = getopt(argc, argv, "hi:I:")) != -1) {
234 switch (opt) {
235 case 'i':
236 iterations = strtol(optarg, NULL, 10);
237 break;
238 case 'I':
239 interval = strtol(optarg, NULL, 10);
240 break;
241 case 'h':
242 default:
243 help(argv[0]);
244 break;
245 }
246 }
247
248 TEST_ASSERT(iterations > 2, "Iteration must be bigger than zero\n");
249 TEST_ASSERT(interval > 0, "Interval must be bigger than zero");
250
251 DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n",
252 iterations, interval);
253
254 srandom(time(0));
255
256 bmap = bitmap_alloc(TEST_MEM_PAGES);
257 host_bmap_track = bitmap_alloc(TEST_MEM_PAGES);
258
259 vm = vm_create_default(VCPU_ID, TEST_MEM_PAGES, guest_code);
260
261 /* Add an extra memory slot for testing dirty logging */
262 vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
263 TEST_MEM_OFFSET,
264 TEST_MEM_SLOT_INDEX,
265 TEST_MEM_PAGES,
266 KVM_MEM_LOG_DIRTY_PAGES);
267 /* Cache the HVA pointer of the region */
268 host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)TEST_MEM_OFFSET);
269
270 /* Do 1:1 mapping for the dirty track memory slot */
271 virt_map(vm, TEST_MEM_OFFSET, TEST_MEM_OFFSET,
272 TEST_MEM_PAGES * getpagesize(), 0);
273
274 vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
275
276 /* Tell the guest about the page size on the system */
277 psize = addr_gva2hva(vm, (vm_vaddr_t)&guest_page_size);
278 *psize = getpagesize();
279
280 /* Start the iterations */
281 iteration = addr_gva2hva(vm, (vm_vaddr_t)&guest_iteration);
282 *iteration = 1;
283
284 /* Start dirtying pages */
285 pthread_create(&vcpu_thread, NULL, vcpu_worker, vm);
286
287 while (*iteration < iterations) {
288 /* Give the vcpu thread some time to dirty some pages */
289 usleep(interval * 1000);
290 kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
291 vm_dirty_log_verify(bmap, *iteration);
292 (*iteration)++;
293 }
294
295 /* Tell the vcpu thread to quit */
296 host_quit = true;
297 pthread_join(vcpu_thread, NULL);
298
299 DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), "
300 "track_next (%"PRIu64")\n", host_dirty_count, host_clear_count,
301 host_track_next_count);
302
303 free(bmap);
304 free(host_bmap_track);
305 kvm_vm_free(vm);
306
307 return 0;
308}
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);
55void kvm_vm_free(struct kvm_vm *vmp); 55void kvm_vm_free(struct kvm_vm *vmp);
56void kvm_vm_restart(struct kvm_vm *vmp, int perm); 56void kvm_vm_restart(struct kvm_vm *vmp, int perm);
57void kvm_vm_release(struct kvm_vm *vmp); 57void kvm_vm_release(struct kvm_vm *vmp);
58void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log);
58 59
59int kvm_memcmp_hva_gva(void *hva, 60int kvm_memcmp_hva_gva(void *hva,
60 struct kvm_vm *vm, const vm_vaddr_t gva, size_t len); 61 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);
80void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot); 81void vm_vcpu_add(struct kvm_vm *vm, uint32_t vcpuid, int pgd_memslot, int gdt_memslot);
81vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min, 82vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min,
82 uint32_t data_memslot, uint32_t pgd_memslot); 83 uint32_t data_memslot, uint32_t pgd_memslot);
84void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
85 size_t size, uint32_t pgd_memslot);
83void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa); 86void *addr_gpa2hva(struct kvm_vm *vm, vm_paddr_t gpa);
84void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva); 87void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva);
85vm_paddr_t addr_hva2gpa(struct kvm_vm *vm, void *hva); 88vm_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)
169 } 169 }
170} 170}
171 171
172void kvm_vm_get_dirty_log(struct kvm_vm *vm, int slot, void *log)
173{
174 struct kvm_dirty_log args = { .dirty_bitmap = log, .slot = slot };
175 int ret;
176
177 ret = ioctl(vm->fd, KVM_GET_DIRTY_LOG, &args);
178 TEST_ASSERT(ret == 0, "%s: KVM_GET_DIRTY_LOG failed: %s",
179 strerror(-ret));
180}
181
172/* Userspace Memory Region Find 182/* Userspace Memory Region Find
173 * 183 *
174 * Input Args: 184 * Input Args:
@@ -924,6 +934,39 @@ vm_vaddr_t vm_vaddr_alloc(struct kvm_vm *vm, size_t sz, vm_vaddr_t vaddr_min,
924 return vaddr_start; 934 return vaddr_start;
925} 935}
926 936
937/*
938 * Map a range of VM virtual address to the VM's physical address
939 *
940 * Input Args:
941 * vm - Virtual Machine
942 * vaddr - Virtuall address to map
943 * paddr - VM Physical Address
944 * size - The size of the range to map
945 * pgd_memslot - Memory region slot for new virtual translation tables
946 *
947 * Output Args: None
948 *
949 * Return: None
950 *
951 * Within the VM given by vm, creates a virtual translation for the
952 * page range starting at vaddr to the page range starting at paddr.
953 */
954void virt_map(struct kvm_vm *vm, uint64_t vaddr, uint64_t paddr,
955 size_t size, uint32_t pgd_memslot)
956{
957 size_t page_size = vm->page_size;
958 size_t npages = size / page_size;
959
960 TEST_ASSERT(vaddr + size > vaddr, "Vaddr overflow");
961 TEST_ASSERT(paddr + size > paddr, "Paddr overflow");
962
963 while (npages--) {
964 virt_pg_map(vm, vaddr, paddr, pgd_memslot);
965 vaddr += page_size;
966 paddr += page_size;
967 }
968}
969
927/* Address VM Physical to Host Virtual 970/* Address VM Physical to Host Virtual
928 * 971 *
929 * Input Args: 972 * Input Args: