1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2020, Google LLC.
4  *
5  * Tests for KVM paravirtual feature disablement
6  */
7 #include <asm/kvm_para.h>
8 #include <linux/kvm_para.h>
9 #include <linux/stringify.h>
10 #include <stdint.h>
11 
12 #include "apic.h"
13 #include "test_util.h"
14 #include "kvm_util.h"
15 #include "processor.h"
16 
17 #define VCPU_ID 0
18 
19 static bool ud_expected;
20 
21 static void guest_ud_handler(struct ex_regs *regs)
22 {
23 	GUEST_ASSERT(ud_expected);
24 	GUEST_DONE();
25 }
26 
27 extern unsigned char svm_hypercall_insn;
28 static uint64_t svm_do_sched_yield(uint8_t apic_id)
29 {
30 	uint64_t ret;
31 
32 	asm volatile("mov %1, %%rax\n\t"
33 		     "mov %2, %%rbx\n\t"
34 		     "svm_hypercall_insn:\n\t"
35 		     "vmmcall\n\t"
36 		     "mov %%rax, %0\n\t"
37 		     : "=r"(ret)
38 		     : "r"((uint64_t)KVM_HC_SCHED_YIELD), "r"((uint64_t)apic_id)
39 		     : "rax", "rbx", "memory");
40 
41 	return ret;
42 }
43 
44 extern unsigned char vmx_hypercall_insn;
45 static uint64_t vmx_do_sched_yield(uint8_t apic_id)
46 {
47 	uint64_t ret;
48 
49 	asm volatile("mov %1, %%rax\n\t"
50 		     "mov %2, %%rbx\n\t"
51 		     "vmx_hypercall_insn:\n\t"
52 		     "vmcall\n\t"
53 		     "mov %%rax, %0\n\t"
54 		     : "=r"(ret)
55 		     : "r"((uint64_t)KVM_HC_SCHED_YIELD), "r"((uint64_t)apic_id)
56 		     : "rax", "rbx", "memory");
57 
58 	return ret;
59 }
60 
61 static void assert_hypercall_insn(unsigned char *exp_insn, unsigned char *obs_insn)
62 {
63 	uint32_t exp = 0, obs = 0;
64 
65 	memcpy(&exp, exp_insn, sizeof(exp));
66 	memcpy(&obs, obs_insn, sizeof(obs));
67 
68 	GUEST_ASSERT_EQ(exp, obs);
69 }
70 
71 static void guest_main(void)
72 {
73 	unsigned char *native_hypercall_insn, *hypercall_insn;
74 	uint8_t apic_id;
75 
76 	apic_id = GET_APIC_ID_FIELD(xapic_read_reg(APIC_ID));
77 
78 	if (is_intel_cpu()) {
79 		native_hypercall_insn = &vmx_hypercall_insn;
80 		hypercall_insn = &svm_hypercall_insn;
81 		svm_do_sched_yield(apic_id);
82 	} else if (is_amd_cpu()) {
83 		native_hypercall_insn = &svm_hypercall_insn;
84 		hypercall_insn = &vmx_hypercall_insn;
85 		vmx_do_sched_yield(apic_id);
86 	} else {
87 		GUEST_ASSERT(0);
88 		/* unreachable */
89 		return;
90 	}
91 
92 	GUEST_ASSERT(!ud_expected);
93 	assert_hypercall_insn(native_hypercall_insn, hypercall_insn);
94 	GUEST_DONE();
95 }
96 
97 static void setup_ud_vector(struct kvm_vm *vm)
98 {
99 	vm_init_descriptor_tables(vm);
100 	vcpu_init_descriptor_tables(vm, VCPU_ID);
101 	vm_install_exception_handler(vm, UD_VECTOR, guest_ud_handler);
102 }
103 
104 static void enter_guest(struct kvm_vm *vm)
105 {
106 	struct kvm_run *run;
107 	struct ucall uc;
108 
109 	run = vcpu_state(vm, VCPU_ID);
110 
111 	vcpu_run(vm, VCPU_ID);
112 	switch (get_ucall(vm, VCPU_ID, &uc)) {
113 	case UCALL_SYNC:
114 		pr_info("%s: %016lx\n", (const char *)uc.args[2], uc.args[3]);
115 		break;
116 	case UCALL_DONE:
117 		return;
118 	case UCALL_ABORT:
119 		TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0], __FILE__, uc.args[1]);
120 	default:
121 		TEST_FAIL("Unhandled ucall: %ld\nexit_reason: %u (%s)",
122 			  uc.cmd, run->exit_reason, exit_reason_str(run->exit_reason));
123 	}
124 }
125 
126 static void test_fix_hypercall(void)
127 {
128 	struct kvm_vm *vm;
129 
130 	vm = vm_create_default(VCPU_ID, 0, guest_main);
131 	setup_ud_vector(vm);
132 
133 	ud_expected = false;
134 	sync_global_to_guest(vm, ud_expected);
135 
136 	virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
137 
138 	enter_guest(vm);
139 }
140 
141 static void test_fix_hypercall_disabled(void)
142 {
143 	struct kvm_enable_cap cap = {0};
144 	struct kvm_vm *vm;
145 
146 	vm = vm_create_default(VCPU_ID, 0, guest_main);
147 	setup_ud_vector(vm);
148 
149 	cap.cap = KVM_CAP_DISABLE_QUIRKS2;
150 	cap.args[0] = KVM_X86_QUIRK_FIX_HYPERCALL_INSN;
151 	vm_enable_cap(vm, &cap);
152 
153 	ud_expected = true;
154 	sync_global_to_guest(vm, ud_expected);
155 
156 	virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
157 
158 	enter_guest(vm);
159 }
160 
161 int main(void)
162 {
163 	if (!(kvm_check_cap(KVM_CAP_DISABLE_QUIRKS2) & KVM_X86_QUIRK_FIX_HYPERCALL_INSN)) {
164 		print_skip("KVM_X86_QUIRK_HYPERCALL_INSN not supported");
165 		exit(KSFT_SKIP);
166 	}
167 
168 	test_fix_hypercall();
169 	test_fix_hypercall_disabled();
170 }
171