libnvmm.c revision 1.17
1/*	$NetBSD: libnvmm.c,v 1.17 2019/10/27 07:08:15 maxv Exp $	*/
2
3/*
4 * Copyright (c) 2018-2019 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Maxime Villard.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <unistd.h>
38#include <fcntl.h>
39#include <errno.h>
40#include <sys/ioctl.h>
41#include <sys/mman.h>
42#include <sys/queue.h>
43#include <machine/vmparam.h>
44
45#include "nvmm.h"
46
47static struct nvmm_capability __capability;
48
49#ifdef __x86_64__
50#include "libnvmm_x86.c"
51#endif
52
53typedef struct __area {
54	LIST_ENTRY(__area) list;
55	gpaddr_t gpa;
56	uintptr_t hva;
57	size_t size;
58	nvmm_prot_t prot;
59} area_t;
60
61typedef LIST_HEAD(, __area) area_list_t;
62
63static int nvmm_fd = -1;
64
65/* -------------------------------------------------------------------------- */
66
67static bool
68__area_isvalid(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
69    size_t size)
70{
71	area_list_t *areas = mach->areas;
72	area_t *ent;
73
74	LIST_FOREACH(ent, areas, list) {
75		/* Collision on GPA */
76		if (gpa >= ent->gpa && gpa < ent->gpa + ent->size) {
77			return false;
78		}
79		if (gpa + size > ent->gpa &&
80		    gpa + size <= ent->gpa + ent->size) {
81			return false;
82		}
83		if (gpa <= ent->gpa && gpa + size >= ent->gpa + ent->size) {
84			return false;
85		}
86	}
87
88	return true;
89}
90
91static int
92__area_add(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa, size_t size,
93    int prot)
94{
95	area_list_t *areas = mach->areas;
96	nvmm_prot_t nprot;
97	area_t *area;
98
99	nprot = 0;
100	if (prot & PROT_READ)
101		nprot |= NVMM_PROT_READ;
102	if (prot & PROT_WRITE)
103		nprot |= NVMM_PROT_WRITE;
104	if (prot & PROT_EXEC)
105		nprot |= NVMM_PROT_EXEC;
106
107	if (!__area_isvalid(mach, hva, gpa, size)) {
108		errno = EINVAL;
109		return -1;
110	}
111
112	area = malloc(sizeof(*area));
113	if (area == NULL)
114		return -1;
115	area->gpa = gpa;
116	area->hva = hva;
117	area->size = size;
118	area->prot = nprot;
119
120	LIST_INSERT_HEAD(areas, area, list);
121
122	return 0;
123}
124
125static int
126__area_delete(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
127    size_t size)
128{
129	area_list_t *areas = mach->areas;
130	area_t *ent, *nxt;
131
132	LIST_FOREACH_SAFE(ent, areas, list, nxt) {
133		if (hva == ent->hva && gpa == ent->gpa && size == ent->size) {
134			LIST_REMOVE(ent, list);
135			free(ent);
136			return 0;
137		}
138	}
139
140	return -1;
141}
142
143static void
144__area_remove_all(struct nvmm_machine *mach)
145{
146	area_list_t *areas = mach->areas;
147	area_t *ent;
148
149	while ((ent = LIST_FIRST(areas)) != NULL) {
150		LIST_REMOVE(ent, list);
151		free(ent);
152	}
153
154	free(areas);
155}
156
157/* -------------------------------------------------------------------------- */
158
159int
160nvmm_init(void)
161{
162	if (nvmm_fd != -1)
163		return 0;
164	nvmm_fd = open("/dev/nvmm", O_RDONLY | O_CLOEXEC);
165	if (nvmm_fd == -1)
166		return -1;
167	if (nvmm_capability(&__capability) == -1) {
168		close(nvmm_fd);
169		nvmm_fd = -1;
170		return -1;
171	}
172	if (__capability.version != NVMM_KERN_VERSION) {
173		close(nvmm_fd);
174		nvmm_fd = -1;
175		errno = EPROGMISMATCH;
176		return -1;
177	}
178
179	return 0;
180}
181
182int
183nvmm_capability(struct nvmm_capability *cap)
184{
185	struct nvmm_ioc_capability args;
186	int ret;
187
188	ret = ioctl(nvmm_fd, NVMM_IOC_CAPABILITY, &args);
189	if (ret == -1)
190		return -1;
191
192	memcpy(cap, &args.cap, sizeof(args.cap));
193
194	return 0;
195}
196
197int
198nvmm_machine_create(struct nvmm_machine *mach)
199{
200	struct nvmm_ioc_machine_create args;
201	struct nvmm_comm_page **pages;
202	area_list_t *areas;
203	int ret;
204
205	areas = calloc(1, sizeof(*areas));
206	if (areas == NULL)
207		return -1;
208
209	pages = calloc(__capability.max_vcpus, sizeof(*pages));
210	if (pages == NULL) {
211		free(areas);
212		return -1;
213	}
214
215	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_CREATE, &args);
216	if (ret == -1) {
217		free(areas);
218		return -1;
219	}
220
221	LIST_INIT(areas);
222
223	memset(mach, 0, sizeof(*mach));
224	mach->machid = args.machid;
225	mach->pages = pages;
226	mach->areas = areas;
227
228	return 0;
229}
230
231int
232nvmm_machine_destroy(struct nvmm_machine *mach)
233{
234	struct nvmm_ioc_machine_destroy args;
235	int ret;
236
237	args.machid = mach->machid;
238
239	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_DESTROY, &args);
240	if (ret == -1)
241		return -1;
242
243	__area_remove_all(mach);
244	free(mach->pages);
245
246	return 0;
247}
248
249int
250nvmm_machine_configure(struct nvmm_machine *mach, uint64_t op, void *conf)
251{
252	struct nvmm_ioc_machine_configure args;
253	int ret;
254
255	args.machid = mach->machid;
256	args.op = op;
257	args.conf = conf;
258
259	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_CONFIGURE, &args);
260	if (ret == -1)
261		return -1;
262
263	return 0;
264}
265
266int
267nvmm_vcpu_create(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
268    struct nvmm_vcpu *vcpu)
269{
270	struct nvmm_ioc_vcpu_create args;
271	struct nvmm_comm_page *comm;
272	int ret;
273
274	args.machid = mach->machid;
275	args.cpuid = cpuid;
276
277	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_CREATE, &args);
278	if (ret == -1)
279		return -1;
280
281	comm = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FILE,
282	    nvmm_fd, NVMM_COMM_OFF(mach->machid, cpuid));
283	if (comm == MAP_FAILED)
284		return -1;
285
286	mach->pages[cpuid] = comm;
287
288	vcpu->cpuid = cpuid;
289	vcpu->state = &comm->state;
290	vcpu->event = &comm->event;
291	vcpu->exit = malloc(sizeof(*vcpu->exit));
292
293	return 0;
294}
295
296int
297nvmm_vcpu_destroy(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
298{
299	struct nvmm_ioc_vcpu_destroy args;
300	struct nvmm_comm_page *comm;
301	int ret;
302
303	args.machid = mach->machid;
304	args.cpuid = vcpu->cpuid;
305
306	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_DESTROY, &args);
307	if (ret == -1)
308		return -1;
309
310	comm = mach->pages[vcpu->cpuid];
311	munmap(comm, PAGE_SIZE);
312	free(vcpu->exit);
313
314	return 0;
315}
316
317int
318nvmm_vcpu_configure(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu,
319    uint64_t op, void *conf)
320{
321	struct nvmm_ioc_vcpu_configure args;
322	int ret;
323
324	switch (op) {
325	case NVMM_VCPU_CONF_CALLBACKS:
326		memcpy(&vcpu->cbs, conf, sizeof(vcpu->cbs));
327		return 0;
328	}
329
330	args.machid = mach->machid;
331	args.cpuid = vcpu->cpuid;
332	args.op = op;
333	args.conf = conf;
334
335	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_CONFIGURE, &args);
336	if (ret == -1)
337		return -1;
338
339	return 0;
340}
341
342int
343nvmm_vcpu_setstate(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu,
344    uint64_t flags)
345{
346	struct nvmm_comm_page *comm;
347
348	comm = mach->pages[vcpu->cpuid];
349	comm->state_commit |= flags;
350	comm->state_cached |= flags;
351
352	return 0;
353}
354
355int
356nvmm_vcpu_getstate(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu,
357    uint64_t flags)
358{
359	struct nvmm_ioc_vcpu_getstate args;
360	struct nvmm_comm_page *comm;
361	int ret;
362
363	comm = mach->pages[vcpu->cpuid];
364
365	if (__predict_true((flags & ~comm->state_cached) == 0)) {
366		return 0;
367	}
368	comm->state_wanted = flags & ~comm->state_cached;
369
370	args.machid = mach->machid;
371	args.cpuid = vcpu->cpuid;
372
373	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_GETSTATE, &args);
374	if (ret == -1)
375		return -1;
376
377	return 0;
378}
379
380int
381nvmm_vcpu_inject(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
382{
383	struct nvmm_comm_page *comm;
384
385	comm = mach->pages[vcpu->cpuid];
386	comm->event_commit = true;
387
388	return 0;
389}
390
391int
392nvmm_vcpu_run(struct nvmm_machine *mach, struct nvmm_vcpu *vcpu)
393{
394	struct nvmm_ioc_vcpu_run args;
395	int ret;
396
397	args.machid = mach->machid;
398	args.cpuid = vcpu->cpuid;
399	memset(&args.exit, 0, sizeof(args.exit));
400
401	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_RUN, &args);
402	if (ret == -1)
403		return -1;
404
405	/* No comm support yet, just copy. */
406	memcpy(vcpu->exit, &args.exit, sizeof(args.exit));
407
408	return 0;
409}
410
411int
412nvmm_gpa_map(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
413    size_t size, int prot)
414{
415	struct nvmm_ioc_gpa_map args;
416	int ret;
417
418	ret = __area_add(mach, hva, gpa, size, prot);
419	if (ret == -1)
420		return -1;
421
422	args.machid = mach->machid;
423	args.hva = hva;
424	args.gpa = gpa;
425	args.size = size;
426	args.prot = prot;
427
428	ret = ioctl(nvmm_fd, NVMM_IOC_GPA_MAP, &args);
429	if (ret == -1) {
430		/* Can't recover. */
431		abort();
432	}
433
434	return 0;
435}
436
437int
438nvmm_gpa_unmap(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
439    size_t size)
440{
441	struct nvmm_ioc_gpa_unmap args;
442	int ret;
443
444	ret = __area_delete(mach, hva, gpa, size);
445	if (ret == -1)
446		return -1;
447
448	args.machid = mach->machid;
449	args.gpa = gpa;
450	args.size = size;
451
452	ret = ioctl(nvmm_fd, NVMM_IOC_GPA_UNMAP, &args);
453	if (ret == -1) {
454		/* Can't recover. */
455		abort();
456	}
457
458	return 0;
459}
460
461int
462nvmm_hva_map(struct nvmm_machine *mach, uintptr_t hva, size_t size)
463{
464	struct nvmm_ioc_hva_map args;
465	int ret;
466
467	args.machid = mach->machid;
468	args.hva = hva;
469	args.size = size;
470
471	ret = ioctl(nvmm_fd, NVMM_IOC_HVA_MAP, &args);
472	if (ret == -1)
473		return -1;
474
475	return 0;
476}
477
478int
479nvmm_hva_unmap(struct nvmm_machine *mach, uintptr_t hva, size_t size)
480{
481	struct nvmm_ioc_hva_unmap args;
482	int ret;
483
484	args.machid = mach->machid;
485	args.hva = hva;
486	args.size = size;
487
488	ret = ioctl(nvmm_fd, NVMM_IOC_HVA_UNMAP, &args);
489	if (ret == -1)
490		return -1;
491
492	return 0;
493}
494
495/*
496 * nvmm_gva_to_gpa(): architecture-specific.
497 */
498
499int
500nvmm_gpa_to_hva(struct nvmm_machine *mach, gpaddr_t gpa, uintptr_t *hva,
501    nvmm_prot_t *prot)
502{
503	area_list_t *areas = mach->areas;
504	area_t *ent;
505
506	LIST_FOREACH(ent, areas, list) {
507		if (gpa >= ent->gpa && gpa < ent->gpa + ent->size) {
508			*hva = ent->hva + (gpa - ent->gpa);
509			*prot = ent->prot;
510			return 0;
511		}
512	}
513
514	errno = ENOENT;
515	return -1;
516}
517
518/*
519 * nvmm_assist_io(): architecture-specific.
520 */
521
522/*
523 * nvmm_assist_mem(): architecture-specific.
524 */
525
526int
527nvmm_ctl(int op, void *data, size_t size)
528{
529	struct nvmm_ioc_ctl args;
530	int ret;
531
532	args.op = op;
533	args.data = data;
534	args.size = size;
535
536	ret = ioctl(nvmm_fd, NVMM_IOC_CTL, &args);
537	if (ret == -1)
538		return -1;
539
540	return 0;
541}
542