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#include <sys/capsicum.h>
40
41#include <errno.h>
42#include <fcntl.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <unistd.h>
47#include <assert.h>
48#include <pthread.h>
49
50#include <lib9p.h>
51#include <backend/fs.h>
52
53#include "bhyverun.h"
54#include "pci_emul.h"
55#include "virtio.h"
56
57#define	VT9P_MAX_IOV	128
58#define VT9P_RINGSZ	256
59#define	VT9P_MAXTAGSZ	256
60#define	VT9P_CONFIGSPACESZ	(VT9P_MAXTAGSZ + sizeof(uint16_t))
61
62static int pci_vt9p_debug;
63#define DPRINTF(params) if (pci_vt9p_debug) printf params
64#define WPRINTF(params) printf params
65
66/*
67 * Per-device softc
68 */
69struct pci_vt9p_softc {
70	struct virtio_softc      vsc_vs;
71	struct vqueue_info       vsc_vq;
72	pthread_mutex_t          vsc_mtx;
73	uint64_t                 vsc_cfg;
74	uint64_t                 vsc_features;
75	char *                   vsc_rootpath;
76	struct pci_vt9p_config * vsc_config;
77	struct l9p_backend *     vsc_fs_backend;
78	struct l9p_server *      vsc_server;
79        struct l9p_connection *  vsc_conn;
80};
81
82struct pci_vt9p_request {
83	struct pci_vt9p_softc *	vsr_sc;
84	struct iovec *		vsr_iov;
85	size_t			vsr_niov;
86	size_t			vsr_respidx;
87	size_t			vsr_iolen;
88	uint16_t		vsr_idx;
89};
90
91struct pci_vt9p_config {
92	uint16_t tag_len;
93	char tag[0];
94} __attribute__((packed));
95
96static int pci_vt9p_send(struct l9p_request *, const struct iovec *,
97    const size_t, const size_t, void *);
98static void pci_vt9p_drop(struct l9p_request *, const struct iovec *, size_t,
99    void *);
100static void pci_vt9p_reset(void *);
101static void pci_vt9p_notify(void *, struct vqueue_info *);
102static int pci_vt9p_cfgread(void *, int, int, uint32_t *);
103static void pci_vt9p_neg_features(void *, uint64_t);
104
105static struct virtio_consts vt9p_vi_consts = {
106	"vt9p",			/* our name */
107	1,			/* we support 1 virtqueue */
108	VT9P_CONFIGSPACESZ,	/* config reg size */
109	pci_vt9p_reset,		/* reset */
110	pci_vt9p_notify,	/* device-wide qnotify */
111	pci_vt9p_cfgread,	/* read virtio config */
112	NULL,			/* write virtio config */
113	pci_vt9p_neg_features,	/* apply negotiated features */
114	(1 << 0),		/* our capabilities */
115};
116
117
118static void
119pci_vt9p_reset(void *vsc)
120{
121	struct pci_vt9p_softc *sc;
122
123	sc = vsc;
124
125	DPRINTF(("vt9p: device reset requested !\n"));
126	vi_reset_dev(&sc->vsc_vs);
127}
128
129static void
130pci_vt9p_neg_features(void *vsc, uint64_t negotiated_features)
131{
132	struct pci_vt9p_softc *sc = vsc;
133
134	sc->vsc_features = negotiated_features;
135}
136
137static int
138pci_vt9p_cfgread(void *vsc, int offset, int size, uint32_t *retval)
139{
140	struct pci_vt9p_softc *sc = vsc;
141	void *ptr;
142
143	ptr = (uint8_t *)sc->vsc_config + offset;
144	memcpy(retval, ptr, size);
145	return (0);
146}
147
148static int
149pci_vt9p_get_buffer(struct l9p_request *req, struct iovec *iov, size_t *niov,
150    void *arg)
151{
152	struct pci_vt9p_request *preq = req->lr_aux;
153	size_t n = preq->vsr_niov - preq->vsr_respidx;
154
155	memcpy(iov, preq->vsr_iov + preq->vsr_respidx,
156	    n * sizeof(struct iovec));
157	*niov = n;
158	return (0);
159}
160
161static int
162pci_vt9p_send(struct l9p_request *req, const struct iovec *iov,
163    const size_t niov, const size_t iolen, void *arg)
164{
165	struct pci_vt9p_request *preq = req->lr_aux;
166	struct pci_vt9p_softc *sc = preq->vsr_sc;
167
168	preq->vsr_iolen = iolen;
169
170	pthread_mutex_lock(&sc->vsc_mtx);
171	vq_relchain(&sc->vsc_vq, preq->vsr_idx, preq->vsr_iolen);
172	vq_endchains(&sc->vsc_vq, 1);
173	pthread_mutex_unlock(&sc->vsc_mtx);
174	free(preq);
175	return (0);
176}
177
178static void
179pci_vt9p_drop(struct l9p_request *req, const struct iovec *iov, size_t niov,
180    void *arg)
181{
182	struct pci_vt9p_request *preq = req->lr_aux;
183	struct pci_vt9p_softc *sc = preq->vsr_sc;
184
185	pthread_mutex_lock(&sc->vsc_mtx);
186	vq_relchain(&sc->vsc_vq, preq->vsr_idx, 0);
187	vq_endchains(&sc->vsc_vq, 1);
188	pthread_mutex_unlock(&sc->vsc_mtx);
189	free(preq);
190}
191
192static void
193pci_vt9p_notify(void *vsc, struct vqueue_info *vq)
194{
195	struct iovec iov[VT9P_MAX_IOV];
196	struct pci_vt9p_softc *sc;
197	struct pci_vt9p_request *preq;
198	uint16_t idx, n, i;
199	uint16_t flags[VT9P_MAX_IOV];
200
201	sc = vsc;
202
203	while (vq_has_descs(vq)) {
204		n = vq_getchain(vq, &idx, iov, VT9P_MAX_IOV, flags);
205		preq = calloc(1, sizeof(struct pci_vt9p_request));
206		preq->vsr_sc = sc;
207		preq->vsr_idx = idx;
208		preq->vsr_iov = iov;
209		preq->vsr_niov = n;
210		preq->vsr_respidx = 0;
211
212		/* Count readable descriptors */
213		for (i = 0; i < n; i++) {
214			if (flags[i] & VRING_DESC_F_WRITE)
215				break;
216
217			preq->vsr_respidx++;
218		}
219
220		for (int i = 0; i < n; i++) {
221			DPRINTF(("vt9p: vt9p_notify(): desc%d base=%p, "
222			    "len=%zu, flags=0x%04x\r\n", i, iov[i].iov_base,
223			    iov[i].iov_len, flags[i]));
224		}
225
226		l9p_connection_recv(sc->vsc_conn, iov, preq->vsr_respidx, preq);
227	}
228}
229
230
231static int
232pci_vt9p_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
233{
234	struct pci_vt9p_softc *sc;
235	char *opt;
236	char *sharename = NULL;
237	char *rootpath = NULL;
238	int rootfd;
239	bool ro = false;
240	cap_rights_t rootcap;
241
242	if (opts == NULL) {
243		printf("virtio-9p: share name and path required\n");
244		return (1);
245	}
246
247	while ((opt = strsep(&opts, ",")) != NULL) {
248		if (strchr(opt, '=') != NULL) {
249			if (sharename != NULL) {
250				printf("virtio-9p: more than one share name given\n");
251				return (1);
252			}
253
254			sharename = strsep(&opt, "=");
255			rootpath = opt;
256			continue;
257		}
258
259		if (strcmp(opt, "ro") == 0) {
260			DPRINTF(("read-only mount requested\r\n"));
261			ro = true;
262			continue;
263		}
264
265		printf("virtio-9p: invalid option '%s'\n", opt);
266		return (1);
267	}
268
269	if (strlen(sharename) > VT9P_MAXTAGSZ) {
270		printf("virtio-9p: share name too long\n");
271		return (1);
272	}
273
274	rootfd = open(rootpath, O_DIRECTORY);
275	if (rootfd < 0)
276		return (-1);
277
278	sc = calloc(1, sizeof(struct pci_vt9p_softc));
279	sc->vsc_config = calloc(1, sizeof(struct pci_vt9p_config) +
280	    VT9P_MAXTAGSZ);
281
282	pthread_mutex_init(&sc->vsc_mtx, NULL);
283
284	cap_rights_init(&rootcap,
285	    CAP_LOOKUP, CAP_ACL_CHECK, CAP_ACL_DELETE, CAP_ACL_GET,
286	    CAP_ACL_SET, CAP_READ, CAP_WRITE, CAP_SEEK, CAP_FSTAT,
287	    CAP_CREATE, CAP_FCHMODAT, CAP_FCHOWNAT, CAP_FTRUNCATE,
288	    CAP_LINKAT_SOURCE, CAP_LINKAT_TARGET, CAP_MKDIRAT, CAP_MKNODAT,
289	    CAP_PREAD, CAP_PWRITE, CAP_RENAMEAT_SOURCE, CAP_RENAMEAT_TARGET,
290	    CAP_SEEK, CAP_SYMLINKAT, CAP_UNLINKAT, CAP_EXTATTR_DELETE,
291	    CAP_EXTATTR_GET, CAP_EXTATTR_LIST, CAP_EXTATTR_SET,
292	    CAP_FUTIMES, CAP_FSTATFS, CAP_FSYNC, CAP_FPATHCONF);
293
294	if (cap_rights_limit(rootfd, &rootcap) != 0)
295		return (1);
296
297	sc->vsc_config->tag_len = (uint16_t)strlen(sharename);
298	memcpy(sc->vsc_config->tag, sharename, sc->vsc_config->tag_len);
299
300	if (l9p_backend_fs_init(&sc->vsc_fs_backend, rootfd, ro) != 0) {
301		errno = ENXIO;
302		return (1);
303	}
304
305	if (l9p_server_init(&sc->vsc_server, sc->vsc_fs_backend) != 0) {
306		errno = ENXIO;
307		return (1);
308	}
309
310	if (l9p_connection_init(sc->vsc_server, &sc->vsc_conn) != 0) {
311		errno = EIO;
312		return (1);
313	}
314
315	sc->vsc_conn->lc_msize = L9P_MAX_IOV * PAGE_SIZE;
316	sc->vsc_conn->lc_lt.lt_get_response_buffer = pci_vt9p_get_buffer;
317	sc->vsc_conn->lc_lt.lt_send_response = pci_vt9p_send;
318	sc->vsc_conn->lc_lt.lt_drop_response = pci_vt9p_drop;
319
320	vi_softc_linkup(&sc->vsc_vs, &vt9p_vi_consts, sc, pi, &sc->vsc_vq);
321	sc->vsc_vs.vs_mtx = &sc->vsc_mtx;
322	sc->vsc_vq.vq_qsize = VT9P_RINGSZ;
323
324	/* initialize config space */
325	pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_9P);
326	pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
327	pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_STORAGE);
328	pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_TYPE_9P);
329	pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_VENDOR);
330
331	if (vi_intr_init(&sc->vsc_vs, 1, fbsdrun_virtio_msix()))
332		return (1);
333	vi_set_io_bar(&sc->vsc_vs, 0);
334
335	return (0);
336}
337
338struct pci_devemu pci_de_v9p = {
339	.pe_emu =	"virtio-9p",
340	.pe_init =	pci_vt9p_init,
341	.pe_barwrite =	vi_pci_write,
342	.pe_barread =	vi_pci_read
343};
344PCI_EMUL_SET(pci_de_v9p);
345