diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -352,6 +352,7 @@
 device		virtio_blk		# VirtIO Block device
 device		virtio_scsi		# VirtIO SCSI device
 device		virtio_balloon		# VirtIO Memory Balloon device
+device		virtio_fs		# VirtIO File System device
 
 # Linux KVM paravirtualization support
 device		kvm_clock		# KVM paravirtual clock driver
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3448,6 +3448,7 @@
 dev/virtio/random/virtio_random.c	optional	virtio_random
 dev/virtio/scmi/virtio_scmi.c		optional	virtio_scmi
 dev/virtio/scsi/virtio_scsi.c		optional	virtio_scsi
+dev/virtio/fs/virtio_fs.c		optional	virtio_fs
 dev/vkbd/vkbd.c			optional vkbd
 dev/vmgenc/vmgenc_acpi.c	optional acpi
 dev/vmware/vmxnet3/if_vmx.c		optional vmx
diff --git a/sys/dev/virtio/fs/virtio_fs.h b/sys/dev/virtio/fs/virtio_fs.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/virtio/fs/virtio_fs.h
@@ -0,0 +1,44 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _VIRTIO_FS_H
+#define _VIRTIO_FS_H
+
+struct vtfs_softc;
+typedef struct vtfs_softc *vtfs_instance;
+typedef void (*vtfs_fuse_cb)(void *, uint32_t);
+typedef void (*vtfs_teardown_cb)(void *);
+
+void vtfs_register_cb(vtfs_instance, vtfs_fuse_cb, vtfs_fuse_cb,
+		vtfs_teardown_cb, void *);
+int vtfs_enqueue(vtfs_instance, void *, struct sglist *, int, int, bool);
+int vtfs_find(char *, vtfs_instance *);
+void vtfs_release(vtfs_instance);
+void vtfs_drain(vtfs_instance);
+void vtfs_unregister_cb(vtfs_instance);
+
+#endif /* _VIRTIO_FS_H */
diff --git a/sys/dev/virtio/fs/virtio_fs.c b/sys/dev/virtio/fs/virtio_fs.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/virtio/fs/virtio_fs.c
@@ -0,0 +1,606 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice unmodified, this list of conditions, and the following
+ *    disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Device for the VirtIO file system. */
+
+#include <sys/cdefs.h>
+
+#include <sys/param.h>
+#include <sys/lock.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mount.h>
+#include <sys/mutex.h>
+#include <sys/selinfo.h>
+#include <sys/sglist.h>
+#include <sys/taskqueue.h>
+
+#include <machine/bus.h>
+#include <sys/bus.h>
+
+#include <dev/virtio/virtio.h>
+#include <dev/virtio/virtqueue.h>
+#include <dev/virtio/fs/virtio_fs.h>
+
+#include "virtio_if.h"
+#include "virtio_fs_internal.h"
+
+/* Methods for the vtfs device. */
+
+struct vtfs_softc;
+
+/* Module-wide device list. */
+
+LIST_HEAD(, vtfs_softc) vtfs_contexts = LIST_HEAD_INITIALIZER(vtfs_contexts);
+struct mtx vtfs_mod_mtx;
+MTX_SYSINIT(vtfs_mod_mtx, &vtfs_mod_mtx, "Virtio FS Global Lock", MTX_DEF);
+
+#define VTFS_LOCK()		mtx_lock(&vtfs_mod_mtx)
+#define VTFS_UNLOCK()		mtx_unlock(&vtfs_mod_mtx)
+
+#define FSQ_LOCK(_fsq)		mtx_lock(&(_fsq)->vtfsq_mtx)
+#define FSQ_UNLOCK(_fsq)	mtx_unlock(&(_fsq)->vtfsq_mtx)
+#define FSQ_WAIT(_fsq)		cv_wait(&(_fsq)->vtfsq_cv, &(_fsq)->vtfsq_mtx)
+#define FSQ_KICK(_fsq)		cv_broadcast(&(_fsq)->vtfsq_cv)
+
+static int	vtfs_modevent(module_t, int, void *);
+
+static int	vtfs_probe(device_t);
+static int	vtfs_attach(device_t);
+static int	vtfs_detach(device_t);
+
+static device_method_t vtfs_methods[] = {
+	DEVMETHOD(device_probe,		vtfs_probe),
+	DEVMETHOD(device_attach,	vtfs_attach),
+	DEVMETHOD(device_detach,	vtfs_detach),
+	DEVMETHOD_END
+};
+
+static driver_t vtfs_driver = {
+	"vtfs",
+	vtfs_methods,
+	sizeof(struct vtfs_softc),
+};
+
+VIRTIO_DRIVER_MODULE(vtfs, vtfs_driver, vtfs_modevent, NULL);
+MODULE_VERSION(vtfs, 1);
+MODULE_DEPEND(vtfs, virtio, 1, 1, 1);
+
+VIRTIO_SIMPLE_PNPINFO(vtfs, VIRTIO_ID_FS, "VirtIO FS Adapter");
+
+static int
+vtfs_modevent(module_t mod, int type, void *unused)
+{
+	int error = 0;
+
+	switch (type) {
+	case MOD_LOAD:
+	case MOD_UNLOAD:
+	case MOD_QUIESCE:
+	case MOD_SHUTDOWN:
+		break;
+	default:
+		error = EOPNOTSUPP;
+		break;
+	}
+
+	return (error);
+}
+
+static int
+vtfs_probe(device_t dev)
+{
+	return (VIRTIO_SIMPLE_PROBE(dev, vtfs));
+}
+
+static int
+vtfs_read_config(struct vtfs_softc *sc)
+{
+	device_t dev;
+	size_t taglen;
+#ifdef INVARIANTS
+	struct vtfs_config fscfg;
+#endif
+
+	dev = sc->vtfs_dev;
+
+	_Static_assert(sizeof(sc->vtfs_nqs) == sizeof(fscfg.num_request_queues),
+		"reading num_request_queues into wrongly typed struct");
+
+	virtio_read_device_config(dev,
+		offsetof(struct vtfs_config, num_request_queues),
+		&sc->vtfs_nqs,
+		sizeof(&sc->vtfs_nqs));
+
+	if (sc->vtfs_nqs <= 0) {
+		VTFS_ERR("read non-positive queue number from host");
+		return (EINVAL);
+	}
+
+	/* Account for the priority queue. */
+	sc->vtfs_nqs += 1;
+
+	virtio_read_device_config_array(dev,
+		offsetof(struct vtfs_config, tag),
+		sc->vtfs_tag, 1, TAG_SIZE);
+
+	/* The read tag may not be NUL-terminated. */
+	taglen = strnlen(sc->vtfs_tag, TAG_SIZE);
+	if (taglen == 0) {
+		VTFS_ERR("read empty tag");
+		return (EINVAL);
+	}
+
+	KASSERT(taglen < sizeof(sc->vtfs_tag), ("overran tag buffer"));
+	sc->vtfs_tag[taglen] = '\0';
+
+	VTFS_DEBUG("fs tag: %s, # vqs: %d", sc->vtfs_tag, sc->vtfs_nqs);
+
+	return (0);
+}
+
+static void
+vtfs_vq_intr(void *xfsq)
+{
+	struct vtfs_fsq *fsq = xfsq;
+	struct virtqueue *vq = fsq->vtfsq_vq;
+	uint32_t len;
+	void *ftick;
+
+	/* We turn off vtfs interrupts before removing the callback. */
+	KASSERT(fsq->vtfsq_cb != NULL, ("missing fuse callback"));
+
+	FSQ_LOCK(fsq);
+
+again:
+	/* Go through the tickets one by one, invoke the fuse callback. */
+	while  ((ftick = virtqueue_dequeue(vq, &len)) != NULL)
+		fsq->vtfsq_cb(ftick, len);
+
+	/* If the host pushed another descriptor in the meantime, go again. */
+	if (virtqueue_enable_intr(vq) != 0) {
+		virtqueue_disable_intr(vq);
+		goto again;
+	}
+
+	FSQ_KICK(fsq);
+	FSQ_UNLOCK(fsq);
+}
+
+static int
+vtfs_init_fsq(struct vtfs_softc *sc, int id)
+{
+	struct vtfs_fsq *fsq = &sc->vtfs_fsqs[id];
+	int error;
+
+	if (id == 0) {
+		snprintf(fsq->vtfsq_name, sizeof(fsq->vtfsq_name), 
+			"%s-priority", device_get_nameunit(sc->vtfs_dev));
+	} else {
+		snprintf(fsq->vtfsq_name, sizeof(fsq->vtfsq_name), 
+			"%s-queue%d", device_get_nameunit(sc->vtfs_dev), id);
+	}
+
+	mtx_init(&fsq->vtfsq_mtx, fsq->vtfsq_name, NULL, MTX_DEF);
+	cv_init(&fsq->vtfsq_cv, "fsqvqcv");
+
+	fsq->vtfsq_tq = taskqueue_create("vtfsqtq", M_WAITOK, 
+			taskqueue_thread_enqueue, &fsq->vtfsq_tq);
+	if (fsq->vtfsq_tq == NULL)
+		return (ENOMEM);
+
+	error = taskqueue_start_threads(&fsq->vtfsq_tq, VTFS_TQTHREAD,
+		PVM, "VirtioFS device");
+	if (error != 0)
+		return (error);
+
+	fsq->vtfsq_sc = sc;
+	VTFS_DEBUG("vq %s online", fsq->vtfsq_name);
+
+	return (0);
+}
+
+static void
+vtfs_fini_fsq(struct vtfs_fsq *fsq)
+{
+
+	if (fsq->vtfsq_sg != NULL) {
+		sglist_free(fsq->vtfsq_sg);
+		fsq->vtfsq_sg = NULL;
+	}
+
+	if (fsq->vtfsq_tq != NULL) {
+		taskqueue_drain_all(fsq->vtfsq_tq);
+		taskqueue_free(fsq->vtfsq_tq);
+		fsq->vtfsq_tq = NULL;
+	}
+
+	if (mtx_initialized(&fsq->vtfsq_mtx) != 0) {
+		cv_destroy(&fsq->vtfsq_cv);
+		mtx_destroy(&fsq->vtfsq_mtx);
+	}
+
+	VTFS_DEBUG("vq %s destroyed", fsq->vtfsq_name);
+}
+
+static int
+vtfs_alloc_fsqueues(struct vtfs_softc *sc)
+{
+	int error;
+	int nvqs;
+	int i, j;
+
+	nvqs = sc->vtfs_nqs;
+	sc->vtfs_fsqs = malloc((sizeof(*sc->vtfs_fsqs)) * nvqs, M_DEVBUF,
+		M_NOWAIT | M_ZERO);
+	if (sc->vtfs_fsqs == NULL)
+		return (ENOMEM);
+
+	for (i = 0; i < nvqs; i++) {
+		error = vtfs_init_fsq(sc, i);
+		if (error != 0)
+			goto fail;
+	}
+
+	return (0);
+
+fail:
+	VTFS_DEBUG("failed while initializing vq %d", i);
+
+	/* Clean up any initialized fsqs. */
+	for (j = 0; j < i; j++)
+		vtfs_fini_fsq(&sc->vtfs_fsqs[j]);
+
+	free(sc->vtfs_fsqs, M_DEVBUF);
+	sc->vtfs_fsqs = NULL;
+
+	return (error);
+}
+
+static void
+vtfs_free_fsqueues(struct vtfs_softc *sc)
+{
+	int i;
+
+	for (i = 0; i < sc->vtfs_nqs; i++)
+		vtfs_fini_fsq(&sc->vtfs_fsqs[i]);
+	
+	free(sc->vtfs_fsqs, M_DEVBUF);
+}
+
+static int
+vtfs_alloc_virtqueues(struct vtfs_softc *sc)
+{
+	struct vq_alloc_info *vq_info;
+	struct vtfs_fsq *fsq;
+	device_t dev;
+	int error;
+	int nqs;
+	int i;
+
+	dev = sc->vtfs_dev;
+	nqs = sc->vtfs_nqs;
+
+	/* 
+	 * We have num_request_queues regular queues and one high-priority 
+	 * queue for FORGET/general priority operations.
+	 */
+	KASSERT(nqs > 1, ("missing regular queues"));
+
+	vq_info = malloc(sizeof(*vq_info) * nqs, M_TEMP, M_NOWAIT);
+	if (vq_info == NULL)
+		return (ENOMEM);
+
+	for (i = 0; i < nqs; i++) {
+		fsq = &sc->vtfs_fsqs[i];
+		VQ_ALLOC_INFO_INIT(&vq_info[i], VTFS_MAXSEGS, vtfs_vq_intr, 
+			fsq, &fsq->vtfsq_vq, "%s", fsq->vtfsq_name);
+	}
+
+	error = virtio_alloc_virtqueues(dev, nqs, vq_info);
+	free(vq_info, M_TEMP);
+
+	VTFS_DEBUG("virtqueues online");
+
+	return (error);
+}
+
+static int
+vtfs_enable_intr(device_t dev)
+{
+	struct vtfs_softc *sc;
+	int error;
+	int i;
+
+	sc = device_get_softc(dev);
+
+	error = virtio_setup_intr(dev, INTR_TYPE_BIO);
+	if (error != 0)
+		return (error);
+
+	for (i = 0; i < sc->vtfs_nqs; i++)
+		virtqueue_enable_intr(sc->vtfs_fsqs[i].vtfsq_vq);
+	
+
+	return (0);
+}
+
+static void
+vtfs_stop(struct vtfs_softc *sc)
+{
+	struct virtqueue *vq;
+	int i;
+
+	/* The file system should have cleared the virtqueues when umounting .*/
+	for (i = 0; i < sc->vtfs_nqs; i++) {
+		vq = sc->vtfs_fsqs[i].vtfsq_vq;
+		virtqueue_disable_intr(vq);
+		KASSERT(virtqueue_empty(vq), ("virtqueue not empty"));
+	}
+
+	virtio_stop(sc->vtfs_dev);
+
+}
+
+static void
+vtfs_add(struct vtfs_softc *sc)
+{
+	VTFS_LOCK();
+	LIST_INSERT_HEAD(&vtfs_contexts, sc, vtfs_link);
+	VTFS_UNLOCK();
+}
+static void
+vtfs_remove(struct vtfs_softc *sc)
+{
+	VTFS_LOCK();
+	LIST_REMOVE(sc, vtfs_link);
+	VTFS_UNLOCK();
+}
+
+int
+vtfs_find(char *tag, struct vtfs_softc **scp)
+{
+	struct vtfs_softc *sc;
+
+	VTFS_LOCK();
+
+	LIST_FOREACH(sc, &vtfs_contexts, vtfs_link) {
+		if (strncmp(sc->vtfs_tag, tag, sizeof(sc->vtfs_tag)) != 0)
+			continue;
+
+		if (sc->vtfs_inuse) {
+			VTFS_UNLOCK();
+			return (EALREADY);
+		}
+
+		sc->vtfs_inuse = true;
+		VTFS_UNLOCK();
+
+		*scp = sc;
+
+		return (0);
+	}
+
+	VTFS_UNLOCK();
+
+	return (EINVAL);
+}
+
+void
+vtfs_release(struct vtfs_softc *sc)
+{
+	VTFS_LOCK();
+	sc->vtfs_inuse = false;
+	VTFS_UNLOCK();
+}
+
+static int
+vtfs_attach(device_t dev)
+{
+	struct vtfs_softc *sc;
+	int error;
+
+	sc = device_get_softc(dev);
+	sc->vtfs_dev = dev;
+	sc->vtfs_inuse = false;
+
+	error = vtfs_read_config(sc);
+	if (error != 0)
+		goto fail;
+
+	error = vtfs_alloc_fsqueues(sc);
+	if (error != 0)
+		goto fail;
+
+	error = vtfs_alloc_virtqueues(sc);
+	if (error != 0)
+		goto fail;
+
+	error = vtfs_enable_intr(dev);
+	if (error != 0)
+		goto fail;
+
+	vtfs_add(sc);
+
+	return (0);
+
+fail:
+	vtfs_detach(dev);
+
+	return (error);	
+}
+
+
+static int
+vtfs_detach(device_t dev)
+{
+	struct vtfs_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	vtfs_remove(sc);
+
+	/* Kill any active FUSE session. */
+	if (sc->vtfs_detach_cb)
+		sc->vtfs_detach_cb(sc->vtfs_detach_cb_arg);
+
+	vtfs_drain(sc);
+
+	vtfs_stop(sc);
+	vtfs_free_fsqueues(sc);
+
+	VTFS_DEBUG("device detached");
+
+	return (0);	
+}
+
+void
+vtfs_register_cb(struct vtfs_softc *sc, vtfs_fuse_cb forget_cb,
+	vtfs_fuse_cb regular_cb, vtfs_teardown_cb detach_cb,
+	void *detach_cb_arg)
+{
+	struct vtfs_fsq *fsq;
+	int i;
+
+	sc->vtfs_detach_cb = detach_cb;
+	sc->vtfs_detach_cb_arg = detach_cb_arg;
+
+	for (i = 0; i < sc->vtfs_nqs; i++) {
+		fsq = &sc->vtfs_fsqs[i];
+		FSQ_LOCK(fsq);
+		if (i == VTFS_FORGET_FSQ)
+			fsq->vtfsq_cb = forget_cb;
+		else
+			fsq->vtfsq_cb = regular_cb;
+		FSQ_UNLOCK(fsq);
+	}
+
+}
+
+void
+vtfs_unregister_cb(struct vtfs_softc *sc)
+{
+	struct vtfs_fsq *fsq;
+	int i;
+
+	for (i = 0; i < sc->vtfs_nqs; i++) {
+		fsq = &sc->vtfs_fsqs[i];
+
+		FSQ_LOCK(fsq);
+		fsq->vtfsq_cb = NULL;
+		FSQ_UNLOCK(fsq);
+	}
+
+	sc->vtfs_detach_cb_arg = NULL;
+	sc->vtfs_detach_cb = NULL;
+}
+
+int
+vtfs_enqueue(struct vtfs_softc *sc, void *ftick, struct sglist *sg,
+	int readable, int writable, bool urgent)
+{
+	struct virtqueue *vq;
+	struct vtfs_fsq *fsq;
+	int error;
+
+	if (urgent)
+		fsq = &sc->vtfs_fsqs[VTFS_FORGET_FSQ];
+	else
+		fsq = &sc->vtfs_fsqs[VTFS_REGULAR_FSQ];
+
+	FSQ_LOCK(fsq);
+	vq = fsq->vtfsq_vq;
+
+	KASSERT(sg->sg_nseg == readable + writable, ("inconsistent segmentation"));
+
+	error = virtqueue_enqueue(vq, ftick, sg, readable, writable);
+	while (error == ENOSPC) {
+		FSQ_WAIT(fsq);
+		error = virtqueue_enqueue(vq, ftick, sg, readable, writable);
+	}
+
+	if (error != 0)
+		return (error);
+	
+	virtqueue_notify(vq);
+
+	FSQ_UNLOCK(fsq);
+	sglist_free(sg);
+
+	return (0);
+}
+
+static void
+vtfs_drain_vq(struct vtfs_fsq *fsq)
+{
+	struct virtqueue *vq = fsq->vtfsq_vq;
+	uint32_t len;
+	void *ftick;
+
+	/* 
+	 * If there is no callback, we don't have an 
+	 * upper-level FUSE session. 
+	 */
+	if (fsq->vtfsq_cb == NULL) {
+		KASSERT(virtqueue_empty(vq), ("virtqueue not empty"));
+		return;
+	}
+
+	vq = fsq->vtfsq_vq;
+
+	while ((ftick = virtqueue_dequeue(vq, &len)) != NULL) {
+		fsq->vtfsq_cb(ftick, len);
+	}
+
+	KASSERT(virtqueue_empty(vq), ("virtqueue not empty"));
+}
+
+void
+vtfs_drain(struct vtfs_softc *sc)
+{
+	struct vtfs_fsq *fsq;
+	int i;
+
+	for (i = 0; i < sc->vtfs_nqs; i++) {
+		fsq = &sc->vtfs_fsqs[i];
+		FSQ_LOCK(fsq);
+		virtqueue_disable_intr(fsq->vtfsq_vq);
+		FSQ_UNLOCK(fsq);
+	}
+
+	for (i = 0; i < sc->vtfs_nqs; i++) {
+		fsq = &sc->vtfs_fsqs[i];
+		FSQ_LOCK(fsq);
+		vtfs_drain_vq(fsq);
+		FSQ_UNLOCK(fsq);
+	}
+
+}
diff --git a/sys/dev/virtio/fs/virtio_fs_internal.h b/sys/dev/virtio/fs/virtio_fs_internal.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/virtio/fs/virtio_fs_internal.h
@@ -0,0 +1,60 @@
+#ifndef _VIRTIO_FS_INTERNAL_
+#define _VIRTIO_FS_INTERNAL_
+
+/* Protocol-specified file system tag size. */
+#define TAG_SIZE (36)
+#define FSQ_NAME_SIZE (16)
+
+#define VTFS_DEBUG(fmt, ...)		\
+	do {			\
+		printf("(%s:%d) " fmt "\n", __func__, __LINE__, ##__VA_ARGS__); \
+	} while (0)		
+
+#define VTFS_ERR(fmt, ...)		\
+	do {			\
+		printf("[ERROR] (%s:%d) " fmt "\n", __func__, __LINE__, ##__VA_ARGS__); \
+	} while (0)		
+
+/* Struct for the file system instance. Provided by the host. */
+struct vtfs_config {
+	/* UTF-8 File system tag. */
+	uint8_t tag[TAG_SIZE];
+	/* Number of request queues. */
+	uint32_t num_request_queues;
+	/* Minimum # bytes for each buffer in the notification queue. */
+	uint32_t notify_buf_size;
+} __packed;
+
+/* A queue structure belonging to a virtio fs device. */
+struct vtfs_fsq {
+	struct mtx		vtfsq_mtx;
+	struct cv		vtfsq_cv;
+	struct virtqueue	*vtfsq_vq;
+	struct taskqueue	*vtfsq_tq;
+	struct sglist 		*vtfsq_sg;
+	char			vtfsq_name[FSQ_NAME_SIZE];
+	struct vtfs_softc	*vtfsq_sc;
+	vtfs_fuse_cb		vtfsq_cb;
+};
+
+/* A single virtio fs device instance. */
+struct vtfs_softc {
+	device_t	vtfs_dev;
+	struct mtx	vtfs_mtx;
+	struct vtfs_fsq	*vtfs_fsqs;
+	bool		vtfs_inuse;	/* protected by the vtfs modulelock */
+	vtfs_teardown_cb	vtfs_detach_cb;
+	void		*vtfs_detach_cb_arg;
+	/* Host-provided config state. */
+	uint8_t 	vtfs_tag[TAG_SIZE + 1];
+	uint32_t 	vtfs_nqs;
+	LIST_ENTRY(vtfs_softc) vtfs_link;
+};
+
+#define VTFS_FORGET_FSQ (0)
+#define VTFS_REGULAR_FSQ (1)
+
+#define VTFS_MAXSEGS (16)
+#define VTFS_TQTHREAD (4)
+
+#endif /* _VIRTIO_FS_INTERNAL_ */
diff --git a/sys/modules/virtio/Makefile b/sys/modules/virtio/Makefile
--- a/sys/modules/virtio/Makefile
+++ b/sys/modules/virtio/Makefile
@@ -22,6 +22,6 @@
 # SUCH DAMAGE.
 #
 
-SUBDIR=	virtio pci network block balloon scsi random console p9fs
+SUBDIR=	virtio pci network block balloon scsi random console p9fs fs
 
 .include <bsd.subdir.mk>
diff --git a/sys/modules/virtio/Makefile b/sys/modules/virtio/fs/Makefile
copy from sys/modules/virtio/Makefile
copy to sys/modules/virtio/fs/Makefile
--- a/sys/modules/virtio/Makefile
+++ b/sys/modules/virtio/fs/Makefile
@@ -1,5 +1,3 @@
-#
-#
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
@@ -22,6 +20,11 @@
 # SUCH DAMAGE.
 #
 
-SUBDIR=	virtio pci network block balloon scsi random console p9fs
+.PATH: ${SRCTOP}/sys/dev/virtio/fs
+
+KMOD=  vtfs
+SRCS=  virtio_fs.c
+SRCS+= virtio_bus_if.h virtio_if.h
+SRCS+= bus_if.h device_if.h
 
-.include <bsd.subdir.mk>
+.include <bsd.kmod.mk>