xref: /illumos-gate/usr/src/cmd/bhyve/pci_virtio_9p.c (revision abb88ab1b9516b1ca12094db7f2cfb5d91e0a135)
1 /*-
2  * Copyright (c) 2015 iXsystems Inc.
3  * Copyright (c) 2017-2018 Jakub Klama <jceel@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer
11  *    in this position and unchanged.
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 AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, 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 /*
30  * VirtIO filesystem passthrough using 9p protocol.
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 #include <sys/param.h>
37 #include <sys/linker_set.h>
38 #include <sys/uio.h>
39 #ifndef WITHOUT_CAPSICUM
40 #include <sys/capsicum.h>
41 #endif
42 
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include <assert.h>
50 #include <pthread.h>
51 
52 #include <lib9p.h>
53 #include <backend/fs.h>
54 
55 #include "bhyverun.h"
56 #include "config.h"
57 #include "debug.h"
58 #include "pci_emul.h"
59 #include "virtio.h"
60 
61 #ifndef __FreeBSD__
62 #include "privileges.h"
63 #endif
64 
65 #define	VT9P_MAX_IOV	128
66 #define VT9P_RINGSZ	256
67 #define	VT9P_MAXTAGSZ	256
68 #define	VT9P_CONFIGSPACESZ	(VT9P_MAXTAGSZ + sizeof(uint16_t))
69 
70 static int pci_vt9p_debug;
71 #define DPRINTF(params) if (pci_vt9p_debug) printf params
72 #define WPRINTF(params) printf params
73 
74 /*
75  * Per-device softc
76  */
77 struct pci_vt9p_softc {
78 	struct virtio_softc      vsc_vs;
79 	struct vqueue_info       vsc_vq;
80 	pthread_mutex_t          vsc_mtx;
81 	uint64_t                 vsc_cfg;
82 	uint64_t                 vsc_features;
83 	char *                   vsc_rootpath;
84 	struct pci_vt9p_config * vsc_config;
85 	struct l9p_backend *     vsc_fs_backend;
86 	struct l9p_server *      vsc_server;
87         struct l9p_connection *  vsc_conn;
88 };
89 
90 struct pci_vt9p_request {
91 	struct pci_vt9p_softc *	vsr_sc;
92 	struct iovec *		vsr_iov;
93 	size_t			vsr_niov;
94 	size_t			vsr_respidx;
95 	size_t			vsr_iolen;
96 	uint16_t		vsr_idx;
97 };
98 
99 struct pci_vt9p_config {
100 	uint16_t tag_len;
101 	char tag[0];
102 } __attribute__((packed));
103 
104 static int pci_vt9p_send(struct l9p_request *, const struct iovec *,
105     const size_t, const size_t, void *);
106 static void pci_vt9p_drop(struct l9p_request *, const struct iovec *, size_t,
107     void *);
108 static void pci_vt9p_reset(void *);
109 static void pci_vt9p_notify(void *, struct vqueue_info *);
110 static int pci_vt9p_cfgread(void *, int, int, uint32_t *);
111 static void pci_vt9p_neg_features(void *, uint64_t);
112 
113 static struct virtio_consts vt9p_vi_consts = {
114 	"vt9p",			/* our name */
115 	1,			/* we support 1 virtqueue */
116 	VT9P_CONFIGSPACESZ,	/* config reg size */
117 	pci_vt9p_reset,		/* reset */
118 	pci_vt9p_notify,	/* device-wide qnotify */
119 	pci_vt9p_cfgread,	/* read virtio config */
120 	NULL,			/* write virtio config */
121 	pci_vt9p_neg_features,	/* apply negotiated features */
122 	(1 << 0),		/* our capabilities */
123 };
124 
125 
126 static void
127 pci_vt9p_reset(void *vsc)
128 {
129 	struct pci_vt9p_softc *sc;
130 
131 	sc = vsc;
132 
133 	DPRINTF(("vt9p: device reset requested !\n"));
134 	vi_reset_dev(&sc->vsc_vs);
135 }
136 
137 static void
138 pci_vt9p_neg_features(void *vsc, uint64_t negotiated_features)
139 {
140 	struct pci_vt9p_softc *sc = vsc;
141 
142 	sc->vsc_features = negotiated_features;
143 }
144 
145 static int
146 pci_vt9p_cfgread(void *vsc, int offset, int size, uint32_t *retval)
147 {
148 	struct pci_vt9p_softc *sc = vsc;
149 	void *ptr;
150 
151 	ptr = (uint8_t *)sc->vsc_config + offset;
152 	memcpy(retval, ptr, size);
153 	return (0);
154 }
155 
156 static int
157 pci_vt9p_get_buffer(struct l9p_request *req, struct iovec *iov, size_t *niov,
158     void *arg)
159 {
160 	struct pci_vt9p_request *preq = req->lr_aux;
161 	size_t n = preq->vsr_niov - preq->vsr_respidx;
162 
163 	memcpy(iov, preq->vsr_iov + preq->vsr_respidx,
164 	    n * sizeof(struct iovec));
165 	*niov = n;
166 	return (0);
167 }
168 
169 static int
170 pci_vt9p_send(struct l9p_request *req, const struct iovec *iov,
171     const size_t niov, const size_t iolen, void *arg)
172 {
173 	struct pci_vt9p_request *preq = req->lr_aux;
174 	struct pci_vt9p_softc *sc = preq->vsr_sc;
175 
176 	preq->vsr_iolen = iolen;
177 
178 	pthread_mutex_lock(&sc->vsc_mtx);
179 	vq_relchain(&sc->vsc_vq, preq->vsr_idx, preq->vsr_iolen);
180 	vq_endchains(&sc->vsc_vq, 1);
181 	pthread_mutex_unlock(&sc->vsc_mtx);
182 	free(preq);
183 	return (0);
184 }
185 
186 static void
187 pci_vt9p_drop(struct l9p_request *req, const struct iovec *iov, size_t niov,
188     void *arg)
189 {
190 	struct pci_vt9p_request *preq = req->lr_aux;
191 	struct pci_vt9p_softc *sc = preq->vsr_sc;
192 
193 	pthread_mutex_lock(&sc->vsc_mtx);
194 	vq_relchain(&sc->vsc_vq, preq->vsr_idx, 0);
195 	vq_endchains(&sc->vsc_vq, 1);
196 	pthread_mutex_unlock(&sc->vsc_mtx);
197 	free(preq);
198 }
199 
200 static void
201 pci_vt9p_notify(void *vsc, struct vqueue_info *vq)
202 {
203 	struct iovec iov[VT9P_MAX_IOV];
204 	struct pci_vt9p_softc *sc;
205 	struct pci_vt9p_request *preq;
206 	struct vi_req req;
207 	int n;
208 
209 	sc = vsc;
210 
211 	while (vq_has_descs(vq)) {
212 		n = vq_getchain(vq, iov, VT9P_MAX_IOV, &req);
213 		assert(n >= 1 && n <= VT9P_MAX_IOV);
214 		preq = calloc(1, sizeof(struct pci_vt9p_request));
215 #ifndef __FreeBSD__
216 		if (preq == NULL) {
217 			EPRINTLN("virtio-9p: allocation failure: %s",
218 			    strerror(errno));
219 			break;
220 		}
221 #endif
222 		preq->vsr_sc = sc;
223 		preq->vsr_idx = req.idx;
224 		preq->vsr_iov = iov;
225 		preq->vsr_niov = n;
226 		preq->vsr_respidx = req.readable;
227 
228 		for (int i = 0; i < n; i++) {
229 			DPRINTF(("vt9p: vt9p_notify(): desc%d base=%p, "
230 			    "len=%zu\r\n", i, iov[i].iov_base,
231 			    iov[i].iov_len));
232 		}
233 
234 		l9p_connection_recv(sc->vsc_conn, iov, preq->vsr_respidx, preq);
235 	}
236 }
237 
238 static int
239 pci_vt9p_legacy_config(nvlist_t *nvl, const char *opts)
240 {
241 	char *sharename = NULL, *tofree, *token, *tokens;
242 
243 	if (opts == NULL)
244 		return (0);
245 
246 	tokens = tofree = strdup(opts);
247 	while ((token = strsep(&tokens, ",")) != NULL) {
248 		if (strchr(token, '=') != NULL) {
249 			if (sharename != NULL) {
250 				EPRINTLN(
251 			    "virtio-9p: more than one share name given");
252 				return (-1);
253 			}
254 
255 			sharename = strsep(&token, "=");
256 			set_config_value_node(nvl, "sharename", sharename);
257 			set_config_value_node(nvl, "path", token);
258 		} else
259 			set_config_bool_node(nvl, token, true);
260 	}
261 	free(tofree);
262 
263 	return (0);
264 }
265 
266 static int
267 pci_vt9p_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl)
268 {
269 	struct pci_vt9p_softc *sc;
270 	const char *value;
271 	const char *sharename;
272 	int rootfd;
273 	bool ro;
274 #ifndef WITHOUT_CAPSICUM
275 	cap_rights_t rootcap;
276 #endif
277 
278 	ro = get_config_bool_node_default(nvl, "ro", false);
279 
280 #ifndef __FreeBSD__
281 	illumos_priv_add_min(PRIV_FILE_DAC_READ, "vt9p");
282 	illumos_priv_add_min(PRIV_FILE_DAC_SEARCH, "vt9p");
283 
284 	if (!ro) {
285 		illumos_priv_add_min(PRIV_FILE_CHOWN, "vt9p");
286 		illumos_priv_add_min(PRIV_FILE_CHOWN_SELF, "vt9p");
287 		illumos_priv_add_min(PRIV_FILE_WRITE, "vt9p");
288 		illumos_priv_add_min(PRIV_FILE_DAC_WRITE, "vt9p");
289 		illumos_priv_add_min(PRIV_FILE_OWNER, "vt9p");
290 		illumos_priv_add_min(PRIV_FILE_LINK_ANY, "vt9p");
291 	}
292 #endif
293 
294 	value = get_config_value_node(nvl, "path");
295 	if (value == NULL) {
296 		EPRINTLN("virtio-9p: path required");
297 		return (1);
298 	}
299 	rootfd = open(value, O_DIRECTORY);
300 	if (rootfd < 0) {
301 		EPRINTLN("virtio-9p: failed to open '%s': %s", value,
302 		    strerror(errno));
303 		return (-1);
304 	}
305 
306 	sharename = get_config_value_node(nvl, "sharename");
307 	if (sharename == NULL) {
308 		EPRINTLN("virtio-9p: share name required");
309 		return (1);
310 	}
311 	if (strlen(sharename) > VT9P_MAXTAGSZ) {
312 		EPRINTLN("virtio-9p: share name too long");
313 		return (1);
314 	}
315 
316 	sc = calloc(1, sizeof(struct pci_vt9p_softc));
317 #ifndef __FreeBSD__
318 	if (sc == NULL) {
319 		EPRINTLN("virtio-9p: soft state allocation failure: %s",
320 		    strerror(errno));
321 		return (1);
322 	}
323 #endif
324 	sc->vsc_config = calloc(1, sizeof(struct pci_vt9p_config) +
325 	    VT9P_MAXTAGSZ);
326 #ifndef __FreeBSD__
327 	if (sc == NULL) {
328 		EPRINTLN("virtio-9p: vsc_config allocation failure: %s",
329 		    strerror(errno));
330 		return (1);
331 	}
332 #endif
333 
334 	pthread_mutex_init(&sc->vsc_mtx, NULL);
335 
336 #ifndef WITHOUT_CAPSICUM
337 	cap_rights_init(&rootcap,
338 	    CAP_LOOKUP, CAP_ACL_CHECK, CAP_ACL_DELETE, CAP_ACL_GET,
339 	    CAP_ACL_SET, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FSTAT,
340 	    CAP_CREATE, CAP_FCHMODAT, CAP_FCHOWNAT, CAP_FTRUNCATE,
341 	    CAP_LINKAT_SOURCE, CAP_LINKAT_TARGET, CAP_MKDIRAT, CAP_MKNODAT,
342 	    CAP_PREAD, CAP_PWRITE, CAP_RENAMEAT_SOURCE, CAP_RENAMEAT_TARGET,
343 	    CAP_SEEK, CAP_SYMLINKAT, CAP_UNLINKAT, CAP_EXTATTR_DELETE,
344 	    CAP_EXTATTR_GET, CAP_EXTATTR_LIST, CAP_EXTATTR_SET,
345 	    CAP_FUTIMES, CAP_FSTATFS, CAP_FSYNC, CAP_FPATHCONF);
346 
347 	if (cap_rights_limit(rootfd, &rootcap) != 0)
348 		return (1);
349 #endif
350 
351 	sc->vsc_config->tag_len = (uint16_t)strlen(sharename);
352 	memcpy(sc->vsc_config->tag, sharename, sc->vsc_config->tag_len);
353 
354 	if (l9p_backend_fs_init(&sc->vsc_fs_backend, rootfd, ro) != 0) {
355 		errno = ENXIO;
356 		return (1);
357 	}
358 
359 	if (l9p_server_init(&sc->vsc_server, sc->vsc_fs_backend) != 0) {
360 		errno = ENXIO;
361 		return (1);
362 	}
363 
364 	if (l9p_connection_init(sc->vsc_server, &sc->vsc_conn) != 0) {
365 		errno = EIO;
366 		return (1);
367 	}
368 
369 	sc->vsc_conn->lc_msize = L9P_MAX_IOV * PAGE_SIZE;
370 	sc->vsc_conn->lc_lt.lt_get_response_buffer = pci_vt9p_get_buffer;
371 	sc->vsc_conn->lc_lt.lt_send_response = pci_vt9p_send;
372 	sc->vsc_conn->lc_lt.lt_drop_response = pci_vt9p_drop;
373 
374 	vi_softc_linkup(&sc->vsc_vs, &vt9p_vi_consts, sc, pi, &sc->vsc_vq);
375 	sc->vsc_vs.vs_mtx = &sc->vsc_mtx;
376 	sc->vsc_vq.vq_qsize = VT9P_RINGSZ;
377 
378 	/* initialize config space */
379 	pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_9P);
380 	pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
381 	pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE);
382 	pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_ID_9P);
383 	pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
384 
385 	if (vi_intr_init(&sc->vsc_vs, 1, fbsdrun_virtio_msix()))
386 		return (1);
387 	vi_set_io_bar(&sc->vsc_vs, 0);
388 
389 	return (0);
390 }
391 
392 struct pci_devemu pci_de_v9p = {
393 	.pe_emu =	"virtio-9p",
394 	.pe_legacy_config = pci_vt9p_legacy_config,
395 	.pe_init =	pci_vt9p_init,
396 	.pe_barwrite =	vi_pci_write,
397 	.pe_barread =	vi_pci_read
398 };
399 PCI_EMUL_SET(pci_de_v9p);
400