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