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