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 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 } 309