diff options
| author | Paolo Bonzini <pbonzini@redhat.com> | 2026-08-18 13:16:16 +0200 |
|---|---|---|
| committer | Paolo Bonzini <pbonzini@redhat.com> | 2026-08-18 13:16:16 +0200 |
| commit | 2bee2e6c983baa3605765621f26173ff0fa40365 (patch) | |
| tree | a5faf981dc3688880d5e76acbf06afdbf9f4ea1c /tools/testing | |
| parent | 9dc54ddece8af88b9d1ee8036c11b16a0be53bfa (diff) | |
| parent | b18ee21055a77eb1fb6070cb3f819cd3372c466e (diff) | |
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
Diffstat (limited to 'tools/testing')
22 files changed, 502 insertions, 160 deletions
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm index eea84f00caa2..52b66d809746 100644 --- a/tools/testing/selftests/kvm/Makefile.kvm +++ b/tools/testing/selftests/kvm/Makefile.kvm @@ -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 diff --git a/tools/testing/selftests/kvm/include/test_util.h b/tools/testing/selftests/kvm/include/test_util.h index a56271c237ae..93d589906fa0 100644 --- a/tools/testing/selftests/kvm/include/test_util.h +++ b/tools/testing/selftests/kvm/include/test_util.h @@ -23,6 +23,7 @@ #include <linux/mman.h> #include <linux/types.h> +#include <linux/stringify.h> #define msecs_to_usecs(msec) ((msec) * 1000ULL) diff --git a/tools/testing/selftests/kvm/include/x86/evmcs.h b/tools/testing/selftests/kvm/include/x86/evmcs.h index be79bda024bf..82a8ea6b661f 100644 --- a/tools/testing/selftests/kvm/include/x86/evmcs.h +++ b/tools/testing/selftests/kvm/include/x86/evmcs.h @@ -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)¤t_evmcs->host_rsp), [host_rip]"r" - ((u64)¤t_evmcs->host_rip) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + ((u64)¤t_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)¤t_evmcs->host_rsp), [host_rip]"r" - ((u64)¤t_evmcs->host_rip) - : "memory", "cc", "rbx", "r8", "r9", "r10", - "r11", "r12", "r13", "r14", "r15"); + ((u64)¤t_evmcs->host_rip), + GUEST_REGS_OFFSETS + : "memory", "cc"); return ret; } diff --git a/tools/testing/selftests/kvm/include/x86/processor.h b/tools/testing/selftests/kvm/include/x86/processor.h index 7d3a27bc0d84..a645262e4767 100644 --- a/tools/testing/selftests/kvm/include/x86/processor.h +++ b/tools/testing/selftests/kvm/include/x86/processor.h @@ -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); diff --git a/tools/testing/selftests/kvm/include/x86/vmx.h b/tools/testing/selftests/kvm/include/x86/vmx.h index 4bcfd60e3aec..04f5e34dea3a 100644 --- a/tools/testing/selftests/kvm/include/x86/vmx.h +++ b/tools/testing/selftests/kvm/include/x86/vmx.h @@ -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; } diff --git a/tools/testing/selftests/kvm/lib/assert.c b/tools/testing/selftests/kvm/lib/assert.c index 8be0d09ecf0f..781a07099782 100644 --- a/tools/testing/selftests/kvm/lib/assert.c +++ b/tools/testing/selftests/kvm/lib/assert.c @@ -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(), diff --git a/tools/testing/selftests/kvm/lib/x86/processor.c b/tools/testing/selftests/kvm/lib/x86/processor.c index ef56dcefe011..d31fa81ea075 100644 --- a/tools/testing/selftests/kvm/lib/x86/processor.c +++ b/tools/testing/selftests/kvm/lib/x86/processor.c @@ -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) diff --git a/tools/testing/selftests/kvm/lib/x86/svm.c b/tools/testing/selftests/kvm/lib/x86/svm.c index 1445b890986f..b05be50f075d 100644 --- a/tools/testing/selftests/kvm/lib/x86/svm.c +++ b/tools/testing/selftests/kvm/lib/x86/svm.c @@ -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"); } diff --git a/tools/testing/selftests/kvm/lib/x86/ucall.c b/tools/testing/selftests/kvm/lib/x86/ucall.c index e7dd5791959b..38050c60a067 100644 --- a/tools/testing/selftests/kvm/lib/x86/ucall.c +++ b/tools/testing/selftests/kvm/lib/x86/ucall.c @@ -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) diff --git a/tools/testing/selftests/kvm/lib/x86/vmx.c b/tools/testing/selftests/kvm/lib/x86/vmx.c index cd09c9de4485..089e1a8af53f 100644 --- a/tools/testing/selftests/kvm/lib/x86/vmx.c +++ b/tools/testing/selftests/kvm/lib/x86/vmx.c @@ -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 */ diff --git a/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c b/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c index e56a3dd6a51e..20388f0b959d 100644 --- a/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c +++ b/tools/testing/selftests/kvm/riscv/sbi_pmu_test.c @@ -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); diff --git a/tools/testing/selftests/kvm/steal_time.c b/tools/testing/selftests/kvm/steal_time.c index 76fcdd1fd3cb..92e7ffcd68b7 100644 --- a/tools/testing/selftests/kvm/steal_time.c +++ b/tools/testing/selftests/kvm/steal_time.c @@ -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); diff --git a/tools/testing/selftests/kvm/vm_types_test.c b/tools/testing/selftests/kvm/vm_types_test.c new file mode 100644 index 000000000000..6c421871e74b --- /dev/null +++ b/tools/testing/selftests/kvm/vm_types_test.c @@ -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; +} diff --git a/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c b/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c index 77ce87c41a86..aa7f3b405fd3 100644 --- a/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c +++ b/tools/testing/selftests/kvm/x86/evmcs_smm_controls_test.c @@ -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)); } diff --git a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c index 753a0e730ea8..4931ec22768e 100644 --- a/tools/testing/selftests/kvm/x86/fix_hypercall_test.c +++ b/tools/testing/selftests/kvm/x86/fix_hypercall_test.c @@ -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" diff --git a/tools/testing/selftests/kvm/x86/hyperv_clock.c b/tools/testing/selftests/kvm/x86/hyperv_clock.c index c083cea546dc..d5d779623cc6 100644 --- a/tools/testing/selftests/kvm/x86/hyperv_clock.c +++ b/tools/testing/selftests/kvm/x86/hyperv_clock.c @@ -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; diff --git a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c index 1bda2cd3f739..63ea1533e4ea 100644 --- a/tools/testing/selftests/kvm/x86/hyperv_evmcs.c +++ b/tools/testing/selftests/kvm/x86/hyperv_evmcs.c @@ -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()); diff --git a/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c new file mode 100644 index 000000000000..507391ab2c93 --- /dev/null +++ b/tools/testing/selftests/kvm/x86/save_restore_pf_stress_test.c @@ -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; +} diff --git a/tools/testing/selftests/kvm/x86/sev_init2_tests.c b/tools/testing/selftests/kvm/x86/sev_init2_tests.c index 689390c10f7c..87bff4fbb7ed 100644 --- a/tools/testing/selftests/kvm/x86/sev_init2_tests.c +++ b/tools/testing/selftests/kvm/x86/sev_init2_tests.c @@ -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){}); diff --git a/tools/testing/selftests/kvm/x86/smm_test.c b/tools/testing/selftests/kvm/x86/smm_test.c index e2542f4ced60..d1edafd5af75 100644 --- a/tools/testing/selftests/kvm/x86/smm_test.c +++ b/tools/testing/selftests/kvm/x86/smm_test.c @@ -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)); } diff --git a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c index 6d88c54f69fa..578283893ab3 100644 --- a/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c +++ b/tools/testing/selftests/kvm/x86/vmx_invalid_nested_guest_state.c @@ -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. */ diff --git a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c index 39ce9a9369f5..3a326c5e74ca 100644 --- a/tools/testing/selftests/kvm/x86/xapic_ipi_test.c +++ b/tools/testing/selftests/kvm/x86/xapic_ipi_test.c @@ -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> |
