xref: /kvm-unit-tests/x86/vmexit.c (revision a3d1fb55268ed1db4b3cba2c5d19c3c8ee783387)
1 #include "libcflat.h"
2 #include "smp.h"
3 #include "processor.h"
4 #include "atomic.h"
5 #include "x86/vm.h"
6 #include "x86/desc.h"
7 #include "x86/pci.h"
8 
9 struct test {
10 	void (*func)(void);
11 	const char *name;
12 	int (*valid)(void);
13 	int parallel;
14 	bool (*next)(struct test *);
15 };
16 
17 static void outb(unsigned short port, unsigned val)
18 {
19     asm volatile("outb %b0, %w1" : : "a"(val), "Nd"(port));
20 }
21 
22 static void outw(unsigned short port, unsigned val)
23 {
24     asm volatile("outw %w0, %w1" : : "a"(val), "Nd"(port));
25 }
26 
27 static void outl(unsigned short port, unsigned val)
28 {
29     asm volatile("outl %0, %w1" : : "a"(val), "Nd"(port));
30 }
31 
32 static unsigned int inb(unsigned short port)
33 {
34     unsigned int val;
35     asm volatile("xorl %0, %0; inb %w1, %b0" : "=a"(val) : "Nd"(port));
36     return val;
37 }
38 
39 static unsigned int inl(unsigned short port)
40 {
41     unsigned int val;
42     asm volatile("inl %w1, %0" : "=a"(val) : "Nd"(port));
43     return val;
44 }
45 
46 #define GOAL (1ull << 30)
47 
48 static int nr_cpus;
49 
50 static void cpuid_test(void)
51 {
52 	asm volatile ("push %%"R "bx; cpuid; pop %%"R "bx"
53 		      : : : "eax", "ecx", "edx");
54 }
55 
56 static void vmcall(void)
57 {
58 	unsigned long a = 0, b, c, d;
59 
60 	asm volatile ("vmcall" : "+a"(a), "=b"(b), "=c"(c), "=d"(d));
61 }
62 
63 #define MSR_TSC_ADJUST 0x3b
64 #define MSR_EFER 0xc0000080
65 #define EFER_NX_MASK            (1ull << 11)
66 
67 #ifdef __x86_64__
68 static void mov_from_cr8(void)
69 {
70 	unsigned long cr8;
71 
72 	asm volatile ("mov %%cr8, %0" : "=r"(cr8));
73 }
74 
75 static void mov_to_cr8(void)
76 {
77 	unsigned long cr8 = 0;
78 
79 	asm volatile ("mov %0, %%cr8" : : "r"(cr8));
80 }
81 #endif
82 
83 static int is_smp(void)
84 {
85 	return cpu_count() > 1;
86 }
87 
88 static void nop(void *junk)
89 {
90 }
91 
92 static void ipi(void)
93 {
94 	on_cpu(1, nop, 0);
95 }
96 
97 static void ipi_halt(void)
98 {
99 	unsigned long long t;
100 
101 	on_cpu(1, nop, 0);
102 	t = rdtsc() + 2000;
103 	while (rdtsc() < t)
104 		;
105 }
106 
107 static void inl_pmtimer(void)
108 {
109     inl(0xb008);
110 }
111 
112 static void inl_nop_qemu(void)
113 {
114     inl(0x1234);
115 }
116 
117 static void inl_nop_kernel(void)
118 {
119     inb(0x4d0);
120 }
121 
122 static void outl_elcr_kernel(void)
123 {
124     outb(0x4d0, 0);
125 }
126 
127 static void mov_dr(void)
128 {
129     asm volatile("mov %0, %%dr7" : : "r" (0x400L));
130 }
131 
132 static void ple_round_robin(void)
133 {
134 	struct counter {
135 		volatile int n1;
136 		int n2;
137 	} __attribute__((aligned(64)));
138 	static struct counter counters[64] = { { -1, 0 } };
139 	int me = smp_id();
140 	int you;
141 	volatile struct counter *p = &counters[me];
142 
143 	while (p->n1 == p->n2)
144 		asm volatile ("pause");
145 
146 	p->n2 = p->n1;
147 	you = me + 1;
148 	if (you == nr_cpus)
149 		you = 0;
150 	++counters[you].n1;
151 }
152 
153 static void rd_tsc_adjust_msr(void)
154 {
155 	rdmsr(MSR_TSC_ADJUST);
156 }
157 
158 static void wr_tsc_adjust_msr(void)
159 {
160 	wrmsr(MSR_TSC_ADJUST, 0x0);
161 }
162 
163 struct pci_test_dev_hdr {
164     uint8_t test;
165     uint8_t width;
166     uint8_t pad0[2];
167     uint32_t offset;
168     uint32_t data;
169     uint32_t count;
170     uint8_t name[];
171 };
172 
173 static struct pci_test {
174 	unsigned iobar;
175 	unsigned ioport;
176 	volatile void *memaddr;
177 	volatile void *mem;
178 	int test_idx;
179 	uint32_t data;
180 	uint32_t offset;
181 } pci_test = {
182 	.test_idx = -1
183 };
184 
185 static void pci_mem_testb(void)
186 {
187 	*(volatile uint8_t *)pci_test.mem = pci_test.data;
188 }
189 
190 static void pci_mem_testw(void)
191 {
192 	*(volatile uint16_t *)pci_test.mem = pci_test.data;
193 }
194 
195 static void pci_mem_testl(void)
196 {
197 	*(volatile uint32_t *)pci_test.mem = pci_test.data;
198 }
199 
200 static void pci_io_testb(void)
201 {
202 	outb(pci_test.ioport, pci_test.data);
203 }
204 
205 static void pci_io_testw(void)
206 {
207 	outw(pci_test.ioport, pci_test.data);
208 }
209 
210 static void pci_io_testl(void)
211 {
212 	outl(pci_test.ioport, pci_test.data);
213 }
214 
215 static uint8_t ioreadb(unsigned long addr, bool io)
216 {
217 	if (io) {
218 		return inb(addr);
219 	} else {
220 		return *(volatile uint8_t *)addr;
221 	}
222 }
223 
224 static uint32_t ioreadl(unsigned long addr, bool io)
225 {
226 	/* Note: assumes little endian */
227 	if (io) {
228 		return inl(addr);
229 	} else {
230 		return *(volatile uint32_t *)addr;
231 	}
232 }
233 
234 static void iowriteb(unsigned long addr, uint8_t data, bool io)
235 {
236 	if (io) {
237 		outb(addr, data);
238 	} else {
239 		*(volatile uint8_t *)addr = data;
240 	}
241 }
242 
243 static bool pci_next(struct test *test, unsigned long addr, bool io)
244 {
245 	int i;
246 	uint8_t width;
247 
248 	if (!pci_test.memaddr) {
249 		test->func = NULL;
250 		return true;
251 	}
252 	pci_test.test_idx++;
253 	iowriteb(addr + offsetof(struct pci_test_dev_hdr, test),
254 		 pci_test.test_idx, io);
255 	width = ioreadb(addr + offsetof(struct pci_test_dev_hdr, width),
256 			io);
257 	switch (width) {
258 		case 1:
259 			test->func = io ? pci_io_testb : pci_mem_testb;
260 			break;
261 		case 2:
262 			test->func = io ? pci_io_testw : pci_mem_testw;
263 			break;
264 		case 4:
265 			test->func = io ? pci_io_testl : pci_mem_testl;
266 			break;
267 		default:
268 			/* Reset index for purposes of the next test */
269 			pci_test.test_idx = -1;
270 			test->func = NULL;
271 			return false;
272 	}
273 	pci_test.data = ioreadl(addr + offsetof(struct pci_test_dev_hdr, data),
274 				io);
275 	pci_test.offset = ioreadl(addr + offsetof(struct pci_test_dev_hdr,
276 						  offset), io);
277 	for (i = 0; i < pci_test.offset; ++i) {
278 		char c = ioreadb(addr + offsetof(struct pci_test_dev_hdr,
279 						 name) + i, io);
280 		if (!c) {
281 			break;
282 		}
283 		printf("%c",c);
284 	}
285 	printf(":");
286 	return true;
287 }
288 
289 static bool pci_mem_next(struct test *test)
290 {
291 	bool ret;
292 	ret = pci_next(test, ((unsigned long)pci_test.memaddr), false);
293 	if (ret) {
294 		pci_test.mem = pci_test.memaddr + pci_test.offset;
295 	}
296 	return ret;
297 }
298 
299 static bool pci_io_next(struct test *test)
300 {
301 	bool ret;
302 	ret = pci_next(test, ((unsigned long)pci_test.iobar), true);
303 	if (ret) {
304 		pci_test.ioport = pci_test.iobar + pci_test.offset;
305 	}
306 	return ret;
307 }
308 
309 static struct test tests[] = {
310 	{ cpuid_test, "cpuid", .parallel = 1,  },
311 	{ vmcall, "vmcall", .parallel = 1, },
312 #ifdef __x86_64__
313 	{ mov_from_cr8, "mov_from_cr8", .parallel = 1, },
314 	{ mov_to_cr8, "mov_to_cr8" , .parallel = 1, },
315 #endif
316 	{ inl_pmtimer, "inl_from_pmtimer", .parallel = 1, },
317 	{ inl_nop_qemu, "inl_from_qemu", .parallel = 1 },
318 	{ inl_nop_kernel, "inl_from_kernel", .parallel = 1 },
319 	{ outl_elcr_kernel, "outl_to_kernel", .parallel = 1 },
320 	{ mov_dr, "mov_dr", .parallel = 1 },
321 	{ ipi, "ipi", is_smp, .parallel = 0, },
322 	{ ipi_halt, "ipi+halt", is_smp, .parallel = 0, },
323 	{ ple_round_robin, "ple-round-robin", .parallel = 1 },
324 	{ wr_tsc_adjust_msr, "wr_tsc_adjust_msr", .parallel = 1 },
325 	{ rd_tsc_adjust_msr, "rd_tsc_adjust_msr", .parallel = 1 },
326 	{ NULL, "pci-mem", .parallel = 0, .next = pci_mem_next },
327 	{ NULL, "pci-io", .parallel = 0, .next = pci_io_next },
328 };
329 
330 unsigned iterations;
331 static atomic_t nr_cpus_done;
332 
333 static void run_test(void *_func)
334 {
335     int i;
336     void (*func)(void) = _func;
337 
338     for (i = 0; i < iterations; ++i)
339         func();
340 
341     atomic_inc(&nr_cpus_done);
342 }
343 
344 static bool do_test(struct test *test)
345 {
346 	int i;
347 	unsigned long long t1, t2;
348         void (*func)(void);
349 
350         iterations = 32;
351 
352         if (test->valid && !test->valid()) {
353 		printf("%s (skipped)\n", test->name);
354 		return false;
355 	}
356 
357 	if (test->next && !test->next(test)) {
358 		return false;
359 	}
360 
361 	func = test->func;
362         if (!func) {
363 		printf("%s (skipped)\n", test->name);
364 		return false;
365 	}
366 
367 	do {
368 		iterations *= 2;
369 		t1 = rdtsc();
370 
371 		if (!test->parallel) {
372 			for (i = 0; i < iterations; ++i)
373 				func();
374 		} else {
375 			atomic_set(&nr_cpus_done, 0);
376 			for (i = cpu_count(); i > 0; i--)
377 				on_cpu_async(i-1, run_test, func);
378 			while (atomic_read(&nr_cpus_done) < cpu_count())
379 				;
380 		}
381 		t2 = rdtsc();
382 	} while ((t2 - t1) < GOAL);
383 	printf("%s %d\n", test->name, (int)((t2 - t1) / iterations));
384 	return test->next;
385 }
386 
387 static void enable_nx(void *junk)
388 {
389 	if (cpuid(0x80000001).d & (1 << 20))
390 		wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_NX_MASK);
391 }
392 
393 bool test_wanted(struct test *test, char *wanted[], int nwanted)
394 {
395 	int i;
396 
397 	if (!nwanted)
398 		return true;
399 
400 	for (i = 0; i < nwanted; ++i)
401 		if (strcmp(wanted[i], test->name) == 0)
402 			return true;
403 
404 	return false;
405 }
406 
407 int main(int ac, char **av)
408 {
409 	int i;
410 	unsigned long membar = 0, base, offset;
411 	void *m;
412 	pcidevaddr_t pcidev;
413 
414 	smp_init();
415 	setup_vm();
416 	nr_cpus = cpu_count();
417 
418 	for (i = cpu_count(); i > 0; i--)
419 		on_cpu(i-1, enable_nx, 0);
420 
421 	pcidev = pci_find_dev(0x1b36, 0x0005);
422 	if (pcidev) {
423 		for (i = 0; i < 2; i++) {
424 			if (!pci_bar_is_valid(pcidev, i)) {
425 				continue;
426 			}
427 			if (pci_bar_is_memory(pcidev, i)) {
428 				membar = pci_bar_addr(pcidev, i);
429 				base = membar & ~4095;
430 				offset = membar - base;
431 				m = alloc_vpages(1);
432 
433 				install_page((void *)read_cr3(), base, m);
434 				pci_test.memaddr = m + offset;
435 			} else {
436 				pci_test.iobar = pci_bar_addr(pcidev, i);
437 			}
438 		}
439 		printf("pci-testdev at 0x%x membar %lx iobar %x\n",
440 		       pcidev, membar, pci_test.iobar);
441 	}
442 
443 	for (i = 0; i < ARRAY_SIZE(tests); ++i)
444 		if (test_wanted(&tests[i], av + 1, ac - 1))
445 			while (do_test(&tests[i])) {}
446 
447 	return 0;
448 }
449