OSDN Git Service

kvm: selftests: move arch-specific files to arch-specific locations
[uclinux-h8/linux.git] / tools / testing / selftests / kvm / x86_64 / 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               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  */
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 ucall uc;
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                 if (run->exit_reason == KVM_EXIT_IO &&
128                     get_ucall(vm, VCPU_ID, &uc) == UCALL_SYNC) {
129                         pages_count += TEST_PAGES_PER_LOOP;
130                         generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
131                 } else {
132                         TEST_ASSERT(false,
133                                     "Invalid guest sync status: "
134                                     "exit_reason=%s\n",
135                                     exit_reason_str(run->exit_reason));
136                 }
137         }
138
139         DEBUG("VCPU exits, dirtied %"PRIu64" pages\n", pages_count);
140
141         return NULL;
142 }
143
144 void vm_dirty_log_verify(unsigned long *bmap, uint64_t iteration)
145 {
146         uint64_t page;
147         uint64_t volatile *value_ptr;
148
149         for (page = 0; page < TEST_MEM_PAGES; page++) {
150                 value_ptr = host_test_mem + page * getpagesize();
151
152                 /* If this is a special page that we were tracking... */
153                 if (test_and_clear_bit(page, host_bmap_track)) {
154                         host_track_next_count++;
155                         TEST_ASSERT(test_bit(page, bmap),
156                                     "Page %"PRIu64" should have its dirty bit "
157                                     "set in this iteration but it is missing",
158                                     page);
159                 }
160
161                 if (test_bit(page, bmap)) {
162                         host_dirty_count++;
163                         /*
164                          * If the bit is set, the value written onto
165                          * the corresponding page should be either the
166                          * previous iteration number or the current one.
167                          */
168                         TEST_ASSERT(*value_ptr == iteration ||
169                                     *value_ptr == iteration - 1,
170                                     "Set page %"PRIu64" value %"PRIu64
171                                     " incorrect (iteration=%"PRIu64")",
172                                     page, *value_ptr, iteration);
173                 } else {
174                         host_clear_count++;
175                         /*
176                          * If cleared, the value written can be any
177                          * value smaller or equals to the iteration
178                          * number.  Note that the value can be exactly
179                          * (iteration-1) if that write can happen
180                          * like this:
181                          *
182                          * (1) increase loop count to "iteration-1"
183                          * (2) write to page P happens (with value
184                          *     "iteration-1")
185                          * (3) get dirty log for "iteration-1"; we'll
186                          *     see that page P bit is set (dirtied),
187                          *     and not set the bit in host_bmap_track
188                          * (4) increase loop count to "iteration"
189                          *     (which is current iteration)
190                          * (5) get dirty log for current iteration,
191                          *     we'll see that page P is cleared, with
192                          *     value "iteration-1".
193                          */
194                         TEST_ASSERT(*value_ptr <= iteration,
195                                     "Clear page %"PRIu64" value %"PRIu64
196                                     " incorrect (iteration=%"PRIu64")",
197                                     page, *value_ptr, iteration);
198                         if (*value_ptr == iteration) {
199                                 /*
200                                  * This page is _just_ modified; it
201                                  * should report its dirtyness in the
202                                  * next run
203                                  */
204                                 set_bit(page, host_bmap_track);
205                         }
206                 }
207         }
208 }
209
210 void help(char *name)
211 {
212         puts("");
213         printf("usage: %s [-i iterations] [-I interval] [-h]\n", name);
214         puts("");
215         printf(" -i: specify iteration counts (default: %"PRIu64")\n",
216                TEST_HOST_LOOP_N);
217         printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n",
218                TEST_HOST_LOOP_INTERVAL);
219         puts("");
220         exit(0);
221 }
222
223 int main(int argc, char *argv[])
224 {
225         pthread_t vcpu_thread;
226         struct kvm_vm *vm;
227         uint64_t volatile *psize, *iteration;
228         unsigned long *bmap, iterations = TEST_HOST_LOOP_N,
229             interval = TEST_HOST_LOOP_INTERVAL;
230         int opt;
231
232         while ((opt = getopt(argc, argv, "hi:I:")) != -1) {
233                 switch (opt) {
234                 case 'i':
235                         iterations = strtol(optarg, NULL, 10);
236                         break;
237                 case 'I':
238                         interval = strtol(optarg, NULL, 10);
239                         break;
240                 case 'h':
241                 default:
242                         help(argv[0]);
243                         break;
244                 }
245         }
246
247         TEST_ASSERT(iterations > 2, "Iteration must be bigger than zero\n");
248         TEST_ASSERT(interval > 0, "Interval must be bigger than zero");
249
250         DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n",
251               iterations, interval);
252
253         srandom(time(0));
254
255         bmap = bitmap_alloc(TEST_MEM_PAGES);
256         host_bmap_track = bitmap_alloc(TEST_MEM_PAGES);
257
258         vm = vm_create_default(VCPU_ID, TEST_MEM_PAGES, guest_code);
259
260         /* Add an extra memory slot for testing dirty logging */
261         vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
262                                     TEST_MEM_OFFSET,
263                                     TEST_MEM_SLOT_INDEX,
264                                     TEST_MEM_PAGES,
265                                     KVM_MEM_LOG_DIRTY_PAGES);
266         /* Cache the HVA pointer of the region */
267         host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)TEST_MEM_OFFSET);
268
269         /* Do 1:1 mapping for the dirty track memory slot */
270         virt_map(vm, TEST_MEM_OFFSET, TEST_MEM_OFFSET,
271                  TEST_MEM_PAGES * getpagesize(), 0);
272
273         vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
274
275         /* Tell the guest about the page size on the system */
276         psize = addr_gva2hva(vm, (vm_vaddr_t)&guest_page_size);
277         *psize = getpagesize();
278
279         /* Start the iterations */
280         iteration = addr_gva2hva(vm, (vm_vaddr_t)&guest_iteration);
281         *iteration = 1;
282
283         /* Start dirtying pages */
284         pthread_create(&vcpu_thread, NULL, vcpu_worker, vm);
285
286         while (*iteration < iterations) {
287                 /* Give the vcpu thread some time to dirty some pages */
288                 usleep(interval * 1000);
289                 kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
290                 vm_dirty_log_verify(bmap, *iteration);
291                 (*iteration)++;
292         }
293
294         /* Tell the vcpu thread to quit */
295         host_quit = true;
296         pthread_join(vcpu_thread, NULL);
297
298         DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), "
299               "track_next (%"PRIu64")\n", host_dirty_count, host_clear_count,
300               host_track_next_count);
301
302         free(bmap);
303         free(host_bmap_track);
304         kvm_vm_free(vm);
305
306         return 0;
307 }