xref: /linux/tools/testing/selftests/kvm/x86/svm_nested_soft_inject_test.c (revision 67f8bc848ee31831336bd478e57d2f993551902e)
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 
22 static_assert(ATOMIC_INT_LOCK_FREE == 2, "atomic int is not lockless");
23 
24 static unsigned int bp_fired;
25 static void guest_bp_handler(struct ex_regs *regs)
26 {
27 	bp_fired++;
28 }
29 
30 static unsigned int int_fired;
31 static void l2_guest_code_int(void);
32 
33 static 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 
39 static 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 
58 static 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)
61 static 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 
74 static void l2_guest_code_nmi(void)
75 {
76 	ud2();
77 }
78 
79 static void l1_guest_code(struct svm_test_data *svm, u64 is_nmi, u64 idt_alt)
80 {
81 	struct vmcb *vmcb = svm->vmcb;
82 
83 	if (is_nmi)
84 		x2apic_enable();
85 
86 	/* Prepare for L2 execution. */
87 	generic_svm_setup(svm, is_nmi ? l2_guest_code_nmi : l2_guest_code_int);
88 
89 	vmcb->control.intercept_exceptions |= BIT(PF_VECTOR) | BIT(UD_VECTOR);
90 	vmcb->control.intercept |= BIT(INTERCEPT_NMI) | BIT(INTERCEPT_HLT);
91 
92 	if (is_nmi) {
93 		vmcb->control.event_inj = SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_NMI;
94 	} else {
95 		vmcb->control.event_inj = INT_NR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_SOFT;
96 		/* The return address pushed on stack */
97 		vmcb->control.next_rip = vmcb->save.rip;
98 	}
99 
100 	run_guest(vmcb, svm->vmcb_gpa);
101 	__GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL,
102 		       "Expected VMMCAL #VMEXIT, got '0x%lx', info1 = '0x%lx, info2 = '0x%lx'",
103 		       vmcb->control.exit_code,
104 		       vmcb->control.exit_info_1, vmcb->control.exit_info_2);
105 
106 	if (is_nmi) {
107 		clgi();
108 		x2apic_write_reg(APIC_ICR, APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_NMI);
109 
110 		GUEST_ASSERT_EQ(nmi_stage_get(), 1);
111 		nmi_stage_inc();
112 
113 		stgi();
114 		/* self-NMI happens here */
115 		while (true)
116 			cpu_relax();
117 	}
118 
119 	/* Skip over VMMCALL */
120 	vmcb->save.rip += 3;
121 
122 	/* Switch to alternate IDT to cause intervening NPF again */
123 	vmcb->save.idtr.base = idt_alt;
124 	vmcb->control.clean = 0; /* &= ~BIT(VMCB_DT) would be enough */
125 
126 	vmcb->control.event_inj = BP_VECTOR | SVM_EVTINJ_VALID | SVM_EVTINJ_TYPE_EXEPT;
127 	/* The return address pushed on stack, skip over UD2 */
128 	vmcb->control.next_rip = vmcb->save.rip + 2;
129 
130 	run_guest(vmcb, svm->vmcb_gpa);
131 	__GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_HLT,
132 		       "Expected HLT #VMEXIT, got '0x%lx', info1 = '0x%lx, info2 = '0x%lx'",
133 		       vmcb->control.exit_code,
134 		       vmcb->control.exit_info_1, vmcb->control.exit_info_2);
135 
136 	GUEST_DONE();
137 }
138 
139 static void run_test(bool is_nmi)
140 {
141 	struct kvm_vcpu *vcpu;
142 	struct kvm_vm *vm;
143 	gva_t svm_gva;
144 	gva_t idt_alt_vm;
145 	struct kvm_guest_debug debug;
146 
147 	pr_info("Running %s test\n", is_nmi ? "NMI" : "soft int");
148 
149 	vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
150 
151 	vm_install_exception_handler(vm, NMI_VECTOR, guest_nmi_handler);
152 	vm_install_exception_handler(vm, BP_VECTOR, guest_bp_handler);
153 	vm_install_exception_handler(vm, INT_NR, guest_int_handler);
154 
155 	vcpu_alloc_svm(vm, &svm_gva);
156 
157 	if (!is_nmi) {
158 		void *idt, *idt_alt;
159 
160 		idt_alt_vm = vm_alloc_page(vm);
161 		idt_alt = addr_gva2hva(vm, idt_alt_vm);
162 		idt = addr_gva2hva(vm, vm->arch.idt);
163 		memcpy(idt_alt, idt, getpagesize());
164 	} else {
165 		idt_alt_vm = 0;
166 	}
167 	vcpu_args_set(vcpu, 3, svm_gva, (u64)is_nmi, (u64)idt_alt_vm);
168 
169 	memset(&debug, 0, sizeof(debug));
170 	vcpu_guest_debug_set(vcpu, &debug);
171 
172 	struct ucall uc;
173 
174 	alarm(2);
175 	vcpu_run(vcpu);
176 	alarm(0);
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 		break;
183 		/* NOT REACHED */
184 	case UCALL_DONE:
185 		goto done;
186 	default:
187 		TEST_FAIL("Unknown ucall 0x%lx.", uc.cmd);
188 	}
189 done:
190 	kvm_vm_free(vm);
191 }
192 
193 int main(int argc, char *argv[])
194 {
195 	TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
196 
197 	TEST_ASSERT(kvm_cpu_has(X86_FEATURE_NRIPS),
198 		    "KVM with nSVM is supposed to unconditionally advertise nRIP Save");
199 
200 	atomic_init(&nmi_stage, 0);
201 
202 	run_test(false);
203 	run_test(true);
204 
205 	return 0;
206 }
207