-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KVM: selftests: Add NX huge pages test
There's currently no test coverage of NX hugepages in KVM selftests, so add a basic test to ensure that the feature works as intended. The test creates a VM with a data slot backed with huge pages. The memory in the data slot is filled with op-codes for the return instruction. The guest then executes a series of accesses on the memory, some reads, some instruction fetches. After each operation, the guest exits and the test performs some checks on the backing page counts to ensure that NX page splitting an reclaim work as expected. Reviewed-by: David Matlack <dmatlack@google.com> Signed-off-by: Ben Gardon <bgardon@google.com> Message-Id: <20220613212523.3436117-7-bgardon@google.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
- Loading branch information
Ben Gardon
authored and
Paolo Bonzini
committed
Jun 24, 2022
1 parent
084cc29
commit 8448ec5
Showing
6 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
tools/testing/selftests/kvm/x86_64/nx_huge_pages_test.c
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
// SPDX-License-Identifier: GPL-2.0-only | ||
/* | ||
* tools/testing/selftests/kvm/nx_huge_page_test.c | ||
* | ||
* Usage: to be run via nx_huge_page_test.sh, which does the necessary | ||
* environment setup and teardown | ||
* | ||
* Copyright (C) 2022, Google LLC. | ||
*/ | ||
|
||
#define _GNU_SOURCE | ||
|
||
#include <fcntl.h> | ||
#include <stdint.h> | ||
#include <time.h> | ||
|
||
#include <test_util.h> | ||
#include "kvm_util.h" | ||
#include "processor.h" | ||
|
||
#define HPAGE_SLOT 10 | ||
#define HPAGE_GPA (4UL << 30) /* 4G prevents collision w/ slot 0 */ | ||
#define HPAGE_GVA HPAGE_GPA /* GVA is arbitrary, so use GPA. */ | ||
#define PAGES_PER_2MB_HUGE_PAGE 512 | ||
#define HPAGE_SLOT_NPAGES (3 * PAGES_PER_2MB_HUGE_PAGE) | ||
|
||
/* | ||
* Passed by nx_huge_pages_test.sh to provide an easy warning if this test is | ||
* being run without it. | ||
*/ | ||
#define MAGIC_TOKEN 887563923 | ||
|
||
/* | ||
* x86 opcode for the return instruction. Used to call into, and then | ||
* immediately return from, memory backed with hugepages. | ||
*/ | ||
#define RETURN_OPCODE 0xC3 | ||
|
||
/* Call the specified memory address. */ | ||
static void guest_do_CALL(uint64_t target) | ||
{ | ||
((void (*)(void)) target)(); | ||
} | ||
|
||
/* | ||
* Exit the VM after each memory access so that the userspace component of the | ||
* test can make assertions about the pages backing the VM. | ||
* | ||
* See the below for an explanation of how each access should affect the | ||
* backing mappings. | ||
*/ | ||
void guest_code(void) | ||
{ | ||
uint64_t hpage_1 = HPAGE_GVA; | ||
uint64_t hpage_2 = hpage_1 + (PAGE_SIZE * 512); | ||
uint64_t hpage_3 = hpage_2 + (PAGE_SIZE * 512); | ||
|
||
READ_ONCE(*(uint64_t *)hpage_1); | ||
GUEST_SYNC(1); | ||
|
||
READ_ONCE(*(uint64_t *)hpage_2); | ||
GUEST_SYNC(2); | ||
|
||
guest_do_CALL(hpage_1); | ||
GUEST_SYNC(3); | ||
|
||
guest_do_CALL(hpage_3); | ||
GUEST_SYNC(4); | ||
|
||
READ_ONCE(*(uint64_t *)hpage_1); | ||
GUEST_SYNC(5); | ||
|
||
READ_ONCE(*(uint64_t *)hpage_3); | ||
GUEST_SYNC(6); | ||
} | ||
|
||
static void check_2m_page_count(struct kvm_vm *vm, int expected_pages_2m) | ||
{ | ||
int actual_pages_2m; | ||
|
||
actual_pages_2m = vm_get_stat(vm, "pages_2m"); | ||
|
||
TEST_ASSERT(actual_pages_2m == expected_pages_2m, | ||
"Unexpected 2m page count. Expected %d, got %d", | ||
expected_pages_2m, actual_pages_2m); | ||
} | ||
|
||
static void check_split_count(struct kvm_vm *vm, int expected_splits) | ||
{ | ||
int actual_splits; | ||
|
||
actual_splits = vm_get_stat(vm, "nx_lpage_splits"); | ||
|
||
TEST_ASSERT(actual_splits == expected_splits, | ||
"Unexpected NX huge page split count. Expected %d, got %d", | ||
expected_splits, actual_splits); | ||
} | ||
|
||
static void wait_for_reclaim(int reclaim_period_ms) | ||
{ | ||
long reclaim_wait_ms; | ||
struct timespec ts; | ||
|
||
reclaim_wait_ms = reclaim_period_ms * 5; | ||
ts.tv_sec = reclaim_wait_ms / 1000; | ||
ts.tv_nsec = (reclaim_wait_ms - (ts.tv_sec * 1000)) * 1000000; | ||
nanosleep(&ts, NULL); | ||
} | ||
|
||
static void help(char *name) | ||
{ | ||
puts(""); | ||
printf("usage: %s [-h] [-p period_ms] [-t token]\n", name); | ||
puts(""); | ||
printf(" -p: The NX reclaim period in miliseconds.\n"); | ||
printf(" -t: The magic token to indicate environment setup is done.\n"); | ||
puts(""); | ||
exit(0); | ||
} | ||
|
||
int main(int argc, char **argv) | ||
{ | ||
int reclaim_period_ms = 0, token = 0, opt; | ||
struct kvm_vcpu *vcpu; | ||
struct kvm_vm *vm; | ||
void *hva; | ||
|
||
while ((opt = getopt(argc, argv, "hp:t:")) != -1) { | ||
switch (opt) { | ||
case 'p': | ||
reclaim_period_ms = atoi(optarg); | ||
break; | ||
case 't': | ||
token = atoi(optarg); | ||
break; | ||
case 'h': | ||
default: | ||
help(argv[0]); | ||
break; | ||
} | ||
} | ||
|
||
if (token != MAGIC_TOKEN) { | ||
print_skip("This test must be run with the magic token %d.\n" | ||
"This is done by nx_huge_pages_test.sh, which\n" | ||
"also handles environment setup for the test.", | ||
MAGIC_TOKEN); | ||
exit(KSFT_SKIP); | ||
} | ||
|
||
if (!reclaim_period_ms) { | ||
print_skip("The NX reclaim period must be specified and non-zero"); | ||
exit(KSFT_SKIP); | ||
} | ||
|
||
vm = vm_create(1); | ||
vcpu = vm_vcpu_add(vm, 0, guest_code); | ||
|
||
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS_HUGETLB, | ||
HPAGE_GPA, HPAGE_SLOT, | ||
HPAGE_SLOT_NPAGES, 0); | ||
|
||
virt_map(vm, HPAGE_GVA, HPAGE_GPA, HPAGE_SLOT_NPAGES); | ||
|
||
hva = addr_gpa2hva(vm, HPAGE_GPA); | ||
memset(hva, RETURN_OPCODE, HPAGE_SLOT_NPAGES * PAGE_SIZE); | ||
|
||
check_2m_page_count(vm, 0); | ||
check_split_count(vm, 0); | ||
|
||
/* | ||
* The guest code will first read from the first hugepage, resulting | ||
* in a huge page mapping being created. | ||
*/ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 1); | ||
check_split_count(vm, 0); | ||
|
||
/* | ||
* Then the guest code will read from the second hugepage, resulting | ||
* in another huge page mapping being created. | ||
*/ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 2); | ||
check_split_count(vm, 0); | ||
|
||
/* | ||
* Next, the guest will execute from the first huge page, causing it | ||
* to be remapped at 4k. | ||
*/ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 1); | ||
check_split_count(vm, 1); | ||
|
||
/* | ||
* Executing from the third huge page (previously unaccessed) will | ||
* cause part to be mapped at 4k. | ||
*/ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 1); | ||
check_split_count(vm, 2); | ||
|
||
/* Reading from the first huge page again should have no effect. */ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 1); | ||
check_split_count(vm, 2); | ||
|
||
/* Give recovery thread time to run. */ | ||
wait_for_reclaim(reclaim_period_ms); | ||
|
||
/* | ||
* Now that the reclaimer has run, all the split pages should be gone. | ||
*/ | ||
check_2m_page_count(vm, 1); | ||
check_split_count(vm, 0); | ||
|
||
/* | ||
* The 4k mapping on hpage 3 should have been removed, so check that | ||
* reading from it causes a huge page mapping to be installed. | ||
*/ | ||
vcpu_run(vcpu); | ||
check_2m_page_count(vm, 2); | ||
check_split_count(vm, 0); | ||
|
||
kvm_vm_free(vm); | ||
|
||
return 0; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#!/bin/bash | ||
# SPDX-License-Identifier: GPL-2.0-only */ | ||
# | ||
# Wrapper script which performs setup and cleanup for nx_huge_pages_test. | ||
# Makes use of root privileges to set up huge pages and KVM module parameters. | ||
# | ||
# tools/testing/selftests/kvm/nx_huge_page_test.sh | ||
# Copyright (C) 2022, Google LLC. | ||
|
||
set -e | ||
|
||
NX_HUGE_PAGES=$(cat /sys/module/kvm/parameters/nx_huge_pages) | ||
NX_HUGE_PAGES_RECOVERY_RATIO=$(cat /sys/module/kvm/parameters/nx_huge_pages_recovery_ratio) | ||
NX_HUGE_PAGES_RECOVERY_PERIOD=$(cat /sys/module/kvm/parameters/nx_huge_pages_recovery_period_ms) | ||
HUGE_PAGES=$(cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages) | ||
|
||
set +e | ||
|
||
function sudo_echo () { | ||
echo "$1" | sudo tee -a "$2" > /dev/null | ||
} | ||
|
||
( | ||
set -e | ||
|
||
sudo_echo 1 /sys/module/kvm/parameters/nx_huge_pages | ||
sudo_echo 1 /sys/module/kvm/parameters/nx_huge_pages_recovery_ratio | ||
sudo_echo 100 /sys/module/kvm/parameters/nx_huge_pages_recovery_period_ms | ||
sudo_echo "$(( $HUGE_PAGES + 3 ))" /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages | ||
|
||
"$(dirname $0)"/nx_huge_pages_test -t 887563923 -p 100 | ||
) | ||
RET=$? | ||
|
||
sudo_echo "$NX_HUGE_PAGES" /sys/module/kvm/parameters/nx_huge_pages | ||
sudo_echo "$NX_HUGE_PAGES_RECOVERY_RATIO" /sys/module/kvm/parameters/nx_huge_pages_recovery_ratio | ||
sudo_echo "$NX_HUGE_PAGES_RECOVERY_PERIOD" /sys/module/kvm/parameters/nx_huge_pages_recovery_period_ms | ||
sudo_echo "$HUGE_PAGES" /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages | ||
|
||
exit $RET |