1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2022 Oracle and/or its affiliates.
4 *
5 * Based on:
6 * svm_int_ctl_test
7 *
8 * Copyright (C) 2021, Red Hat, Inc.
9 *
10 */
11
12 #include <stdatomic.h>
13 #include <stdio.h>
14 #include <unistd.h>
15 #include "apic.h"
16 #include "kvm_util.h"
17 #include "processor.h"
18 #include "svm_util.h"
19 #include "test_util.h"
20
21 #define INT_NR 0x20
22
23 static_assert(ATOMIC_INT_LOCK_FREE == 2, "atomic int is not lockless");
24
25 static unsigned int bp_fired;
guest_bp_handler(struct ex_regs * regs)26 static void guest_bp_handler(struct ex_regs *regs)
27 {
28 bp_fired++;
29 }
30
31 static unsigned int int_fired;
32 static void l2_guest_code_int(void);
33
guest_int_handler(struct ex_regs * regs)34 static void guest_int_handler(struct ex_regs *regs)
35 {
36 int_fired++;
37 GUEST_ASSERT_2(regs->rip == (unsigned long)l2_guest_code_int,
38 regs->rip, (unsigned long)l2_guest_code_int);
39 }
40
l2_guest_code_int(void)41 static void l2_guest_code_int(void)
42 {
43 GUEST_ASSERT_1(int_fired == 1, int_fired);
44
45 /*
46 * Same as the vmmcall() function, but with a ud2 sneaked after the
47 * vmmcall. The caller injects an exception with the return address
48 * increased by 2, so the "pop rbp" must be after the ud2 and we cannot
49 * use vmmcall() directly.
50 */
51 __asm__ __volatile__("push %%rbp; vmmcall; ud2; pop %%rbp"
52 : : "a"(0xdeadbeef), "c"(0xbeefdead)
53 : "rbx", "rdx", "rsi", "rdi", "r8", "r9",
54 "r10", "r11", "r12", "r13", "r14", "r15");
55
56 GUEST_ASSERT_1(bp_fired == 1, bp_fired);
57 hlt();
58 }
59
60 static atomic_int nmi_stage;
61 #define nmi_stage_get() atomic_load_explicit(&nmi_stage, memory_order_acquire)
62 #define nmi_stage_inc() atomic_fetch_add_explicit(&nmi_stage, 1, memory_order_acq_rel)
guest_nmi_handler(struct ex_regs * regs)63 static void guest_nmi_handler(struct ex_regs *regs)
64 {
65 nmi_stage_inc();
66
67 if (nmi_stage_get() == 1) {
68 vmmcall();
69 GUEST_ASSERT(false);
70 } else {
71 GUEST_ASSERT_1(nmi_stage_get() == 3, nmi_stage_get());
72 GUEST_DONE();
73 }
74 }
75
l2_guest_code_nmi(void)76 static void l2_guest_code_nmi(void)
77 {
78 ud2();
79 }
80
l1_guest_code(struct svm_test_data * svm,uint64_t is_nmi,uint64_t idt_alt)81 static void l1_guest_code(struct svm_test_data *svm, uint64_t is_nmi, uint64_t idt_alt)
82 {
83 #define L2_GUEST_STACK_SIZE 64
84 unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
85 struct vmcb *vmcb = svm->vmcb;
86
87 if (is_nmi)
88 x2apic_enable();
89
90 /* Prepare for L2 execution. */
91 generic_svm_setup(svm,
92 is_nmi ? l2_guest_code_nmi : l2_guest_code_int,
93 &l2_guest_stack[L2_GUEST_STACK_SIZE]);
94
95 vmcb->control.intercept_exceptions |= BIT(PF_VECTOR) | BIT(UD_VECTOR);
96 vmcb->control.intercept |= BIT(INTERCEPT_NMI) | BIT(INTERCEPT_HLT);
97
98 if (is_nmi) {
99 vmcb->control.event_inj = SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_NMI;
100 } else {
101 vmcb->control.event_inj = INT_NR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_SOFT;
102 /* The return address pushed on stack */
103 vmcb->control.next_rip = vmcb->save.rip;
104 }
105
106 run_guest(vmcb, svm->vmcb_gpa);
107 GUEST_ASSERT_3(vmcb->control.exit_code == SVM_EXIT_VMMCALL,
108 vmcb->control.exit_code,
109 vmcb->control.exit_info_1, vmcb->control.exit_info_2);
110
111 if (is_nmi) {
112 clgi();
113 x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_NMI);
114
115 GUEST_ASSERT_1(nmi_stage_get() == 1, nmi_stage_get());
116 nmi_stage_inc();
117
118 stgi();
119 /* self-NMI happens here */
120 while (true)
121 cpu_relax();
122 }
123
124 /* Skip over VMMCALL */
125 vmcb->save.rip += 3;
126
127 /* Switch to alternate IDT to cause intervening NPF again */
128 vmcb->save.idtr.base = idt_alt;
129 vmcb->control.clean = 0; /* &= ~BIT(VMCB_DT) would be enough */
130
131 vmcb->control.event_inj = BP_VECTOR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_EXEPT;
132 /* The return address pushed on stack, skip over UD2 */
133 vmcb->control.next_rip = vmcb->save.rip + 2;
134
135 run_guest(vmcb, svm->vmcb_gpa);
136 GUEST_ASSERT_3(vmcb->control.exit_code == SVM_EXIT_HLT,
137 vmcb->control.exit_code,
138 vmcb->control.exit_info_1, vmcb->control.exit_info_2);
139
140 GUEST_DONE();
141 }
142
run_test(bool is_nmi)143 static void run_test(bool is_nmi)
144 {
145 struct kvm_vcpu *vcpu;
146 struct kvm_vm *vm;
147 vm_vaddr_t svm_gva;
148 vm_vaddr_t idt_alt_vm;
149 struct kvm_guest_debug debug;
150
151 pr_info("Running %s test\n", is_nmi ? "NMI" : "soft int");
152
153 vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
154
155 vm_init_descriptor_tables(vm);
156 vcpu_init_descriptor_tables(vcpu);
157
158 vm_install_exception_handler(vm, NMI_VECTOR, guest_nmi_handler);
159 vm_install_exception_handler(vm, BP_VECTOR, guest_bp_handler);
160 vm_install_exception_handler(vm, INT_NR, guest_int_handler);
161
162 vcpu_alloc_svm(vm, &svm_gva);
163
164 if (!is_nmi) {
165 void *idt, *idt_alt;
166
167 idt_alt_vm = vm_vaddr_alloc_page(vm);
168 idt_alt = addr_gva2hva(vm, idt_alt_vm);
169 idt = addr_gva2hva(vm, vm->idt);
170 memcpy(idt_alt, idt, getpagesize());
171 } else {
172 idt_alt_vm = 0;
173 }
174 vcpu_args_set(vcpu, 3, svm_gva, (uint64_t)is_nmi, (uint64_t)idt_alt_vm);
175
176 memset(&debug, 0, sizeof(debug));
177 vcpu_guest_debug_set(vcpu, &debug);
178
179 struct kvm_run *run = vcpu->run;
180 struct ucall uc;
181
182 alarm(2);
183 vcpu_run(vcpu);
184 alarm(0);
185 TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
186 "Got exit_reason other than KVM_EXIT_IO: %u (%s)\n",
187 run->exit_reason,
188 exit_reason_str(run->exit_reason));
189
190 switch (get_ucall(vcpu, &uc)) {
191 case UCALL_ABORT:
192 REPORT_GUEST_ASSERT_3(uc, "vals = 0x%lx 0x%lx 0x%lx");
193 break;
194 /* NOT REACHED */
195 case UCALL_DONE:
196 goto done;
197 default:
198 TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
199 }
200 done:
201 kvm_vm_free(vm);
202 }
203
main(int argc,char * argv[])204 int main(int argc, char *argv[])
205 {
206 TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
207
208 TEST_ASSERT(kvm_cpu_has(X86_FEATURE_NRIPS),
209 "KVM with nSVM is supposed to unconditionally advertise nRIP Save");
210
211 atomic_init(&nmi_stage, 0);
212
213 run_test(false);
214 run_test(true);
215
216 return 0;
217 }
218