xref: /dragonfly/test/nvmm/demo/toyvirt/main.c (revision 7d3e9a5b)
1 /*
2  * Copyright (c) 2018-2021 Maxime Villard, m00nbsd.net
3  * All rights reserved.
4  *
5  * This code is part of the NVMM hypervisor.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/mman.h>
31 
32 #include <assert.h>
33 #include <err.h>
34 #include <pthread.h>
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 
41 #include "common.h"
42 
43 #undef  MSR_APICBASE
44 #define MSR_APICBASE	0x01b
45 #undef  APICBASE_BSP
46 #define APICBASE_BSP	0x00000100 /* bootstrap processor */
47 #undef  APICBASE_EN
48 #define APICBASE_EN	0x00000800 /* software enable */
49 
50 /* -------------------------------------------------------------------------- */
51 
52 uintptr_t
53 toyvirt_mem_add(struct nvmm_machine *mach, gpaddr_t gpa, size_t size)
54 {
55 	uintptr_t hva;
56 	void *buf;
57 
58 	assert(size > 0);
59 
60 	buf = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
61 	if (buf == MAP_FAILED)
62 		err(EXIT_FAILURE, "mmap");
63 
64 	hva = (uintptr_t)buf;
65 	if (nvmm_hva_map(mach, hva, size) == -1)
66 		err(EXIT_FAILURE, "nvmm_hva_map");
67 	if (nvmm_gpa_map(mach, hva, gpa, size, PROT_READ|PROT_WRITE|PROT_EXEC) == -1)
68 		err(EXIT_FAILURE, "nvmm_gpa_map");
69 
70 	return hva;
71 }
72 
73 /* -------------------------------------------------------------------------- */
74 
75 static bool can_take_int = false;
76 static bool can_take_nmi = false;
77 static bool has_int_pending = false;
78 static bool has_nmi_pending = false;
79 static struct nvmm_vcpu_event pending_int;
80 static struct nvmm_vcpu_event pending_nmi;
81 
82 static void
83 toyvirt_event_inject(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu,
84     struct nvmm_vcpu_event *event)
85 {
86 	memcpy(vcpu->event, event, sizeof(*event));
87 
88 	/* INT. */
89 	if (event->vector != 2) {
90 		if (can_take_int) {
91 			if (nvmm_vcpu_inject(mach, vcpu) == -1)
92 				err(EXIT_FAILURE, "nvmm_vcpu_inject");
93 			has_int_pending = false;
94 		} else {
95 			memcpy(&pending_int, event, sizeof(pending_int));
96 			has_int_pending = true;
97 		}
98 	}
99 
100 	/* NMI. */
101 	if (event->vector == 2) {
102 		if (can_take_nmi) {
103 			if (nvmm_vcpu_inject(mach, vcpu) == -1)
104 				err(EXIT_FAILURE, "nvmm_vcpu_inject");
105 			has_nmi_pending = false;
106 		} else {
107 			memcpy(&pending_nmi, event, sizeof(pending_nmi));
108 			has_nmi_pending = true;
109 		}
110 	}
111 }
112 
113 static void
114 toyvirt_event_reinject(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
115 {
116 	struct nvmm_vcpu_exit *exit = vcpu->exit;
117 
118 	if (exit->reason == NVMM_VCPU_EXIT_INT_READY) {
119 		if (!has_int_pending)
120 			errx(EXIT_FAILURE, "no INT pending!");
121 		toyvirt_event_inject(mach, vcpu, &pending_int);
122 	} else {
123 		if (!has_nmi_pending)
124 			errx(EXIT_FAILURE, "no NMI pending!");
125 		toyvirt_event_inject(mach, vcpu, &pending_nmi);
126 	}
127 }
128 
129 /* -------------------------------------------------------------------------- */
130 
131 static void
132 toycpu_io_callback(struct nvmm_io *io)
133 {
134 	/* Hand over to toydev. */
135 	toydev_io(io->port, io->in, io->data, io->size);
136 }
137 
138 static void
139 toyvirt_mem_callback(struct nvmm_mem *mem)
140 {
141 	/* Hand over to toydev. */
142 	toydev_mmio(mem->gpa, mem->write, mem->data, mem->size);
143 }
144 
145 static struct nvmm_assist_callbacks callbacks = {
146 	.io = toycpu_io_callback,
147 	.mem = toyvirt_mem_callback
148 };
149 
150 /* -------------------------------------------------------------------------- */
151 
152 static void
153 toyvirt_vcpu_configure(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
154 {
155 	struct nvmm_vcpu_conf_cpuid cpuid;
156 	int ret;
157 
158 	/*
159 	 * Register the assist callbacks.
160 	 */
161 	ret = nvmm_vcpu_configure(mach, vcpu, NVMM_VCPU_CONF_CALLBACKS,
162 	    &callbacks);
163 	if (ret == -1)
164 		err(EXIT_FAILURE, "nvmm_vcpu_configure");
165 
166 	/*
167 	 * Hide the No-Execute bit. No particular reason, just to demonstrate.
168 	 */
169 	memset(&cpuid, 0, sizeof(cpuid));
170 	cpuid.mask = 1;
171 	cpuid.leaf = 0x80000001;
172 	cpuid.u.mask.del.edx = CPUID_8_01_EDX_XD;
173 	ret = nvmm_vcpu_configure(mach, vcpu, NVMM_VCPU_CONF_CPUID, &cpuid);
174 	if (ret == -1)
175 		err(EXIT_FAILURE, "nvmm_vcpu_configure");
176 }
177 
178 static void
179 toyvirt_init_seg(struct nvmm_x64_state_seg *seg, int type, int sel, int limit)
180 {
181 	seg->selector = sel;
182 	seg->attrib.type = type;
183 	seg->attrib.s = (type & 0b10000) != 0;
184 	seg->attrib.dpl = 0;
185 	seg->attrib.p = 1;
186 	seg->attrib.avl = 1;
187 	seg->attrib.l = 0;
188 	seg->attrib.def = 1;
189 	seg->attrib.g = 1;
190 	seg->limit = limit;
191 	seg->base = 0x00000000;
192 }
193 
194 static void
195 toyvirt_init(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu,
196     const char *path)
197 {
198 	struct nvmm_vcpu_state *state = vcpu->state;
199 	uint64_t rip;
200 
201 	if (nvmm_vcpu_getstate(mach, vcpu, NVMM_X64_STATE_ALL) == -1)
202 		errx(EXIT_FAILURE, "nvmm_vcpu_getstate");
203 
204 	/* Default. */
205 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_CS],
206 	    27 /* memory execute read accessed */, 0, 0xFFFFFFFF);
207 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_SS],
208 	    19 /* memory read write accessed */, 0, 0xFFFFFFFF);
209 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_DS],
210 	    19 /* memory read write accessed */, 0, 0xFFFFFFFF);
211 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_ES],
212 	    19 /* memory read write accessed */, 0, 0xFFFFFFFF);
213 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_FS],
214 	    19 /* memory read write accessed */, 0, 0xFFFFFFFF);
215 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_GS],
216 	    19 /* memory read write accessed */, 0, 0xFFFFFFFF);
217 
218 	/* Blank. */
219 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_GDT], 0, 0, 0x0000FFFF);
220 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_IDT], 0, 0, 0x0000FFFF);
221 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_LDT], 2, 0, 0xFFFFFFFF);
222 	toyvirt_init_seg(&state->segs[NVMM_X64_SEG_TR], 11, 0, 0xFFFFFFFF);
223 
224 	/* Protected mode enabled. */
225 	state->crs[NVMM_X64_CR_CR0] = CR0_PE | CR0_ET | CR0_NW | CR0_CD;
226 
227 	/* Map the VM. */
228 	if (elf_map(mach, path, &rip) != 0)
229 		errx(EXIT_FAILURE, "unable to map the vm");
230 
231 	state->gprs[NVMM_X64_GPR_RIP] = rip; /* jump here */
232 
233 	if (nvmm_vcpu_setstate(mach, vcpu, NVMM_X64_STATE_ALL) == -1)
234 		err(EXIT_FAILURE, "nvmm_vcpu_setstate");
235 }
236 
237 /* -------------------------------------------------------------------------- */
238 
239 static uint8_t toyvirt_prio = 0;
240 
241 static struct {
242 	struct nvmm_machine *mach;
243 	struct nvmm_vcpu *vcpu;
244 } toyvirt;
245 
246 /*
247  * Create mess in the VCPU. Inject random events at regular intervals.
248  */
249 static void *
250 toyvirt_mess(void *arg __unused)
251 {
252 	struct nvmm_machine *mach = toyvirt.mach;
253 	struct nvmm_vcpu *vcpu = toyvirt.vcpu;
254 	struct nvmm_vcpu_event event;
255 
256 	while (1) {
257 		sleep(3);
258 
259 		/* Inject a #GP */
260 		printf("[+] Inject #GP event\n");
261 		event.type = NVMM_VCPU_EVENT_EXCP;
262 		event.vector = 13;
263 		event.u.excp.error = 0;
264 		toyvirt_event_inject(mach, vcpu, &event);
265 
266 		sleep(3);
267 
268 		/* Inject an #NMI */
269 		printf("[+] Inject #NMI event\n");
270 		event.type = NVMM_VCPU_EVENT_INTR;
271 		event.vector = 2;
272 		toyvirt_event_inject(mach, vcpu, &event);
273 
274 		sleep(3);
275 
276 		/* Inject an interrupt */
277 		if (15 > toyvirt_prio) {
278 			printf("[+] Inject hardware interrupt event\n");
279 			event.type = NVMM_VCPU_EVENT_INTR;
280 			event.vector = 200;
281 			toyvirt_event_inject(mach, vcpu, &event);
282 		}
283 	}
284 
285 	pthread_exit(NULL);
286 }
287 
288 /* -------------------------------------------------------------------------- */
289 
290 /*
291  * Support one MSR: MSR_APICBASE.
292  */
293 static int
294 toycpu_rdmsr(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
295 {
296 	struct nvmm_vcpu_state *state = vcpu->state;
297 	struct nvmm_vcpu_exit *exit = vcpu->exit;
298 	uint64_t val;
299 
300 	if (exit->u.rdmsr.msr != MSR_APICBASE) {
301 		printf("Unknown MSR!\n");
302 		return -1;
303 	}
304 
305 	if (nvmm_vcpu_getstate(mach, vcpu, NVMM_X64_STATE_GPRS) == -1)
306 		err(EXIT_FAILURE, "nvmm_vcpu_getstate");
307 
308 	val = APICBASE_BSP | APICBASE_EN | 0xfee00000;
309 
310 	state->gprs[NVMM_X64_GPR_RAX] = (val & 0xFFFFFFFF);
311 	state->gprs[NVMM_X64_GPR_RDX] = (val >> 32);
312 	state->gprs[NVMM_X64_GPR_RIP] = exit->u.rdmsr.npc;
313 
314 	if (nvmm_vcpu_setstate(mach, vcpu, NVMM_X64_STATE_GPRS) == -1)
315 		err(EXIT_FAILURE, "nvmm_vcpu_setstate");
316 
317 	return 0;
318 }
319 
320 static void
321 toyvirt_invalid(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
322 {
323 	struct nvmm_vcpu_state *state = vcpu->state;
324 
325 	if (nvmm_vcpu_getstate(mach, vcpu, NVMM_X64_STATE_GPRS) == -1)
326 		err(EXIT_FAILURE, "nvmm_vcpu_getstate");
327 
328 	printf("[!] Invalid exit: rip=%p\n",
329 	    (void *)state->gprs[NVMM_X64_GPR_RIP]);
330 }
331 
332 static void
333 toyvirt_run(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
334 {
335 	struct nvmm_vcpu_exit *exit = vcpu->exit;
336 	pthread_t thid;
337 	int ret;
338 
339 	toyvirt.mach = mach;
340 	toyvirt.vcpu = vcpu;
341 	pthread_create(&thid, NULL, toyvirt_mess, NULL);
342 
343 	while (1) {
344 		if (nvmm_vcpu_run(mach, vcpu) == -1)
345 			err(EXIT_FAILURE, "nvmm_vcpu_run");
346 
347 		toyvirt_prio = exit->exitstate.cr8;
348 		can_take_int = !exit->exitstate.int_window_exiting;
349 		can_take_nmi = !exit->exitstate.nmi_window_exiting;
350 
351 		switch (exit->reason) {
352 		case NVMM_VCPU_EXIT_NONE:
353 			/*
354 			 * A VMEXIT caused by whatever internal reason, that
355 			 * we shouldn't take care of. Keep rolling.
356 			 */
357 			continue;
358 
359 		case NVMM_VCPU_EXIT_IO:
360 			ret = nvmm_assist_io(mach, vcpu);
361 			if (ret == -1)
362 				err(EXIT_FAILURE, "nvmm_assist_io");
363 			continue;
364 
365 		case NVMM_VCPU_EXIT_RDMSR:
366 			toycpu_rdmsr(mach, vcpu);
367 			continue;
368 
369 		case NVMM_VCPU_EXIT_MEMORY:
370 			ret = nvmm_assist_mem(mach, vcpu);
371 			if (ret == -1)
372 				err(EXIT_FAILURE, "nvmm_assist_mem");
373 			continue;
374 
375 		case NVMM_VCPU_EXIT_INT_READY:
376 		case NVMM_VCPU_EXIT_NMI_READY:
377 			printf("[+] Machine ready to INT/NMI\n");
378 			toyvirt_event_reinject(mach, vcpu);
379 			return;
380 
381 		case NVMM_VCPU_EXIT_SHUTDOWN:
382 			/* Stop the VM here. */
383 			printf("[+] Machine received shutdown\n");
384 			return;
385 
386 		case NVMM_VCPU_EXIT_INVALID:
387 		default:
388 			toyvirt_invalid(mach, vcpu);
389 			return;
390 		}
391 	}
392 }
393 
394 int main(int argc, char *argv[])
395 {
396 	struct nvmm_machine mach;
397 	struct nvmm_vcpu vcpu;
398 
399 	if (argc != 2)
400 		errx(EXIT_FAILURE, "usage: %s file-path", argv[0]);
401 
402 	if (nvmm_init() == -1)
403 		err(EXIT_FAILURE, "nvmm_init");
404 	printf("[+] NVMM initialization succeeded\n");
405 
406 	if (nvmm_machine_create(&mach) == -1)
407 		err(EXIT_FAILURE, "nvmm_machine_create");
408 	printf("[+] Machine creation succeeded\n");
409 
410 	if (nvmm_vcpu_create(&mach, 120, &vcpu) == -1)
411 		err(EXIT_FAILURE, "nvmm_vcpu_create");
412 	printf("[+] VCPU creation succeeded\n");
413 
414 	toyvirt_vcpu_configure(&mach, &vcpu);
415 	printf("[+] VCPU configuration succeeded\n");
416 
417 	toyvirt_init(&mach, &vcpu, argv[1]);
418 	printf("[+] State set\n");
419 
420 	toyvirt_run(&mach, &vcpu);
421 	printf("[+] Machine execution successful\n");
422 
423 	if (nvmm_machine_destroy(&mach) == -1)
424 		err(EXIT_FAILURE, "nvmm_machine_destroy");
425 
426 	printf("[+] Machine destroyed\n");
427 
428 	return 0;
429 }
430