GNU Linux-libre 4.19.207-gnu1
[releases.git] / tools / testing / selftests / kvm / dirty_log_test.c
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               32UL
35 /* Interval for each host loop (ms) */
36 #define  TEST_HOST_LOOP_INTERVAL        10UL
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  */
45 uint64_t guest_random_array[TEST_PAGES_PER_LOOP];
46 uint64_t guest_iteration;
47 uint64_t guest_page_size;
48
49 /*
50  * Writes to the first byte of a random page within the testing memory
51  * region continuously.
52  */
53 void 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  */
79 bool host_quit;
80
81 /* Points to the test VM memory region on which we track dirty logs */
82 void *host_test_mem;
83
84 /* For statistics only */
85 uint64_t host_dirty_count;
86 uint64_t host_clear_count;
87 uint64_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  */
96 unsigned long *host_bmap_track;
97
98 void 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
107 void *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
145 void 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
211 void 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
224 int 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 }