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
commit
3e0797f6dd
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
@ -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);
|
||||
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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, ¶ms);
|
||||
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, ¶ms);
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue