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

KVM selftests changes for 7.3, part 1

 - Clean up nested SVM's handling of GPRs on L2<=>L1 transitions, reuse the
   functionality for nested VMX, and drop the ucall hack that was fudging
   around the lack of GPR switching on nVMX.

 - Add a stress test to verify KVM doesn't clobber/drop #PF state, e.g. CR2,
   across save/restore, including when L2 is active.

 - Add a test to verify KVM_CREATE_VM accepts exactly what is reported by
   KVM_CAP_VM_TYPES.

 - Misc selftests fixes and cleanups
This commit is contained in:
Paolo Bonzini
2026-08-18 13:16:16 +02:00
22 changed files with 502 additions and 160 deletions

View File

@@ -66,6 +66,7 @@ TEST_GEN_PROGS_COMMON += kvm_page_table_test
TEST_GEN_PROGS_COMMON += set_memory_region_test
TEST_GEN_PROGS_COMMON += memslot_modification_stress_test
TEST_GEN_PROGS_COMMON += memslot_perf_test
TEST_GEN_PROGS_COMMON += vm_types_test
# Compiled test targets
TEST_GEN_PROGS_x86 = $(TEST_GEN_PROGS_COMMON)
@@ -106,6 +107,7 @@ TEST_GEN_PROGS_x86 += x86/pmu_counters_test
TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
TEST_GEN_PROGS_x86 += x86/private_mem_conversions_test
TEST_GEN_PROGS_x86 += x86/private_mem_kvm_exits_test
TEST_GEN_PROGS_x86 += x86/save_restore_pf_stress_test
TEST_GEN_PROGS_x86 += x86/set_boot_cpu_id
TEST_GEN_PROGS_x86 += x86/set_sregs_test
TEST_GEN_PROGS_x86 += x86/smaller_maxphyaddr_emulation_test

View File

@@ -23,6 +23,7 @@
#include <linux/mman.h>
#include <linux/types.h>
#include <linux/stringify.h>
#define msecs_to_usecs(msec) ((msec) * 1000ULL)

View File

@@ -1207,30 +1207,23 @@ static inline int evmcs_vmlaunch(void)
current_evmcs->hv_clean_fields = 0;
__asm__ __volatile__("push %%rbp;"
"push %%rcx;"
"push %%rdx;"
"push %%rsi;"
"push %%rdi;"
"push $0;"
__asm__ __volatile__("push $0;"
"mov %%rsp, (%[host_rsp]);"
"lea 1f(%%rip), %%rax;"
"mov %%rax, (%[host_rip]);"
VMX_SWITCH_GPRS_ASM
"vmlaunch;"
"incq (%%rsp);"
"1: pop %%rax;"
"pop %%rdi;"
"pop %%rsi;"
"pop %%rdx;"
"pop %%rcx;"
"pop %%rbp;"
"1: ;"
VMX_SWITCH_GPRS_ASM
"pop %%rax;"
: [ret]"=&a"(ret)
: [host_rsp]"r"
((u64)&current_evmcs->host_rsp),
[host_rip]"r"
((u64)&current_evmcs->host_rip)
: "memory", "cc", "rbx", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15");
((u64)&current_evmcs->host_rip),
GUEST_REGS_OFFSETS
: "memory", "cc");
return ret;
}
@@ -1246,30 +1239,23 @@ static inline int evmcs_vmresume(void)
/* HOST_RSP */
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
__asm__ __volatile__("push %%rbp;"
"push %%rcx;"
"push %%rdx;"
"push %%rsi;"
"push %%rdi;"
"push $0;"
__asm__ __volatile__("push $0;"
"mov %%rsp, (%[host_rsp]);"
"lea 1f(%%rip), %%rax;"
"mov %%rax, (%[host_rip]);"
VMX_SWITCH_GPRS_ASM
"vmresume;"
"incq (%%rsp);"
"1: pop %%rax;"
"pop %%rdi;"
"pop %%rsi;"
"pop %%rdx;"
"pop %%rcx;"
"pop %%rbp;"
"1: ;"
VMX_SWITCH_GPRS_ASM
"pop %%rax;"
: [ret]"=&a"(ret)
: [host_rsp]"r"
((u64)&current_evmcs->host_rsp),
[host_rip]"r"
((u64)&current_evmcs->host_rip)
: "memory", "cc", "rbx", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15");
((u64)&current_evmcs->host_rip),
GUEST_REGS_OFFSETS
: "memory", "cc");
return ret;
}

View File

