dirty_log_test.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // SPDX-License-Identifier: GPL-2.0
  2. /*
  3. * KVM dirty page logging test
  4. *
  5. * Copyright (C) 2018, Red Hat, Inc.
  6. */
  7. #define _GNU_SOURCE /* for program_invocation_name */
  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. #include "test_util.h"
  16. #include "kvm_util.h"
  17. #include "processor.h"
  18. #define DEBUG printf
  19. #define VCPU_ID 1
  20. /* The memory slot index to track dirty pages */
  21. #define TEST_MEM_SLOT_INDEX 1
  22. /* Default guest test memory offset, 1G */
  23. #define DEFAULT_GUEST_TEST_MEM 0x40000000
  24. /* How many pages to dirty for each guest loop */
  25. #define TEST_PAGES_PER_LOOP 1024
  26. /* How many host loops to run (one KVM_GET_DIRTY_LOG for each loop) */
  27. #define TEST_HOST_LOOP_N 32UL
  28. /* Interval for each host loop (ms) */
  29. #define TEST_HOST_LOOP_INTERVAL 10UL
  30. /*
  31. * Guest/Host shared variables. Ensure addr_gva2hva() and/or
  32. * sync_global_to/from_guest() are used when accessing from
  33. * the host. READ/WRITE_ONCE() should also be used with anything
  34. * that may change.
  35. */
  36. static uint64_t host_page_size;
  37. static uint64_t guest_page_size;
  38. static uint64_t guest_num_pages;
  39. static uint64_t random_array[TEST_PAGES_PER_LOOP];
  40. static uint64_t iteration;
  41. /*
  42. * GPA offset of the testing memory slot. Must be bigger than
  43. * DEFAULT_GUEST_PHY_PAGES.
  44. */
  45. static uint64_t guest_test_mem = DEFAULT_GUEST_TEST_MEM;
  46. /*
  47. * Continuously write to the first 8 bytes of a random pages within
  48. * the testing memory region.
  49. */
  50. static void guest_code(void)
  51. {
  52. int i;
  53. while (true) {
  54. for (i = 0; i < TEST_PAGES_PER_LOOP; i++) {
  55. uint64_t addr = guest_test_mem;
  56. addr += (READ_ONCE(random_array[i]) % guest_num_pages)
  57. * guest_page_size;
  58. addr &= ~(host_page_size - 1);
  59. *(uint64_t *)addr = READ_ONCE(iteration);
  60. }
  61. /* Tell the host that we need more random numbers */
  62. GUEST_SYNC(1);
  63. }
  64. }
  65. /* Host variables */
  66. static bool host_quit;
  67. /* Points to the test VM memory region on which we track dirty logs */
  68. static void *host_test_mem;
  69. static uint64_t host_num_pages;
  70. /* For statistics only */
  71. static uint64_t host_dirty_count;
  72. static uint64_t host_clear_count;
  73. static uint64_t host_track_next_count;
  74. /*
  75. * We use this bitmap to track some pages that should have its dirty
  76. * bit set in the _next_ iteration. For example, if we detected the
  77. * page value changed to current iteration but at the same time the
  78. * page bit is cleared in the latest bitmap, then the system must
  79. * report that write in the next get dirty log call.
  80. */
  81. static unsigned long *host_bmap_track;
  82. static void generate_random_array(uint64_t *guest_array, uint64_t size)
  83. {
  84. uint64_t i;
  85. for (i = 0; i < size; i++)
  86. guest_array[i] = random();
  87. }
  88. static void *vcpu_worker(void *data)
  89. {
  90. int ret;
  91. struct kvm_vm *vm = data;
  92. uint64_t *guest_array;
  93. uint64_t pages_count = 0;
  94. struct kvm_run *run;
  95. struct ucall uc;
  96. run = vcpu_state(vm, VCPU_ID);
  97. guest_array = addr_gva2hva(vm, (vm_vaddr_t)random_array);
  98. generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
  99. while (!READ_ONCE(host_quit)) {
  100. /* Let the guest dirty the random pages */
  101. ret = _vcpu_run(vm, VCPU_ID);
  102. if (get_ucall(vm, VCPU_ID, &uc) == UCALL_SYNC) {
  103. pages_count += TEST_PAGES_PER_LOOP;
  104. generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
  105. } else {
  106. TEST_ASSERT(false,
  107. "Invalid guest sync status: "
  108. "exit_reason=%s\n",
  109. exit_reason_str(run->exit_reason));
  110. }
  111. }
  112. DEBUG("Dirtied %"PRIu64" pages\n", pages_count);
  113. return NULL;
  114. }
  115. static void vm_dirty_log_verify(unsigned long *bmap)
  116. {
  117. uint64_t page;
  118. uint64_t *value_ptr;
  119. uint64_t step = host_page_size >= guest_page_size ? 1 :
  120. guest_page_size / host_page_size;
  121. for (page = 0; page < host_num_pages; page += step) {
  122. value_ptr = host_test_mem + page * host_page_size;
  123. /* If this is a special page that we were tracking... */
  124. if (test_and_clear_bit(page, host_bmap_track)) {
  125. host_track_next_count++;
  126. TEST_ASSERT(test_bit(page, bmap),
  127. "Page %"PRIu64" should have its dirty bit "
  128. "set in this iteration but it is missing",
  129. page);
  130. }
  131. if (test_bit(page, bmap)) {
  132. host_dirty_count++;
  133. /*
  134. * If the bit is set, the value written onto
  135. * the corresponding page should be either the
  136. * previous iteration number or the current one.
  137. */
  138. TEST_ASSERT(*value_ptr == iteration ||
  139. *value_ptr == iteration - 1,
  140. "Set page %"PRIu64" value %"PRIu64
  141. " incorrect (iteration=%"PRIu64")",
  142. page, *value_ptr, iteration);
  143. } else {
  144. host_clear_count++;
  145. /*
  146. * If cleared, the value written can be any
  147. * value smaller or equals to the iteration
  148. * number. Note that the value can be exactly
  149. * (iteration-1) if that write can happen
  150. * like this:
  151. *
  152. * (1) increase loop count to "iteration-1"
  153. * (2) write to page P happens (with value
  154. * "iteration-1")
  155. * (3) get dirty log for "iteration-1"; we'll
  156. * see that page P bit is set (dirtied),
  157. * and not set the bit in host_bmap_track
  158. * (4) increase loop count to "iteration"
  159. * (which is current iteration)
  160. * (5) get dirty log for current iteration,
  161. * we'll see that page P is cleared, with
  162. * value "iteration-1".
  163. */
  164. TEST_ASSERT(*value_ptr <= iteration,
  165. "Clear page %"PRIu64" value %"PRIu64
  166. " incorrect (iteration=%"PRIu64")",
  167. page, *value_ptr, iteration);
  168. if (*value_ptr == iteration) {
  169. /*
  170. * This page is _just_ modified; it
  171. * should report its dirtyness in the
  172. * next run
  173. */
  174. set_bit(page, host_bmap_track);
  175. }
  176. }
  177. }
  178. }
  179. static struct kvm_vm *create_vm(enum vm_guest_mode mode, uint32_t vcpuid,
  180. uint64_t extra_mem_pages, void *guest_code)
  181. {
  182. struct kvm_vm *vm;
  183. uint64_t extra_pg_pages = extra_mem_pages / 512 * 2;
  184. vm = vm_create(mode, DEFAULT_GUEST_PHY_PAGES + extra_pg_pages, O_RDWR);
  185. kvm_vm_elf_load(vm, program_invocation_name, 0, 0);
  186. #ifdef __x86_64__
  187. vm_create_irqchip(vm);
  188. #endif
  189. vm_vcpu_add_default(vm, vcpuid, guest_code);
  190. return vm;
  191. }
  192. static void run_test(enum vm_guest_mode mode, unsigned long iterations,
  193. unsigned long interval, bool top_offset)
  194. {
  195. unsigned int guest_pa_bits, guest_page_shift;
  196. pthread_t vcpu_thread;
  197. struct kvm_vm *vm;
  198. uint64_t max_gfn;
  199. unsigned long *bmap;
  200. switch (mode) {
  201. case VM_MODE_P52V48_4K:
  202. guest_pa_bits = 52;
  203. guest_page_shift = 12;
  204. break;
  205. case VM_MODE_P52V48_64K:
  206. guest_pa_bits = 52;
  207. guest_page_shift = 16;
  208. break;
  209. case VM_MODE_P40V48_4K:
  210. guest_pa_bits = 40;
  211. guest_page_shift = 12;
  212. break;
  213. case VM_MODE_P40V48_64K:
  214. guest_pa_bits = 40;
  215. guest_page_shift = 16;
  216. break;
  217. default:
  218. TEST_ASSERT(false, "Unknown guest mode, mode: 0x%x", mode);
  219. }
  220. DEBUG("Testing guest mode: %s\n", vm_guest_mode_string(mode));
  221. max_gfn = (1ul << (guest_pa_bits - guest_page_shift)) - 1;
  222. guest_page_size = (1ul << guest_page_shift);
  223. /* 1G of guest page sized pages */
  224. guest_num_pages = (1ul << (30 - guest_page_shift));
  225. host_page_size = getpagesize();
  226. host_num_pages = (guest_num_pages * guest_page_size) / host_page_size +
  227. !!((guest_num_pages * guest_page_size) % host_page_size);
  228. if (top_offset) {
  229. guest_test_mem = (max_gfn - guest_num_pages) * guest_page_size;
  230. guest_test_mem &= ~(host_page_size - 1);
  231. }
  232. DEBUG("guest test mem offset: 0x%lx\n", guest_test_mem);
  233. bmap = bitmap_alloc(host_num_pages);
  234. host_bmap_track = bitmap_alloc(host_num_pages);
  235. vm = create_vm(mode, VCPU_ID, guest_num_pages, guest_code);
  236. /* Add an extra memory slot for testing dirty logging */
  237. vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
  238. guest_test_mem,
  239. TEST_MEM_SLOT_INDEX,
  240. guest_num_pages,
  241. KVM_MEM_LOG_DIRTY_PAGES);
  242. /* Do 1:1 mapping for the dirty track memory slot */
  243. virt_map(vm, guest_test_mem, guest_test_mem,
  244. guest_num_pages * guest_page_size, 0);
  245. /* Cache the HVA pointer of the region */
  246. host_test_mem = addr_gpa2hva(vm, (vm_paddr_t)guest_test_mem);
  247. #ifdef __x86_64__
  248. vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
  249. #endif
  250. #ifdef __aarch64__
  251. ucall_init(vm, UCALL_MMIO, NULL);
  252. #endif
  253. /* Export the shared variables to the guest */
  254. sync_global_to_guest(vm, host_page_size);
  255. sync_global_to_guest(vm, guest_page_size);
  256. sync_global_to_guest(vm, guest_test_mem);
  257. sync_global_to_guest(vm, guest_num_pages);
  258. /* Start the iterations */
  259. iteration = 1;
  260. sync_global_to_guest(vm, iteration);
  261. host_quit = false;
  262. host_dirty_count = 0;
  263. host_clear_count = 0;
  264. host_track_next_count = 0;
  265. pthread_create(&vcpu_thread, NULL, vcpu_worker, vm);
  266. while (iteration < iterations) {
  267. /* Give the vcpu thread some time to dirty some pages */
  268. usleep(interval * 1000);
  269. kvm_vm_get_dirty_log(vm, TEST_MEM_SLOT_INDEX, bmap);
  270. vm_dirty_log_verify(bmap);
  271. iteration++;
  272. sync_global_to_guest(vm, iteration);
  273. }
  274. /* Tell the vcpu thread to quit */
  275. host_quit = true;
  276. pthread_join(vcpu_thread, NULL);
  277. DEBUG("Total bits checked: dirty (%"PRIu64"), clear (%"PRIu64"), "
  278. "track_next (%"PRIu64")\n", host_dirty_count, host_clear_count,
  279. host_track_next_count);
  280. free(bmap);
  281. free(host_bmap_track);
  282. ucall_uninit(vm);
  283. kvm_vm_free(vm);
  284. }
  285. static struct vm_guest_modes {
  286. enum vm_guest_mode mode;
  287. bool supported;
  288. bool enabled;
  289. } vm_guest_modes[NUM_VM_MODES] = {
  290. #if defined(__x86_64__)
  291. { VM_MODE_P52V48_4K, 1, 1, },
  292. { VM_MODE_P52V48_64K, 0, 0, },
  293. { VM_MODE_P40V48_4K, 0, 0, },
  294. { VM_MODE_P40V48_64K, 0, 0, },
  295. #elif defined(__aarch64__)
  296. { VM_MODE_P52V48_4K, 0, 0, },
  297. { VM_MODE_P52V48_64K, 0, 0, },
  298. { VM_MODE_P40V48_4K, 1, 1, },
  299. { VM_MODE_P40V48_64K, 1, 1, },
  300. #endif
  301. };
  302. static void help(char *name)
  303. {
  304. int i;
  305. puts("");
  306. printf("usage: %s [-h] [-i iterations] [-I interval] "
  307. "[-o offset] [-t] [-m mode]\n", name);
  308. puts("");
  309. printf(" -i: specify iteration counts (default: %"PRIu64")\n",
  310. TEST_HOST_LOOP_N);
  311. printf(" -I: specify interval in ms (default: %"PRIu64" ms)\n",
  312. TEST_HOST_LOOP_INTERVAL);
  313. printf(" -o: guest test memory offset (default: 0x%lx)\n",
  314. DEFAULT_GUEST_TEST_MEM);
  315. printf(" -t: map guest test memory at the top of the allowed "
  316. "physical address range\n");
  317. printf(" -m: specify the guest mode ID to test "
  318. "(default: test all supported modes)\n"
  319. " This option may be used multiple times.\n"
  320. " Guest mode IDs:\n");
  321. for (i = 0; i < NUM_VM_MODES; ++i) {
  322. printf(" %d: %s%s\n",
  323. vm_guest_modes[i].mode,
  324. vm_guest_mode_string(vm_guest_modes[i].mode),
  325. vm_guest_modes[i].supported ? " (supported)" : "");
  326. }
  327. puts("");
  328. exit(0);
  329. }
  330. int main(int argc, char *argv[])
  331. {
  332. unsigned long iterations = TEST_HOST_LOOP_N;
  333. unsigned long interval = TEST_HOST_LOOP_INTERVAL;
  334. bool mode_selected = false;
  335. bool top_offset = false;
  336. unsigned int mode;
  337. int opt, i;
  338. while ((opt = getopt(argc, argv, "hi:I:o:tm:")) != -1) {
  339. switch (opt) {
  340. case 'i':
  341. iterations = strtol(optarg, NULL, 10);
  342. break;
  343. case 'I':
  344. interval = strtol(optarg, NULL, 10);
  345. break;
  346. case 'o':
  347. guest_test_mem = strtoull(optarg, NULL, 0);
  348. break;
  349. case 't':
  350. top_offset = true;
  351. break;
  352. case 'm':
  353. if (!mode_selected) {
  354. for (i = 0; i < NUM_VM_MODES; ++i)
  355. vm_guest_modes[i].enabled = 0;
  356. mode_selected = true;
  357. }
  358. mode = strtoul(optarg, NULL, 10);
  359. TEST_ASSERT(mode < NUM_VM_MODES,
  360. "Guest mode ID %d too big", mode);
  361. vm_guest_modes[mode].enabled = 1;
  362. break;
  363. case 'h':
  364. default:
  365. help(argv[0]);
  366. break;
  367. }
  368. }
  369. TEST_ASSERT(iterations > 2, "Iterations must be greater than two");
  370. TEST_ASSERT(interval > 0, "Interval must be greater than zero");
  371. TEST_ASSERT(!top_offset || guest_test_mem == DEFAULT_GUEST_TEST_MEM,
  372. "Cannot use both -o [offset] and -t at the same time");
  373. DEBUG("Test iterations: %"PRIu64", interval: %"PRIu64" (ms)\n",
  374. iterations, interval);
  375. srandom(time(0));
  376. for (i = 0; i < NUM_VM_MODES; ++i) {
  377. if (!vm_guest_modes[i].enabled)
  378. continue;
  379. TEST_ASSERT(vm_guest_modes[i].supported,
  380. "Guest mode ID %d (%s) not supported.",
  381. vm_guest_modes[i].mode,
  382. vm_guest_mode_string(vm_guest_modes[i].mode));
  383. run_test(vm_guest_modes[i].mode, iterations, interval, top_offset);
  384. }
  385. return 0;
  386. }