#include <sys/param.h>
#include <sys/errno.h>
#include <sys/module.h>
#include <sys/sglist.h>
#include <sys/queue.h>
#include <sys/bus.h>
#include <sys/kthread.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <fs/p9fs/p9_client.h>
#include <fs/p9fs/p9_debug.h>
#include <fs/p9fs/p9_protocol.h>
#include <fs/p9fs/p9_transport.h>
#include <dev/virtio/virtio.h>
#include <dev/virtio/virtqueue.h>
#include <dev/virtio/virtio_ring.h>
#include <dev/virtio/p9fs/virtio_p9fs.h>
#define VT9P_MTX(_sc) (&(_sc)->vt9p_mtx)
#define VT9P_LOCK(_sc) mtx_lock(VT9P_MTX(_sc))
#define VT9P_UNLOCK(_sc) mtx_unlock(VT9P_MTX(_sc))
#define VT9P_LOCK_INIT(_sc) mtx_init(VT9P_MTX(_sc), \
"VIRTIO 9P CHAN lock", NULL, MTX_DEF)
#define VT9P_LOCK_DESTROY(_sc) mtx_destroy(VT9P_MTX(_sc))
#define MAX_SUPPORTED_SGS 20
static MALLOC_DEFINE(M_P9FS_MNTTAG, "p9fs_mount_tag", "P9fs Mounttag");
struct vt9p_softc {
device_t vt9p_dev;
struct mtx vt9p_mtx;
struct sglist *vt9p_sglist;
struct cv submit_cv;
bool busy;
struct virtqueue *vt9p_vq;
int max_nsegs;
uint16_t mount_tag_len;
char *mount_tag;
STAILQ_ENTRY(vt9p_softc) chan_next;
};
static STAILQ_HEAD( ,vt9p_softc) global_chan_list =
STAILQ_HEAD_INITIALIZER(global_chan_list);
struct mtx global_chan_list_mtx;
MTX_SYSINIT(global_chan_list_mtx, &global_chan_list_mtx, "9pglobal", MTX_DEF);
static struct virtio_feature_desc virtio_9p_feature_desc[] = {
{ VIRTIO_9PNET_F_MOUNT_TAG, "9PMountTag" },
{ 0, NULL }
};
VIRTIO_SIMPLE_PNPINFO(virtio_p9fs, VIRTIO_ID_9P, "VirtIO 9P Transport");
static int
vt9p_cancel(void *handle, struct p9_req_t *req)
{
return (1);
}
SYSCTL_NODE(_vfs, OID_AUTO, 9p, CTLFLAG_RW, 0, "9P File System Protocol");
static unsigned int vt9p_ackmaxidle = 120;
SYSCTL_UINT(_vfs_9p, OID_AUTO, ackmaxidle, CTLFLAG_RW, &vt9p_ackmaxidle, 0,
"Maximum time request thread waits for ack from host");
static int
vt9p_req_wait(struct vt9p_softc *chan, struct p9_req_t *req)
{
KASSERT(req->tc->tag != req->rc->tag,
("%s: request %p already completed", __func__, req));
if (msleep(req, VT9P_MTX(chan), 0, "chan lock", vt9p_ackmaxidle * hz)) {
P9_DEBUG(ERROR, "Timeout after waiting %u seconds"
"for an ack from host\n", vt9p_ackmaxidle);
return (EIO);
}
KASSERT(req->tc->tag == req->rc->tag,
("%s spurious event on request %p", __func__, req));
return (0);
}
static int
vt9p_request(void *handle, struct p9_req_t *req)
{
int error;
struct vt9p_softc *chan;
int readable, writable;
struct sglist *sg;
struct virtqueue *vq;
chan = handle;
sg = chan->vt9p_sglist;
vq = chan->vt9p_vq;
P9_DEBUG(TRANS, "%s: req=%p\n", __func__, req);
VT9P_LOCK(chan);
req_retry:
sglist_reset(sg);
error = sglist_append(sg, req->tc->sdata, req->tc->size);
if (error != 0) {
P9_DEBUG(ERROR, "%s: sglist append failed\n", __func__);
VT9P_UNLOCK(chan);
return (error);
}
readable = sg->sg_nseg;
error = sglist_append(sg, req->rc->sdata, req->rc->capacity);
if (error != 0) {
P9_DEBUG(ERROR, "%s: sglist append failed\n", __func__);
VT9P_UNLOCK(chan);
return (error);
}
writable = sg->sg_nseg - readable;
error = virtqueue_enqueue(vq, req, sg, readable, writable);
if (error != 0) {
if (error == ENOSPC) {
cv_wait(&chan->submit_cv, VT9P_MTX(chan));
P9_DEBUG(TRANS, "%s: retry virtio request\n", __func__);
goto req_retry;
} else {
P9_DEBUG(ERROR, "%s: virtio enuqueue failed \n", __func__);
VT9P_UNLOCK(chan);
return (EIO);
}
}
virtqueue_notify(vq);
error = vt9p_req_wait(chan, req);
if (error != 0) {
VT9P_UNLOCK(chan);
return (error);
}
VT9P_UNLOCK(chan);
P9_DEBUG(TRANS, "%s: virtio request kicked\n", __func__);
return (0);
}
static void
vt9p_intr_complete(void *xsc)
{
struct vt9p_softc *chan;
struct virtqueue *vq;
struct p9_req_t *curreq;
chan = (struct vt9p_softc *)xsc;
vq = chan->vt9p_vq;
P9_DEBUG(TRANS, "%s: completing\n", __func__);
VT9P_LOCK(chan);
again:
while ((curreq = virtqueue_dequeue(vq, NULL)) != NULL) {
curreq->rc->tag = curreq->tc->tag;
wakeup_one(curreq);
}
if (virtqueue_enable_intr(vq) != 0) {
virtqueue_disable_intr(vq);
goto again;
}
cv_signal(&chan->submit_cv);
VT9P_UNLOCK(chan);
}
static int
vt9p_alloc_virtqueue(struct vt9p_softc *sc)
{
struct vq_alloc_info vq_info;
device_t dev;
dev = sc->vt9p_dev;
VQ_ALLOC_INFO_INIT(&vq_info, sc->max_nsegs,
vt9p_intr_complete, sc, &sc->vt9p_vq,
"%s request", device_get_nameunit(dev));
return (virtio_alloc_virtqueues(dev, 1, &vq_info));
}
static int
vt9p_probe(device_t dev)
{
return (VIRTIO_SIMPLE_PROBE(dev, virtio_p9fs));
}
static void
vt9p_stop(struct vt9p_softc *sc)
{
virtqueue_disable_intr(sc->vt9p_vq);
virtio_stop(sc->vt9p_dev);
}
static int
vt9p_detach(device_t dev)
{
struct vt9p_softc *sc;
sc = device_get_softc(dev);
VT9P_LOCK(sc);
vt9p_stop(sc);
VT9P_UNLOCK(sc);
if (sc->vt9p_sglist) {
sglist_free(sc->vt9p_sglist);
sc->vt9p_sglist = NULL;
}
if (sc->mount_tag) {
free(sc->mount_tag, M_P9FS_MNTTAG);
sc->mount_tag = NULL;
}
mtx_lock(&global_chan_list_mtx);
STAILQ_REMOVE(&global_chan_list, sc, vt9p_softc, chan_next);
mtx_unlock(&global_chan_list_mtx);
VT9P_LOCK_DESTROY(sc);
cv_destroy(&sc->submit_cv);
return (0);
}
static int
vt9p_attach(device_t dev)
{
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree;
struct vt9p_softc *chan;
char *mount_tag;
int error;
uint16_t mount_tag_len;
chan = device_get_softc(dev);
chan->vt9p_dev = dev;
VT9P_LOCK_INIT(chan);
cv_init(&chan->submit_cv, "Conditional variable for submit queue" );
chan->max_nsegs = MAX_SUPPORTED_SGS;
chan->vt9p_sglist = sglist_alloc(chan->max_nsegs, M_WAITOK);
virtio_set_feature_desc(dev, virtio_9p_feature_desc);
virtio_negotiate_features(dev, VIRTIO_9PNET_F_MOUNT_TAG);
if (virtio_with_feature(dev, VIRTIO_9PNET_F_MOUNT_TAG))
mount_tag_len = virtio_read_dev_config_2(dev,
offsetof(struct virtio_9pnet_config, mount_tag_len));
else {
error = EINVAL;
P9_DEBUG(ERROR, "%s: Mount tag feature not supported by host\n", __func__);
goto out;
}
mount_tag = malloc(mount_tag_len + 1, M_P9FS_MNTTAG,
M_WAITOK | M_ZERO);
virtio_read_device_config_array(dev,
offsetof(struct virtio_9pnet_config, mount_tag),
mount_tag, 1, mount_tag_len);
device_printf(dev, "Mount tag: %s\n", mount_tag);
mount_tag_len++;
chan->mount_tag_len = mount_tag_len;
chan->mount_tag = mount_tag;
ctx = device_get_sysctl_ctx(dev);
tree = device_get_sysctl_tree(dev);
SYSCTL_ADD_STRING(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "p9fs_mount_tag",
CTLFLAG_RD, chan->mount_tag, 0, "Mount tag");
error = vt9p_alloc_virtqueue(chan);
if (error != 0) {
P9_DEBUG(ERROR, "%s: Allocating the virtqueue failed \n", __func__);
goto out;
}
error = virtio_setup_intr(dev, INTR_TYPE_MISC|INTR_MPSAFE);
if (error != 0) {
P9_DEBUG(ERROR, "%s: Cannot setup virtqueue interrupt\n", __func__);
goto out;
}
error = virtqueue_enable_intr(chan->vt9p_vq);
if (error != 0) {
P9_DEBUG(ERROR, "%s: Cannot enable virtqueue interrupt\n", __func__);
goto out;
}
mtx_lock(&global_chan_list_mtx);
STAILQ_INSERT_HEAD(&global_chan_list, chan, chan_next);
mtx_unlock(&global_chan_list_mtx);
return (0);
out:
vt9p_detach(dev);
return (error);
}
static int
vt9p_create(const char *mount_tag, void **handlep)
{
struct vt9p_softc *sc, *chan;
chan = NULL;
mtx_lock(&global_chan_list_mtx);
STAILQ_FOREACH(sc, &global_chan_list, chan_next) {
if (!strcmp(sc->mount_tag, mount_tag)) {
chan = sc;
break;
}
}
mtx_unlock(&global_chan_list_mtx);
if (chan && chan->busy) {
return (EBUSY);
}
if (chan) {
*handlep = (void *)chan;
chan->busy = true;
} else {
P9_DEBUG(TRANS, "%s: No Global channel with mount_tag=%s\n",
__func__, mount_tag);
return (EINVAL);
}
return (0);
}
static void
vt9p_close(void *handle)
{
struct vt9p_softc *chan = handle;
chan->busy = false;
}
static struct p9_trans_module vt9p_trans = {
.name = "virtio",
.create = vt9p_create,
.close = vt9p_close,
.request = vt9p_request,
.cancel = vt9p_cancel,
};
static device_method_t vt9p_mthds[] = {
DEVMETHOD(device_probe, vt9p_probe),
DEVMETHOD(device_attach, vt9p_attach),
DEVMETHOD(device_detach, vt9p_detach),
DEVMETHOD_END
};
static driver_t vt9p_drv = {
"virtio_p9fs",
vt9p_mthds,
sizeof(struct vt9p_softc)
};
static int
vt9p_modevent(module_t mod, int type, void *unused)
{
int error;
error = 0;
switch (type) {
case MOD_LOAD:
p9_init_zones();
p9_register_trans(&vt9p_trans);
break;
case MOD_UNLOAD:
p9_destroy_zones();
break;
case MOD_SHUTDOWN:
break;
default:
error = EOPNOTSUPP;
break;
}
return (error);
}
VIRTIO_DRIVER_MODULE(virtio_p9fs, vt9p_drv, vt9p_modevent, NULL);
MODULE_VERSION(virtio_p9fs, 1);
MODULE_DEPEND(virtio_p9fs, virtio, 1, 1, 1);
MODULE_DEPEND(virtio_p9fs, p9fs, 1, 1, 1);