diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h
index 18fd130..08a5f23 100644
--- a/include/linux/miscdevice.h
+++ b/include/linux/miscdevice.h
@@ -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;
diff --git a/include/net/9p/client.h b/include/net/9p/client.h
index ccc3ab7..50a4d10 100644
--- a/include/net/9p/client.h
+++ b/include/net/9p/client.h
@@ -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,
diff --git a/net/9p/Kconfig b/net/9p/Kconfig
index 7ed75c7..7dba1cb 100644
--- a/net/9p/Kconfig
+++ b/net/9p/Kconfig
@@ -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
diff --git a/net/9p/Makefile b/net/9p/Makefile
index 198a640..477e62f 100644
--- a/net/9p/Makefile
+++ b/net/9p/Makefile
@@ -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 \
+
diff --git a/net/9p/trans_dev.c b/net/9p/trans_dev.c
new file mode 100644
index 0000000..2ea9718
--- /dev/null
+++ b/net/9p/trans_dev.c
@@ -0,0 +1,685 @@
+/*
+ * 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 (fc->sg) {
+		offset = 0;
+		sg_miter_start(&miter, fc->sg, fc->sgcount, 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->buf, 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 (fc->sg) {
+		offset = 0;
+		sg_miter_start(&miter, fc->sg, fc->sgcount, SG_MITER_TO_SG);
+		while (sg_miter_next(&miter) && offset < datalen) {
+			len = min(miter.length, datalen - offset);
+			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 {
+		err = copy_from_user(fc->buf, 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 (!req->rc) {
+			req->rc = p9_fcall_alloc(client, size);
+			if (!req->rc) {
+				err = -ENOMEM;
+				goto error;
+			}
+		}
+
+		if (size==7) {
+			memcpy(req->rc->buf, buf, 7);
+			req->rc->size = 7;
+		} else {
+			err = p9dev_copy_from_user(req->rc, 0, data, size);
+			if (err < 0)
+				goto error;
+
+			req->rc->size = err;
+		}
+
+		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");
