KVM selftests changes for 6.16:

- Add support for SNP to the various SEV selftests.
 
  - Add a selftest to verify fastops instructions via forced emulation.
 
  - Add MGLRU support to the access tracking perf test.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEKTobbabEP7vbhhN9OlYIJqCjN/0FAmgwmd0ACgkQOlYIJqCj
 N/3WCBAAsQ5zS8e+B1+7xopSz41eCou8L7KBDZZSe4B9TAuT+hMslXBEculyOJqh
 tIlBFlvrQA/hC2tNYla58jIeA6/f08Jq4/sV1URMNvORFKMcIvgnKpxmMJfKujve
 L2iHvJigJs4hoBCXYHCZHTkd5VAtB6j++7y9rqZS+RznM6z6/NI9SalX7pHr7Sri
 DQeaMc71UYJfllvLyLmI+MbQccdLfQ1v4dmkt6pz29K5s0pX9PQYp54+Hu1Z73Te
 aFdrG+CuDchra1jxLFoell5P9bD6nq9SsNBfdf+6VjYk/1MMHP4yX/dAFEtEqMbm
 RJNX95bewY4mms3fj6e9j8jVXDLBiXR2an8yJI8k6CP6VPsIXQn+RG2pUQMcOUj0
 zcWikbfXvfn+ReIoaeReWPyZ7tPMW33mhnHDPy/saWHdZ9sycI4w2DstKgc2pe9E
 e6jI9H5JiH49CoMnue38kwnACNUIIvolJDpWeU6K0vQz4p5k6eUNTMSTEEVZbwiV
 Y8MVqMIf+Cu+y6UY1co5OhH387kFuLgYMC/LIFz/4nOrlopRCAzMvYcFEqo9gIOO
 0+Ls/lkPc/hU5D2f3/20UjAGKVY/GfTwKJDRFptzaYMfmiMWW0pl2zlHagYp1huM
 7k8p0vVh5rFOLUJxiftXC8+jBVyJKXLGgwPxBdLVapFMs9DU9gI=
 =v9bp
 -----END PGP SIGNATURE-----

Merge tag 'kvm-x86-selftests-6.16' of https://github.com/kvm-x86/linux into HEAD

KVM selftests changes for 6.16:

 - Add support for SNP to the various SEV selftests.

 - Add a selftest to verify fastops instructions via forced emulation.

 - Add MGLRU support to the access tracking perf test.
pull/1255/head
Paolo Bonzini 2025-05-27 12:15:26 -04:00
commit 3e0797f6dd
23 changed files with 1275 additions and 211 deletions

View File

@ -845,6 +845,7 @@ struct kvm_sev_snp_launch_start {
};
/* Kept in sync with firmware values for simplicity. */
#define KVM_SEV_PAGE_TYPE_INVALID 0x0
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4

View File

@ -844,6 +844,7 @@ struct kvm_sev_snp_launch_start {
};
/* Kept in sync with firmware values for simplicity. */
#define KVM_SEV_PAGE_TYPE_INVALID 0x0
#define KVM_SEV_SNP_PAGE_TYPE_NORMAL 0x1
#define KVM_SEV_SNP_PAGE_TYPE_ZERO 0x3
#define KVM_SEV_SNP_PAGE_TYPE_UNMEASURED 0x4

View File

@ -21,14 +21,15 @@ TEST_GEN_PROGS += test_zswap
LOCAL_HDRS += $(selfdir)/clone3/clone3_selftests.h $(selfdir)/pidfd/pidfd.h
include ../lib.mk
include lib/libcgroup.mk
$(OUTPUT)/test_core: cgroup_util.c
$(OUTPUT)/test_cpu: cgroup_util.c
$(OUTPUT)/test_cpuset: cgroup_util.c
$(OUTPUT)/test_freezer: cgroup_util.c
$(OUTPUT)/test_hugetlb_memcg: cgroup_util.c
$(OUTPUT)/test_kill: cgroup_util.c
$(OUTPUT)/test_kmem: cgroup_util.c
$(OUTPUT)/test_memcontrol: cgroup_util.c
$(OUTPUT)/test_pids: cgroup_util.c
$(OUTPUT)/test_zswap: cgroup_util.c
$(OUTPUT)/test_core: $(LIBCGROUP_O)
$(OUTPUT)/test_cpu: $(LIBCGROUP_O)
$(OUTPUT)/test_cpuset: $(LIBCGROUP_O)
$(OUTPUT)/test_freezer: $(LIBCGROUP_O)
$(OUTPUT)/test_hugetlb_memcg: $(LIBCGROUP_O)
$(OUTPUT)/test_kill: $(LIBCGROUP_O)
$(OUTPUT)/test_kmem: $(LIBCGROUP_O)
$(OUTPUT)/test_memcontrol: $(LIBCGROUP_O)
$(OUTPUT)/test_pids: $(LIBCGROUP_O)
$(OUTPUT)/test_zswap: $(LIBCGROUP_O)

View File

@ -17,10 +17,10 @@
#include <unistd.h>
#include "cgroup_util.h"
#include "../clone3/clone3_selftests.h"
#include "../../clone3/clone3_selftests.h"
/* Returns read len on success, or -errno on failure. */
static ssize_t read_text(const char *path, char *buf, size_t max_len)
ssize_t read_text(const char *path, char *buf, size_t max_len)
{
ssize_t len;
int fd;
@ -39,7 +39,7 @@ static ssize_t read_text(const char *path, char *buf, size_t max_len)
}
/* Returns written len on success, or -errno on failure. */
static ssize_t write_text(const char *path, char *buf, ssize_t len)
ssize_t write_text(const char *path, char *buf, ssize_t len)
{
int fd;
@ -217,7 +217,8 @@ int cg_write_numeric(const char *cgroup, const char *control, long value)
return cg_write(cgroup, control, buf);
}
int cg_find_unified_root(char *root, size_t len, bool *nsdelegate)
static int cg_find_root(char *root, size_t len, const char *controller,
bool *nsdelegate)
{
char buf[10 * PAGE_SIZE];
char *fs, *mount, *type, *options;
@ -236,18 +237,37 @@ int cg_find_unified_root(char *root, size_t len, bool *nsdelegate)
options = strtok(NULL, delim);
strtok(NULL, delim);
strtok(NULL, delim);
if (strcmp(type, "cgroup2") == 0) {
strncpy(root, mount, len);
if (nsdelegate)
*nsdelegate = !!strstr(options, "nsdelegate");
return 0;
if (strcmp(type, "cgroup") == 0) {
if (!controller || !strstr(options, controller))
continue;
} else if (strcmp(type, "cgroup2") == 0) {
if (controller &&
cg_read_strstr(mount, "cgroup.controllers", controller))
continue;
} else {
continue;
}
strncpy(root, mount, len);
if (nsdelegate)
*nsdelegate = !!strstr(options, "nsdelegate");
return 0;
}
return -1;
}
int cg_find_controller_root(char *root, size_t len, const char *controller)
{
return cg_find_root(root, len, controller, NULL);
}
int cg_find_unified_root(char *root, size_t len, bool *nsdelegate)
{
return cg_find_root(root, len, NULL, nsdelegate);
}
int cg_create(const char *cgroup)
{
return mkdir(cgroup, 0755);
@ -488,84 +508,6 @@ int cg_run_nowait(const char *cgroup,
return pid;
}
int get_temp_fd(void)
{
return open(".", O_TMPFILE | O_RDWR | O_EXCL);
}
int alloc_pagecache(int fd, size_t size)
{
char buf[PAGE_SIZE];
struct stat st;
int i;
if (fstat(fd, &st))
goto cleanup;
size += st.st_size;
if (ftruncate(fd, size))
goto cleanup;
for (i = 0; i < size; i += sizeof(buf))
read(fd, buf, sizeof(buf));
return 0;
cleanup:
return -1;
}
int alloc_anon(const char *cgroup, void *arg)
{
size_t size = (unsigned long)arg;
char *buf, *ptr;
buf = malloc(size);
for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE)
*ptr = 0;
free(buf);
return 0;
}
int is_swap_enabled(void)
{
char buf[PAGE_SIZE];
const char delim[] = "\n";
int cnt = 0;
char *line;
if (read_text("/proc/swaps", buf, sizeof(buf)) <= 0)
return -1;
for (line = strtok(buf, delim); line; line = strtok(NULL, delim))
cnt++;
return cnt > 1;
}
int set_oom_adj_score(int pid, int score)
{
char path[PATH_MAX];
int fd, len;
sprintf(path, "/proc/%d/oom_score_adj", pid);
fd = open(path, O_WRONLY | O_APPEND);
if (fd < 0)
return fd;
len = dprintf(fd, "%d", score);
if (len < 0) {
close(fd);
return len;
}
close(fd);
return 0;
}
int proc_mount_contains(const char *option)
{
char buf[4 * PAGE_SIZE];

View File

@ -2,9 +2,9 @@
#include <stdbool.h>
#include <stdlib.h>
#include "../kselftest.h"
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
#define MB(x) (x << 20)
@ -21,6 +21,10 @@ static inline int values_close(long a, long b, int err)
return labs(a - b) <= (a + b) / 100 * err;
}
extern ssize_t read_text(const char *path, char *buf, size_t max_len);
extern ssize_t write_text(const char *path, char *buf, ssize_t len);
extern int cg_find_controller_root(char *root, size_t len, const char *controller);
extern int cg_find_unified_root(char *root, size_t len, bool *nsdelegate);
extern char *cg_name(const char *root, const char *name);
extern char *cg_name_indexed(const char *root, const char *name, int index);
@ -49,11 +53,6 @@ extern int cg_enter_current_thread(const char *cgroup);
extern int cg_run_nowait(const char *cgroup,
int (*fn)(const char *cgroup, void *arg),
void *arg);
extern int get_temp_fd(void);
extern int alloc_pagecache(int fd, size_t size);
extern int alloc_anon(const char *cgroup, void *arg);
extern int is_swap_enabled(void);
extern int set_oom_adj_score(int pid, int score);
extern int cg_wait_for_proc_count(const char *cgroup, int count);
extern int cg_killall(const char *cgroup);
int proc_mount_contains(const char *option);

