@@ -40,6 +40,7 @@
#define BTRFS_MINOR 234
#define AUTOFS_MINOR 235
#define MAPPER_CTRL_MINOR 236
+#define P9_MINOR 237
#define MISC_DYNAMIC_MINOR 255
struct device;
@@ -64,6 +64,7 @@ enum p9_trans_status {
* @REQ_STATUS_IDLE: request slot unused
* @REQ_STATUS_ALLOC: request has been allocated but not sent
* @REQ_STATUS_UNSENT: request waiting to be sent
+ * @REQ_STATUS_SENDING: request in process of being sent
* @REQ_STATUS_SENT: request sent to server
* @REQ_STATUS_FLSH: a flush has been sent for this request
* @REQ_STATUS_RCVD: response received from server
@@ -80,6 +81,7 @@ enum p9_req_status_t {
REQ_STATUS_IDLE,
REQ_STATUS_ALLOC,
REQ_STATUS_UNSENT,
+ REQ_STATUS_SENDING,
REQ_STATUS_SENT,
REQ_STATUS_FLSH,
REQ_STATUS_RCVD,
@@ -28,6 +28,12 @@ config NET_9P_RDMA
help
This builds support for an RDMA transport.
+config NET_9P_DEV
+ depends on EXPERIMENTAL
+ tristate "9P Dev Transport (Experimental)"
+ help
+ This builds support for an 9P character device transport.
+
config NET_9P_DEBUG
bool "Debug information"
help
@@ -1,6 +1,7 @@
obj-$(CONFIG_NET_9P) := 9pnet.o
obj-$(CONFIG_NET_9P_VIRTIO) += 9pnet_virtio.o
obj-$(CONFIG_NET_9P_RDMA) += 9pnet_rdma.o
+obj-$(CONFIG_NET_9P_DEV) += 9pnet_dev.o
9pnet-objs := \
mod.o \
@@ -15,3 +16,7 @@ obj-$(CONFIG_NET_9P_RDMA) += 9pnet_rdma.o
9pnet_rdma-objs := \
trans_rdma.o \
+
+9pnet_dev-objs := \
+ trans_dev.o \
+
new file mode 100644
@@ -0,0 +1,678 @@
+/*
+ * linux/fs/9p/trans_dev.c
+ *
+ * Dev transport layer.
+ *
+ * Copyright (C) 2010 by Latchesar Ionkov <lucho@ionkov.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to:
+ * Free Software Foundation
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA 02111-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kthread.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/uaccess.h>
+#include <linux/idr.h>
+#include <linux/parser.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <net/9p/9p.h>
+#include <net/9p/client.h>
+#include <net/9p/transport.h>
+
+struct p9dev_opts {
+ int fdno;
+};
+
+struct p9dev_trans {
+ atomic_t ref;
+ struct p9_client *client;
+ int err;
+ struct list_head req_list;
+ struct list_head unsent_req_list;
+ wait_queue_head_t wq;
+};
+
+struct file_operations p9dev_operations;
+
+/*
+ * Option Parsing (code inspired by NFS code)
+ * - a little lazy - parse all dev-transport options
+ */
+
+enum {
+ /* Options that take integer arguments */
+ Opt_fdno, Opt_err,
+};
+
+static const match_table_t tokens = {
+ {Opt_fdno, "fdno=%u"},
+ {Opt_err, NULL},
+};
+
+static struct p9_trans_module p9dev_trans;
+
+DEFINE_MUTEX(p9dev_mutex);
+DECLARE_WAIT_QUEUE_HEAD(p9dev_wq);
+
+static void p9dev_disconnect(struct p9dev_trans *ts, int err);
+
+static struct p9dev_trans *p9dev_get(struct file *filp)
+{
+ struct p9dev_trans *ts;
+
+ ts = filp->private_data;
+ if (ts)
+ atomic_inc(&ts->ref);
+
+ return ts;
+}
+
+static void p9dev_put(struct file *filp, struct p9dev_trans *ts)
+{
+ if (atomic_dec_and_test(&ts->ref)) {
+ if (filp)
+ filp->private_data = NULL;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "kfree ts\n");
+ kfree(ts);
+ }
+}
+
+/**
+ * parse_opts - parse mount options into p9_fd_opts structure
+ * @params: options string passed from mount
+ * @opts: fd transport-specific structure to parse options into
+ *
+ * Returns 0 upon success, -ERRNO upon failure
+ */
+
+static int parse_opts(char *params, struct p9dev_opts *opts)
+{
+ char *p;
+ substring_t args[MAX_OPT_ARGS];
+ int option;
+ char *options, *tmp_options;
+ int ret;
+
+ opts->fdno = ~0;
+ if (!params)
+ return 0;
+
+ tmp_options = kstrdup(params, GFP_KERNEL);
+ if (!tmp_options) {
+ P9_DPRINTK(P9_DEBUG_ERROR,
+ "failed to allocate copy of option string\n");
+ return -ENOMEM;
+ }
+ options = tmp_options;
+
+ while ((p = strsep(&options, ",")) != NULL) {
+ int token;
+ int r;
+ if (!*p)
+ continue;
+ token = match_token(p, tokens, args);
+ if (token != Opt_err) {
+ r = match_int(&args[0], &option);
+ if (r < 0) {
+ P9_DPRINTK(P9_DEBUG_ERROR,
+ "integer field, but no integer?\n");
+ ret = r;
+ continue;
+ }
+ }
+ switch (token) {
+ case Opt_fdno:
+ opts->fdno = option;
+ break;
+ default:
+ continue;
+ }
+ }
+
+ kfree(tmp_options);
+ return 0;
+}
+
+static int p9dev_create(struct p9_client *client, const char *addr, char *args)
+{
+ int err;
+ struct file *file;
+ struct p9dev_opts opts;
+ struct p9dev_trans *ts;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "args %p %s\n", client, args);
+ err = parse_opts(args, &opts);
+ if (err < 0)
+ return err;
+
+ if (opts.fdno==-1) {
+ printk(KERN_ERR "v9fs: missing file descriptor option");
+ return -ENOPROTOOPT;
+ }
+
+ err = -EINVAL;
+ file = fget(opts.fdno);
+ if (!file) {
+ goto done;
+ }
+
+ if (file->f_op != &p9dev_operations) {
+ goto done;
+ }
+
+ ts = kmalloc(sizeof(*ts), GFP_KERNEL);
+ if (!ts) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ ts->client = client;
+ ts->err = 0;
+ atomic_set(&ts->ref, 1);
+ INIT_LIST_HEAD(&ts->req_list);
+ INIT_LIST_HEAD(&ts->unsent_req_list);
+ init_waitqueue_head(&ts->wq);
+ client->trans = ts;
+ client->status = Connected;
+
+ mutex_lock(&p9dev_mutex);
+ if (file->private_data != NULL) {
+ goto unlock_done;
+ }
+
+ file->private_data = ts;
+ fput(file);
+ err = 0;
+unlock_done:
+ mutex_unlock(&p9dev_mutex);
+
+done:
+ wake_up_all(&p9dev_wq);
+ return err;
+}
+
+static void p9dev_close(struct p9_client *client)
+{
+ struct p9dev_trans *ts;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p\n", client);
+ if (!client)
+ return;
+
+ ts = client->trans;
+ if (!ts)
+ return;
+
+ p9dev_disconnect(ts, -ECONNRESET);
+ client->status = Disconnected;
+ p9dev_put(NULL, ts);
+}
+
+static void p9dev_disconnect(struct p9dev_trans *ts, int err)
+{
+ struct p9_req_t *req, *rtmp;
+ LIST_HEAD(reqlist);
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p\n", ts->client);
+ spin_lock(&ts->client->lock);
+ if (ts->err) {
+ spin_unlock(&ts->client->lock);
+ return;
+ }
+
+ ts->err = err;
+ list_for_each_entry_safe(req, rtmp, &ts->unsent_req_list, req_list) {
+ req->status = REQ_STATUS_ERROR;
+ req->t_err = err;
+ list_move_tail(&req->req_list, &reqlist);
+ }
+ list_for_each_entry_safe(req, rtmp, &ts->req_list, req_list) {
+ req->status = REQ_STATUS_ERROR;
+ req->t_err = err;
+ list_move_tail(&req->req_list, &reqlist);
+ }
+ spin_unlock(&ts->client->lock);
+
+ list_for_each_entry_safe(req, rtmp, &reqlist, req_list) {
+ list_del(&req->req_list);
+ p9_client_cb(ts->client, req);
+ }
+
+ wake_up_all(&ts->wq);
+}
+
+static int p9dev_request(struct p9_client *client, struct p9_req_t *req)
+{
+ int err;
+ struct p9dev_trans *ts;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p req %p\n", client, req);
+ ts = client->trans;
+ spin_lock(&client->lock);
+ if (ts->err) {
+ err = ts->err;
+ spin_unlock(&client->lock);
+ return err;
+ }
+
+ req->status = REQ_STATUS_UNSENT;
+ list_add_tail(&req->req_list, &ts->unsent_req_list);
+ spin_unlock(&client->lock);
+ wake_up(&ts->wq);
+ return 0;
+}
+
+static int p9dev_cancel(struct p9_client *client, struct p9_req_t *req)
+{
+ int ret;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p req %p\n", client, req);
+ ret = 0;
+ spin_lock(&client->lock);
+ if (req->status == REQ_STATUS_UNSENT) {
+ list_del(&req->req_list);
+ req->status = REQ_STATUS_FLSHD;
+ } else if (req->status == REQ_STATUS_SENT || req->status == REQ_STATUS_SENDING)
+ req->status = REQ_STATUS_FLSH;
+ spin_unlock(&client->lock);
+ return ret;
+}
+
+static struct p9_trans_module p9dev_trans = {
+ .name = "dev",
+ .maxsize = 1024*1024,
+ .def = 0,
+ .flags = P9_TRANS_SG,
+ .create = p9dev_create,
+ .close = p9dev_close,
+ .request = p9dev_request,
+ .cancel = p9dev_cancel,
+ .owner = THIS_MODULE,
+};
+
+static ssize_t p9dev_copy_to_user(char __user *udata, size_t datalen,
+ struct p9_fcall *fc)
+{
+ int err, offset, len;
+ struct sg_mapping_iter miter;
+
+ if (p9_fcall_has_sg(fc)) {
+ offset = 0;
+ sg_miter_start(&miter, fc->sgpkt, fc->sgnum, SG_MITER_FROM_SG);
+ while (sg_miter_next(&miter) && offset < datalen) {
+ len = min(miter.length, datalen - offset);
+ err = copy_to_user(udata + offset, miter.addr, len);
+ if (err) {
+ err = -EINVAL;
+ break;
+ }
+
+ offset += len;
+ }
+ sg_miter_stop(&miter);
+ if (!err)
+ err = offset;
+ } else {
+ err = copy_to_user(udata, fc->sdata, fc->size);
+ if (!err)
+ err = fc->size;
+ else
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static ssize_t p9dev_read(struct file *filp, char __user *udata, size_t count,
+ loff_t * offset)
+{
+ int i, m, n, err, empty;
+ struct p9dev_trans *ts;
+ struct p9_client *client;
+ struct p9_req_t *req, *rtmp;
+ struct p9_req_t *reqs[16];
+ struct p9_fcall *tc;
+
+ while (!filp->private_data) {
+ err = wait_event_interruptible(p9dev_wq, filp->private_data != NULL);
+ if (err < 0)
+ return err;
+ }
+
+ ts = p9dev_get(filp);
+ client = ts->client;
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p count %ld\n", client, count);
+
+again:
+ /* get as many requests as we can fit in the buffer */
+ spin_lock(&client->lock);
+ if (ts->err) {
+ err = ts->err;
+ spin_unlock(&client->lock);
+ goto error;
+ }
+
+ n = count;
+ m = 0;
+ empty = list_empty(&ts->unsent_req_list);
+ list_for_each_entry_safe(req, rtmp, &ts->unsent_req_list, req_list) {
+ if (n<req->tc->size || m>=ARRAY_SIZE(reqs))
+ break;
+
+ req->status = REQ_STATUS_SENDING;
+ reqs[m++] = req;
+ n -= req->tc->size;
+ }
+ spin_unlock(&client->lock);
+
+ /* if there are no request, we wait for one */
+ if (n == count) {
+ if (err < 0) {
+ return err;
+ }
+
+ if (!empty) {
+ /* there are unsent requests, but the buffer is too
+ small to fit even one */
+ err = -EINVAL;
+ goto error;
+ }
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p wait for requests\n", client);
+ err = wait_event_interruptible(ts->wq, !list_empty(&ts->unsent_req_list));
+ if (err < 0)
+ goto error;
+
+ goto again;
+ }
+
+ /* copy the data to the user buffer */
+ err = 0;
+ for(i = 0, n = count; i < m; i++) {
+ tc = reqs[i]->tc;
+ err = p9dev_copy_to_user(udata, n, tc);
+ if (err < 0)
+ break;
+
+ udata += err;
+ n -= err;
+ err = 0;
+ }
+
+ /* move the requests to the sent list */
+ spin_lock(&client->lock);
+ if (err) {
+ /* don't send anything, report the error */
+ for(i = 0; i < m; i++)
+ reqs[i]->status = REQ_STATUS_UNSENT;
+ } else {
+ for(i = 0; i < m; i++) {
+ reqs[i]->status = REQ_STATUS_SENT;
+ list_move_tail(&reqs[i]->req_list, &ts->req_list);
+ }
+ }
+ spin_unlock(&client->lock);
+
+ if (err)
+ goto error;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p total %ld\n", client, count - n);
+ *offset += count - n;
+ p9dev_put(filp, ts);
+ return count - n;
+
+error:
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p error %d\n", client, err);
+ p9dev_put(filp, ts);
+ return err;
+}
+
+static ssize_t p9dev_copy_from_user(struct p9_fcall *fc, int off,
+ const char __user *udata, size_t datalen)
+{
+ int err, offset, len;
+ struct sg_mapping_iter miter;
+
+ if (p9_fcall_has_sg(fc)) {
+ offset = 0;
+ sg_miter_start(&miter, fc->sgpkt, fc->sgnum, SG_MITER_TO_SG);
+ while (sg_miter_next(&miter) && offset < datalen) {
+ len = min(miter.length, datalen - offset);
+ P9_DPRINTK(P9_DEBUG_TRANS, "len %d miter.length %d\n", len, miter.length);
+ err = copy_from_user(miter.addr + off, udata + offset, len);
+ if (err) {
+ err = -EINVAL;
+ break;
+ }
+
+ offset += len;
+ off = 0;
+ }
+ sg_miter_stop(&miter);
+ if (err >= 0)
+ err = offset;
+ } else {
+ int i;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "datalen %d\n", datalen);
+ err = copy_from_user(fc->pkt, udata, datalen);
+ if (!err)
+ err = datalen;
+ }
+
+ return err;
+}
+
+static ssize_t p9dev_write(struct file *filp, const char __user * data,
+ size_t count, loff_t * offset)
+{
+ int err, n;
+ u8 buf[8], type;
+ u16 tag;
+ u32 size;
+ struct p9dev_trans *ts;
+ struct p9_client *client;
+ struct p9_req_t *req;
+
+ while (!filp->private_data) {
+ err = wait_event_interruptible(p9dev_wq, filp->private_data != NULL);
+ if (err < 0)
+ return err;
+ }
+
+ ts = p9dev_get(filp);
+ client = ts->client;
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p count %ld\n", client, count);
+ n = count;
+ while (n >= 7) {
+ if (n >= 8)
+ err = copy_from_user(buf, data, 8);
+ else
+ err = copy_from_user(buf, data, 7);
+
+ if (err < 0)
+ goto error;
+
+ size = le32_to_cpu(*(__le32 *) &buf[0]);
+ type = buf[4];
+ tag = le16_to_cpu(*(__le32 *) &buf[5]);
+
+ if (n < size) {
+ /* we don't accept partial fcalls */
+ break;
+ }
+
+ req = p9_tag_lookup(client, tag);
+ if (!req || (req->status!=REQ_STATUS_SENT &&
+ req->status!=REQ_STATUS_FLSH)) {
+ P9_DPRINTK(P9_DEBUG_ERROR,
+ "unexpected packet tag %d type %d\n", tag, type);
+ err = -EIO;
+ goto error;
+ }
+
+ if (size==7)
+ memcpy(req->rc->pkt, buf, 7);
+ else {
+ err = p9dev_copy_from_user(req->rc, 0, data, size);
+ if (err < 0)
+ goto error;
+ }
+
+ spin_lock(&client->lock);
+ if (ts->err) {
+ err = ts->err;
+ spin_unlock(&client->lock);
+ goto error;
+ }
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p response for req %p\n", client, req);
+ if (req->status != REQ_STATUS_ERROR)
+ req->status = REQ_STATUS_RCVD;
+ list_del(&req->req_list);
+ spin_unlock(&client->lock);
+ p9_client_cb(client, req);
+ n -= size;
+ data += size;
+ }
+
+ /* if we can't read even one fcall, signal error */
+ if (n == count) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *offset += count - n;
+ p9dev_put(filp, ts);
+ return count - n;
+
+error:
+ p9dev_put(filp, ts);
+ return err;
+}
+
+static unsigned int p9dev_poll(struct file *file, poll_table *wait)
+{
+ int err;
+ unsigned int mask;
+ struct p9dev_trans *ts;
+
+ mask = 0;
+
+ /* wait until the file is mounted */
+ while (!file->private_data) {
+ err = wait_event_interruptible(p9dev_wq, file->private_data != NULL);
+ if (err < 0)
+ return err;
+ }
+
+ ts = p9dev_get(file);
+ P9_DPRINTK(P9_DEBUG_TRANS, "%p\n", ts->client);
+
+again:
+ spin_lock(&ts->client->lock);
+ if (ts->err)
+ mask |= POLLERR;
+
+ if (!list_empty(&ts->unsent_req_list))
+ mask |= POLLIN | POLLRDNORM;
+
+ if (!list_empty(&ts->req_list))
+ mask |= POLLOUT | POLLWRNORM;
+ spin_unlock(&ts->client->lock);
+
+ if (!mask) {
+ err = wait_event_interruptible(ts->wq,
+ !list_empty(&ts->unsent_req_list) ||
+ !list_empty(&ts->req_list));
+ if (err < 0) {
+ return POLLERR;
+ }
+
+ goto again;
+ }
+
+ p9dev_put(file, ts);
+ return mask;
+}
+
+static int p9dev_release(struct inode *inode, struct file *file)
+{
+ struct p9dev_trans *ts;
+
+ ts = p9dev_get(file);
+ if (!ts)
+ return 0;
+
+ P9_DPRINTK(P9_DEBUG_TRANS, "client %p\n", ts->client);
+ file->private_data = NULL;
+ p9dev_disconnect(ts, -ECONNRESET);
+ p9dev_put(file, ts);
+ return 0;
+}
+
+struct file_operations p9dev_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = p9dev_read,
+ .aio_read = generic_file_aio_read,
+ .splice_read = generic_file_splice_read,
+ .write = p9dev_write,
+ .aio_write = generic_file_aio_write,
+ .splice_write = generic_file_splice_write,
+ .poll = p9dev_poll,
+ .release = p9dev_release,
+};
+EXPORT_SYMBOL_GPL(p9dev_operations);
+
+static struct miscdevice p9_miscdevice = {
+ .minor = P9_MINOR,
+ .name = "p9",
+ .fops = &p9dev_operations,
+};
+
+static int __init p9dev_init(void)
+{
+ int err;
+
+ err = misc_register(&p9_miscdevice);
+ if (err)
+ return err;
+
+ v9fs_register_trans(&p9dev_trans);
+ return 0;
+}
+
+static void __exit p9dev_exit(void)
+{
+ v9fs_unregister_trans(&p9dev_trans);
+ misc_deregister(&p9_miscdevice);
+}
+
+module_init(p9dev_init);
+module_exit(p9dev_exit);
+
+MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
+MODULE_DESCRIPTION("9P Dev Transport");
+MODULE_LICENSE("GPL");