18448ec59SBen Gardon // SPDX-License-Identifier: GPL-2.0-only 28448ec59SBen Gardon /* 38448ec59SBen Gardon * tools/testing/selftests/kvm/nx_huge_page_test.c 48448ec59SBen Gardon * 58448ec59SBen Gardon * Usage: to be run via nx_huge_page_test.sh, which does the necessary 68448ec59SBen Gardon * environment setup and teardown 78448ec59SBen Gardon * 88448ec59SBen Gardon * Copyright (C) 2022, Google LLC. 98448ec59SBen Gardon */ 108448ec59SBen Gardon 118448ec59SBen Gardon #define _GNU_SOURCE 128448ec59SBen Gardon 138448ec59SBen Gardon #include <fcntl.h> 148448ec59SBen Gardon #include <stdint.h> 158448ec59SBen Gardon #include <time.h> 168448ec59SBen Gardon 178448ec59SBen Gardon #include <test_util.h> 188448ec59SBen Gardon #include "kvm_util.h" 198448ec59SBen Gardon #include "processor.h" 208448ec59SBen Gardon 218448ec59SBen Gardon #define HPAGE_SLOT 10 228448ec59SBen Gardon #define HPAGE_GPA (4UL << 30) /* 4G prevents collision w/ slot 0 */ 238448ec59SBen Gardon #define HPAGE_GVA HPAGE_GPA /* GVA is arbitrary, so use GPA. */ 248448ec59SBen Gardon #define PAGES_PER_2MB_HUGE_PAGE 512 258448ec59SBen Gardon #define HPAGE_SLOT_NPAGES (3 * PAGES_PER_2MB_HUGE_PAGE) 268448ec59SBen Gardon 278448ec59SBen Gardon /* 288448ec59SBen Gardon * Passed by nx_huge_pages_test.sh to provide an easy warning if this test is 298448ec59SBen Gardon * being run without it. 308448ec59SBen Gardon */ 318448ec59SBen Gardon #define MAGIC_TOKEN 887563923 328448ec59SBen Gardon 338448ec59SBen Gardon /* 348448ec59SBen Gardon * x86 opcode for the return instruction. Used to call into, and then 358448ec59SBen Gardon * immediately return from, memory backed with hugepages. 368448ec59SBen Gardon */ 378448ec59SBen Gardon #define RETURN_OPCODE 0xC3 388448ec59SBen Gardon 398448ec59SBen Gardon /* Call the specified memory address. */ 408448ec59SBen Gardon static void guest_do_CALL(uint64_t target) 418448ec59SBen Gardon { 428448ec59SBen Gardon ((void (*)(void)) target)(); 438448ec59SBen Gardon } 448448ec59SBen Gardon 458448ec59SBen Gardon /* 468448ec59SBen Gardon * Exit the VM after each memory access so that the userspace component of the 478448ec59SBen Gardon * test can make assertions about the pages backing the VM. 488448ec59SBen Gardon * 498448ec59SBen Gardon * See the below for an explanation of how each access should affect the 508448ec59SBen Gardon * backing mappings. 518448ec59SBen Gardon */ 528448ec59SBen Gardon void guest_code(void) 538448ec59SBen Gardon { 548448ec59SBen Gardon uint64_t hpage_1 = HPAGE_GVA; 558448ec59SBen Gardon uint64_t hpage_2 = hpage_1 + (PAGE_SIZE * 512); 568448ec59SBen Gardon uint64_t hpage_3 = hpage_2 + (PAGE_SIZE * 512); 578448ec59SBen Gardon 588448ec59SBen Gardon READ_ONCE(*(uint64_t *)hpage_1); 598448ec59SBen Gardon GUEST_SYNC(1); 608448ec59SBen Gardon 618448ec59SBen Gardon READ_ONCE(*(uint64_t *)hpage_2); 628448ec59SBen Gardon GUEST_SYNC(2); 638448ec59SBen Gardon 648448ec59SBen Gardon guest_do_CALL(hpage_1); 658448ec59SBen Gardon GUEST_SYNC(3); 668448ec59SBen Gardon 678448ec59SBen Gardon guest_do_CALL(hpage_3); 688448ec59SBen Gardon GUEST_SYNC(4); 698448ec59SBen Gardon 708448ec59SBen Gardon READ_ONCE(*(uint64_t *)hpage_1); 718448ec59SBen Gardon GUEST_SYNC(5); 728448ec59SBen Gardon 738448ec59SBen Gardon READ_ONCE(*(uint64_t *)hpage_3); 748448ec59SBen Gardon GUEST_SYNC(6); 758448ec59SBen Gardon } 768448ec59SBen Gardon 778448ec59SBen Gardon static void check_2m_page_count(struct kvm_vm *vm, int expected_pages_2m) 788448ec59SBen Gardon { 798448ec59SBen Gardon int actual_pages_2m; 808448ec59SBen Gardon 818448ec59SBen Gardon actual_pages_2m = vm_get_stat(vm, "pages_2m"); 828448ec59SBen Gardon 838448ec59SBen Gardon TEST_ASSERT(actual_pages_2m == expected_pages_2m, 848448ec59SBen Gardon "Unexpected 2m page count. Expected %d, got %d", 858448ec59SBen Gardon expected_pages_2m, actual_pages_2m); 868448ec59SBen Gardon } 878448ec59SBen Gardon 888448ec59SBen Gardon static void check_split_count(struct kvm_vm *vm, int expected_splits) 898448ec59SBen Gardon { 908448ec59SBen Gardon int actual_splits; 918448ec59SBen Gardon 928448ec59SBen Gardon actual_splits = vm_get_stat(vm, "nx_lpage_splits"); 938448ec59SBen Gardon 948448ec59SBen Gardon TEST_ASSERT(actual_splits == expected_splits, 958448ec59SBen Gardon "Unexpected NX huge page split count. Expected %d, got %d", 968448ec59SBen Gardon expected_splits, actual_splits); 978448ec59SBen Gardon } 988448ec59SBen Gardon 998448ec59SBen Gardon static void wait_for_reclaim(int reclaim_period_ms) 1008448ec59SBen Gardon { 1018448ec59SBen Gardon long reclaim_wait_ms; 1028448ec59SBen Gardon struct timespec ts; 1038448ec59SBen Gardon 1048448ec59SBen Gardon reclaim_wait_ms = reclaim_period_ms * 5; 1058448ec59SBen Gardon ts.tv_sec = reclaim_wait_ms / 1000; 1068448ec59SBen Gardon ts.tv_nsec = (reclaim_wait_ms - (ts.tv_sec * 1000)) * 1000000; 1078448ec59SBen Gardon nanosleep(&ts, NULL); 1088448ec59SBen Gardon } 1098448ec59SBen Gardon 110b774da3fSBen Gardon void run_test(int reclaim_period_ms, bool disable_nx_huge_pages, 111b774da3fSBen Gardon bool reboot_permissions) 1128448ec59SBen Gardon { 1138448ec59SBen Gardon struct kvm_vcpu *vcpu; 1148448ec59SBen Gardon struct kvm_vm *vm; 115*458e9874SDavid Matlack uint64_t nr_bytes; 1168448ec59SBen Gardon void *hva; 117b774da3fSBen Gardon int r; 1188448ec59SBen Gardon 1198448ec59SBen Gardon vm = vm_create(1); 120b774da3fSBen Gardon 121b774da3fSBen Gardon if (disable_nx_huge_pages) { 122b774da3fSBen Gardon /* 123b774da3fSBen Gardon * Cannot run the test without NX huge pages if the kernel 124b774da3fSBen Gardon * does not support it. 125b774da3fSBen Gardon */ 126b774da3fSBen Gardon if (!kvm_check_cap(KVM_CAP_VM_DISABLE_NX_HUGE_PAGES)) 127b774da3fSBen Gardon return; 128b774da3fSBen Gardon 129b774da3fSBen Gardon r = __vm_disable_nx_huge_pages(vm); 130b774da3fSBen Gardon if (reboot_permissions) { 131b774da3fSBen Gardon TEST_ASSERT(!r, "Disabling NX huge pages should succeed if process has reboot permissions"); 132b774da3fSBen Gardon } else { 133b774da3fSBen Gardon TEST_ASSERT(r == -1 && errno == EPERM, 134b774da3fSBen Gardon "This process should not have permission to disable NX huge pages"); 135b774da3fSBen Gardon return; 136b774da3fSBen Gardon } 137b774da3fSBen Gardon } 138b774da3fSBen Gardon 1398448ec59SBen Gardon vcpu = vm_vcpu_add(vm, 0, guest_code); 1408448ec59SBen Gardon 1418448ec59SBen Gardon vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_HUGETLB, 1428448ec59SBen Gardon HPAGE_GPA, HPAGE_SLOT, 1438448ec59SBen Gardon HPAGE_SLOT_NPAGES, 0); 1448448ec59SBen Gardon 145*458e9874SDavid Matlack nr_bytes = HPAGE_SLOT_NPAGES * vm->page_size; 146*458e9874SDavid Matlack 147*458e9874SDavid Matlack /* 148*458e9874SDavid Matlack * Ensure that KVM can map HPAGE_SLOT with huge pages by mapping the 149*458e9874SDavid Matlack * region into the guest with 2MiB pages whenever TDP is disabled (i.e. 150*458e9874SDavid Matlack * whenever KVM is shadowing the guest page tables). 151*458e9874SDavid Matlack * 152*458e9874SDavid Matlack * When TDP is enabled, KVM should be able to map HPAGE_SLOT with huge 153*458e9874SDavid Matlack * pages irrespective of the guest page size, so map with 4KiB pages 154*458e9874SDavid Matlack * to test that that is the case. 155*458e9874SDavid Matlack */ 156*458e9874SDavid Matlack if (kvm_is_tdp_enabled()) 157*458e9874SDavid Matlack virt_map_level(vm, HPAGE_GVA, HPAGE_GPA, nr_bytes, PG_LEVEL_4K); 158*458e9874SDavid Matlack else 159*458e9874SDavid Matlack virt_map_level(vm, HPAGE_GVA, HPAGE_GPA, nr_bytes, PG_LEVEL_2M); 1608448ec59SBen Gardon 1618448ec59SBen Gardon hva = addr_gpa2hva(vm, HPAGE_GPA); 162*458e9874SDavid Matlack memset(hva, RETURN_OPCODE, nr_bytes); 1638448ec59SBen Gardon 1648448ec59SBen Gardon check_2m_page_count(vm, 0); 1658448ec59SBen Gardon check_split_count(vm, 0); 1668448ec59SBen Gardon 1678448ec59SBen Gardon /* 1688448ec59SBen Gardon * The guest code will first read from the first hugepage, resulting 1698448ec59SBen Gardon * in a huge page mapping being created. 1708448ec59SBen Gardon */ 1718448ec59SBen Gardon vcpu_run(vcpu); 1728448ec59SBen Gardon check_2m_page_count(vm, 1); 1738448ec59SBen Gardon check_split_count(vm, 0); 1748448ec59SBen Gardon 1758448ec59SBen Gardon /* 1768448ec59SBen Gardon * Then the guest code will read from the second hugepage, resulting 1778448ec59SBen Gardon * in another huge page mapping being created. 1788448ec59SBen Gardon */ 1798448ec59SBen Gardon vcpu_run(vcpu); 1808448ec59SBen Gardon check_2m_page_count(vm, 2); 1818448ec59SBen Gardon check_split_count(vm, 0); 1828448ec59SBen Gardon 1838448ec59SBen Gardon /* 1848448ec59SBen Gardon * Next, the guest will execute from the first huge page, causing it 1858448ec59SBen Gardon * to be remapped at 4k. 186b774da3fSBen Gardon * 187b774da3fSBen Gardon * If NX huge pages are disabled, this should have no effect. 1888448ec59SBen Gardon */ 1898448ec59SBen Gardon vcpu_run(vcpu); 190b774da3fSBen Gardon check_2m_page_count(vm, disable_nx_huge_pages ? 2 : 1); 191b774da3fSBen Gardon check_split_count(vm, disable_nx_huge_pages ? 0 : 1); 1928448ec59SBen Gardon 1938448ec59SBen Gardon /* 1948448ec59SBen Gardon * Executing from the third huge page (previously unaccessed) will 1958448ec59SBen Gardon * cause part to be mapped at 4k. 196b774da3fSBen Gardon * 197b774da3fSBen Gardon * If NX huge pages are disabled, it should be mapped at 2M. 1988448ec59SBen Gardon */ 1998448ec59SBen Gardon vcpu_run(vcpu); 200b774da3fSBen Gardon check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1); 201b774da3fSBen Gardon check_split_count(vm, disable_nx_huge_pages ? 0 : 2); 2028448ec59SBen Gardon 2038448ec59SBen Gardon /* Reading from the first huge page again should have no effect. */ 2048448ec59SBen Gardon vcpu_run(vcpu); 205b774da3fSBen Gardon check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1); 206b774da3fSBen Gardon check_split_count(vm, disable_nx_huge_pages ? 0 : 2); 2078448ec59SBen Gardon 2088448ec59SBen Gardon /* Give recovery thread time to run. */ 2098448ec59SBen Gardon wait_for_reclaim(reclaim_period_ms); 2108448ec59SBen Gardon 2118448ec59SBen Gardon /* 2128448ec59SBen Gardon * Now that the reclaimer has run, all the split pages should be gone. 213b774da3fSBen Gardon * 214b774da3fSBen Gardon * If NX huge pages are disabled, the relaimer will not run, so 215b774da3fSBen Gardon * nothing should change from here on. 2168448ec59SBen Gardon */ 217b774da3fSBen Gardon check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1); 2188448ec59SBen Gardon check_split_count(vm, 0); 2198448ec59SBen Gardon 2208448ec59SBen Gardon /* 2218448ec59SBen Gardon * The 4k mapping on hpage 3 should have been removed, so check that 2228448ec59SBen Gardon * reading from it causes a huge page mapping to be installed. 2238448ec59SBen Gardon */ 2248448ec59SBen Gardon vcpu_run(vcpu); 225b774da3fSBen Gardon check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 2); 2268448ec59SBen Gardon check_split_count(vm, 0); 2278448ec59SBen Gardon 2288448ec59SBen Gardon kvm_vm_free(vm); 229b774da3fSBen Gardon } 230b774da3fSBen Gardon 231b774da3fSBen Gardon static void help(char *name) 232b774da3fSBen Gardon { 233b774da3fSBen Gardon puts(""); 234b774da3fSBen Gardon printf("usage: %s [-h] [-p period_ms] [-t token]\n", name); 235b774da3fSBen Gardon puts(""); 236b774da3fSBen Gardon printf(" -p: The NX reclaim period in miliseconds.\n"); 237b774da3fSBen Gardon printf(" -t: The magic token to indicate environment setup is done.\n"); 238b774da3fSBen Gardon printf(" -r: The test has reboot permissions and can disable NX huge pages.\n"); 239b774da3fSBen Gardon puts(""); 240b774da3fSBen Gardon exit(0); 241b774da3fSBen Gardon } 242b774da3fSBen Gardon 243b774da3fSBen Gardon int main(int argc, char **argv) 244b774da3fSBen Gardon { 245b774da3fSBen Gardon int reclaim_period_ms = 0, token = 0, opt; 246b774da3fSBen Gardon bool reboot_permissions = false; 247b774da3fSBen Gardon 248b774da3fSBen Gardon while ((opt = getopt(argc, argv, "hp:t:r")) != -1) { 249b774da3fSBen Gardon switch (opt) { 250b774da3fSBen Gardon case 'p': 251b774da3fSBen Gardon reclaim_period_ms = atoi(optarg); 252b774da3fSBen Gardon break; 253b774da3fSBen Gardon case 't': 254b774da3fSBen Gardon token = atoi(optarg); 255b774da3fSBen Gardon break; 256b774da3fSBen Gardon case 'r': 257b774da3fSBen Gardon reboot_permissions = true; 258b774da3fSBen Gardon break; 259b774da3fSBen Gardon case 'h': 260b774da3fSBen Gardon default: 261b774da3fSBen Gardon help(argv[0]); 262b774da3fSBen Gardon break; 263b774da3fSBen Gardon } 264b774da3fSBen Gardon } 265b774da3fSBen Gardon 266b774da3fSBen Gardon if (token != MAGIC_TOKEN) { 267b774da3fSBen Gardon print_skip("This test must be run with the magic token %d.\n" 268b774da3fSBen Gardon "This is done by nx_huge_pages_test.sh, which\n" 269b774da3fSBen Gardon "also handles environment setup for the test.", 270b774da3fSBen Gardon MAGIC_TOKEN); 271b774da3fSBen Gardon exit(KSFT_SKIP); 272b774da3fSBen Gardon } 273b774da3fSBen Gardon 274b774da3fSBen Gardon if (!reclaim_period_ms) { 275b774da3fSBen Gardon print_skip("The NX reclaim period must be specified and non-zero"); 276b774da3fSBen Gardon exit(KSFT_SKIP); 277b774da3fSBen Gardon } 278b774da3fSBen Gardon 279b774da3fSBen Gardon run_test(reclaim_period_ms, false, reboot_permissions); 280b774da3fSBen Gardon run_test(reclaim_period_ms, true, reboot_permissions); 2818448ec59SBen Gardon 2828448ec59SBen Gardon return 0; 2838448ec59SBen Gardon } 2848448ec59SBen Gardon 285