Add tests to exercise the code paths for em_{rdmsr,wrmsr} and emulator_{get,set}_msr. For the generic instruction emulator to work the module parameter kvm.force_emulation_prefix=1 has to be enabled. If it isn't the tests will be skipped. Signed-off-by: Aaron Lewis <aaronlewis@xxxxxxxxxx> --- v1 -> v2 - This commit was added to test the changes made to rdmsr and wrmsr when going through the generic instruction emulator which were made by Alexander Graf <graf@xxxxxxxxxx>. --- .../selftests/kvm/x86_64/userspace_msr_exit.c | 158 +++++++++++++++++- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/kvm/x86_64/userspace_msr_exit.c b/tools/testing/selftests/kvm/x86_64/userspace_msr_exit.c index 79acfe004e78..04e695eb8ed5 100644 --- a/tools/testing/selftests/kvm/x86_64/userspace_msr_exit.c +++ b/tools/testing/selftests/kvm/x86_64/userspace_msr_exit.c @@ -12,8 +12,12 @@ #include "kvm_util.h" #include "vmx.h" -#define VCPU_ID 1 +/* Forced emulation prefix, used to invoke the emulator unconditionally. */ +#define KVM_FEP "ud2; .byte 'k', 'v', 'm';" +#define KVM_FEP_LENGTH 5 +static int fep_available = 1; +#define VCPU_ID 1 #define MSR_NON_EXISTENT 0x474f4f00 uint32_t msrs[] = { @@ -63,6 +67,39 @@ static noinline void test_wrmsr(uint32_t msr, uint64_t value) extern char rdmsr_start, rdmsr_end; extern char wrmsr_start, wrmsr_end; +/* + * Note: Force test_em_rdmsr() to not be inlined to prevent the labels, + * rdmsr_start and rdmsr_end, from being defined multiple times. + */ +static noinline uint64_t test_em_rdmsr(uint32_t msr) +{ + uint32_t a, d; + + guest_exception_count = 0; + + __asm__ __volatile__(KVM_FEP "em_rdmsr_start: rdmsr; em_rdmsr_end:" : + "=a"(a), "=d"(d) : "c"(msr) : "memory"); + + return a | ((uint64_t) d << 32); +} + +/* + * Note: Force test_em_wrmsr() to not be inlined to prevent the labels, + * wrmsr_start and wrmsr_end, from being defined multiple times. + */ +static noinline void test_em_wrmsr(uint32_t msr, uint64_t value) +{ + uint32_t a = value; + uint32_t d = value >> 32; + + guest_exception_count = 0; + + __asm__ __volatile__(KVM_FEP "em_wrmsr_start: wrmsr; em_wrmsr_end:" :: + "a"(a), "d"(d), "c"(msr) : "memory"); +} + +extern char em_rdmsr_start, em_rdmsr_end; +extern char em_wrmsr_start, em_wrmsr_end; static void guest_code(void) { @@ -112,17 +149,55 @@ static void guest_code(void) GUEST_ASSERT(data == 2); GUEST_ASSERT(guest_exception_count == 0); + /* + * Test to see if the instruction emulator is available (ie: the module + * parameter 'kvm.force_emulation_prefix=1' is set). This instruction + * will #UD if it isn't available. + */ + __asm__ __volatile__(KVM_FEP "nop"); + + if (fep_available) { + /* Let userspace know we aren't done. */ + GUEST_SYNC(0); + + /* + * Now run the same tests with the instruction emulator. + */ + data = test_em_rdmsr(MSR_IA32_XSS); + GUEST_ASSERT(data == 0); + GUEST_ASSERT(guest_exception_count == 0); + test_em_wrmsr(MSR_IA32_XSS, 0); + GUEST_ASSERT(guest_exception_count == 0); + test_em_wrmsr(MSR_IA32_XSS, 1); + GUEST_ASSERT(guest_exception_count == 1); + + test_em_rdmsr(MSR_IA32_FLUSH_CMD); + GUEST_ASSERT(guest_exception_count == 1); + test_em_wrmsr(MSR_IA32_FLUSH_CMD, 0); + GUEST_ASSERT(guest_exception_count == 1); + test_em_wrmsr(MSR_IA32_FLUSH_CMD, 1); + GUEST_ASSERT(guest_exception_count == 0); + + test_em_wrmsr(MSR_NON_EXISTENT, 2); + GUEST_ASSERT(guest_exception_count == 0); + data = test_em_rdmsr(MSR_NON_EXISTENT); + GUEST_ASSERT(data == 2); + GUEST_ASSERT(guest_exception_count == 0); + } + GUEST_DONE(); } -static void guest_gp_handler(struct ex_regs *regs) +static void __guest_gp_handler(struct ex_regs *regs, + char *r_start, char *r_end, + char *w_start, char *w_end) { - if (regs->rip == (uintptr_t)&rdmsr_start) { - regs->rip = (uintptr_t)&rdmsr_end; + if (regs->rip == (uintptr_t)r_start) { + regs->rip = (uintptr_t)r_end; regs->rax = 0; regs->rdx = 0; - } else if (regs->rip == (uintptr_t)&wrmsr_start) { - regs->rip = (uintptr_t)&wrmsr_end; + } else if (regs->rip == (uintptr_t)w_start) { + regs->rip = (uintptr_t)w_end; } else { GUEST_ASSERT(!"RIP is at an unknown location!"); } @@ -130,6 +205,24 @@ static void guest_gp_handler(struct ex_regs *regs) ++guest_exception_count; } +static void guest_gp_handler(struct ex_regs *regs) +{ + __guest_gp_handler(regs, &rdmsr_start, &rdmsr_end, + &wrmsr_start, &wrmsr_end); +} + +static void guest_fep_gp_handler(struct ex_regs *regs) +{ + __guest_gp_handler(regs, &em_rdmsr_start, &em_rdmsr_end, + &em_wrmsr_start, &em_wrmsr_end); +} + +static void guest_ud_handler(struct ex_regs *regs) +{ + fep_available = 0; + regs->rip += KVM_FEP_LENGTH; +} + static void run_guest(struct kvm_vm *vm) { int rc; @@ -225,6 +318,32 @@ static void process_ucall_done(struct kvm_vm *vm) uc.cmd, UCALL_DONE); } +static uint64_t process_ucall(struct kvm_vm *vm) +{ + struct kvm_run *run = vcpu_state(vm, VCPU_ID); + struct ucall uc = {}; + + TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, + "Unexpected exit reason: %u (%s)", + run->exit_reason, + exit_reason_str(run->exit_reason)); + + switch (get_ucall(vm, VCPU_ID, &uc)) { + case UCALL_SYNC: + break; + case UCALL_ABORT: + check_for_guest_assert(vm); + break; + case UCALL_DONE: + process_ucall_done(vm); + break; + default: + TEST_ASSERT(false, "Unexpected ucall"); + } + + return uc.cmd; +} + static void run_guest_then_process_rdmsr(struct kvm_vm *vm, uint32_t msr_index) { run_guest(vm); @@ -260,7 +379,7 @@ int main(int argc, char *argv[]) vm_handle_exception(vm, GP_VECTOR, guest_gp_handler); - /* Process guest code userspace exits */ + /* Process guest code userspace exits. */ run_guest_then_process_rdmsr(vm, MSR_IA32_XSS); run_guest_then_process_wrmsr(vm, MSR_IA32_XSS); run_guest_then_process_wrmsr(vm, MSR_IA32_XSS); @@ -272,7 +391,30 @@ int main(int argc, char *argv[]) run_guest_then_process_wrmsr(vm, MSR_NON_EXISTENT); run_guest_then_process_rdmsr(vm, MSR_NON_EXISTENT); - run_guest_then_process_ucall_done(vm); + vm_handle_exception(vm, UD_VECTOR, guest_ud_handler); + run_guest(vm); + vm_handle_exception(vm, UD_VECTOR, NULL); + + if (process_ucall(vm) != UCALL_DONE) { + vm_handle_exception(vm, GP_VECTOR, guest_fep_gp_handler); + + /* Process emulated rdmsr and wrmsr instructions. */ + run_guest_then_process_rdmsr(vm, MSR_IA32_XSS); + run_guest_then_process_wrmsr(vm, MSR_IA32_XSS); + run_guest_then_process_wrmsr(vm, MSR_IA32_XSS); + + run_guest_then_process_rdmsr(vm, MSR_IA32_FLUSH_CMD); + run_guest_then_process_wrmsr(vm, MSR_IA32_FLUSH_CMD); + run_guest_then_process_wrmsr(vm, MSR_IA32_FLUSH_CMD); + + run_guest_then_process_wrmsr(vm, MSR_NON_EXISTENT); + run_guest_then_process_rdmsr(vm, MSR_NON_EXISTENT); + + /* Confirm the guest completed without issues. */ + run_guest_then_process_ucall_done(vm); + } else { + printf("To run the instruction emulated tests set the module parameter 'kvm.force_emulation_prefix=1'\n"); + } kvm_vm_free(vm); return 0; -- 2.28.0.220.ged08abb693-goog