// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2021, Red Hat, Inc. * * Tests for Hyper-V features enablement */ #include #include #include #include "test_util.h" #include "kvm_util.h" #include "processor.h" #include "hyperv.h" #define VCPU_ID 0 #define LINUX_OS_ID ((u64)0x8100 << 48) extern unsigned char rdmsr_start; extern unsigned char rdmsr_end; static u64 do_rdmsr(u32 idx) { u32 lo, hi; asm volatile("rdmsr_start: rdmsr;" "rdmsr_end:" : "=a"(lo), "=c"(hi) : "c"(idx)); return (((u64) hi) << 32) | lo; } extern unsigned char wrmsr_start; extern unsigned char wrmsr_end; static void do_wrmsr(u32 idx, u64 val) { u32 lo, hi; lo = val; hi = val >> 32; asm volatile("wrmsr_start: wrmsr;" "wrmsr_end:" : : "a"(lo), "c"(idx), "d"(hi)); } static int nr_gp; static inline u64 hypercall(u64 control, vm_vaddr_t input_address, vm_vaddr_t output_address) { u64 hv_status; asm volatile("mov %3, %%r8\n" "vmcall" : "=a" (hv_status), "+c" (control), "+d" (input_address) : "r" (output_address) : "cc", "memory", "r8", "r9", "r10", "r11"); return hv_status; } static void guest_gp_handler(struct ex_regs *regs) { unsigned char *rip = (unsigned char *)regs->rip; bool r, w; r = rip == &rdmsr_start; w = rip == &wrmsr_start; GUEST_ASSERT(r || w); nr_gp++; if (r) regs->rip = (uint64_t)&rdmsr_end; else regs->rip = (uint64_t)&wrmsr_end; } struct msr_data { uint32_t idx; bool available; bool write; u64 write_val; }; struct hcall_data { uint64_t control; uint64_t expect; }; static void guest_msr(struct msr_data *msr) { int i = 0; while (msr->idx) { WRITE_ONCE(nr_gp, 0); if (!msr->write) do_rdmsr(msr->idx); else do_wrmsr(msr->idx, msr->write_val); if (msr->available) GUEST_ASSERT(READ_ONCE(nr_gp) == 0); else GUEST_ASSERT(READ_ONCE(nr_gp) == 1); GUEST_SYNC(i++); } GUEST_DONE(); } static void guest_hcall(vm_vaddr_t pgs_gpa, struct hcall_data *hcall) { int i = 0; wrmsr(HV_X64_MSR_GUEST_OS_ID, LINUX_OS_ID); wrmsr(HV_X64_MSR_HYPERCALL, pgs_gpa); while (hcall->control) { GUEST_ASSERT(hypercall(hcall->control, pgs_gpa, pgs_gpa + 4096) == hcall->expect); GUEST_SYNC(i++); } GUEST_DONE(); } static void hv_set_cpuid(struct kvm_vm *vm, struct kvm_cpuid2 *cpuid, struct kvm_cpuid_entry2 *feat, struct kvm_cpuid_entry2 *recomm, struct kvm_cpuid_entry2 *dbg) { TEST_ASSERT(set_cpuid(cpuid, feat), "failed to set KVM_CPUID_FEATURES leaf"); TEST_ASSERT(set_cpuid(cpuid, recomm), "failed to set HYPERV_CPUID_ENLIGHTMENT_INFO leaf"); TEST_ASSERT(set_cpuid(cpuid, dbg), "failed to set HYPERV_CPUID_SYNDBG_PLATFORM_CAPABILITIES leaf"); vcpu_set_cpuid(vm, VCPU_ID, cpuid); } static void guest_test_msrs_access(struct kvm_vm *vm, struct msr_data *msr, struct kvm_cpuid2 *best) { struct kvm_run *run; struct ucall uc; int stage = 0, r; struct kvm_cpuid_entry2 feat = { .function = HYPERV_CPUID_FEATURES }; struct kvm_cpuid_entry2 recomm = { .function = HYPERV_CPUID_ENLIGHTMENT_INFO }; struct kvm_cpuid_entry2 dbg = { .function = HYPERV_CPUID_SYNDBG_PLATFORM_CAPABILITIES }; struct kvm_enable_cap cap = {0}; run = vcpu_state(vm, VCPU_ID); while (true) { switch (stage) { case 0: /* * Only available when Hyper-V identification is set */ msr->idx = HV_X64_MSR_GUEST_OS_ID; msr->write = 0; msr->available = 0; break; case 1: msr->idx = HV_X64_MSR_HYPERCALL; msr->write = 0; msr->available = 0; break; case 2: feat.eax |= HV_MSR_HYPERCALL_AVAILABLE; /* * HV_X64_MSR_GUEST_OS_ID has to be written first to make * HV_X64_MSR_HYPERCALL available. */ msr->idx = HV_X64_MSR_GUEST_OS_ID; msr->write = 1; msr->write_val = LINUX_OS_ID; msr->available = 1; break; case 3: msr->idx = HV_X64_MSR_GUEST_OS_ID; msr->write = 0; msr->available = 1; break; case 4: msr->idx = HV_X64_MSR_HYPERCALL; msr->write = 0; msr->available = 1; break; case 5: msr->idx = HV_X64_MSR_VP_RUNTIME; msr->write = 0; msr->available = 0; break; case 6: feat.eax |= HV_MSR_VP_RUNTIME_AVAILABLE; msr->write = 0; msr->available = 1; break; case 7: /* Read only */ msr->write = 1; msr->write_val = 1; msr->available = 0; break; case 8: msr->idx = HV_X64_MSR_TIME_REF_COUNT; msr->write = 0; msr->available = 0; break; case 9: feat.eax |= HV_MSR_TIME_REF_COUNT_AVAILABLE; msr->write = 0; msr->available = 1; break; case 10: /* Read only */ msr->write = 1; msr->write_val = 1; msr->available = 0; break; case 11: msr->idx = HV_X64_MSR_VP_INDEX; msr->write = 0; msr->available = 0; break; case 12: feat.eax |= HV_MSR_VP_INDEX_AVAILABLE; msr->write = 0; msr->available = 1; break; case 13: /* Read only */ msr->write = 1; msr->write_val = 1; msr->available = 0; break; case 14: msr->idx = HV_X64_MSR_RESET; msr->write = 0; msr->available = 0; break; case 15: feat.eax |= HV_MSR_RESET_AVAILABLE; msr->write = 0; msr->available = 1; break; case 16: msr->write = 1; msr->write_val = 0; msr->available = 1; break; case 17: msr->idx = HV_X64_MSR_REFERENCE_TSC; msr->write = 0; msr->available = 0; break; case 18: feat.eax |= HV_MSR_REFERENCE_TSC_AVAILABLE; msr->write = 0; msr->available = 1; break; case 19: msr->write = 1; msr->write_val = 0; msr->available = 1; break; case 20: msr->idx = HV_X64_MSR_EOM; msr->write = 0; msr->available = 0; break; case 21: /* * Remains unavailable even with KVM_CAP_HYPERV_SYNIC2 * capability enabled and guest visible CPUID bit unset. */ cap.cap = KVM_CAP_HYPERV_SYNIC2; vcpu_enable_cap(vm, VCPU_ID, &cap); break; case 22: feat.eax |= HV_MSR_SYNIC_AVAILABLE; msr->write = 0; msr->available = 1; break; case 23: msr->write = 1; msr->write_val = 0; msr->available = 1; break; case 24: msr->idx = HV_X64_MSR_STIMER0_CONFIG; msr->write = 0; msr->available = 0; break; case 25: feat.eax |= HV_MSR_SYNTIMER_AVAILABLE; msr->write = 0; msr->available = 1; break; case 26: msr->write = 1; msr->write_val = 0; msr->available = 1; break; case 27: /* Direct mode test */ msr->write = 1; msr->write_val = 1 << 12; msr->available = 0; break; case 28: feat.edx |= HV_STIMER_DIRECT_MODE_AVAILABLE; msr->available = 1; break; case 29: msr->idx = HV_X64_MSR_EOI; msr->write = 0; msr->available = 0; break; case 30: feat.eax |= HV_MSR_APIC_ACCESS_AVAILABLE; msr->write = 1; msr->write_val = 1; msr->available = 1; break; case 31: msr->idx = HV_X64_MSR_TSC_FREQUENCY; msr->write = 0; msr->available = 0; break; case 32: feat.eax |= HV_ACCESS_FREQUENCY_MSRS; msr->write = 0; msr->available = 1; break; case 33: /* Read only */ msr->write = 1; msr->write_val = 1; msr->available = 0; break; case 34: msr->idx = HV_X64_MSR_REENLIGHTENMENT_CONTROL; msr->write = 0; msr->available = 0; break; case 35: feat.eax |= HV_ACCESS_REENLIGHTENMENT; msr->write = 0; msr->available = 1; break; case 36: msr->write = 1; msr->write_val = 1; msr->available = 1; break; case 37: /* Can only write '0' */ msr->idx = HV_X64_MSR_TSC_EMULATION_STATUS; msr->write = 1; msr->write_val = 1; msr->available = 0; break; case 38: msr->idx = HV_X64_MSR_CRASH_P0; msr->write = 0; msr->available = 0; break; case 39: feat.edx |= HV_FEATURE_GUEST_CRASH_MSR_AVAILABLE; msr->write = 0; msr->available = 1; break; case 40: msr->write = 1; msr->write_val = 1; msr->available = 1; break; case 41: msr->idx = HV_X64_MSR_SYNDBG_STATUS; msr->write = 0; msr->available = 0; break; case 42: feat.edx |= HV_FEATURE_DEBUG_MSRS_AVAILABLE; dbg.eax |= HV_X64_SYNDBG_CAP_ALLOW_KERNEL_DEBUGGING; msr->write = 0; msr->available = 1; break; case 43: msr->write = 1; msr->write_val = 0; msr->available = 1; break; case 44: /* END */ msr->idx = 0; break; } hv_set_cpuid(vm, best, &feat, &recomm, &dbg); if (msr->idx) pr_debug("Stage %d: testing msr: 0x%x for %s\n", stage, msr->idx, msr->write ? "write" : "read"); else pr_debug("Stage %d: finish\n", stage); r = _vcpu_run(vm, VCPU_ID); TEST_ASSERT(!r, "vcpu_run failed: %d\n", r); 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: TEST_ASSERT(uc.args[1] == stage, "Unexpected stage: %ld (%d expected)\n", uc.args[1], stage); break; case UCALL_ABORT: TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0], __FILE__, uc.args[1]); return; case UCALL_DONE: return; } stage++; } } static void guest_test_hcalls_access(struct kvm_vm *vm, struct hcall_data *hcall, void *input, void *output, struct kvm_cpuid2 *best) { struct kvm_run *run; struct ucall uc; int stage = 0, r; struct kvm_cpuid_entry2 feat = { .function = HYPERV_CPUID_FEATURES, .eax = HV_MSR_HYPERCALL_AVAILABLE }; struct kvm_cpuid_entry2 recomm = { .function = HYPERV_CPUID_ENLIGHTMENT_INFO }; struct kvm_cpuid_entry2 dbg = { .function = HYPERV_CPUID_SYNDBG_PLATFORM_CAPABILITIES }; run = vcpu_state(vm, VCPU_ID); while (true) { switch (stage) { case 0: hcall->control = 0xdeadbeef; hcall->expect = HV_STATUS_INVALID_HYPERCALL_CODE; break; case 1: hcall->control = HVCALL_POST_MESSAGE; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 2: feat.ebx |= HV_POST_MESSAGES; hcall->expect = HV_STATUS_INVALID_HYPERCALL_INPUT; break; case 3: hcall->control = HVCALL_SIGNAL_EVENT; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 4: feat.ebx |= HV_SIGNAL_EVENTS; hcall->expect = HV_STATUS_INVALID_HYPERCALL_INPUT; break; case 5: hcall->control = HVCALL_RESET_DEBUG_SESSION; hcall->expect = HV_STATUS_INVALID_HYPERCALL_CODE; break; case 6: dbg.eax |= HV_X64_SYNDBG_CAP_ALLOW_KERNEL_DEBUGGING; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 7: feat.ebx |= HV_DEBUGGING; hcall->expect = HV_STATUS_OPERATION_DENIED; break; case 8: hcall->control = HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 9: recomm.eax |= HV_X64_REMOTE_TLB_FLUSH_RECOMMENDED; hcall->expect = HV_STATUS_SUCCESS; break; case 10: hcall->control = HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE_EX; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 11: recomm.eax |= HV_X64_EX_PROCESSOR_MASKS_RECOMMENDED; hcall->expect = HV_STATUS_SUCCESS; break; case 12: hcall->control = HVCALL_SEND_IPI; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 13: recomm.eax |= HV_X64_CLUSTER_IPI_RECOMMENDED; hcall->expect = HV_STATUS_INVALID_HYPERCALL_INPUT; break; case 14: /* Nothing in 'sparse banks' -> success */ hcall->control = HVCALL_SEND_IPI_EX; hcall->expect = HV_STATUS_SUCCESS; break; case 15: hcall->control = HVCALL_NOTIFY_LONG_SPIN_WAIT; hcall->expect = HV_STATUS_ACCESS_DENIED; break; case 16: recomm.ebx = 0xfff; hcall->expect = HV_STATUS_SUCCESS; break; case 17: /* END */ hcall->control = 0; break; } hv_set_cpuid(vm, best, &feat, &recomm, &dbg); if (hcall->control) pr_debug("Stage %d: testing hcall: 0x%lx\n", stage, hcall->control); else pr_debug("Stage %d: finish\n", stage); r = _vcpu_run(vm, VCPU_ID); TEST_ASSERT(!r, "vcpu_run failed: %d\n", r); 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: TEST_ASSERT(uc.args[1] == stage, "Unexpected stage: %ld (%d expected)\n", uc.args[1], stage); break; case UCALL_ABORT: TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0], __FILE__, uc.args[1]); return; case UCALL_DONE: return; } stage++; } } int main(void) { struct kvm_cpuid2 *best; struct kvm_vm *vm; vm_vaddr_t msr_gva, hcall_page, hcall_params; struct kvm_enable_cap cap = { .cap = KVM_CAP_HYPERV_ENFORCE_CPUID, .args = {1} }; /* Test MSRs */ vm = vm_create_default(VCPU_ID, 0, guest_msr); msr_gva = vm_vaddr_alloc_page(vm); memset(addr_gva2hva(vm, msr_gva), 0x0, getpagesize()); vcpu_args_set(vm, VCPU_ID, 1, msr_gva); vcpu_enable_cap(vm, VCPU_ID, &cap); vcpu_set_hv_cpuid(vm, VCPU_ID); best = kvm_get_supported_hv_cpuid(); vm_init_descriptor_tables(vm); vcpu_init_descriptor_tables(vm, VCPU_ID); vm_install_exception_handler(vm, GP_VECTOR, guest_gp_handler); pr_info("Testing access to Hyper-V specific MSRs\n"); guest_test_msrs_access(vm, addr_gva2hva(vm, msr_gva), best); kvm_vm_free(vm); /* Test hypercalls */ vm = vm_create_default(VCPU_ID, 0, guest_hcall); /* Hypercall input/output */ hcall_page = vm_vaddr_alloc_pages(vm, 2); memset(addr_gva2hva(vm, hcall_page), 0x0, 2 * getpagesize()); hcall_params = vm_vaddr_alloc_page(vm); memset(addr_gva2hva(vm, hcall_params), 0x0, getpagesize()); vcpu_args_set(vm, VCPU_ID, 2, addr_gva2gpa(vm, hcall_page), hcall_params); vcpu_enable_cap(vm, VCPU_ID, &cap); vcpu_set_hv_cpuid(vm, VCPU_ID); best = kvm_get_supported_hv_cpuid(); pr_info("Testing access to Hyper-V hypercalls\n"); guest_test_hcalls_access(vm, addr_gva2hva(vm, hcall_params), addr_gva2hva(vm, hcall_page), addr_gva2hva(vm, hcall_page) + getpagesize(), best); kvm_vm_free(vm); }