View File

@ -0,0 +1,19 @@
CGROUP_DIR := $(selfdir)/cgroup
LIBCGROUP_C := lib/cgroup_util.c
LIBCGROUP_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBCGROUP_C))
LIBCGROUP_O_DIRS := $(shell dirname $(LIBCGROUP_O) | uniq)
CFLAGS += -I$(CGROUP_DIR)/lib/include
EXTRA_HDRS := $(selfdir)/clone3/clone3_selftests.h
$(LIBCGROUP_O_DIRS):
mkdir -p $@
$(LIBCGROUP_O): $(OUTPUT)/%.o : $(CGROUP_DIR)/%.c $(EXTRA_HDRS) $(LIBCGROUP_O_DIRS)
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o $@
EXTRA_CLEAN += $(LIBCGROUP_O)

View File

@ -24,6 +24,84 @@
static bool has_localevents;
static bool has_recursiveprot;
int get_temp_fd(void)
{
return open(".", O_TMPFILE | O_RDWR | O_EXCL);
}
int alloc_pagecache(int fd, size_t size)
{
char buf[PAGE_SIZE];
struct stat st;
int i;
if (fstat(fd, &st))
goto cleanup;
size += st.st_size;
if (ftruncate(fd, size))
goto cleanup;
for (i = 0; i < size; i += sizeof(buf))
read(fd, buf, sizeof(buf));
return 0;
cleanup:
return -1;
}
int alloc_anon(const char *cgroup, void *arg)
{
size_t size = (unsigned long)arg;
char *buf, *ptr;
buf = malloc(size);
for (ptr = buf; ptr < buf + size; ptr += PAGE_SIZE)
*ptr = 0;
free(buf);
return 0;
}
int is_swap_enabled(void)
{
char buf[PAGE_SIZE];
const char delim[] = "\n";
int cnt = 0;
char *line;
if (read_text("/proc/swaps", buf, sizeof(buf)) <= 0)
return -1;
for (line = strtok(buf, delim); line; line = strtok(NULL, delim))
cnt++;
return cnt > 1;
}
int set_oom_adj_score(int pid, int score)
{
char path[PATH_MAX];
int fd, len;
sprintf(path, "/proc/%d/oom_score_adj", pid);
fd = open(path, O_WRONLY | O_APPEND);
if (fd < 0)
return fd;
len = dprintf(fd, "%d", score);
if (len < 0) {
close(fd);
return len;
}
close(fd);
return 0;
}
/*
* This test creates two nested cgroups with and without enabling
* the memory controller.

View File

@ -8,6 +8,7 @@ LIBKVM += lib/elf.c
LIBKVM += lib/guest_modes.c
LIBKVM += lib/io.c
LIBKVM += lib/kvm_util.c
LIBKVM += lib/lru_gen_util.c
LIBKVM += lib/memstress.c
LIBKVM += lib/guest_sprintf.c
LIBKVM += lib/rbtree.c
@ -70,6 +71,7 @@ TEST_GEN_PROGS_x86 += x86/cr4_cpuid_sync_test
TEST_GEN_PROGS_x86 += x86/dirty_log_page_splitting_test
TEST_GEN_PROGS_x86 += x86/feature_msrs_test
TEST_GEN_PROGS_x86 += x86/exit_on_emulation_failure_test
TEST_GEN_PROGS_x86 += x86/fastops_test
TEST_GEN_PROGS_x86 += x86/fix_hypercall_test
TEST_GEN_PROGS_x86 += x86/hwcr_msr_test
TEST_GEN_PROGS_x86 += x86/hyperv_clock
@ -222,6 +224,7 @@ OVERRIDE_TARGETS = 1
# importantly defines, i.e. overwrites, $(CC) (unless `make -e` or `make CC=`,
# which causes the environment variable to override the makefile).
include ../lib.mk
include ../cgroup/lib/libcgroup.mk
INSTALL_HDR_PATH = $(top_srcdir)/usr
LINUX_HDR_PATH = $(INSTALL_HDR_PATH)/include/
@ -275,7 +278,7 @@ LIBKVM_S := $(filter %.S,$(LIBKVM))
LIBKVM_C_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_C))
LIBKVM_S_OBJ := $(patsubst %.S, $(OUTPUT)/%.o, $(LIBKVM_S))
LIBKVM_STRING_OBJ := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBKVM_STRING))
LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ)
LIBKVM_OBJS = $(LIBKVM_C_OBJ) $(LIBKVM_S_OBJ) $(LIBKVM_STRING_OBJ) $(LIBCGROUP_O)
SPLIT_TEST_GEN_PROGS := $(patsubst %, $(OUTPUT)/%, $(SPLIT_TESTS))
SPLIT_TEST_GEN_OBJ := $(patsubst %, $(OUTPUT)/$(ARCH)/%.o, $(SPLIT_TESTS))

View File

@ -7,9 +7,11 @@
* This test measures the performance effects of KVM's access tracking.
* Access tracking is driven by the MMU notifiers test_young, clear_young, and
* clear_flush_young. These notifiers do not have a direct userspace API,
* however the clear_young notifier can be triggered by marking a pages as idle
* in /sys/kernel/mm/page_idle/bitmap. This test leverages that mechanism to
* enable access tracking on guest memory.
* however the clear_young notifier can be triggered either by
* 1. marking a pages as idle in /sys/kernel/mm/page_idle/bitmap OR
* 2. adding a new MGLRU generation using the lru_gen debugfs file.
* This test leverages page_idle to enable access tracking on guest memory
* unless MGLRU is enabled, in which case MGLRU is used.
*
* To measure performance this test runs a VM with a configurable number of
* vCPUs that each touch every page in disjoint regions of memory. Performance
@ -17,10 +19,11 @@
* predefined region.
*
* Note that a deterministic correctness test of access tracking is not possible
* by using page_idle as it exists today. This is for a few reasons:
* by using page_idle or MGLRU aging as it exists today. This is for a few
* reasons:
*
* 1. page_idle only issues clear_young notifiers, which lack a TLB flush. This
* means subsequent guest accesses are not guaranteed to see page table
* 1. page_idle and MGLRU only issue clear_young notifiers, which lack a TLB flush.
* This means subsequent guest accesses are not guaranteed to see page table
* updates made by KVM until some time in the future.
*
* 2. page_idle only operates on LRU pages. Newly allocated pages are not
@ -48,9 +51,17 @@
#include "guest_modes.h"
#include "processor.h"
#include "cgroup_util.h"
#include "lru_gen_util.h"
static const char *TEST_MEMCG_NAME = "access_tracking_perf_test";
/* Global variable used to synchronize all of the vCPU threads. */
static int iteration;
/* The cgroup memory controller root. Needed for lru_gen-based aging. */
char cgroup_root[PATH_MAX];
/* Defines what vCPU threads should do during a given iteration. */
static enum {
/* Run the vCPU to access all its memory. */
@ -65,6 +76,25 @@ static int vcpu_last_completed_iteration[KVM_MAX_VCPUS];
/* Whether to overlap the regions of memory vCPUs access. */
static bool overlap_memory_access;
/*
* If the test should only warn if there are too many idle pages (i.e., it is
* expected).
* -1: Not yet set.
* 0: We do not expect too many idle pages, so FAIL if too many idle pages.
* 1: Having too many idle pages is expected, so merely print a warning if
* too many idle pages are found.
*/
static int idle_pages_warn_only = -1;
/* Whether or not to use MGLRU instead of page_idle for access tracking */
static bool use_lru_gen;
/* Total number of pages to expect in the memcg after touching everything */
static long test_pages;
/* Last generation we found the pages in */
static int lru_gen_last_gen = -1;
struct test_params {
/* The backing source for the region of memory. */
enum vm_mem_backing_src_type backing_src;
@ -123,8 +153,24 @@ static void mark_page_idle(int page_idle_fd, uint64_t pfn)
"Set page_idle bits for PFN 0x%" PRIx64, pfn);
}
static void mark_vcpu_memory_idle(struct kvm_vm *vm,
struct memstress_vcpu_args *vcpu_args)
static void too_many_idle_pages(long idle_pages, long total_pages, int vcpu_idx)
{
char prefix[18] = {};
if (vcpu_idx >= 0)
snprintf(prefix, 18, "vCPU%d: ", vcpu_idx);
TEST_ASSERT(idle_pages_warn_only,
"%sToo many pages still idle (%lu out of %lu)",
prefix, idle_pages, total_pages);
printf("WARNING: %sToo many pages still idle (%lu out of %lu), "
"this will affect performance results.\n",
prefix, idle_pages, total_pages);
}
static void pageidle_mark_vcpu_memory_idle(struct kvm_vm *vm,
struct memstress_vcpu_args *vcpu_args)
{
int vcpu_idx = vcpu_args->vcpu_idx;
uint64_t base_gva = vcpu_args->gva;
@ -177,27 +223,79 @@ static void mark_vcpu_memory_idle(struct kvm_vm *vm,
* arbitrary; high enough that we ensure most memory access went through
* access tracking but low enough as to not make the test too brittle
* over time and across architectures.
*
* When running the guest as a nested VM, "warn" instead of asserting
* as the TLB size is effectively unlimited and the KVM doesn't
* explicitly flush the TLB when aging SPTEs. As a result, more pages
* are cached and the guest won't see the "idle" bit cleared.
*/
if (still_idle >= pages / 10) {
#ifdef __x86_64__
TEST_ASSERT(this_cpu_has(X86_FEATURE_HYPERVISOR),
"vCPU%d: Too many pages still idle (%lu out of %lu)",
vcpu_idx, still_idle, pages);
#endif
printf("WARNING: vCPU%d: Too many pages still idle (%lu out of %lu), "
"this will affect performance results.\n",
vcpu_idx, still_idle, pages);
}
if (still_idle >= pages / 10)
too_many_idle_pages(still_idle, pages,
overlap_memory_access ? -1 : vcpu_idx);
close(page_idle_fd);
close(pagemap_fd);
}
int find_generation(struct memcg_stats *stats, long total_pages)
{
/*
* For finding the generation that contains our pages, use the same
* 90% threshold that page_idle uses.
*/
int gen = lru_gen_find_generation(stats, total_pages * 9 / 10);
if (gen >= 0)
return gen;
if (!idle_pages_warn_only) {
TEST_FAIL("Could not find a generation with 90%% of guest memory (%ld pages).",
total_pages * 9 / 10);
return gen;
}
/*
* We couldn't find a generation with 90% of guest memory, which can
* happen if access tracking is unreliable. Simply look for a majority
* of pages.
*/
puts("WARNING: Couldn't find a generation with 90% of guest memory. "
"Performance results may not be accurate.");
gen = lru_gen_find_generation(stats, total_pages / 2);
TEST_ASSERT(gen >= 0,
"Could not find a generation with 50%% of guest memory (%ld pages).",
total_pages / 2);
return gen;
}
static void lru_gen_mark_memory_idle(struct kvm_vm *vm)
{
struct timespec ts_start;
struct timespec ts_elapsed;
struct memcg_stats stats;
int new_gen;
/* Make a new generation */
clock_gettime(CLOCK_MONOTONIC, &ts_start);
lru_gen_do_aging(&stats, TEST_MEMCG_NAME);
ts_elapsed = timespec_elapsed(ts_start);
/* Check the generation again */
new_gen = find_generation(&stats, test_pages);
/*
* This function should only be invoked with newly-accessed pages,
* so pages should always move to a newer generation.
*/
if (new_gen <= lru_gen_last_gen) {
/* We did not move to a newer generation. */
long idle_pages = lru_gen_sum_memcg_stats_for_gen(lru_gen_last_gen,
&stats);
too_many_idle_pages(min_t(long, idle_pages, test_pages),
test_pages, -1);
}
pr_info("%-30s: %ld.%09lds\n",
"Mark memory idle (lru_gen)", ts_elapsed.tv_sec,
ts_elapsed.tv_nsec);
lru_gen_last_gen = new_gen;
}
static void assert_ucall(struct kvm_vcpu *vcpu, uint64_t expected_ucall)
{
struct ucall uc;
@ -237,7 +335,7 @@ static void vcpu_thread_main(struct memstress_vcpu_args *vcpu_args)
assert_ucall(vcpu, UCALL_SYNC);
break;
case ITERATION_MARK_IDLE:
mark_vcpu_memory_idle(vm, vcpu_args);
pageidle_mark_vcpu_memory_idle(vm, vcpu_args);
break;
}
@ -289,15 +387,18 @@ static void access_memory(struct kvm_vm *vm, int nr_vcpus,
static void mark_memory_idle(struct kvm_vm *vm, int nr_vcpus)
{
if (use_lru_gen)
return lru_gen_mark_memory_idle(vm);
/*
* Even though this parallelizes the work across vCPUs, this is still a
* very slow operation because page_idle forces the test to mark one pfn
* at a time and the clear_young notifier serializes on the KVM MMU
* at a time and the clear_young notifier may serialize on the KVM MMU
* lock.
*/
pr_debug("Marking VM memory idle (slow)...\n");
iteration_work = ITERATION_MARK_IDLE;
run_iteration(vm, nr_vcpus, "Mark memory idle");
run_iteration(vm, nr_vcpus, "Mark memory idle (page_idle)");
}
static void run_test(enum vm_guest_mode mode, void *arg)
@ -309,11 +410,38 @@ static void run_test(enum vm_guest_mode mode, void *arg)
vm = memstress_create_vm(mode, nr_vcpus, params->vcpu_memory_bytes, 1,
params->backing_src, !overlap_memory_access);
/*
* If guest_page_size is larger than the host's page size, the
* guest (memstress) will only fault in a subset of the host's pages.
*/
test_pages = params->nr_vcpus * params->vcpu_memory_bytes /
max(memstress_args.guest_page_size,
(uint64_t)getpagesize());
memstress_start_vcpu_threads(nr_vcpus, vcpu_thread_main);
pr_info("\n");
access_memory(vm, nr_vcpus, ACCESS_WRITE, "Populating memory");
if (use_lru_gen) {
struct memcg_stats stats;
/*
* Do a page table scan now. Following initial population, aging
* may not cause the pages to move to a newer generation. Do
* an aging pass now so that future aging passes always move
* pages to a newer generation.
*/
printf("Initial aging pass (lru_gen)\n");
lru_gen_do_aging(&stats, TEST_MEMCG_NAME);
TEST_ASSERT(lru_gen_sum_memcg_stats(&stats) >= test_pages,
"Not all pages accounted for (looking for %ld). "
"Was the memcg set up correctly?", test_pages);
access_memory(vm, nr_vcpus, ACCESS_WRITE, "Re-populating memory");
lru_gen_read_memcg_stats(&stats, TEST_MEMCG_NAME);
lru_gen_last_gen = find_generation(&stats, test_pages);
}
/* As a control, read and write to the populated memory first. */
access_memory(vm, nr_vcpus, ACCESS_WRITE, "Writing to populated memory");
access_memory(vm, nr_vcpus, ACCESS_READ, "Reading from populated memory");
@ -328,6 +456,37 @@ static void run_test(enum vm_guest_mode mode, void *arg)
memstress_destroy_vm(vm);
}
static int access_tracking_unreliable(void)
{
#ifdef __x86_64__
/*
* When running nested, the TLB size may be effectively unlimited (for
* example, this is the case when running on KVM L0), and KVM doesn't
* explicitly flush the TLB when aging SPTEs. As a result, more pages
* are cached and the guest won't see the "idle" bit cleared.
*/
if (this_cpu_has(X86_FEATURE_HYPERVISOR)) {
puts("Skipping idle page count sanity check, because the test is run nested");
return 1;
}
#endif
/*
* When NUMA balancing is enabled, guest memory will be unmapped to get
* NUMA faults, dropping the Accessed bits.
*/
if (is_numa_balancing_enabled()) {
puts("Skipping idle page count sanity check, because NUMA balancing is enabled");
return 1;
}
return 0;
}
static int run_test_for_each_guest_mode(const char *cgroup, void *arg)
{
for_each_guest_mode(run_test, arg);
return 0;
}
static void help(char *name)
{
puts("");
@ -342,11 +501,22 @@ static void help(char *name)
printf(" -v: specify the number of vCPUs to run.\n");
printf(" -o: Overlap guest memory accesses instead of partitioning\n"
" them into a separate region of memory for each vCPU.\n");
printf(" -w: Control whether the test warns or fails if more than 10%%\n"
" of pages are still seen as idle/old after accessing guest\n"
" memory. >0 == warn only, 0 == fail, <0 == auto. For auto\n"
" mode, the test fails by default, but switches to warn only\n"
" if NUMA balancing is enabled or the test detects it's running\n"
" in a VM.\n");
backing_src_help("-s");
puts("");
exit(0);
}
void destroy_cgroup(char *cg)
{
printf("Destroying cgroup: %s\n", cg);
}
int main(int argc, char *argv[])
{
struct test_params params = {
@ -354,12 +524,13 @@ int main(int argc, char *argv[])
.vcpu_memory_bytes = DEFAULT_PER_VCPU_MEM_SIZE,
.nr_vcpus = 1,
};
char *new_cg = NULL;
int page_idle_fd;
int opt;
guest_modes_append_default();
while ((opt = getopt(argc, argv, "hm:b:v:os:")) != -1) {
while ((opt = getopt(argc, argv, "hm:b:v:os:w:")) != -1) {
switch (opt) {
case 'm':
guest_modes_cmdline(optarg);
@ -376,6 +547,11 @@ int main(int argc, char *argv[])
case 's':
params.backing_src = parse_backing_src_type(optarg);
break;
case 'w':
idle_pages_warn_only =
atoi_non_negative("Idle pages warning",
optarg);
break;
case 'h':
default:
help(argv[0]);
@ -383,12 +559,53 @@ int main(int argc, char *argv[])
}
}
page_idle_fd = open("/sys/kernel/mm/page_idle/bitmap", O_RDWR);
__TEST_REQUIRE(page_idle_fd >= 0,
"CONFIG_IDLE_PAGE_TRACKING is not enabled");
close(page_idle_fd);
if (idle_pages_warn_only == -1)
idle_pages_warn_only = access_tracking_unreliable();
for_each_guest_mode(run_test, &params);
if (lru_gen_usable()) {
bool cg_created = true;
int ret;
puts("Using lru_gen for aging");
use_lru_gen = true;
if (cg_find_controller_root(cgroup_root, sizeof(cgroup_root), "memory"))
ksft_exit_skip("Cannot find memory cgroup controller\n");
new_cg = cg_name(cgroup_root, TEST_MEMCG_NAME);
printf("Creating cgroup: %s\n", new_cg);
if (cg_create(new_cg)) {
if (errno == EEXIST) {
printf("Found existing cgroup");
cg_created = false;
} else {
ksft_exit_skip("could not create new cgroup: %s\n", new_cg);
}
}
/*
* This will fork off a new process to run the test within
* a new memcg, so we need to properly propagate the return
* value up.
*/
ret = cg_run(new_cg, &run_test_for_each_guest_mode, &params);
if (cg_created)
cg_destroy(new_cg);
if (ret < 0)
TEST_FAIL("child did not spawn or was abnormally killed");
if (ret)
return ret;
} else {
page_idle_fd = open("/sys/kernel/mm/page_idle/bitmap", O_RDWR);
__TEST_REQUIRE(page_idle_fd >= 0,
"Couldn't open /sys/kernel/mm/page_idle/bitmap. "
"Is CONFIG_IDLE_PAGE_TRACKING enabled?");
close(page_idle_fd);
puts("Using page_idle for aging");
run_test_for_each_guest_mode(NULL, &params);
}
return 0;
}

View File

@ -555,6 +555,41 @@ void kvm_get_stat(struct kvm_binary_stats *stats, const char *name,
#define vm_get_stat(vm, stat) __get_stat(&(vm)->stats, stat)
#define vcpu_get_stat(vcpu, stat) __get_stat(&(vcpu)->stats, stat)
static inline bool read_smt_control(char *buf, size_t buf_size)
{
FILE *f = fopen("/sys/devices/system/cpu/smt/control", "r");
bool ret;
if (!f)
return false;
ret = fread(buf, sizeof(*buf), buf_size, f) > 0;
fclose(f);
return ret;
}
static inline bool is_smt_possible(void)
{
char buf[16];
if (read_smt_control(buf, sizeof(buf)) &&
(!strncmp(buf, "forceoff", 8) || !strncmp(buf, "notsupported", 12)))
return false;
return true;
}
static inline bool is_smt_on(void)
{
char buf[16];
if (read_smt_control(buf, sizeof(buf)) && !strncmp(buf, "on", 2))
return true;
return false;
}
void vm_create_irqchip(struct kvm_vm *vm);
static inline int __vm_create_guest_memfd(struct kvm_vm *vm, uint64_t size,

View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Tools for integrating with lru_gen, like parsing the lru_gen debugfs output.
*
* Copyright (C) 2025, Google LLC.
*/
#ifndef SELFTEST_KVM_LRU_GEN_UTIL_H
#define SELFTEST_KVM_LRU_GEN_UTIL_H
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include "test_util.h"
#define MAX_NR_GENS 16 /* MAX_NR_GENS in include/linux/mmzone.h */
#define MAX_NR_NODES 4 /* Maximum number of nodes supported by the test */
#define LRU_GEN_DEBUGFS "/sys/kernel/debug/lru_gen"
#define LRU_GEN_ENABLED_PATH "/sys/kernel/mm/lru_gen/enabled"
#define LRU_GEN_ENABLED 1
#define LRU_GEN_MM_WALK 2
struct generation_stats {
int gen;
long age_ms;
long nr_anon;
long nr_file;
};
struct node_stats {
int node;
int nr_gens; /* Number of populated gens entries. */
struct generation_stats gens[MAX_NR_GENS];
};
struct memcg_stats {
unsigned long memcg_id;
int nr_nodes; /* Number of populated nodes entries. */
struct node_stats nodes[MAX_NR_NODES];
};
void lru_gen_read_memcg_stats(struct memcg_stats *stats, const char *memcg);
long lru_gen_sum_memcg_stats(const struct memcg_stats *stats);
long lru_gen_sum_memcg_stats_for_gen(int gen, const struct memcg_stats *stats);
void lru_gen_do_aging(struct memcg_stats *stats, const char *memcg);
int lru_gen_find_generation(const struct memcg_stats *stats,
unsigned long total_pages);
bool lru_gen_usable(void);
#endif /* SELFTEST_KVM_LRU_GEN_UTIL_H */

View File

@ -153,6 +153,7 @@ bool is_backing_src_hugetlb(uint32_t i);
void backing_src_help(const char *flag);
enum vm_mem_backing_src_type parse_backing_src_type(const char *type_name);
long get_run_delay(void);
bool is_numa_balancing_enabled(void);
/*
* Whether or not the given source type is shared memory (as opposed to

View File

@ -203,6 +203,7 @@ struct kvm_x86_cpu_feature {
#define X86_FEATURE_IDLE_HLT KVM_X86_CPU_FEATURE(0x8000000A, 0, EDX, 30)
#define X86_FEATURE_SEV KVM_X86_CPU_FEATURE(0x8000001F, 0, EAX, 1)
#define X86_FEATURE_SEV_ES KVM_X86_CPU_FEATURE(0x8000001F, 0, EAX, 3)
#define X86_FEATURE_SEV_SNP KVM_X86_CPU_FEATURE(0x8000001F, 0, EAX, 4)
#define X86_FEATURE_PERFMON_V2 KVM_X86_CPU_FEATURE(0x80000022, 0, EAX, 0)
#define X86_FEATURE_LBR_PMC_FREEZE KVM_X86_CPU_FEATURE(0x80000022, 0, EAX, 2)

View File

@ -25,18 +25,50 @@ enum sev_guest_state {
#define SEV_POLICY_NO_DBG (1UL << 0)
#define SEV_POLICY_ES (1UL << 2)
#define SNP_POLICY_SMT (1ULL << 16)
#define SNP_POLICY_RSVD_MBO (1ULL << 17)
#define SNP_POLICY_DBG (1ULL << 19)
#define GHCB_MSR_TERM_REQ 0x100
static inline bool is_sev_snp_vm(struct kvm_vm *vm)
{
return vm->type == KVM_X86_SNP_VM;
}
static inline bool is_sev_es_vm(struct kvm_vm *vm)
{
return is_sev_snp_vm(vm) || vm->type == KVM_X86_SEV_ES_VM;
}
static inline bool is_sev_vm(struct kvm_vm *vm)
{
return is_sev_es_vm(vm) || vm->type == KVM_X86_SEV_VM;
}
void sev_vm_launch(struct kvm_vm *vm, uint32_t policy);
void sev_vm_launch_measure(struct kvm_vm *vm, uint8_t *measurement);
void sev_vm_launch_finish(struct kvm_vm *vm);
void snp_vm_launch_start(struct kvm_vm *vm, uint64_t policy);
void snp_vm_launch_update(struct kvm_vm *vm);
void snp_vm_launch_finish(struct kvm_vm *vm);
struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
struct kvm_vcpu **cpu);
void vm_sev_launch(struct kvm_vm *vm, uint32_t policy, uint8_t *measurement);
void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement);
kvm_static_assert(SEV_RET_SUCCESS == 0);
/*
* A SEV-SNP VM requires the policy reserved bit to always be set.
* The SMT policy bit is also required to be set based on SMT being
* available and active on the system.
*/
static inline u64 snp_default_policy(void)
{
return SNP_POLICY_RSVD_MBO | (is_smt_on() ? SNP_POLICY_SMT : 0);
}
/*
* The KVM_MEMORY_ENCRYPT_OP uAPI is utter garbage and takes an "unsigned long"
* instead of a proper struct. The size of the parameter is embedded in the
@ -70,6 +102,12 @@ kvm_static_assert(SEV_RET_SUCCESS == 0);
void sev_vm_init(struct kvm_vm *vm);
void sev_es_vm_init(struct kvm_vm *vm);
void snp_vm_init(struct kvm_vm *vm);
static inline void vmgexit(void)
{
__asm__ __volatile__("rep; vmmcall");
}
static inline void sev_register_encrypted_memory(struct kvm_vm *vm,
struct userspace_mem_region *region)
@ -93,4 +131,17 @@ static inline void sev_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_DATA, &update_data);
}
static inline void snp_launch_update_data(struct kvm_vm *vm, vm_paddr_t gpa,
uint64_t hva, uint64_t size, uint8_t type)
{
struct kvm_sev_snp_launch_update update_data = {
.uaddr = hva,
.gfn_start = gpa >> PAGE_SHIFT,
.len = size,
.type = type,
};
vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_UPDATE, &update_data);
}
#endif /* SELFTEST_KVM_SEV_H */

View File

@ -447,6 +447,15 @@ void kvm_set_files_rlimit(uint32_t nr_vcpus)
}
static bool is_guest_memfd_required(struct vm_shape shape)
{
#ifdef __x86_64__
return shape.type == KVM_X86_SNP_VM;
#else
return false;
#endif
}
struct kvm_vm *__vm_create(struct vm_shape shape, uint32_t nr_runnable_vcpus,
uint64_t nr_extra_pages)
{
@ -454,7 +463,7 @@ struct kvm_vm *__vm_create(struct vm_shape shape, uint32_t nr_runnable_vcpus,
nr_extra_pages);
struct userspace_mem_region *slot0;
struct kvm_vm *vm;
int i;
int i, flags;
kvm_set_files_rlimit(nr_runnable_vcpus);
@ -463,7 +472,15 @@ struct kvm_vm *__vm_create(struct vm_shape shape, uint32_t nr_runnable_vcpus,
vm = ____vm_create(shape);
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, 0);
/*
* Force GUEST_MEMFD for the primary memory region if necessary, e.g.
* for CoCo VMs that require GUEST_MEMFD backed private memory.
*/
flags = 0;
if (is_guest_memfd_required(shape))
flags |= KVM_MEM_GUEST_MEMFD;
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, 0, 0, nr_pages, flags);
for (i = 0; i < NR_MEM_REGIONS; i++)
vm->memslots[i] = 0;

View File

@ -0,0 +1,387 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2025, Google LLC.
*/
#include <time.h>
#include "lru_gen_util.h"
/*
* Tracks state while we parse memcg lru_gen stats. The file we're parsing is
* structured like this (some extra whitespace elided):
*
* memcg (id) (path)
* node (id)
* (gen_nr) (age_in_ms) (nr_anon_pages) (nr_file_pages)
*/
struct memcg_stats_parse_context {
bool consumed; /* Whether or not this line was consumed */
/* Next parse handler to invoke */
void (*next_handler)(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line);
int current_node_idx; /* Current index in nodes array */
const char *name; /* The name of the memcg we're looking for */
};
static void memcg_stats_handle_searching(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line);
static void memcg_stats_handle_in_memcg(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line);
static void memcg_stats_handle_in_node(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line);
struct split_iterator {
char *str;
char *save;
};
static char *split_next(struct split_iterator *it)
{
char *ret = strtok_r(it->str, " \t\n\r", &it->save);
it->str = NULL;
return ret;
}
static void memcg_stats_handle_searching(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line)
{
struct split_iterator it = { .str = line };
char *prefix = split_next(&it);
char *memcg_id = split_next(&it);
char *memcg_name = split_next(&it);
char *end;
ctx->consumed = true;
if (!prefix || strcmp("memcg", prefix))
return; /* Not a memcg line (maybe empty), skip */
TEST_ASSERT(memcg_id && memcg_name,
"malformed memcg line; no memcg id or memcg_name");
if (strcmp(memcg_name + 1, ctx->name))
return; /* Wrong memcg, skip */
/* Found it! */
stats->memcg_id = strtoul(memcg_id, &end, 10);
TEST_ASSERT(*end == '\0', "malformed memcg id '%s'", memcg_id);
if (!stats->memcg_id)
return; /* Removed memcg? */
ctx->next_handler = memcg_stats_handle_in_memcg;
}
static void memcg_stats_handle_in_memcg(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line)
{
struct split_iterator it = { .str = line };
char *prefix = split_next(&it);
char *id = split_next(&it);
long found_node_id;
char *end;
ctx->consumed = true;
ctx->current_node_idx = -1;
if (!prefix)
return; /* Skip empty lines */
if (!strcmp("memcg", prefix)) {
/* Memcg done, found next one; stop. */
ctx->next_handler = NULL;
return;
} else if (strcmp("node", prefix))
TEST_ASSERT(false, "found malformed line after 'memcg ...',"
"token: '%s'", prefix);
/* At this point we know we have a node line. Parse the ID. */
TEST_ASSERT(id, "malformed node line; no node id");
found_node_id = strtol(id, &end, 10);
TEST_ASSERT(*end == '\0', "malformed node id '%s'", id);
ctx->current_node_idx = stats->nr_nodes++;
TEST_ASSERT(ctx->current_node_idx < MAX_NR_NODES,
"memcg has stats for too many nodes, max is %d",
MAX_NR_NODES);
stats->nodes[ctx->current_node_idx].node = found_node_id;
ctx->next_handler = memcg_stats_handle_in_node;
}
static void memcg_stats_handle_in_node(struct memcg_stats *stats,
struct memcg_stats_parse_context *ctx,
char *line)
{
char *my_line = strdup(line);
struct split_iterator it = { .str = my_line };
char *gen, *age, *nr_anon, *nr_file;
struct node_stats *node_stats;
struct generation_stats *gen_stats;
char *end;
TEST_ASSERT(it.str, "failed to copy input line");
gen = split_next(&it);
if (!gen)
goto out_consume; /* Skip empty lines */
if (!strcmp("memcg", gen) || !strcmp("node", gen)) {
/*
* Reached next memcg or node section. Don't consume, let the
* other handler deal with this.
*/
ctx->next_handler = memcg_stats_handle_in_memcg;
goto out;
}
node_stats = &stats->nodes[ctx->current_node_idx];
TEST_ASSERT(node_stats->nr_gens < MAX_NR_GENS,
"found too many generation lines; max is %d",
MAX_NR_GENS);
gen_stats = &node_stats->gens[node_stats->nr_gens++];
age = split_next(&it);
nr_anon = split_next(&it);
nr_file = split_next(&it);
TEST_ASSERT(age && nr_anon && nr_file,
"malformed generation line; not enough tokens");
gen_stats->gen = (int)strtol(gen, &end, 10);
TEST_ASSERT(*end == '\0', "malformed generation number '%s'", gen);
gen_stats->age_ms = strtol(age, &end, 10);
TEST_ASSERT(*end == '\0', "malformed generation age '%s'", age);
gen_stats->nr_anon = strtol(nr_anon, &end, 10);
TEST_ASSERT(*end == '\0', "malformed anonymous page count '%s'",
nr_anon);
gen_stats->nr_file = strtol(nr_file, &end, 10);
TEST_ASSERT(*end == '\0', "malformed file page count '%s'", nr_file);
out_consume:
ctx->consumed = true;
out:
free(my_line);
}
static void print_memcg_stats(const struct memcg_stats *stats, const char *name)
{
int node, gen;
pr_debug("stats for memcg %s (id %lu):\n", name, stats->memcg_id);
for (node = 0; node < stats->nr_nodes; ++node) {
pr_debug("\tnode %d\n", stats->nodes[node].node);
for (gen = 0; gen < stats->nodes[node].nr_gens; ++gen) {
const struct generation_stats *gstats =
&stats->nodes[node].gens[gen];
pr_debug("\t\tgen %d\tage_ms %ld"
"\tnr_anon %ld\tnr_file %ld\n",
gstats->gen, gstats->age_ms, gstats->nr_anon,
gstats->nr_file);
}
}
}
/* Re-read lru_gen debugfs information for @memcg into @stats. */
void lru_gen_read_memcg_stats(struct memcg_stats *stats, const char *memcg)
{
FILE *f;
ssize_t read = 0;
char *line = NULL;
size_t bufsz;
struct memcg_stats_parse_context ctx = {
.next_handler = memcg_stats_handle_searching,
.name = memcg,
};
memset(stats, 0, sizeof(struct memcg_stats));
f = fopen(LRU_GEN_DEBUGFS, "r");
TEST_ASSERT(f, "fopen(%s) failed", LRU_GEN_DEBUGFS);
while (ctx.next_handler && (read = getline(&line, &bufsz, f)) > 0) {
ctx.consumed = false;
do {
ctx.next_handler(stats, &ctx, line);
if (!ctx.next_handler)
break;
} while (!ctx.consumed);
}
if (read < 0 && !feof(f))
TEST_ASSERT(false, "getline(%s) failed", LRU_GEN_DEBUGFS);
TEST_ASSERT(stats->memcg_id > 0, "Couldn't find memcg: %s\n"
"Did the memcg get created in the proper mount?",
memcg);
if (line)
free(line);
TEST_ASSERT(!fclose(f), "fclose(%s) failed", LRU_GEN_DEBUGFS);
print_memcg_stats(stats, memcg);
}
/*
* Find all pages tracked by lru_gen for this memcg in generation @target_gen.
*
* If @target_gen is negative, look for all generations.
*/
long lru_gen_sum_memcg_stats_for_gen(int target_gen,
const struct memcg_stats *stats)
{
int node, gen;
long total_nr = 0;
for (node = 0; node < stats->nr_nodes; ++node) {
const struct node_stats *node_stats = &stats->nodes[node];
for (gen = 0; gen < node_stats->nr_gens; ++gen) {
const struct generation_stats *gen_stats =
&node_stats->gens[gen];
if (target_gen >= 0 && gen_stats->gen != target_gen)
continue;
total_nr += gen_stats->nr_anon + gen_stats->nr_file;
}
}
return total_nr;
}
/* Find all pages tracked by lru_gen for this memcg. */
long lru_gen_sum_memcg_stats(const struct memcg_stats *stats)
{
return lru_gen_sum_memcg_stats_for_gen(-1, stats);
}
/*
* If lru_gen aging should force page table scanning.
*
* If you want to set this to false, you will need to do eviction
* before doing extra aging passes.
*/
static const bool force_scan = true;
static void run_aging_impl(unsigned long memcg_id, int node_id, int max_gen)
{
FILE *f = fopen(LRU_GEN_DEBUGFS, "w");
char *command;
size_t sz;
TEST_ASSERT(f, "fopen(%s) failed", LRU_GEN_DEBUGFS);
sz = asprintf(&command, "+ %lu %d %d 1 %d\n",
memcg_id, node_id, max_gen, force_scan);
TEST_ASSERT(sz > 0, "creating aging command failed");
pr_debug("Running aging command: %s", command);
if (fwrite(command, sizeof(char), sz, f) < sz) {
TEST_ASSERT(false, "writing aging command %s to %s failed",
command, LRU_GEN_DEBUGFS);
}
TEST_ASSERT(!fclose(f), "fclose(%s) failed", LRU_GEN_DEBUGFS);
}
void lru_gen_do_aging(struct memcg_stats *stats, const char *memcg)
{
int node, gen;
pr_debug("lru_gen: invoking aging...\n");
/* Must read memcg stats to construct the proper aging command. */
lru_gen_read_memcg_stats(stats, memcg);
for (node = 0; node < stats->nr_nodes; ++node) {
int max_gen = 0;
for (gen = 0; gen < stats->nodes[node].nr_gens; ++gen) {
int this_gen = stats->nodes[node].gens[gen].gen;
max_gen = max_gen > this_gen ? max_gen : this_gen;
}
run_aging_impl(stats->memcg_id, stats->nodes[node].node,
max_gen);
}
/* Re-read so callers get updated information */
lru_gen_read_memcg_stats(stats, memcg);
}
/*
* Find which generation contains at least @pages pages, assuming that
* such a generation exists.
*/
int lru_gen_find_generation(const struct memcg_stats *stats,
unsigned long pages)
{
int node, gen, gen_idx, min_gen = INT_MAX, max_gen = -1;
for (node = 0; node < stats->nr_nodes; ++node)
for (gen_idx = 0; gen_idx < stats->nodes[node].nr_gens;
++gen_idx) {
gen = stats->nodes[node].gens[gen_idx].gen;
max_gen = gen > max_gen ? gen : max_gen;
min_gen = gen < min_gen ? gen : min_gen;
}
for (gen = min_gen; gen <= max_gen; ++gen)
/* See if this generation has enough pages. */
if (lru_gen_sum_memcg_stats_for_gen(gen, stats) > pages)
return gen;
return -1;
}
bool lru_gen_usable(void)
{
long required_features = LRU_GEN_ENABLED | LRU_GEN_MM_WALK;
int lru_gen_fd, lru_gen_debug_fd;
char mglru_feature_str[8] = {};
long mglru_features;
lru_gen_fd = open(LRU_GEN_ENABLED_PATH, O_RDONLY);
if (lru_gen_fd < 0) {
puts("lru_gen: Could not open " LRU_GEN_ENABLED_PATH);
return false;
}
if (read(lru_gen_fd, &mglru_feature_str, 7) < 7) {
puts("lru_gen: Could not read from " LRU_GEN_ENABLED_PATH);
close(lru_gen_fd);
return false;
}
close(lru_gen_fd);
mglru_features = strtol(mglru_feature_str, NULL, 16);
if ((mglru_features & required_features) != required_features) {
printf("lru_gen: missing features, got: 0x%lx, expected: 0x%lx\n",
mglru_features, required_features);
printf("lru_gen: Try 'echo 0x%lx > /sys/kernel/mm/lru_gen/enabled'\n",
required_features);
return false;
}
lru_gen_debug_fd = open(LRU_GEN_DEBUGFS, O_RDWR);
__TEST_REQUIRE(lru_gen_debug_fd >= 0,
"lru_gen: Could not open " LRU_GEN_DEBUGFS ", "
"but lru_gen is enabled, so cannot use page_idle.");
close(lru_gen_debug_fd);
return true;
}

View File

@ -132,35 +132,55 @@ void print_skip(const char *fmt, ...)
puts(", skipping test");
}
bool thp_configured(void)
static bool test_sysfs_path(const char *path)
{
int ret;
struct stat statbuf;
int ret;
ret = stat("/sys/kernel/mm/transparent_hugepage", &statbuf);
ret = stat(path, &statbuf);
TEST_ASSERT(ret == 0 || (ret == -1 && errno == ENOENT),
"Error in stating /sys/kernel/mm/transparent_hugepage");
"Error in stat()ing '%s'", path);
return ret == 0;
}
size_t get_trans_hugepagesz(void)
bool thp_configured(void)
{
return test_sysfs_path("/sys/kernel/mm/transparent_hugepage");
}
static size_t get_sysfs_val(const char *path)
{
size_t size;
FILE *f;
int ret;
f = fopen(path, "r");
TEST_ASSERT(f, "Error opening '%s'", path);
ret = fscanf(f, "%ld", &size);
TEST_ASSERT(ret > 0, "Error reading '%s'", path);
/* Re-scan the input stream to verify the entire file was read. */
ret = fscanf(f, "%ld", &size);
TEST_ASSERT(ret < 1, "Error reading '%s'", path);
fclose(f);
return size;
}
size_t get_trans_hugepagesz(void)
{
TEST_ASSERT(thp_configured(), "THP is not configured in host kernel");
f = fopen("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", "r");
TEST_ASSERT(f != NULL, "Error in opening transparent_hugepage/hpage_pmd_size");
return get_sysfs_val("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size");
}
ret = fscanf(f, "%ld", &size);
ret = fscanf(f, "%ld", &size);
TEST_ASSERT(ret < 1, "Error reading transparent_hugepage/hpage_pmd_size");
fclose(f);
return size;
bool is_numa_balancing_enabled(void)
{
if (!test_sysfs_path("/proc/sys/kernel/numa_balancing"))
return false;
return get_sysfs_val("/proc/sys/kernel/numa_balancing") == 1;
}
size_t get_def_hugetlb_pagesz(void)

View File

@ -639,7 +639,7 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm)
sync_global_to_guest(vm, host_cpu_is_amd);
sync_global_to_guest(vm, is_forced_emulation_enabled);
if (vm->type == KVM_X86_SEV_VM || vm->type == KVM_X86_SEV_ES_VM) {
if (is_sev_vm(vm)) {
struct kvm_sev_init init = { 0 };
vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
@ -1156,7 +1156,7 @@ void kvm_get_cpu_address_width(unsigned int *pa_bits, unsigned int *va_bits)
void kvm_init_vm_address_properties(struct kvm_vm *vm)
{
if (vm->type == KVM_X86_SEV_VM || vm->type == KVM_X86_SEV_ES_VM) {
if (is_sev_vm(vm)) {
vm->arch.sev_fd = open_sev_dev_path_or_exit();
vm->arch.c_bit = BIT_ULL(this_cpu_property(X86_PROPERTY_SEV_C_BIT));
vm->gpa_tag_mask = vm->arch.c_bit;

View File

@ -14,7 +14,8 @@
* and find the first range, but that's correct because the condition
* expression would cause us to quit the loop.
*/
static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region)
static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *region,
uint8_t page_type, bool private)
{
const struct sparsebit *protected_phy_pages = region->protected_phy_pages;
const vm_paddr_t gpa_base = region->region.guest_phys_addr;
@ -24,25 +25,35 @@ static void encrypt_region(struct kvm_vm *vm, struct userspace_mem_region *regio
if (!sparsebit_any_set(protected_phy_pages))
return;
sev_register_encrypted_memory(vm, region);
if (!is_sev_snp_vm(vm))
sev_register_encrypted_memory(vm, region);
sparsebit_for_each_set_range(protected_phy_pages, i, j) {
const uint64_t size = (j - i + 1) * vm->page_size;
const uint64_t offset = (i - lowest_page_in_region) * vm->page_size;
sev_launch_update_data(vm, gpa_base + offset, size);
if (private)
vm_mem_set_private(vm, gpa_base + offset, size);
if (is_sev_snp_vm(vm))
snp_launch_update_data(vm, gpa_base + offset,
(uint64_t)addr_gpa2hva(vm, gpa_base + offset),
size, page_type);
else
sev_launch_update_data(vm, gpa_base + offset, size);
}
}
void sev_vm_init(struct kvm_vm *vm)
{
if (vm->type == KVM_X86_DEFAULT_VM) {
assert(vm->arch.sev_fd == -1);
TEST_ASSERT_EQ(vm->arch.sev_fd, -1);
vm->arch.sev_fd = open_sev_dev_path_or_exit();
vm_sev_ioctl(vm, KVM_SEV_INIT, NULL);
} else {
struct kvm_sev_init init = { 0 };
assert(vm->type == KVM_X86_SEV_VM);
TEST_ASSERT_EQ(vm->type, KVM_X86_SEV_VM);
vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
}
}
@ -50,16 +61,24 @@ void sev_vm_init(struct kvm_vm *vm)
void sev_es_vm_init(struct kvm_vm *vm)
{
if (vm->type == KVM_X86_DEFAULT_VM) {
assert(vm->arch.sev_fd == -1);
TEST_ASSERT_EQ(vm->arch.sev_fd, -1);
vm->arch.sev_fd = open_sev_dev_path_or_exit();
vm_sev_ioctl(vm, KVM_SEV_ES_INIT, NULL);
} else {
struct kvm_sev_init init = { 0 };
assert(vm->type == KVM_X86_SEV_ES_VM);
TEST_ASSERT_EQ(vm->type, KVM_X86_SEV_ES_VM);
vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
}
}
void snp_vm_init(struct kvm_vm *vm)
{
struct kvm_sev_init init = { 0 };
TEST_ASSERT_EQ(vm->type, KVM_X86_SNP_VM);
vm_sev_ioctl(vm, KVM_SEV_INIT2, &init);
}
void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
{
struct kvm_sev_launch_start launch_start = {
@ -76,7 +95,7 @@ void sev_vm_launch(struct kvm_vm *vm, uint32_t policy)
TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_LAUNCH_UPDATE);
hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
encrypt_region(vm, region);
encrypt_region(vm, region, KVM_SEV_PAGE_TYPE_INVALID, false);
if (policy & SEV_POLICY_ES)
vm_sev_ioctl(vm, KVM_SEV_LAUNCH_UPDATE_VMSA, NULL);
@ -112,6 +131,33 @@ void sev_vm_launch_finish(struct kvm_vm *vm)
TEST_ASSERT_EQ(status.state, SEV_GUEST_STATE_RUNNING);
}
void snp_vm_launch_start(struct kvm_vm *vm, uint64_t policy)
{
struct kvm_sev_snp_launch_start launch_start = {
.policy = policy,
};
vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_START, &launch_start);
}
void snp_vm_launch_update(struct kvm_vm *vm)
{
struct userspace_mem_region *region;
int ctr;
hash_for_each(vm->regions.slot_hash, ctr, region, slot_node)
encrypt_region(vm, region, KVM_SEV_SNP_PAGE_TYPE_NORMAL, true);
vm->arch.is_pt_protected = true;
}
void snp_vm_launch_finish(struct kvm_vm *vm)
{
struct kvm_sev_snp_launch_finish launch_finish = { 0 };
vm_sev_ioctl(vm, KVM_SEV_SNP_LAUNCH_FINISH, &launch_finish);
}
struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
struct kvm_vcpu **cpu)
{
@ -128,8 +174,20 @@ struct kvm_vm *vm_sev_create_with_one_vcpu(uint32_t type, void *guest_code,
return vm;
}
void vm_sev_launch(struct kvm_vm *vm, uint32_t policy, uint8_t *measurement)
void vm_sev_launch(struct kvm_vm *vm, uint64_t policy, uint8_t *measurement)
{
if (is_sev_snp_vm(vm)) {
vm_enable_cap(vm, KVM_CAP_EXIT_HYPERCALL, BIT(KVM_HC_MAP_GPA_RANGE));
snp_vm_launch_start(vm, policy);
snp_vm_launch_update(vm);
snp_vm_launch_finish(vm);
return;
}
sev_vm_launch(vm, policy);
if (!measurement)

View File

@ -0,0 +1,165 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
/*
* Execute a fastop() instruction, with or without forced emulation. BT bit 0
* to set RFLAGS.CF based on whether or not the input is even or odd, so that
* instructions like ADC and SBB are deterministic.
*/
#define guest_execute_fastop_1(FEP, insn, __val, __flags) \
({ \
__asm__ __volatile__("bt $0, %[val]\n\t" \
FEP insn " %[val]\n\t" \
"pushfq\n\t" \
"pop %[flags]\n\t" \
: [val]"+r"(__val), [flags]"=r"(__flags) \
: : "cc", "memory"); \
})
#define guest_test_fastop_1(insn, type_t, __val) \
({ \
type_t val = __val, ex_val = __val, input = __val; \
uint64_t flags, ex_flags; \
\
guest_execute_fastop_1("", insn, ex_val, ex_flags); \
guest_execute_fastop_1(KVM_FEP, insn, val, flags); \
\
__GUEST_ASSERT(val == ex_val, \
"Wanted 0x%lx for '%s 0x%lx', got 0x%lx", \
(uint64_t)ex_val, insn, (uint64_t)input, (uint64_t)val); \
__GUEST_ASSERT(flags == ex_flags, \
"Wanted flags 0x%lx for '%s 0x%lx', got 0x%lx", \
ex_flags, insn, (uint64_t)input, flags); \
})
#define guest_execute_fastop_2(FEP, insn, __input, __output, __flags) \
({ \
__asm__ __volatile__("bt $0, %[output]\n\t" \
FEP insn " %[input], %[output]\n\t" \
"pushfq\n\t" \
"pop %[flags]\n\t" \
: [output]"+r"(__output), [flags]"=r"(__flags) \
: [input]"r"(__input) : "cc", "memory"); \
})
#define guest_test_fastop_2(insn, type_t, __val1, __val2) \
({ \
type_t input = __val1, input2 = __val2, output = __val2, ex_output = __val2; \
uint64_t flags, ex_flags; \
\
guest_execute_fastop_2("", insn, input, ex_output, ex_flags); \
guest_execute_fastop_2(KVM_FEP, insn, input, output, flags); \
\
__GUEST_ASSERT(output == ex_output, \
"Wanted 0x%lx for '%s 0x%lx 0x%lx', got 0x%lx", \
(uint64_t)ex_output, insn, (uint64_t)input, \
(uint64_t)input2, (uint64_t)output); \
__GUEST_ASSERT(flags == ex_flags, \
"Wanted flags 0x%lx for '%s 0x%lx, 0x%lx', got 0x%lx", \
ex_flags, insn, (uint64_t)input, (uint64_t)input2, flags); \
})
#define guest_execute_fastop_cl(FEP, insn, __shift, __output, __flags) \
({ \
__asm__ __volatile__("bt $0, %[output]\n\t" \
FEP insn " %%cl, %[output]\n\t" \
"pushfq\n\t" \
"pop %[flags]\n\t" \
: [output]"+r"(__output), [flags]"=r"(__flags) \
: "c"(__shift) : "cc", "memory"); \
})
#define guest_test_fastop_cl(insn, type_t, __val1, __val2) \
({ \
type_t output = __val2, ex_output = __val2, input = __val2; \
uint8_t shift = __val1; \
uint64_t flags, ex_flags; \
\
guest_execute_fastop_cl("", insn, shift, ex_output, ex_flags); \
guest_execute_fastop_cl(KVM_FEP, insn, shift, output, flags); \
\
__GUEST_ASSERT(output == ex_output, \
"Wanted 0x%lx for '%s 0x%x, 0x%lx', got 0x%lx", \
(uint64_t)ex_output, insn, shift, (uint64_t)input, \
(uint64_t)output); \
__GUEST_ASSERT(flags == ex_flags, \
"Wanted flags 0x%lx for '%s 0x%x, 0x%lx', got 0x%lx", \
ex_flags, insn, shift, (uint64_t)input, flags); \
})
static const uint64_t vals[] = {
0,
1,
2,
4,
7,
0x5555555555555555,
0xaaaaaaaaaaaaaaaa,
0xfefefefefefefefe,
0xffffffffffffffff,
};
#define guest_test_fastops(type_t, suffix) \
do { \
int i, j; \
\
for (i = 0; i < ARRAY_SIZE(vals); i++) { \
guest_test_fastop_1("dec" suffix, type_t, vals[i]); \
guest_test_fastop_1("inc" suffix, type_t, vals[i]); \
guest_test_fastop_1("neg" suffix, type_t, vals[i]); \
guest_test_fastop_1("not" suffix, type_t, vals[i]); \
\
for (j = 0; j < ARRAY_SIZE(vals); j++) { \
guest_test_fastop_2("add" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("adc" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("and" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("bsf" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("bsr" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("bt" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("btc" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("btr" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("bts" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("cmp" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("imul" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("or" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("sbb" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("sub" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("test" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_2("xor" suffix, type_t, vals[i], vals[j]); \
\
guest_test_fastop_cl("rol" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("ror" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("rcl" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("rcr" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("sar" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("shl" suffix, type_t, vals[i], vals[j]); \
guest_test_fastop_cl("shr" suffix, type_t, vals[i], vals[j]); \
} \
} \
} while (0)
static void guest_code(void)
{
guest_test_fastops(uint16_t, "w");
guest_test_fastops(uint32_t, "l");
guest_test_fastops(uint64_t, "q");
GUEST_DONE();
}
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
TEST_REQUIRE(is_forced_emulation_enabled);
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
vcpu_run(vcpu);
TEST_ASSERT_EQ(get_ucall(vcpu, NULL), UCALL_DONE);
kvm_vm_free(vm);
}

View File

@ -22,25 +22,6 @@ static void guest_code(void)
{
}
static bool smt_possible(void)
{
char buf[16];
FILE *f;
bool res = true;
f = fopen("/sys/devices/system/cpu/smt/control", "r");
if (f) {
if (fread(buf, sizeof(*buf), sizeof(buf), f) > 0) {
if (!strncmp(buf, "forceoff", 8) ||
!strncmp(buf, "notsupported", 12))
res = false;
}
fclose(f);
}
return res;
}
static void test_hv_cpuid(struct kvm_vcpu *vcpu, bool evmcs_expected)
{
const bool has_irqchip = !vcpu || vcpu->vm->has_irqchip;
@ -93,7 +74,7 @@ static void test_hv_cpuid(struct kvm_vcpu *vcpu, bool evmcs_expected)
case 0x40000004:
test_val = entry->eax & (1UL << 18);
TEST_ASSERT(!!test_val == !smt_possible(),
TEST_ASSERT(!!test_val == !is_smt_possible(),
"NoNonArchitecturalCoreSharing bit"
" doesn't reflect SMT setting");

View File

@ -28,6 +28,7 @@
int kvm_fd;
u64 supported_vmsa_features;
bool have_sev_es;
bool have_snp;
static int __sev_ioctl(int vm_fd, int cmd_id, void *data)
{
@ -83,6 +84,9 @@ void test_vm_types(void)
if (have_sev_es)
test_init2(KVM_X86_SEV_ES_VM, &(struct kvm_sev_init){});
if (have_snp)
test_init2(KVM_X86_SNP_VM, &(struct kvm_sev_init){});
test_init2_invalid(0, &(struct kvm_sev_init){},
"VM type is KVM_X86_DEFAULT_VM");
if (kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SW_PROTECTED_VM))
@ -138,15 +142,24 @@ int main(int argc, char *argv[])
"sev-es: KVM_CAP_VM_TYPES (%x) does not match cpuid (checking %x)",
kvm_check_cap(KVM_CAP_VM_TYPES), 1 << KVM_X86_SEV_ES_VM);
have_snp = kvm_cpu_has(X86_FEATURE_SEV_SNP);
TEST_ASSERT(have_snp == !!(kvm_check_cap(KVM_CAP_VM_TYPES) & BIT(KVM_X86_SNP_VM)),
"sev-snp: KVM_CAP_VM_TYPES (%x) indicates SNP support (bit %d), but CPUID does not",
kvm_check_cap(KVM_CAP_VM_TYPES), KVM_X86_SNP_VM);
test_vm_types();
test_flags(KVM_X86_SEV_VM);
if (have_sev_es)
test_flags(KVM_X86_SEV_ES_VM);
if (have_snp)
test_flags(KVM_X86_SNP_VM);
test_features(KVM_X86_SEV_VM, 0);
if (have_sev_es)
test_features(KVM_X86_SEV_ES_VM, supported_vmsa_features);
if (have_snp)
test_features(KVM_X86_SNP_VM, supported_vmsa_features);
return 0;
}

View File

@ -16,6 +16,18 @@
#define XFEATURE_MASK_X87_AVX (XFEATURE_MASK_FP | XFEATURE_MASK_SSE | XFEATURE_MASK_YMM)
static void guest_snp_code(void)
{
uint64_t sev_msr = rdmsr(MSR_AMD64_SEV);
GUEST_ASSERT(sev_msr & MSR_AMD64_SEV_ENABLED);
GUEST_ASSERT(sev_msr & MSR_AMD64_SEV_ES_ENABLED);
GUEST_ASSERT(sev_msr & MSR_AMD64_SEV_SNP_ENABLED);
wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
vmgexit();
}
static void guest_sev_es_code(void)
{
/* TODO: Check CPUID after GHCB-based hypercall support is added. */
@ -27,7 +39,7 @@ static void guest_sev_es_code(void)
* force "termination" to signal "done" via the GHCB MSR protocol.
*/
wrmsr(MSR_AMD64_SEV_ES_GHCB, GHCB_MSR_TERM_REQ);
__asm__ __volatile__("rep; vmmcall");
vmgexit();
}
static void guest_sev_code(void)
@ -62,7 +74,7 @@ static void compare_xsave(u8 *from_host, u8 *from_guest)
abort();
}
static void test_sync_vmsa(uint32_t policy)
static void test_sync_vmsa(uint32_t type, uint64_t policy)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
@ -72,7 +84,7 @@ static void test_sync_vmsa(uint32_t policy)
double x87val = M_PI;
struct kvm_xsave __attribute__((aligned(64))) xsave = { 0 };
vm = vm_sev_create_with_one_vcpu(KVM_X86_SEV_ES_VM, guest_code_xsave, &vcpu);
vm = vm_sev_create_with_one_vcpu(type, guest_code_xsave, &vcpu);
gva = vm_vaddr_alloc_shared(vm, PAGE_SIZE, KVM_UTIL_MIN_VADDR,
MEM_REGION_TEST_DATA);
hva = addr_gva2hva(vm, gva);
@ -89,7 +101,7 @@ static void test_sync_vmsa(uint32_t policy)
: "ymm4", "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)");
vcpu_xsave_set(vcpu, &xsave);
vm_sev_launch(vm, SEV_POLICY_ES | policy, NULL);
vm_sev_launch(vm, policy, NULL);
/* This page is shared, so make it decrypted. */
memset(hva, 0, 4096);
@ -108,14 +120,12 @@ static void test_sync_vmsa(uint32_t policy)
kvm_vm_free(vm);
}
static void test_sev(void *guest_code, uint64_t policy)
static void test_sev(void *guest_code, uint32_t type, uint64_t policy)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
uint32_t type = policy & SEV_POLICY_ES ? KVM_X86_SEV_ES_VM : KVM_X86_SEV_VM;
vm = vm_sev_create_with_one_vcpu(type, guest_code, &vcpu);
/* TODO: Validate the measurement is as expected. */
@ -124,7 +134,7 @@ static void test_sev(void *guest_code, uint64_t policy)
for (;;) {
vcpu_run(vcpu);
if (policy & SEV_POLICY_ES) {
if (is_sev_es_vm(vm)) {
TEST_ASSERT(vcpu->run->exit_reason == KVM_EXIT_SYSTEM_EVENT,
"Wanted SYSTEM_EVENT, got %s",
exit_reason_str(vcpu->run->exit_reason));
@ -161,16 +171,14 @@ static void guest_shutdown_code(void)
__asm__ __volatile__("ud2");
}
static void test_sev_es_shutdown(void)
static void test_sev_shutdown(uint32_t type, uint64_t policy)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
uint32_t type = KVM_X86_SEV_ES_VM;
vm = vm_sev_create_with_one_vcpu(type, guest_shutdown_code, &vcpu);
vm_sev_launch(vm, SEV_POLICY_ES, NULL);
vm_sev_launch(vm, policy, NULL);
vcpu_run(vcpu);
TEST_ASSERT(vcpu->run->exit_reason == KVM_EXIT_SHUTDOWN,
@ -180,27 +188,42 @@ static void test_sev_es_shutdown(void)
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
static void test_sev_smoke(void *guest, uint32_t type, uint64_t policy)
{
const u64 xf_mask = XFEATURE_MASK_X87_AVX;
if (type == KVM_X86_SNP_VM)
test_sev(guest, type, policy | SNP_POLICY_DBG);
else
test_sev(guest, type, policy | SEV_POLICY_NO_DBG);
test_sev(guest, type, policy);
if (type == KVM_X86_SEV_VM)
return;
test_sev_shutdown(type, policy);
if (kvm_has_cap(KVM_CAP_XCRS) &&
(xgetbv(0) & kvm_cpu_supported_xcr0() & xf_mask) == xf_mask) {
test_sync_vmsa(type, policy);
if (type == KVM_X86_SNP_VM)
test_sync_vmsa(type, policy | SNP_POLICY_DBG);
else
test_sync_vmsa(type, policy | SEV_POLICY_NO_DBG);
}
}
int main(int argc, char *argv[])
{
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SEV));
test_sev(guest_sev_code, SEV_POLICY_NO_DBG);
test_sev(guest_sev_code, 0);
test_sev_smoke(guest_sev_code, KVM_X86_SEV_VM, 0);
if (kvm_cpu_has(X86_FEATURE_SEV_ES)) {
test_sev(guest_sev_es_code, SEV_POLICY_ES | SEV_POLICY_NO_DBG);
test_sev(guest_sev_es_code, SEV_POLICY_ES);
if (kvm_cpu_has(X86_FEATURE_SEV_ES))
test_sev_smoke(guest_sev_es_code, KVM_X86_SEV_ES_VM, SEV_POLICY_ES);
test_sev_es_shutdown();
if (kvm_has_cap(KVM_CAP_XCRS) &&
(xgetbv(0) & kvm_cpu_supported_xcr0() & xf_mask) == xf_mask) {
test_sync_vmsa(0);
test_sync_vmsa(SEV_POLICY_NO_DBG);
}
}
if (kvm_cpu_has(X86_FEATURE_SEV_SNP))
test_sev_smoke(guest_snp_code, KVM_X86_SNP_VM, snp_default_policy());
return 0;
}