From patchwork Sun Aug 29 16:30:07 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Latchesar Ionkov X-Patchwork-Id: 140661 Received: from lists.sourceforge.net (lists.sourceforge.net [216.34.181.88]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id o7TGUqrG025820 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Sun, 29 Aug 2010 16:31:28 GMT Received: from localhost ([127.0.0.1] helo=sfs-ml-4.v29.ch3.sourceforge.com) by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69) (envelope-from ) id 1OpkmK-0007i4-Kw; Sun, 29 Aug 2010 16:30:44 +0000 Received: from sog-mx-4.v43.ch3.sourceforge.com ([172.29.43.194] helo=mx.sourceforge.net) by sfs-ml-4.v29.ch3.sourceforge.com with esmtp (Exim 4.69) (envelope-from ) id 1OpkmI-0007hx-Rv for v9fs-developer@lists.sourceforge.net; Sun, 29 Aug 2010 16:30:42 +0000 Received-SPF: pass (sog-mx-4.v43.ch3.sourceforge.com: domain of gmail.com designates 209.85.214.175 as permitted sender) client-ip=209.85.214.175; envelope-from=lionkov@gmail.com; helo=mail-iw0-f175.google.com; Received: from mail-iw0-f175.google.com ([209.85.214.175]) by sog-mx-4.v43.ch3.sourceforge.com with esmtp (Exim 4.69) id 1OpkmH-0005KQ-Gy for v9fs-developer@lists.sourceforge.net; Sun, 29 Aug 2010 16:30:42 +0000 Received: by iwn2 with SMTP id 2so5182456iwn.34 for ; Sun, 29 Aug 2010 09:30:36 -0700 (PDT) Received: by 10.231.171.7 with SMTP id f7mr4079558ibz.72.1283099412413; Sun, 29 Aug 2010 09:30:12 -0700 (PDT) Received: from valinor (174-28-37-12.albq.qwest.net [174.28.37.12]) by mx.google.com with ESMTPS id e8sm6344377ibb.14.2010.08.29.09.30.10 (version=TLSv1/SSLv3 cipher=RC4-MD5); Sun, 29 Aug 2010 09:30:11 -0700 (PDT) Date: Sun, 29 Aug 2010 10:30:07 -0600 From: Latchesar Ionkov To: v9fs-developer@lists.sourceforge.net Message-ID: <20100829163007.GB3691@valinor> MIME-Version: 1.0 Content-Disposition: inline User-Agent: Mutt/1.5.20 (2009-06-14) X-Spam-Score: -1.6 (-) X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 RCVD_IN_DNSWL_NONE RBL: Sender listed at http://www.dnswl.org/, low trust [209.85.214.175 listed in list.dnswl.org] -1.5 SPF_CHECK_PASS SPF reports sender host as permitted sender for sender-domain 0.0 FREEMAIL_FROM Sender email is freemail (lionkov[at]gmail.com) -0.0 SPF_PASS SPF: sender matches SPF record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.0 T_TO_NO_BRKTS_FREEMAIL T_TO_NO_BRKTS_FREEMAIL X-Headers-End: 1OpkmH-0005KQ-Gy Subject: [V9fs-developer] [RFC] [PATCH] 9p/net: character device transport X-BeenThere: v9fs-developer@lists.sourceforge.net X-Mailman-Version: 2.1.9 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: v9fs-developer-bounces@lists.sourceforge.net X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Sun, 29 Aug 2010 16:31:28 +0000 (UTC) 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 d1aa2cf..ded9dd9 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..ab181a1 --- /dev/null +++ b/net/9p/trans_dev.c @@ -0,0 +1,678 @@ +/* + * linux/fs/9p/trans_dev.c + * + * Dev transport layer. + * + * Copyright (C) 2010 by Latchesar Ionkov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 (ntc->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 "); +MODULE_DESCRIPTION("9P Dev Transport"); +MODULE_LICENSE("GPL");