Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * tools/testing/selftests/kvm/nx_huge_page_test.c
0004  *
0005  * Usage: to be run via nx_huge_page_test.sh, which does the necessary
0006  * environment setup and teardown
0007  *
0008  * Copyright (C) 2022, Google LLC.
0009  */
0010 
0011 #define _GNU_SOURCE
0012 
0013 #include <fcntl.h>
0014 #include <stdint.h>
0015 #include <time.h>
0016 
0017 #include <test_util.h>
0018 #include "kvm_util.h"
0019 #include "processor.h"
0020 
0021 #define HPAGE_SLOT      10
0022 #define HPAGE_GPA       (4UL << 30) /* 4G prevents collision w/ slot 0 */
0023 #define HPAGE_GVA       HPAGE_GPA /* GVA is arbitrary, so use GPA. */
0024 #define PAGES_PER_2MB_HUGE_PAGE 512
0025 #define HPAGE_SLOT_NPAGES   (3 * PAGES_PER_2MB_HUGE_PAGE)
0026 
0027 /*
0028  * Passed by nx_huge_pages_test.sh to provide an easy warning if this test is
0029  * being run without it.
0030  */
0031 #define MAGIC_TOKEN 887563923
0032 
0033 /*
0034  * x86 opcode for the return instruction. Used to call into, and then
0035  * immediately return from, memory backed with hugepages.
0036  */
0037 #define RETURN_OPCODE 0xC3
0038 
0039 /* Call the specified memory address. */
0040 static void guest_do_CALL(uint64_t target)
0041 {
0042     ((void (*)(void)) target)();
0043 }
0044 
0045 /*
0046  * Exit the VM after each memory access so that the userspace component of the
0047  * test can make assertions about the pages backing the VM.
0048  *
0049  * See the below for an explanation of how each access should affect the
0050  * backing mappings.
0051  */
0052 void guest_code(void)
0053 {
0054     uint64_t hpage_1 = HPAGE_GVA;
0055     uint64_t hpage_2 = hpage_1 + (PAGE_SIZE * 512);
0056     uint64_t hpage_3 = hpage_2 + (PAGE_SIZE * 512);
0057 
0058     READ_ONCE(*(uint64_t *)hpage_1);
0059     GUEST_SYNC(1);
0060 
0061     READ_ONCE(*(uint64_t *)hpage_2);
0062     GUEST_SYNC(2);
0063 
0064     guest_do_CALL(hpage_1);
0065     GUEST_SYNC(3);
0066 
0067     guest_do_CALL(hpage_3);
0068     GUEST_SYNC(4);
0069 
0070     READ_ONCE(*(uint64_t *)hpage_1);
0071     GUEST_SYNC(5);
0072 
0073     READ_ONCE(*(uint64_t *)hpage_3);
0074     GUEST_SYNC(6);
0075 }
0076 
0077 static void check_2m_page_count(struct kvm_vm *vm, int expected_pages_2m)
0078 {
0079     int actual_pages_2m;
0080 
0081     actual_pages_2m = vm_get_stat(vm, "pages_2m");
0082 
0083     TEST_ASSERT(actual_pages_2m == expected_pages_2m,
0084             "Unexpected 2m page count. Expected %d, got %d",
0085             expected_pages_2m, actual_pages_2m);
0086 }
0087 
0088 static void check_split_count(struct kvm_vm *vm, int expected_splits)
0089 {
0090     int actual_splits;
0091 
0092     actual_splits = vm_get_stat(vm, "nx_lpage_splits");
0093 
0094     TEST_ASSERT(actual_splits == expected_splits,
0095             "Unexpected NX huge page split count. Expected %d, got %d",
0096             expected_splits, actual_splits);
0097 }
0098 
0099 static void wait_for_reclaim(int reclaim_period_ms)
0100 {
0101     long reclaim_wait_ms;
0102     struct timespec ts;
0103 
0104     reclaim_wait_ms = reclaim_period_ms * 5;
0105     ts.tv_sec = reclaim_wait_ms / 1000;
0106     ts.tv_nsec = (reclaim_wait_ms - (ts.tv_sec * 1000)) * 1000000;
0107     nanosleep(&ts, NULL);
0108 }
0109 
0110 void run_test(int reclaim_period_ms, bool disable_nx_huge_pages,
0111           bool reboot_permissions)
0112 {
0113     struct kvm_vcpu *vcpu;
0114     struct kvm_vm *vm;
0115     void *hva;
0116     int r;
0117 
0118     vm = vm_create(1);
0119 
0120     if (disable_nx_huge_pages) {
0121         /*
0122          * Cannot run the test without NX huge pages if the kernel
0123          * does not support it.
0124          */
0125         if (!kvm_check_cap(KVM_CAP_VM_DISABLE_NX_HUGE_PAGES))
0126             return;
0127 
0128         r = __vm_disable_nx_huge_pages(vm);
0129         if (reboot_permissions) {
0130             TEST_ASSERT(!r, "Disabling NX huge pages should succeed if process has reboot permissions");
0131         } else {
0132             TEST_ASSERT(r == -1 && errno == EPERM,
0133                     "This process should not have permission to disable NX huge pages");
0134             return;
0135         }
0136     }
0137 
0138     vcpu = vm_vcpu_add(vm, 0, guest_code);
0139 
0140     vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_HUGETLB,
0141                     HPAGE_GPA, HPAGE_SLOT,
0142                     HPAGE_SLOT_NPAGES, 0);
0143 
0144     virt_map(vm, HPAGE_GVA, HPAGE_GPA, HPAGE_SLOT_NPAGES);
0145 
0146     hva = addr_gpa2hva(vm, HPAGE_GPA);
0147     memset(hva, RETURN_OPCODE, HPAGE_SLOT_NPAGES * PAGE_SIZE);
0148 
0149     check_2m_page_count(vm, 0);
0150     check_split_count(vm, 0);
0151 
0152     /*
0153      * The guest code will first read from the first hugepage, resulting
0154      * in a huge page mapping being created.
0155      */
0156     vcpu_run(vcpu);
0157     check_2m_page_count(vm, 1);
0158     check_split_count(vm, 0);
0159 
0160     /*
0161      * Then the guest code will read from the second hugepage, resulting
0162      * in another huge page mapping being created.
0163      */
0164     vcpu_run(vcpu);
0165     check_2m_page_count(vm, 2);
0166     check_split_count(vm, 0);
0167 
0168     /*
0169      * Next, the guest will execute from the first huge page, causing it
0170      * to be remapped at 4k.
0171      *
0172      * If NX huge pages are disabled, this should have no effect.
0173      */
0174     vcpu_run(vcpu);
0175     check_2m_page_count(vm, disable_nx_huge_pages ? 2 : 1);
0176     check_split_count(vm, disable_nx_huge_pages ? 0 : 1);
0177 
0178     /*
0179      * Executing from the third huge page (previously unaccessed) will
0180      * cause part to be mapped at 4k.
0181      *
0182      * If NX huge pages are disabled, it should be mapped at 2M.
0183      */
0184     vcpu_run(vcpu);
0185     check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1);
0186     check_split_count(vm, disable_nx_huge_pages ? 0 : 2);
0187 
0188     /* Reading from the first huge page again should have no effect. */
0189     vcpu_run(vcpu);
0190     check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1);
0191     check_split_count(vm, disable_nx_huge_pages ? 0 : 2);
0192 
0193     /* Give recovery thread time to run. */
0194     wait_for_reclaim(reclaim_period_ms);
0195 
0196     /*
0197      * Now that the reclaimer has run, all the split pages should be gone.
0198      *
0199      * If NX huge pages are disabled, the relaimer will not run, so
0200      * nothing should change from here on.
0201      */
0202     check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 1);
0203     check_split_count(vm, 0);
0204 
0205     /*
0206      * The 4k mapping on hpage 3 should have been removed, so check that
0207      * reading from it causes a huge page mapping to be installed.
0208      */
0209     vcpu_run(vcpu);
0210     check_2m_page_count(vm, disable_nx_huge_pages ? 3 : 2);
0211     check_split_count(vm, 0);
0212 
0213     kvm_vm_free(vm);
0214 }
0215 
0216 static void help(char *name)
0217 {
0218     puts("");
0219     printf("usage: %s [-h] [-p period_ms] [-t token]\n", name);
0220     puts("");
0221     printf(" -p: The NX reclaim period in miliseconds.\n");
0222     printf(" -t: The magic token to indicate environment setup is done.\n");
0223     printf(" -r: The test has reboot permissions and can disable NX huge pages.\n");
0224     puts("");
0225     exit(0);
0226 }
0227 
0228 int main(int argc, char **argv)
0229 {
0230     int reclaim_period_ms = 0, token = 0, opt;
0231     bool reboot_permissions = false;
0232 
0233     while ((opt = getopt(argc, argv, "hp:t:r")) != -1) {
0234         switch (opt) {
0235         case 'p':
0236             reclaim_period_ms = atoi(optarg);
0237             break;
0238         case 't':
0239             token = atoi(optarg);
0240             break;
0241         case 'r':
0242             reboot_permissions = true;
0243             break;
0244         case 'h':
0245         default:
0246             help(argv[0]);
0247             break;
0248         }
0249     }
0250 
0251     if (token != MAGIC_TOKEN) {
0252         print_skip("This test must be run with the magic token %d.\n"
0253                "This is done by nx_huge_pages_test.sh, which\n"
0254                "also handles environment setup for the test.",
0255                MAGIC_TOKEN);
0256         exit(KSFT_SKIP);
0257     }
0258 
0259     if (!reclaim_period_ms) {
0260         print_skip("The NX reclaim period must be specified and non-zero");
0261         exit(KSFT_SKIP);
0262     }
0263 
0264     run_test(reclaim_period_ms, false, reboot_permissions);
0265     run_test(reclaim_period_ms, true, reboot_permissions);
0266 
0267     return 0;
0268 }
0269