1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2022, Red Hat, Inc. 4 * 5 * Tests for Hyper-V extensions to SVM. 6 */ 7 #define _GNU_SOURCE /* for program_invocation_short_name */ 8 #include <fcntl.h> 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <sys/ioctl.h> 13 #include <linux/bitmap.h> 14 15 #include "test_util.h" 16 17 #include "kvm_util.h" 18 #include "processor.h" 19 #include "svm_util.h" 20 #include "hyperv.h" 21 22 #define L2_GUEST_STACK_SIZE 256 23 24 /* Exit to L1 from L2 with RDMSR instruction */ 25 static inline void rdmsr_from_l2(uint32_t msr) 26 { 27 /* Currently, L1 doesn't preserve GPRs during vmexits. */ 28 __asm__ __volatile__ ("rdmsr" : : "c"(msr) : 29 "rax", "rbx", "rdx", "rsi", "rdi", "r8", "r9", 30 "r10", "r11", "r12", "r13", "r14", "r15"); 31 } 32 33 void l2_guest_code(void) 34 { 35 u64 unused; 36 37 GUEST_SYNC(3); 38 /* Exit to L1 */ 39 vmmcall(); 40 41 /* MSR-Bitmap tests */ 42 rdmsr_from_l2(MSR_FS_BASE); /* intercepted */ 43 rdmsr_from_l2(MSR_FS_BASE); /* intercepted */ 44 rdmsr_from_l2(MSR_GS_BASE); /* not intercepted */ 45 vmmcall(); 46 rdmsr_from_l2(MSR_GS_BASE); /* intercepted */ 47 48 GUEST_SYNC(5); 49 50 /* L2 TLB flush tests */ 51 hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE | 52 HV_HYPERCALL_FAST_BIT, 0x0, 53 HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES | 54 HV_FLUSH_ALL_PROCESSORS); 55 rdmsr_from_l2(MSR_FS_BASE); 56 /* 57 * Note: hypercall status (RAX) is not preserved correctly by L1 after 58 * synthetic vmexit, use unchecked version. 59 */ 60 __hyperv_hypercall(HVCALL_FLUSH_VIRTUAL_ADDRESS_SPACE | 61 HV_HYPERCALL_FAST_BIT, 0x0, 62 HV_FLUSH_ALL_VIRTUAL_ADDRESS_SPACES | 63 HV_FLUSH_ALL_PROCESSORS, &unused); 64 65 /* Done, exit to L1 and never come back. */ 66 vmmcall(); 67 } 68 69 static void __attribute__((__flatten__)) guest_code(struct svm_test_data *svm, 70 struct hyperv_test_pages *hv_pages, 71 vm_vaddr_t pgs_gpa) 72 { 73 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE]; 74 struct vmcb *vmcb = svm->vmcb; 75 struct hv_vmcb_enlightenments *hve = &vmcb->control.hv_enlightenments; 76 77 GUEST_SYNC(1); 78 79 wrmsr(HV_X64_MSR_GUEST_OS_ID, HYPERV_LINUX_OS_ID); 80 wrmsr(HV_X64_MSR_HYPERCALL, pgs_gpa); 81 enable_vp_assist(hv_pages->vp_assist_gpa, hv_pages->vp_assist); 82 83 GUEST_ASSERT(svm->vmcb_gpa); 84 /* Prepare for L2 execution. */ 85 generic_svm_setup(svm, l2_guest_code, 86 &l2_guest_stack[L2_GUEST_STACK_SIZE]); 87 88 /* L2 TLB flush setup */ 89 hve->partition_assist_page = hv_pages->partition_assist_gpa; 90 hve->hv_enlightenments_control.nested_flush_hypercall = 1; 91 hve->hv_vm_id = 1; 92 hve->hv_vp_id = 1; 93 current_vp_assist->nested_control.features.directhypercall = 1; 94 *(u32 *)(hv_pages->partition_assist) = 0; 95 96 GUEST_SYNC(2); 97 run_guest(vmcb, svm->vmcb_gpa); 98 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL); 99 GUEST_SYNC(4); 100 vmcb->save.rip += 3; 101 102 /* Intercept RDMSR 0xc0000100 */ 103 vmcb->control.intercept |= 1ULL << INTERCEPT_MSR_PROT; 104 __set_bit(2 * (MSR_FS_BASE & 0x1fff), svm->msr + 0x800); 105 run_guest(vmcb, svm->vmcb_gpa); 106 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR); 107 vmcb->save.rip += 2; /* rdmsr */ 108 109 /* Enable enlightened MSR bitmap */ 110 hve->hv_enlightenments_control.msr_bitmap = 1; 111 run_guest(vmcb, svm->vmcb_gpa); 112 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR); 113 vmcb->save.rip += 2; /* rdmsr */ 114 115 /* Intercept RDMSR 0xc0000101 without telling KVM about it */ 116 __set_bit(2 * (MSR_GS_BASE & 0x1fff), svm->msr + 0x800); 117 /* Make sure HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP is set */ 118 vmcb->control.clean |= HV_VMCB_NESTED_ENLIGHTENMENTS; 119 run_guest(vmcb, svm->vmcb_gpa); 120 /* Make sure we don't see SVM_EXIT_MSR here so eMSR bitmap works */ 121 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL); 122 vmcb->save.rip += 3; /* vmcall */ 123 124 /* Now tell KVM we've changed MSR-Bitmap */ 125 vmcb->control.clean &= ~HV_VMCB_NESTED_ENLIGHTENMENTS; 126 run_guest(vmcb, svm->vmcb_gpa); 127 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR); 128 vmcb->save.rip += 2; /* rdmsr */ 129 130 131 /* 132 * L2 TLB flush test. First VMCALL should be handled directly by L0, 133 * no VMCALL exit expected. 134 */ 135 run_guest(vmcb, svm->vmcb_gpa); 136 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR); 137 vmcb->save.rip += 2; /* rdmsr */ 138 /* Enable synthetic vmexit */ 139 *(u32 *)(hv_pages->partition_assist) = 1; 140 run_guest(vmcb, svm->vmcb_gpa); 141 GUEST_ASSERT(vmcb->control.exit_code == HV_SVM_EXITCODE_ENL); 142 GUEST_ASSERT(vmcb->control.exit_info_1 == HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH); 143 144 run_guest(vmcb, svm->vmcb_gpa); 145 GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL); 146 GUEST_SYNC(6); 147 148 GUEST_DONE(); 149 } 150 151 int main(int argc, char *argv[]) 152 { 153 vm_vaddr_t nested_gva = 0, hv_pages_gva = 0; 154 vm_vaddr_t hcall_page; 155 struct kvm_vcpu *vcpu; 156 struct kvm_vm *vm; 157 struct ucall uc; 158 int stage; 159 160 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM)); 161 TEST_REQUIRE(kvm_has_cap(KVM_CAP_HYPERV_DIRECT_TLBFLUSH)); 162 163 /* Create VM */ 164 vm = vm_create_with_one_vcpu(&vcpu, guest_code); 165 vcpu_set_hv_cpuid(vcpu); 166 vcpu_alloc_svm(vm, &nested_gva); 167 vcpu_alloc_hyperv_test_pages(vm, &hv_pages_gva); 168 169 hcall_page = vm_vaddr_alloc_pages(vm, 1); 170 memset(addr_gva2hva(vm, hcall_page), 0x0, getpagesize()); 171 172 vcpu_args_set(vcpu, 3, nested_gva, hv_pages_gva, addr_gva2gpa(vm, hcall_page)); 173 vcpu_set_msr(vcpu, HV_X64_MSR_VP_INDEX, vcpu->id); 174 175 for (stage = 1;; stage++) { 176 vcpu_run(vcpu); 177 TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO); 178 179 switch (get_ucall(vcpu, &uc)) { 180 case UCALL_ABORT: 181 REPORT_GUEST_ASSERT(uc); 182 /* NOT REACHED */ 183 case UCALL_SYNC: 184 break; 185 case UCALL_DONE: 186 goto done; 187 default: 188 TEST_FAIL("Unknown ucall %lu", uc.cmd); 189 } 190 191 /* UCALL_SYNC is handled here. */ 192 TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") && 193 uc.args[1] == stage, "Stage %d: Unexpected register values vmexit, got %lx", 194 stage, (ulong)uc.args[1]); 195 196 } 197 198 done: 199 kvm_vm_free(vm); 200 } 201