@@ -24,6 +24,7 @@ extern bool host_cpu_is_amd;
extern bool host_cpu_is_hygon;
extern bool host_cpu_is_amd_compatible;
extern u64 guest_tsc_khz;
extern struct kvm_mmu guest_mmu;
#ifndef MAX_NR_CPUID_ENTRIES
#define MAX_NR_CPUID_ENTRIES 100
@@ -396,8 +397,7 @@ static inline unsigned int x86_model(unsigned int eax)
#define PTE_GET_PA(pte) ((pte) & PHYSICAL_PAGE_MASK)
#define PTE_GET_PFN(pte) (PTE_GET_PA(pte) >> PAGE_SHIFT)
/* General Registers in 64-Bit Mode */
struct gpr64_regs {
struct guest_regs {
u64 rax;
u64 rcx;
u64 rdx;
@@ -414,8 +414,38 @@ struct gpr64_regs {
u64 r13;
u64 r14;
u64 r15;
u64 rflags;
};
extern struct guest_regs guest_regs;
#define GUEST_REG_OFFSET(name) \
[off_##name] "i" (offsetof(struct guest_regs, name))
#define GUEST_REGS_OFFSETS \
GUEST_REG_OFFSET(rax), \
GUEST_REG_OFFSET(rcx), \
GUEST_REG_OFFSET(rdx), \
GUEST_REG_OFFSET(rbx), \
GUEST_REG_OFFSET(rsp), \
GUEST_REG_OFFSET(rbp), \
GUEST_REG_OFFSET(rsi), \
GUEST_REG_OFFSET(rdi), \
GUEST_REG_OFFSET(r8), \
GUEST_REG_OFFSET(r9), \
GUEST_REG_OFFSET(r10), \
GUEST_REG_OFFSET(r11), \
GUEST_REG_OFFSET(r12), \
GUEST_REG_OFFSET(r13), \
GUEST_REG_OFFSET(r14), \
GUEST_REG_OFFSET(r15), \
GUEST_REG_OFFSET(rflags)
#define GUEST_REG(name) "guest_regs + %c[off_" #name "]"
#define GUEST_SWITCH_GPR_ASM(name) \
"xchg %%" #name ", " GUEST_REG(name) "\n\t"
struct desc64 {
u16 limit0;
u16 base0;
@@ -582,6 +612,14 @@ static inline void set_cr0(u64 val)
__asm__ __volatile__("mov %0, %%cr0" : : "r" (val) : "memory");
}
static inline u64 get_cr2(void)
{
u64 cr2;
__asm__ __volatile__("mov %%cr2, %[cr2]" : [cr2]"=r"(cr2));
return cr2;
}
static inline u64 get_cr3(void)
{
u64 cr3;
@@ -877,6 +915,11 @@ static inline void write_sse_reg(int reg, const sse128_t *data)
}
}
static inline void invlpg(u64 addr)
{
__asm__ __volatile__("invlpg (%0)" : : "r"(addr) : "memory");
}
static inline void cpu_relax(void)
{
asm volatile("rep; nop" ::: "memory");
@@ -913,6 +956,11 @@ struct kvm_x86_state *vcpu_save_state(struct kvm_vcpu *vcpu);
void vcpu_load_state(struct kvm_vcpu *vcpu, struct kvm_x86_state *state);
void kvm_x86_state_cleanup(struct kvm_x86_state *state);
static inline bool kvm_x86_state_is_guest_mode(struct kvm_x86_state *state)
{
return state->nested.size && (state->nested.flags & KVM_STATE_NESTED_GUEST_MODE);
}
const struct kvm_msr_list *kvm_get_msr_index_list(void);
const struct kvm_msr_list *kvm_get_feature_msr_index_list(void);
bool kvm_msr_is_in_save_restore_list(u32 msr_index);

View File

@@ -290,6 +290,23 @@ struct vmx_msr_entry {
u64 value;
} __attribute__ ((aligned(16)));
#define VMX_SWITCH_GPRS_ASM \
GUEST_SWITCH_GPR_ASM(rax) \
GUEST_SWITCH_GPR_ASM(rbx) \
GUEST_SWITCH_GPR_ASM(rcx) \
GUEST_SWITCH_GPR_ASM(rdx) \
GUEST_SWITCH_GPR_ASM(rbp) \
GUEST_SWITCH_GPR_ASM(rsi) \
GUEST_SWITCH_GPR_ASM(rdi) \
GUEST_SWITCH_GPR_ASM(r8) \
GUEST_SWITCH_GPR_ASM(r9) \
GUEST_SWITCH_GPR_ASM(r10) \
GUEST_SWITCH_GPR_ASM(r11) \
GUEST_SWITCH_GPR_ASM(r12) \
GUEST_SWITCH_GPR_ASM(r13) \
GUEST_SWITCH_GPR_ASM(r14) \
GUEST_SWITCH_GPR_ASM(r15)
#include "evmcs.h"
static inline int vmxon(u64 phys)
@@ -363,9 +380,6 @@ static inline u64 vmptrstz(void)
return value;
}
/*
* No guest state (e.g. GPRs) is established by this vmlaunch.
*/
static inline int vmlaunch(void)
{
int ret;
@@ -373,34 +387,24 @@ static inline int vmlaunch(void)
if (enable_evmcs)
return evmcs_vmlaunch();
__asm__ __volatile__("push %%rbp;"
"push %%rcx;"
"push %%rdx;"
"push %%rsi;"
"push %%rdi;"
"push $0;"
__asm__ __volatile__("push $0;"
"vmwrite %%rsp, %[host_rsp];"
"lea 1f(%%rip), %%rax;"
"vmwrite %%rax, %[host_rip];"
VMX_SWITCH_GPRS_ASM
"vmlaunch;"
"incq (%%rsp);"
"1: pop %%rax;"
"pop %%rdi;"
"pop %%rsi;"
"pop %%rdx;"
"pop %%rcx;"
"pop %%rbp;"
"1: ;"
VMX_SWITCH_GPRS_ASM
"pop %%rax;"
: [ret]"=&a"(ret)
: [host_rsp]"r"((u64)HOST_RSP),
[host_rip]"r"((u64)HOST_RIP)
: "memory", "cc", "rbx", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15");
[host_rip]"r"((u64)HOST_RIP),
GUEST_REGS_OFFSETS
: "memory", "cc");
return ret;
}
/*
* No guest state (e.g. GPRs) is established by this vmresume.
*/
static inline int vmresume(void)
{
int ret;
@@ -408,28 +412,21 @@ static inline int vmresume(void)
if (enable_evmcs)
return evmcs_vmresume();
__asm__ __volatile__("push %%rbp;"
"push %%rcx;"
"push %%rdx;"
"push %%rsi;"
"push %%rdi;"
"push $0;"
__asm__ __volatile__("push $0;"
"vmwrite %%rsp, %[host_rsp];"
"lea 1f(%%rip), %%rax;"
"vmwrite %%rax, %[host_rip];"
VMX_SWITCH_GPRS_ASM
"vmresume;"
"incq (%%rsp);"
"1: pop %%rax;"
"pop %%rdi;"
"pop %%rsi;"
"pop %%rdx;"
"pop %%rcx;"
"pop %%rbp;"
"1: ;"
VMX_SWITCH_GPRS_ASM
"pop %%rax;"
: [ret]"=&a"(ret)
: [host_rsp]"r"((u64)HOST_RSP),
[host_rip]"r"((u64)HOST_RIP)
: "memory", "cc", "rbx", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15");
[host_rip]"r"((u64)HOST_RIP),
GUEST_REGS_OFFSETS
: "memory", "cc");
return ret;
}

View File

@@ -78,7 +78,7 @@ test_assert(bool exp, const char *exp_str,
if (!(exp)) {
va_start(ap, fmt);
fprintf(stderr, "==== Test Assertion Failure ====\n"
fprintf(stderr, "\n==== Test Assertion Failure ====\n"
" %s:%u: %s\n"
" pid=%d tid=%d errno=%d - %s\n",
file, line, exp_str, getpid(), _gettid(),

View File

@@ -28,6 +28,9 @@ bool host_cpu_is_hygon;
bool host_cpu_is_amd_compatible;
bool is_forced_emulation_enabled;
u64 guest_tsc_khz;
struct kvm_mmu guest_mmu;
struct guest_regs guest_regs;
const char *ex_str(int vector)
{
@@ -829,6 +832,17 @@ void kvm_arch_vm_post_create(struct kvm_vm *vm, unsigned int nr_vcpus)
TEST_ASSERT(r > 0, "KVM_GET_TSC_KHZ did not provide a valid TSC frequency.");
guest_tsc_khz = r;
sync_global_to_guest(vm, guest_tsc_khz);
/*
* The guest MMU is just a placeholder to provide access to PTE masks
* (for now). The guest does not have mappings for its own page tables
* by default, so any meaningful use of guest page tables requires
* explicit setup by the test. Zero the PGD to make it obvious the guest
* page tables are not immediately usable by guest code.
*/
guest_mmu = vm->mmu;
guest_mmu.pgd = 0;
sync_global_to_guest(vm, guest_mmu);
}
void vcpu_arch_set_entry_point(struct kvm_vcpu *vcpu, void *guest_code)

View File

@@ -13,9 +13,6 @@
#define SEV_DEV_PATH "/dev/sev"
struct gpr64_regs guest_regs;
u64 rflags;
/* Allocate memory regions for nested SVM tests.
*
* Input Args:
@@ -137,46 +134,47 @@ void generic_svm_setup(struct svm_test_data *svm, void *guest_rip)
* save/restore 64-bit general registers except rax, rip, rsp
* which are directly handed through the VMCB guest processor state
*/
#define SAVE_GPR_C \
"xchg %%rbx, guest_regs+0x20\n\t" \
"xchg %%rcx, guest_regs+0x10\n\t" \
"xchg %%rdx, guest_regs+0x18\n\t" \
"xchg %%rbp, guest_regs+0x30\n\t" \
"xchg %%rsi, guest_regs+0x38\n\t" \
"xchg %%rdi, guest_regs+0x40\n\t" \
"xchg %%r8, guest_regs+0x48\n\t" \
"xchg %%r9, guest_regs+0x50\n\t" \
"xchg %%r10, guest_regs+0x58\n\t" \
"xchg %%r11, guest_regs+0x60\n\t" \
"xchg %%r12, guest_regs+0x68\n\t" \
"xchg %%r13, guest_regs+0x70\n\t" \
"xchg %%r14, guest_regs+0x78\n\t" \
"xchg %%r15, guest_regs+0x80\n\t"
#define LOAD_GPR_C SAVE_GPR_C
#define SVM_SWITCH_GPRS_ASM \
GUEST_SWITCH_GPR_ASM(rbx) \
GUEST_SWITCH_GPR_ASM(rcx) \
GUEST_SWITCH_GPR_ASM(rdx) \
GUEST_SWITCH_GPR_ASM(rbp) \
GUEST_SWITCH_GPR_ASM(rsi) \
GUEST_SWITCH_GPR_ASM(rdi) \
GUEST_SWITCH_GPR_ASM(r8) \
GUEST_SWITCH_GPR_ASM(r9) \
GUEST_SWITCH_GPR_ASM(r10) \
GUEST_SWITCH_GPR_ASM(r11) \
GUEST_SWITCH_GPR_ASM(r12) \
GUEST_SWITCH_GPR_ASM(r13) \
GUEST_SWITCH_GPR_ASM(r14) \
GUEST_SWITCH_GPR_ASM(r15)
/*
* selftests do not use interrupts so we dropped clgi/sti/cli/stgi
* for now. registers involved in LOAD/SAVE_GPR_C are eventually
* for now. Registers involved in SVM_SWITCH_GPRS_ASM are eventually
* unmodified so they do not need to be in the clobber list.
*/
void run_guest(struct vmcb *vmcb, u64 vmcb_gpa)
{
asm volatile (
"vmload %[vmcb_gpa]\n\t"
"mov rflags, %%r15\n\t" // rflags
"mov %%r15, 0x170(%[vmcb])\n\t"
"mov guest_regs, %%r15\n\t" // rax
"mov %%r15, 0x1f8(%[vmcb])\n\t"
LOAD_GPR_C
"mov " GUEST_REG(rflags) ", %%r15\n\t"
"mov %%r15, %[vmcb_rflags]\n\t"
"mov " GUEST_REG(rax) ", %%r15\n\t"
"mov %%r15, %[vmcb_rax]\n\t"
SVM_SWITCH_GPRS_ASM
"vmrun %[vmcb_gpa]\n\t"
SAVE_GPR_C
"mov 0x170(%[vmcb]), %%r15\n\t" // rflags
"mov %%r15, rflags\n\t"
"mov 0x1f8(%[vmcb]), %%r15\n\t" // rax
"mov %%r15, guest_regs\n\t"
SVM_SWITCH_GPRS_ASM
"mov %[vmcb_rflags], %%r15\n\t"
"mov %%r15, " GUEST_REG(rflags) "\n\t"
"mov %[vmcb_rax], %%r15\n\t" // rax
"mov %%r15, " GUEST_REG(rax) "\n\t"
"vmsave %[vmcb_gpa]\n\t"
: : [vmcb] "r" (vmcb), [vmcb_gpa] "a" (vmcb_gpa)
: [vmcb_rflags] "+m" (vmcb->save.rflags),
[vmcb_rax] "+m" (vmcb->save.rax)
: [vmcb_gpa] "a" (vmcb_gpa),
GUEST_REGS_OFFSETS
: "r15", "memory");
}

View File

@@ -10,36 +10,8 @@
void ucall_arch_do_ucall(gva_t uc)
{
/*
* FIXME: Revert this hack (the entire commit that added it) once nVMX
* preserves L2 GPRs across a nested VM-Exit. If a ucall from L2, e.g.
* to do a GUEST_SYNC(), lands the vCPU in L1, any and all GPRs can be
* clobbered by L1. Save and restore non-volatile GPRs (clobbering RBP
* in particular is problematic) along with RDX and RDI (which are
* inputs), and clobber volatile GPRs. *sigh*
*/
#define HORRIFIC_L2_UCALL_CLOBBER_HACK \
"rcx", "rsi", "r8", "r9", "r10", "r11"
asm volatile("push %%rbp\n\t"
"push %%r15\n\t"
"push %%r14\n\t"
"push %%r13\n\t"
"push %%r12\n\t"
"push %%rbx\n\t"
"push %%rdx\n\t"
"push %%rdi\n\t"
"in %[port], %%al\n\t"
"pop %%rdi\n\t"
"pop %%rdx\n\t"
"pop %%rbx\n\t"
"pop %%r12\n\t"
"pop %%r13\n\t"
"pop %%r14\n\t"
"pop %%r15\n\t"
"pop %%rbp\n\t"
: : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory",
HORRIFIC_L2_UCALL_CLOBBER_HACK);
asm volatile("in %[port], %%al"
: : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax", "memory");
}
void *ucall_arch_get_ucall(struct kvm_vcpu *vcpu)

View File

@@ -232,7 +232,7 @@ static inline void init_vmcs_control_fields(struct vmx_pages *vmx)
vmwrite(EXCEPTION_BITMAP, 0);
vmwrite(PAGE_FAULT_ERROR_CODE_MASK, 0);
vmwrite(PAGE_FAULT_ERROR_CODE_MATCH, -1); /* Never match */
vmwrite(PAGE_FAULT_ERROR_CODE_MATCH, 0);
vmwrite(CR3_TARGET_COUNT, 0);
vmwrite(VM_EXIT_CONTROLS, rdmsr(MSR_IA32_VMX_EXIT_CTLS) |
VM_EXIT_HOST_ADDR_SPACE_SIZE); /* 64-bit host */

View File

@@ -492,7 +492,7 @@ static void test_pmu_events_snaphost(void)
struct riscv_pmu_snapshot_data *snapshot_data = snapshot_gva;
int i;
/* Verify presence of SBI PMU and minimum requrired SBI version */
/* Verify presence of SBI PMU and minimum required SBI version */
verify_sbi_requirement_assert();
snapshot_set_shmem(snapshot_gpa, 0);
@@ -518,7 +518,7 @@ static void test_pmu_events_overflow(void)
{
int num_counters = 0, i = 0;
/* Verify presence of SBI PMU and minimum requrired SBI version */
/* Verify presence of SBI PMU and minimum required SBI version */
verify_sbi_requirement_assert();
snapshot_set_shmem(snapshot_gpa, 0);

View File

@@ -508,23 +508,18 @@ int main(int ac, char **av)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
struct kvm_vm *vm;
pthread_attr_t attr;
pthread_t thread;
cpu_set_t cpuset;
unsigned int gpages;
long stolen_time;
long run_delay;
bool verbose;
int i;
int i, cpu;
verbose = ac > 1 && (!strncmp(av[1], "-v", 3) || !strncmp(av[1], "--verbose", 10));
/* Set CPU affinity so we can force preemption of the VCPU */
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
pthread_attr_init(&attr);
pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
cpu = pin_self_to_any_cpu();
/* Create a VM and an identity mapped memslot for the steal time structure */
vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
@@ -558,7 +553,11 @@ int main(int ac, char **av)
/* Steal time from the VCPU. The steal time thread has the same CPU affinity as the VCPUs. */
run_delay = get_run_delay();
pthread_create(&thread, &attr, do_steal_time, NULL);
pthread_create(&thread, NULL, do_steal_time, NULL);
pthread_getaffinity_np(thread, sizeof(cpuset), &cpuset);
TEST_ASSERT(CPU_COUNT(&cpuset) == 1 && CPU_ISSET(cpu, &cpuset),
"Worker failed to inherit parent's CPU affinity");
do
sched_yield();
while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS);

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Verify that KVM_CREATE_VM accepts exactly the VM types enumerated by
* KVM_CAP_VM_TYPES, and rejects every other type with -EINVAL.
*/
#include <errno.h>
#include <stdbool.h>
#include <unistd.h>
#include <linux/kvm.h>
#include "kvm_util.h"
#include "test_util.h"
int main(void)
{
unsigned long type, supported_types;
int kvm_fd;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_VM_TYPES));
kvm_fd = open_kvm_dev_path_or_exit();
supported_types = kvm_check_cap(KVM_CAP_VM_TYPES);
pr_info("Supported VM types: 0x%lx\n", supported_types);
/*
* For compatibility with 32-bit kernels, KVM_CHECK_EXTENSION restricts
* its return to 32-bit values, i.e. only types 0..31 can be advertised.
* Walk past that range as well to confirm that any out-of-range type is
* rejected rather than silently accepted (or truncated).
*/
for (type = 0; type < BITS_PER_TYPE(supported_types); type++) {
int fd = __kvm_ioctl(kvm_fd, KVM_CREATE_VM, (void *)type);
if (supported_types & BIT(type)) {
TEST_ASSERT(fd >= 0,
"KVM_CREATE_VM(%lu) should succeed, supported types = 0x%lx",
type, supported_types);
kvm_close(fd);
} else {
TEST_ASSERT(fd < 0 && errno == EINVAL,
"KVM_CREATE_VM(%lu) should fail with EINVAL, supported types = 0x%lx",
type, supported_types);
}
}
return 0;
}

View File

@@ -22,9 +22,6 @@
#define SYNC_PORT 0xe
#define STR(x) #x
#define XSTR(s) STR(s)
/*
* SMI handler: runs in real-address mode.
* Reports SMRAM_STAGE via port IO, then does RSM.
@@ -37,7 +34,7 @@ static u8 smi_handler[] = {
static inline void sync_with_host(u64 phase)
{
asm volatile("in $" XSTR(SYNC_PORT) ", %%al \n"
asm volatile("in $" __stringify(SYNC_PORT) ", %%al \n"
: "+a" (phase));
}

View File

@@ -6,7 +6,6 @@
*/
#include <asm/kvm_para.h>
#include <linux/kvm_para.h>
#include <linux/stringify.h>
#include <stdint.h>
#include "kvm_test_harness.h"

View File

@@ -56,7 +56,7 @@ static inline void check_tsc_msr_rdtsc(void)
tsc_freq = rdmsr(HV_X64_MSR_TSC_FREQUENCY);
GUEST_ASSERT(tsc_freq > 0);
/* For increased accuracy, take mean rdtsc() before and afrer rdmsr() */
/* For increased accuracy, take mean rdtsc() before and after rdmsr() */
r1 = rdtsc();
t1 = rdmsr(HV_X64_MSR_TIME_REF_COUNT);
r1 = (r1 + rdtsc()) / 2;
@@ -181,7 +181,7 @@ static void host_check_tsc_msr_rdtsc(struct kvm_vcpu *vcpu)
tsc_freq = vcpu_get_msr(vcpu, HV_X64_MSR_TSC_FREQUENCY);
TEST_ASSERT(tsc_freq > 0, "TSC frequency must be nonzero");
/* For increased accuracy, take mean rdtsc() before and afrer ioctl */
/* For increased accuracy, take mean rdtsc() before and after ioctl */
r1 = rdtsc();
t1 = vcpu_get_msr(vcpu, HV_X64_MSR_TIME_REF_COUNT);
r1 = (r1 + rdtsc()) / 2;

View File

@@ -125,7 +125,7 @@ void guest_code(struct vmx_pages *vmx_pages, struct hyperv_test_pages *hv_pages,
/*
* NMI forces L2->L1 exit, resuming L2 and hope that EVMCS is
* up-to-date (RIP points where it should and not at the beginning
* of l2_guest_code(). GUEST_SYNC(9) checkes that.
* of l2_guest_code(). GUEST_SYNC(9) checks that.
*/
GUEST_ASSERT(!vmresume());

View File

@@ -0,0 +1,288 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <getopt.h>
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
#include "svm_util.h"
#include "vmx.h"
#define NR_ITERATIONS 500
#define PTRS_PER_PTE 512
#define PXD_INDEX(vaddr, level) (((vaddr) >> PG_LEVEL_SHIFT(level)) & (PTRS_PER_PTE - 1))
#define TEST_MEM_BASE_GVA 0xc0000000ULL
#define TEST_PGTABLE_GVA_OFFSET 0xd0000000ULL
#define PATTERN 0xabcdefabcdefabcdULL
static u64 expected_vaddr;
static u64 guest_faults;
static u64 *guest_get_pte(u64 vaddr)
{
u64 pgtable_pa, pte;
u64 *pgtable;
int level;
level = (get_cr4() & X86_CR4_LA57) ? PG_LEVEL_256T : PG_LEVEL_512G;
pgtable_pa = get_cr3() & PHYSICAL_PAGE_MASK;
for (; level > PG_LEVEL_4K; level--) {
pgtable = (u64 *)(pgtable_pa + TEST_PGTABLE_GVA_OFFSET);
pte = pgtable[PXD_INDEX(vaddr, level)];
GUEST_ASSERT(pte & PTE_PRESENT_MASK(&guest_mmu));
GUEST_ASSERT(!(pte & PTE_HUGE_MASK(&guest_mmu)));
pgtable_pa = PTE_GET_PA(pte);
}
pgtable = (u64 *)(pgtable_pa + TEST_PGTABLE_GVA_OFFSET);
return &pgtable[PXD_INDEX(vaddr, PG_LEVEL_4K)];
}
static void guest_pf_handler(struct ex_regs *regs)
{
u64 fault_addr;
u64 *ptep;
fault_addr = get_cr2();
GUEST_ASSERT_EQ(fault_addr, READ_ONCE(expected_vaddr));
ptep = guest_get_pte(fault_addr);
GUEST_ASSERT(ptep);
GUEST_ASSERT(!(*ptep & PTE_PRESENT_MASK(&guest_mmu)));
*ptep |= PTE_PRESENT_MASK(&guest_mmu);
guest_faults++;
}
static void guest_access_memory(void *arg)
{
u64 vaddr, val;
int i;
for (i = 0; ; i++) {
vaddr = TEST_MEM_BASE_GVA + (i % PTRS_PER_PTE) * PAGE_SIZE;
WRITE_ONCE(expected_vaddr, vaddr);
/* Read to trigger #PF */
val = READ_ONCE(*(u64 *)vaddr);
GUEST_ASSERT_EQ(val, PATTERN);
/* Clear the present bit again so it faults next time */
*guest_get_pte(vaddr) &= ~PTE_PRESENT_MASK(&guest_mmu);
invlpg(vaddr);
}
}
static void l1_svm_code(struct svm_test_data *svm)
{
generic_svm_setup(svm, guest_access_memory);
svm->vmcb->control.intercept_exceptions |= BIT(UD_VECTOR);
while (1) {
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code,
(SVM_EXIT_EXCP_BASE + UD_VECTOR));
}
}
static void l1_vmx_code(struct vmx_pages *vmx)
{
GUEST_ASSERT(prepare_for_vmx_operation(vmx));
GUEST_ASSERT(load_vmcs(vmx));
prepare_vmcs(vmx, guest_access_memory);
GUEST_ASSERT(!vmwrite(EXCEPTION_BITMAP, BIT(UD_VECTOR)));
GUEST_ASSERT(!vmlaunch());
while (1) {
GUEST_ASSERT_EQ(vmreadz(VM_EXIT_REASON), EXIT_REASON_EXCEPTION_NMI);
GUEST_ASSERT_EQ(vmreadz(VM_EXIT_INTR_INFO) & 0xff, UD_VECTOR);
GUEST_ASSERT(!vmresume());
}
}
static void l1_guest_code(void *test_data)
{
if (this_cpu_has(X86_FEATURE_SVM))
l1_svm_code(test_data);
else
l1_vmx_code(test_data);
}
static void *sigusr_thread_fn(void *arg)
{
pthread_t vcpu_thread = (pthread_t)arg;
for (;;) {
pthread_testcancel();
pthread_kill(vcpu_thread, SIGUSR1);
usleep(msecs_to_usecs(1));
}
return NULL;
}
static void dummy_signal_handler(int signo) {}
static struct sigaction sa;
static void vcpu_sigusr_listen(void)
{
sa.sa_handler = dummy_signal_handler;
sigaction(SIGUSR1, &sa, NULL);
}
static void vcpu_sigusr_ignore(void)
{
sa.sa_handler = SIG_IGN;
sigaction(SIGUSR1, &sa, NULL);
}
static void kvm_x86_state_queue_ud(struct kvm_x86_state *state)
{
if (state->events.exception.pending || state->events.exception.injected)
return;
state->events.flags |= KVM_VCPUEVENT_VALID_PAYLOAD;
state->events.exception.pending = true;
state->events.exception.injected = false;
state->events.exception.nr = UD_VECTOR;
state->events.exception.has_error_code = false;
state->events.exception_has_payload = false;
}
static void run_test(bool nested)
{
struct kvm_x86_state *state;
int r, i, level;
pthread_t sigusr_thread;
gpa_t gpa, pgtable_gpa;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
u64 *pgtable;
gva_t gva;
u64 pte;
vm = vm_create_with_one_vcpu(&vcpu, nested ? l1_guest_code : guest_access_memory);
vm_install_exception_handler(vm, PF_VECTOR, guest_pf_handler);
if (nested) {
vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
if (kvm_cpu_has(X86_FEATURE_SVM))
vcpu_alloc_svm(vm, &gva);
else
vcpu_alloc_vmx(vm, &gva);
vcpu_args_set(vcpu, 1, gva);
}
/* Allocate a page and write the pattern to it */
gva = vm_alloc_page(vm);
*(u64 *)addr_gva2hva(vm, gva) = PATTERN;
gpa = addr_gva2gpa(vm, gva);
/*
* Map all virtual addresses to the pattern page and clear the present
* bit such that guest accesses will cause a #PF.
*/
for (i = 0; i < PTRS_PER_PTE; i++) {
gva = TEST_MEM_BASE_GVA + i * getpagesize();
virt_pg_map(vm, gva, gpa);
*vm_get_pte(vm, gva) &= ~PTE_PRESENT_MASK(&vm->mmu);
}
/*
* Now create mappings for the page tables created above so that the
* guest #PF handler can walk them. All PTEs for test virtual addresses
* should lie on the same PTE page, so one page is mapped for each page
* table level.
*
* Use an offset for the GVA instead of creating identity mappings to
* avoid collision with existing mappings at low GVAs (e.g. ELF).
*/
pgtable_gpa = vm->mmu.pgd;
for (level = vm->mmu.pgtable_levels; level >= PG_LEVEL_4K; level--) {
virt_map(vm, pgtable_gpa + TEST_PGTABLE_GVA_OFFSET, pgtable_gpa, 1);
pgtable = addr_gpa2hva(vm, pgtable_gpa);
pte = pgtable[PXD_INDEX(TEST_MEM_BASE_GVA, level)];
pgtable_gpa = PTE_GET_PA(pte);
}
/* Initialize the thread sending SIGUSR and install the handler */
vcpu_sigusr_ignore();
r = pthread_create(&sigusr_thread, NULL, sigusr_thread_fn,
(void *)pthread_self());
TEST_ASSERT(!r, "pthread_create() failed: %d", r);
for (i = 1; i <= NR_ITERATIONS; i++) {
/*
* Only handle SIGUSR while the vCPU is running, otherwise
* ignore it to avoid interrupting other ioctls/syscalls.
*/
vcpu_sigusr_listen();
r = __vcpu_run(vcpu);
TEST_ASSERT(!r || errno == EINTR, "Expected success or SIGUSR1");
vcpu_sigusr_ignore();
/* The guest only exits due to a signal or failed assertion */
if (!r) {
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
TEST_ASSERT_EQ(get_ucall(vcpu, &uc), UCALL_ABORT);
REPORT_GUEST_ASSERT(uc);
break;
}
state = vcpu_save_state(vcpu);
/*
* If the vCPU is in guest mode, inject a #UD to trigger an
* L2->L1 VM-Exit every other iteration.
*/
if (kvm_x86_state_is_guest_mode(state) && i % 2 == 0)
kvm_x86_state_queue_ud(state);
kvm_vm_release(vm);
vcpu = vm_recreate_with_one_vcpu(vm);
if (nested)
vm_enable_cap(vm, KVM_CAP_EXCEPTION_PAYLOAD, -2ul);
vcpu_load_state(vcpu, state);
kvm_x86_state_cleanup(state);
pr_info("\rSave+restore iterations: %d", i);
}
pr_info("\n");
sync_global_from_guest(vm, guest_faults);
TEST_ASSERT(guest_faults, "No guest page faults triggered");
pr_info("Guest page faults%s: %lu\n", nested ? " (in L2)" : "", guest_faults);
pthread_cancel(sigusr_thread);
pthread_join(sigusr_thread, NULL);
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
pr_info("Running save+restore stress test...\n");
run_test(/*nested=*/false);
if (!kvm_has_cap(KVM_CAP_EXCEPTION_PAYLOAD) ||
!kvm_has_cap(KVM_CAP_NESTED_STATE) ||
(!kvm_cpu_has(X86_FEATURE_SVM) && !kvm_cpu_has(X86_FEATURE_VMX))) {
pr_info("Nested virtualization not supported, skipping nested test\n");
return 0;
}
pr_info("Running save+restore stress test with a nested guest...\n");
run_test(/*nested=*/true);
return 0;
}

View File

@@ -77,10 +77,6 @@ void test_vm_types(void)
{
test_init2(KVM_X86_SEV_VM, &(struct kvm_sev_init){});
/*
* TODO: check that unsupported types cannot be created. Probably
* a separate selftest.
*/
if (have_sev_es)
test_init2(KVM_X86_SEV_ES_VM, &(struct kvm_sev_init){});

View File

@@ -22,9 +22,6 @@
#define SMRAM_GPA 0x1000000
#define SMRAM_STAGE 0xfe
#define STR(x) #x
#define XSTR(s) STR(s)
#define SYNC_PORT 0xe
#define DONE 0xff
@@ -42,7 +39,7 @@ u8 smi_handler[] = {
static inline void sync_with_host(u64 phase)
{
asm volatile("in $" XSTR(SYNC_PORT)", %%al \n"
asm volatile("in $" __stringify(SYNC_PORT)", %%al \n"
: "+a" (phase));
}

View File

@@ -77,7 +77,7 @@ int main(int argc, char *argv[])
ARBITRARY_IO_PORT, run->io.port);
/*
* Stuff invalid guest state for L2 by making TR unusuable. The next
* Stuff invalid guest state for L2 by making TR unusable. The next
* KVM_RUN should induce a TRIPLE_FAULT in L2 as KVM doesn't support
* emulating invalid guest state for L2.
*/

View File

@@ -17,7 +17,7 @@
* amongst the available numa nodes on the machine.
*
* Migration is a command line option. When used on non-numa machines will
* exit with error. Test is still usefull on non-numa for testing IPIs.
* exit with error. Test is still useful on non-numa for testing IPIs.
*/
#include <getopt.h>
#include <pthread.h>