libnvmm.c revision 1.13
1/*	$NetBSD: libnvmm.c,v 1.13 2019/05/11 07:31:57 maxv Exp $	*/
2
3/*
4 * Copyright (c) 2018 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
159static int
160nvmm_init(void)
161{
162	if (nvmm_fd != -1)
163		return 0;
164	nvmm_fd = open("/dev/nvmm", O_RDWR);
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	return 0;
173}
174
175int
176nvmm_capability(struct nvmm_capability *cap)
177{
178	struct nvmm_ioc_capability args;
179	int ret;
180
181	if (nvmm_init() == -1) {
182		return -1;
183	}
184
185	ret = ioctl(nvmm_fd, NVMM_IOC_CAPABILITY, &args);
186	if (ret == -1)
187		return -1;
188
189	memcpy(cap, &args.cap, sizeof(args.cap));
190
191	return 0;
192}
193
194int
195nvmm_machine_create(struct nvmm_machine *mach)
196{
197	struct nvmm_ioc_machine_create args;
198	struct nvmm_comm_page **pages;
199	area_list_t *areas;
200	int ret;
201
202	if (nvmm_init() == -1) {
203		return -1;
204	}
205
206	areas = calloc(1, sizeof(*areas));
207	if (areas == NULL)
208		return -1;
209
210	pages = calloc(__capability.max_vcpus, sizeof(*pages));
211	if (pages == NULL) {
212		free(areas);
213		return -1;
214	}
215
216	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_CREATE, &args);
217	if (ret == -1) {
218		free(areas);
219		return -1;
220	}
221
222	LIST_INIT(areas);
223
224	memset(mach, 0, sizeof(*mach));
225	mach->machid = args.machid;
226	mach->pages = pages;
227	mach->npages = __capability.max_vcpus;
228	mach->areas = areas;
229
230	return 0;
231}
232
233int
234nvmm_machine_destroy(struct nvmm_machine *mach)
235{
236	struct nvmm_ioc_machine_destroy args;
237	int ret;
238
239	args.machid = mach->machid;
240
241	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_DESTROY, &args);
242	if (ret == -1)
243		return -1;
244
245	__area_remove_all(mach);
246	free(mach->pages);
247
248	return 0;
249}
250
251int
252nvmm_machine_configure(struct nvmm_machine *mach, uint64_t op, void *conf)
253{
254	struct nvmm_ioc_machine_configure args;
255	int ret;
256
257	switch (op) {
258	case NVMM_MACH_CONF_CALLBACKS:
259		memcpy(&mach->cbs, conf, sizeof(mach->cbs));
260		return 0;
261	}
262
263	args.machid = mach->machid;
264	args.op = op;
265	args.conf = conf;
266
267	ret = ioctl(nvmm_fd, NVMM_IOC_MACHINE_CONFIGURE, &args);
268	if (ret == -1)
269		return -1;
270
271	return 0;
272}
273
274int
275nvmm_vcpu_create(struct nvmm_machine *mach, nvmm_cpuid_t cpuid)
276{
277	struct nvmm_ioc_vcpu_create args;
278	struct nvmm_comm_page *comm;
279	int ret;
280
281	args.machid = mach->machid;
282	args.cpuid = cpuid;
283
284	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_CREATE, &args);
285	if (ret == -1)
286		return -1;
287
288	comm = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FILE,
289	    nvmm_fd, NVMM_COMM_OFF(mach->machid, cpuid));
290	if (comm == MAP_FAILED)
291		return -1;
292
293	mach->pages[cpuid] = comm;
294
295	return 0;
296}
297
298int
299nvmm_vcpu_destroy(struct nvmm_machine *mach, nvmm_cpuid_t cpuid)
300{
301	struct nvmm_ioc_vcpu_destroy args;
302	struct nvmm_comm_page *comm;
303	int ret;
304
305	args.machid = mach->machid;
306	args.cpuid = cpuid;
307
308	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_DESTROY, &args);
309	if (ret == -1)
310		return -1;
311
312	comm = mach->pages[cpuid];
313	munmap(comm, PAGE_SIZE);
314
315	return 0;
316}
317
318int
319nvmm_vcpu_setstate(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
320    void *state, uint64_t flags)
321{
322	struct nvmm_comm_page *comm;
323
324	if (__predict_false(cpuid >= mach->npages)) {
325		return -1;
326	}
327	comm = mach->pages[cpuid];
328
329	nvmm_arch_copystate(&comm->state, state, flags);
330	comm->state_commit |= flags;
331	comm->state_cached |= flags;
332
333	return 0;
334}
335
336int
337nvmm_vcpu_getstate(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
338    void *state, uint64_t flags)
339{
340	struct nvmm_ioc_vcpu_getstate args;
341	struct nvmm_comm_page *comm;
342	int ret;
343
344	if (__predict_false(cpuid >= mach->npages)) {
345		return -1;
346	}
347	comm = mach->pages[cpuid];
348
349	if (__predict_true((flags & ~comm->state_cached) == 0)) {
350		goto out;
351	}
352	comm->state_wanted = flags & ~comm->state_cached;
353
354	args.machid = mach->machid;
355	args.cpuid = cpuid;
356
357	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_GETSTATE, &args);
358	if (ret == -1)
359		return -1;
360
361out:
362	nvmm_arch_copystate(state, &comm->state, flags);
363	return 0;
364}
365
366int
367nvmm_vcpu_inject(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
368    struct nvmm_event *event)
369{
370	struct nvmm_comm_page *comm;
371
372	if (__predict_false(cpuid >= mach->npages)) {
373		return -1;
374	}
375	comm = mach->pages[cpuid];
376
377	memcpy(&comm->event, event, sizeof(comm->event));
378	comm->event_commit = true;
379
380	return 0;
381}
382
383int
384nvmm_vcpu_run(struct nvmm_machine *mach, nvmm_cpuid_t cpuid,
385    struct nvmm_exit *exit)
386{
387	struct nvmm_ioc_vcpu_run args;
388	int ret;
389
390	args.machid = mach->machid;
391	args.cpuid = cpuid;
392	memset(&args.exit, 0, sizeof(args.exit));
393
394	ret = ioctl(nvmm_fd, NVMM_IOC_VCPU_RUN, &args);
395	if (ret == -1)
396		return -1;
397
398	memcpy(exit, &args.exit, sizeof(args.exit));
399
400	return 0;
401}
402
403int
404nvmm_gpa_map(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
405    size_t size, int prot)
406{
407	struct nvmm_ioc_gpa_map args;
408	int ret;
409
410	ret = __area_add(mach, hva, gpa, size, prot);
411	if (ret == -1)
412		return -1;
413
414	args.machid = mach->machid;
415	args.hva = hva;
416	args.gpa = gpa;
417	args.size = size;
418	args.prot = prot;
419
420	ret = ioctl(nvmm_fd, NVMM_IOC_GPA_MAP, &args);
421	if (ret == -1) {
422		/* Can't recover. */
423		abort();
424	}
425
426	return 0;
427}
428
429int
430nvmm_gpa_unmap(struct nvmm_machine *mach, uintptr_t hva, gpaddr_t gpa,
431    size_t size)
432{
433	struct nvmm_ioc_gpa_unmap args;
434	int ret;
435
436	ret = __area_delete(mach, hva, gpa, size);
437	if (ret == -1)
438		return -1;
439
440	args.machid = mach->machid;
441	args.gpa = gpa;
442	args.size = size;
443
444	ret = ioctl(nvmm_fd, NVMM_IOC_GPA_UNMAP, &args);
445	if (ret == -1) {
446		/* Can't recover. */
447		abort();
448	}
449
450	return 0;
451}
452
453int
454nvmm_hva_map(struct nvmm_machine *mach, uintptr_t hva, size_t size)
455{
456	struct nvmm_ioc_hva_map args;
457	int ret;
458
459	args.machid = mach->machid;
460	args.hva = hva;
461	args.size = size;
462
463	ret = ioctl(nvmm_fd, NVMM_IOC_HVA_MAP, &args);
464	if (ret == -1)
465		return -1;
466
467	return 0;
468}
469
470int
471nvmm_hva_unmap(struct nvmm_machine *mach, uintptr_t hva, size_t size)
472{
473	struct nvmm_ioc_hva_unmap args;
474	int ret;
475
476	args.machid = mach->machid;
477	args.hva = hva;
478	args.size = size;
479
480	ret = ioctl(nvmm_fd, NVMM_IOC_HVA_UNMAP, &args);
481	if (ret == -1)
482		return -1;
483
484	return 0;
485}
486
487/*
488 * nvmm_gva_to_gpa(): architecture-specific.
489 */
490
491int
492nvmm_gpa_to_hva(struct nvmm_machine *mach, gpaddr_t gpa, uintptr_t *hva,
493    nvmm_prot_t *prot)
494{
495	area_list_t *areas = mach->areas;
496	area_t *ent;
497
498	LIST_FOREACH(ent, areas, list) {
499		if (gpa >= ent->gpa && gpa < ent->gpa + ent->size) {
500			*hva = ent->hva + (gpa - ent->gpa);
501			*prot = ent->prot;
502			return 0;
503		}
504	}
505
506	errno = ENOENT;
507	return -1;
508}
509
510/*
511 * nvmm_assist_io(): architecture-specific.
512 */
513
514/*
515 * nvmm_assist_mem(): architecture-specific.
516 */
517
518int
519nvmm_ctl(int op, void *data, size_t size)
520{
521	struct nvmm_ioc_ctl args;
522	int ret;
523
524	if (nvmm_init() == -1) {
525		return -1;
526	}
527
528	args.op = op;
529	args.data = data;
530	args.size = size;
531
532	ret = ioctl(nvmm_fd, NVMM_IOC_CTL, &args);
533	if (ret == -1)
534		return -1;
535
536	return 0;
537}
538