diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-07 03:06:58 +0100 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2008-02-07 03:06:58 +0100 |
commit | 488b5ec871191359b9b79262a3d48456dae7ea5f (patch) | |
tree | af9d1ca5d16e7cd195cf132e7af74222dc00a6f9 | |
parent | docbook: dmapool: fix fatal changed filename (diff) | |
parent | 9p: fix p9_printfcall export (diff) | |
download | linux-488b5ec871191359b9b79262a3d48456dae7ea5f.tar.xz linux-488b5ec871191359b9b79262a3d48456dae7ea5f.zip |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs:
9p: fix p9_printfcall export
9p: transport API reorganization
9p: add remove function to trans_virtio
9p: Convert semaphore to spinlock for p9_idpool
9p: fix mmap to be read-only
9p: add support for sticky bit
9p: Fix soft lockup in virtio transport
9p: fix bug in attach-per-user
9p: block-based virtio client
9p: create transport rpc cut-thru
9p: fix bug in p9_clone_stat
-rw-r--r-- | fs/9p/fid.c | 4 | ||||
-rw-r--r-- | fs/9p/v9fs.c | 51 | ||||
-rw-r--r-- | fs/9p/v9fs.h | 5 | ||||
-rw-r--r-- | fs/9p/vfs_file.c | 4 | ||||
-rw-r--r-- | fs/9p/vfs_inode.c | 5 | ||||
-rw-r--r-- | include/net/9p/9p.h | 1 | ||||
-rw-r--r-- | include/net/9p/client.h | 5 | ||||
-rw-r--r-- | include/net/9p/conn.h | 57 | ||||
-rw-r--r-- | include/net/9p/transport.h | 11 | ||||
-rw-r--r-- | net/9p/Makefile | 1 | ||||
-rw-r--r-- | net/9p/client.c | 161 | ||||
-rw-r--r-- | net/9p/fcprint.c | 4 | ||||
-rw-r--r-- | net/9p/mod.c | 9 | ||||
-rw-r--r-- | net/9p/mux.c | 1060 | ||||
-rw-r--r-- | net/9p/trans_fd.c | 1103 | ||||
-rw-r--r-- | net/9p/trans_virtio.c | 355 | ||||
-rw-r--r-- | net/9p/util.c | 20 |
17 files changed, 1466 insertions, 1390 deletions
diff --git a/fs/9p/fid.c b/fs/9p/fid.c index b364da70ff28..dfebdbe7440e 100644 --- a/fs/9p/fid.c +++ b/fs/9p/fid.c @@ -175,7 +175,7 @@ struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) if (!wnames) return ERR_PTR(-ENOMEM); - for (d = dentry, i = n; i >= 0; i--, d = d->d_parent) + for (d = dentry, i = (n-1); i >= 0; i--, d = d->d_parent) wnames[i] = (char *) d->d_name.name; clone = 1; @@ -183,7 +183,7 @@ struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) while (i < n) { l = min(n - i, P9_MAXWELEM); fid = p9_client_walk(fid, l, &wnames[i], clone); - if (!fid) { + if (IS_ERR(fid)) { kfree(wnames); return fid; } diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index fbb12dadba83..9b0f0222e8bb 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -3,7 +3,7 @@ * * This file contains functions assisting in mapping VFS to 9P2000 * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> * * This program is free software; you can redistribute it and/or modify @@ -31,7 +31,6 @@ #include <linux/idr.h> #include <net/9p/9p.h> #include <net/9p/transport.h> -#include <net/9p/conn.h> #include <net/9p/client.h> #include "v9fs.h" #include "v9fs_vfs.h" @@ -43,11 +42,11 @@ enum { /* Options that take integer arguments */ - Opt_debug, Opt_msize, Opt_dfltuid, Opt_dfltgid, Opt_afid, + Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, /* String options */ Opt_uname, Opt_remotename, Opt_trans, /* Options that take no arguments */ - Opt_legacy, Opt_nodevmap, + Opt_nodevmap, /* Cache options */ Opt_cache_loose, /* Access options */ @@ -58,14 +57,11 @@ enum { static match_table_t tokens = { {Opt_debug, "debug=%x"}, - {Opt_msize, "msize=%u"}, {Opt_dfltuid, "dfltuid=%u"}, {Opt_dfltgid, "dfltgid=%u"}, {Opt_afid, "afid=%u"}, {Opt_uname, "uname=%s"}, {Opt_remotename, "aname=%s"}, - {Opt_trans, "trans=%s"}, - {Opt_legacy, "noextend"}, {Opt_nodevmap, "nodevmap"}, {Opt_cache_loose, "cache=loose"}, {Opt_cache_loose, "loose"}, @@ -85,16 +81,14 @@ static void v9fs_parse_options(struct v9fs_session_info *v9ses) char *options; substring_t args[MAX_OPT_ARGS]; char *p; - int option; - int ret; + int option = 0; char *s, *e; + int ret; /* setup defaults */ - v9ses->maxdata = 8192; v9ses->afid = ~0; v9ses->debug = 0; v9ses->cache = 0; - v9ses->trans = v9fs_default_trans(); if (!v9ses->options) return; @@ -106,7 +100,8 @@ static void v9fs_parse_options(struct v9fs_session_info *v9ses) continue; token = match_token(p, tokens, args); if (token < Opt_uname) { - if ((ret = match_int(&args[0], &option)) < 0) { + ret = match_int(&args[0], &option); + if (ret < 0) { P9_DPRINTK(P9_DEBUG_ERROR, "integer field, but no integer?\n"); continue; @@ -119,9 +114,7 @@ static void v9fs_parse_options(struct v9fs_session_info *v9ses) p9_debug_level = option; #endif break; - case Opt_msize: - v9ses->maxdata = option; - break; + case Opt_dfltuid: v9ses->dfltuid = option; break; @@ -131,18 +124,12 @@ static void v9fs_parse_options(struct v9fs_session_info *v9ses) case Opt_afid: v9ses->afid = option; break; - case Opt_trans: - v9ses->trans = v9fs_match_trans(&args[0]); - break; case Opt_uname: match_strcpy(v9ses->uname, &args[0]); break; case Opt_remotename: match_strcpy(v9ses->aname, &args[0]); break; - case Opt_legacy: - v9ses->flags &= ~V9FS_EXTENDED; - break; case Opt_nodevmap: v9ses->nodev = 1; break; @@ -185,7 +172,6 @@ struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, const char *dev_name, char *data) { int retval = -EINVAL; - struct p9_trans *trans = NULL; struct p9_fid *fid; v9ses->uname = __getname(); @@ -207,24 +193,7 @@ struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, v9ses->options = kstrdup(data, GFP_KERNEL); v9fs_parse_options(v9ses); - if (v9ses->trans == NULL) { - retval = -EPROTONOSUPPORT; - P9_DPRINTK(P9_DEBUG_ERROR, - "No transport defined or default transport\n"); - goto error; - } - - trans = v9ses->trans->create(dev_name, v9ses->options); - if (IS_ERR(trans)) { - retval = PTR_ERR(trans); - trans = NULL; - goto error; - } - if ((v9ses->maxdata+P9_IOHDRSZ) > v9ses->trans->maxsize) - v9ses->maxdata = v9ses->trans->maxsize-P9_IOHDRSZ; - - v9ses->clnt = p9_client_create(trans, v9ses->maxdata+P9_IOHDRSZ, - v9fs_extended(v9ses)); + v9ses->clnt = p9_client_create(dev_name, v9ses->options); if (IS_ERR(v9ses->clnt)) { retval = PTR_ERR(v9ses->clnt); @@ -236,6 +205,8 @@ struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, if (!v9ses->clnt->dotu) v9ses->flags &= ~V9FS_EXTENDED; + v9ses->maxdata = v9ses->clnt->msize; + /* for legacy mode, fall back to V9FS_ACCESS_ANY */ if (!v9fs_extended(v9ses) && ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index db4b4193f2e2..7d3a1018db52 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -1,7 +1,7 @@ /* * V9FS definitions. * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> * * This program is free software; you can redistribute it and/or modify @@ -28,7 +28,6 @@ struct v9fs_session_info { /* options */ - unsigned int maxdata; unsigned char flags; /* session flags */ unsigned char nodev; /* set to 1 if no disable device mapping */ unsigned short debug; /* debug level */ @@ -38,10 +37,10 @@ struct v9fs_session_info { char *options; /* copy of mount options */ char *uname; /* user name to mount as */ char *aname; /* name of remote hierarchy being mounted */ + unsigned int maxdata; /* max data for client interface */ unsigned int dfltuid; /* default uid/muid for legacy support */ unsigned int dfltgid; /* default gid for legacy support */ u32 uid; /* if ACCESS_SINGLE, the uid that has access */ - struct p9_trans_module *trans; /* 9p transport */ struct p9_client *clnt; /* 9p client */ struct dentry *debugfs_dir; }; diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index ba4b1caa9c43..a616fff8906d 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -184,7 +184,7 @@ static const struct file_operations v9fs_cached_file_operations = { .open = v9fs_file_open, .release = v9fs_dir_release, .lock = v9fs_file_lock, - .mmap = generic_file_mmap, + .mmap = generic_file_readonly_mmap, }; const struct file_operations v9fs_file_operations = { @@ -194,5 +194,5 @@ const struct file_operations v9fs_file_operations = { .open = v9fs_file_open, .release = v9fs_dir_release, .lock = v9fs_file_lock, - .mmap = generic_file_mmap, + .mmap = generic_file_readonly_mmap, }; diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 23581bcb599b..5c5137c11484 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -77,6 +77,8 @@ static int unixmode2p9mode(struct v9fs_session_info *v9ses, int mode) res |= P9_DMSETUID; if ((mode & S_ISGID) == S_ISGID) res |= P9_DMSETGID; + if ((mode & S_ISVTX) == S_ISVTX) + res |= P9_DMSETVTX; if ((mode & P9_DMLINK)) res |= P9_DMLINK; } @@ -119,6 +121,9 @@ static int p9mode2unixmode(struct v9fs_session_info *v9ses, int mode) if ((mode & P9_DMSETGID) == P9_DMSETGID) res |= S_ISGID; + + if ((mode & P9_DMSETVTX) == P9_DMSETVTX) + res |= S_ISVTX; } return res; diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h index 625346c47ee2..585eb4496990 100644 --- a/include/net/9p/9p.h +++ b/include/net/9p/9p.h @@ -124,6 +124,7 @@ enum { P9_DMSOCKET = 0x00100000, P9_DMSETUID = 0x00080000, P9_DMSETGID = 0x00040000, + P9_DMSETVTX = 0x00010000, }; /* qid.types */ diff --git a/include/net/9p/client.h b/include/net/9p/client.h index 9b9221a21392..e52f93d9ac5f 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -3,6 +3,7 @@ * * 9P Client Definitions * + * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com> * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net> * * This program is free software; you can redistribute it and/or modify @@ -29,6 +30,7 @@ struct p9_client { spinlock_t lock; /* protect client structure */ int msize; unsigned char dotu; + struct p9_trans_module *trans_mod; struct p9_trans *trans; struct p9_conn *conn; @@ -52,8 +54,7 @@ struct p9_fid { struct list_head dlist; /* list of all fids attached to a dentry */ }; -struct p9_client *p9_client_create(struct p9_trans *trans, int msize, - int dotu); +struct p9_client *p9_client_create(const char *dev_name, char *options); void p9_client_destroy(struct p9_client *clnt); void p9_client_disconnect(struct p9_client *clnt); struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid, diff --git a/include/net/9p/conn.h b/include/net/9p/conn.h deleted file mode 100644 index 756d8784f953..000000000000 --- a/include/net/9p/conn.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * include/net/9p/conn.h - * - * Connection Definitions - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> - * - * 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 - * - */ - -#ifndef NET_9P_CONN_H -#define NET_9P_CONN_H - -#undef P9_NONBLOCK - -struct p9_conn; -struct p9_req; - -/** - * p9_mux_req_callback - callback function that is called when the - * response of a request is received. The callback is called from - * a workqueue and shouldn't block. - * - * @req - request - * @a - the pointer that was specified when the request was send to be - * passed to the callback - */ -typedef void (*p9_conn_req_callback)(struct p9_req *req, void *a); - -struct p9_conn *p9_conn_create(struct p9_trans *trans, int msize, - unsigned char *dotu); -void p9_conn_destroy(struct p9_conn *); -int p9_conn_rpc(struct p9_conn *m, struct p9_fcall *tc, struct p9_fcall **rc); - -#ifdef P9_NONBLOCK -int p9_conn_rpcnb(struct p9_conn *m, struct p9_fcall *tc, - p9_conn_req_callback cb, void *a); -#endif /* P9_NONBLOCK */ - -void p9_conn_cancel(struct p9_conn *m, int err); - -#endif /* NET_9P_CONN_H */ diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h index 9dd4a05619a8..d2209ae9d18b 100644 --- a/include/net/9p/transport.h +++ b/include/net/9p/transport.h @@ -4,7 +4,7 @@ * Transport Definition * * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 @@ -34,11 +34,12 @@ enum p9_trans_status { struct p9_trans { enum p9_trans_status status; + int msize; + unsigned char extended; void *priv; - int (*write) (struct p9_trans *, void *, int); - int (*read) (struct p9_trans *, void *, int); void (*close) (struct p9_trans *); - unsigned int (*poll)(struct p9_trans *, struct poll_table_struct *); + int (*rpc) (struct p9_trans *t, struct p9_fcall *tc, + struct p9_fcall **rc); }; struct p9_trans_module { @@ -46,7 +47,7 @@ struct p9_trans_module { char *name; /* name of transport */ int maxsize; /* max message size of transport */ int def; /* this transport should be default */ - struct p9_trans * (*create)(const char *devname, char *options); + struct p9_trans * (*create)(const char *, char *, int, unsigned char); }; void v9fs_register_trans(struct p9_trans_module *m); diff --git a/net/9p/Makefile b/net/9p/Makefile index d3abb246ccab..8a1051101898 100644 --- a/net/9p/Makefile +++ b/net/9p/Makefile @@ -4,7 +4,6 @@ obj-$(CONFIG_NET_9P_VIRTIO) += 9pnet_virtio.o 9pnet-objs := \ mod.o \ - mux.o \ client.o \ conv.o \ error.o \ diff --git a/net/9p/client.c b/net/9p/client.c index af9199364049..84e087e24146 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -3,6 +3,7 @@ * * 9P Client * + * Copyright (C) 2008 by Eric Van Hensbergen <ericvh@gmail.com> * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net> * * This program is free software; you can redistribute it and/or modify @@ -25,6 +26,7 @@ #include <linux/module.h> #include <linux/errno.h> #include <linux/fs.h> +#include <linux/poll.h> #include <linux/idr.h> #include <linux/mutex.h> #include <linux/sched.h> @@ -32,15 +34,97 @@ #include <net/9p/9p.h> #include <linux/parser.h> #include <net/9p/transport.h> -#include <net/9p/conn.h> #include <net/9p/client.h> static struct p9_fid *p9_fid_create(struct p9_client *clnt); static void p9_fid_destroy(struct p9_fid *fid); static struct p9_stat *p9_clone_stat(struct p9_stat *st, int dotu); -struct p9_client *p9_client_create(struct p9_trans *trans, int msize, - int dotu) +/* + * Client Option Parsing (code inspired by NFS code) + * - a little lazy - parse all client options + */ + +enum { + Opt_msize, + Opt_trans, + Opt_legacy, + Opt_err, +}; + +static match_table_t tokens = { + {Opt_msize, "msize=%u"}, + {Opt_legacy, "noextend"}, + {Opt_trans, "trans=%s"}, + {Opt_err, NULL}, +}; + +/** + * v9fs_parse_options - parse mount options into session structure + * @options: options string passed from mount + * @v9ses: existing v9fs session information + * + */ + +static void parse_opts(char *options, struct p9_client *clnt) +{ + char *p; + substring_t args[MAX_OPT_ARGS]; + int option; + int ret; + + clnt->trans_mod = v9fs_default_trans(); + clnt->dotu = 1; + clnt->msize = 8192; + + if (!options) + return; + + while ((p = strsep(&options, ",")) != NULL) { + int token; + if (!*p) + continue; + token = match_token(p, tokens, args); + if (token < Opt_trans) { + ret = match_int(&args[0], &option); + if (ret < 0) { + P9_DPRINTK(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + continue; + } + } + switch (token) { + case Opt_msize: + clnt->msize = option; + break; + case Opt_trans: + clnt->trans_mod = v9fs_match_trans(&args[0]); + break; + case Opt_legacy: + clnt->dotu = 0; + break; + default: + continue; + } + } +} + + +/** + * p9_client_rpc - sends 9P request and waits until a response is available. + * The function can be interrupted. + * @c: client data + * @tc: request to be sent + * @rc: pointer where a pointer to the response is stored + */ +int +p9_client_rpc(struct p9_client *c, struct p9_fcall *tc, + struct p9_fcall **rc) +{ + return c->trans->rpc(c->trans, tc, rc); +} + +struct p9_client *p9_client_create(const char *dev_name, char *options) { int err, n; struct p9_client *clnt; @@ -54,12 +138,7 @@ struct p9_client *p9_client_create(struct p9_trans *trans, int msize, if (!clnt) return ERR_PTR(-ENOMEM); - P9_DPRINTK(P9_DEBUG_9P, "clnt %p trans %p msize %d dotu %d\n", - clnt, trans, msize, dotu); spin_lock_init(&clnt->lock); - clnt->trans = trans; - clnt->msize = msize; - clnt->dotu = dotu; INIT_LIST_HEAD(&clnt->fidlist); clnt->fidpool = p9_idpool_create(); if (!clnt->fidpool) { @@ -68,13 +147,29 @@ struct p9_client *p9_client_create(struct p9_trans *trans, int msize, goto error; } - clnt->conn = p9_conn_create(clnt->trans, clnt->msize, &clnt->dotu); - if (IS_ERR(clnt->conn)) { - err = PTR_ERR(clnt->conn); - clnt->conn = NULL; + parse_opts(options, clnt); + if (clnt->trans_mod == NULL) { + err = -EPROTONOSUPPORT; + P9_DPRINTK(P9_DEBUG_ERROR, + "No transport defined or default transport\n"); goto error; } + P9_DPRINTK(P9_DEBUG_9P, "clnt %p trans %p msize %d dotu %d\n", + clnt, clnt->trans_mod, clnt->msize, clnt->dotu); + + + clnt->trans = clnt->trans_mod->create(dev_name, options, clnt->msize, + clnt->dotu); + if (IS_ERR(clnt->trans)) { + err = PTR_ERR(clnt->trans); + clnt->trans = NULL; + goto error; + } + + if ((clnt->msize+P9_IOHDRSZ) > clnt->trans_mod->maxsize) + clnt->msize = clnt->trans_mod->maxsize-P9_IOHDRSZ; + tc = p9_create_tversion(clnt->msize, clnt->dotu?"9P2000.u":"9P2000"); if (IS_ERR(tc)) { err = PTR_ERR(tc); @@ -82,7 +177,7 @@ struct p9_client *p9_client_create(struct p9_trans *trans, int msize, goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -117,10 +212,6 @@ void p9_client_destroy(struct p9_client *clnt) struct p9_fid *fid, *fidptr; P9_DPRINTK(P9_DEBUG_9P, "clnt %p\n", clnt); - if (clnt->conn) { - p9_conn_destroy(clnt->conn); - clnt->conn = NULL; - } if (clnt->trans) { clnt->trans->close(clnt->trans); @@ -142,7 +233,6 @@ void p9_client_disconnect(struct p9_client *clnt) { P9_DPRINTK(P9_DEBUG_9P, "clnt %p\n", clnt); clnt->trans->status = Disconnected; - p9_conn_cancel(clnt->conn, -EIO); } EXPORT_SYMBOL(p9_client_disconnect); @@ -174,7 +264,7 @@ struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid, goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -219,7 +309,7 @@ struct p9_fid *p9_client_auth(struct p9_client *clnt, char *uname, goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -270,7 +360,7 @@ struct p9_fid *p9_client_walk(struct p9_fid *oldfid, int nwname, char **wnames, goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) { if (rc && rc->id == P9_RWALK) goto clunk_fid; @@ -305,7 +395,7 @@ clunk_fid: goto error; } - p9_conn_rpc(clnt->conn, tc, &rc); + p9_client_rpc(clnt, tc, &rc); error: kfree(tc); @@ -339,7 +429,7 @@ int p9_client_open(struct p9_fid *fid, int mode) goto done; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto done; @@ -378,7 +468,7 @@ int p9_client_fcreate(struct p9_fid *fid, char *name, u32 perm, int mode, goto done; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto done; @@ -411,7 +501,7 @@ int p9_client_clunk(struct p9_fid *fid) goto done; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto done; @@ -443,7 +533,7 @@ int p9_client_remove(struct p9_fid *fid) goto done; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto done; @@ -485,7 +575,7 @@ int p9_client_read(struct p9_fid *fid, char *data, u64 offset, u32 count) goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -542,7 +632,7 @@ int p9_client_write(struct p9_fid *fid, char *data, u64 offset, u32 count) goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -596,7 +686,7 @@ p9_client_uread(struct p9_fid *fid, char __user *data, u64 offset, u32 count) goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -660,7 +750,7 @@ p9_client_uwrite(struct p9_fid *fid, const char __user *data, u64 offset, goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -731,7 +821,7 @@ struct p9_stat *p9_client_stat(struct p9_fid *fid) goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -773,7 +863,7 @@ int p9_client_wstat(struct p9_fid *fid, struct p9_wstat *wst) goto done; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); done: kfree(tc); @@ -830,7 +920,7 @@ struct p9_stat *p9_client_dirread(struct p9_fid *fid, u64 offset) goto error; } - err = p9_conn_rpc(clnt->conn, tc, &rc); + err = p9_client_rpc(clnt, tc, &rc); if (err) goto error; @@ -901,16 +991,21 @@ static struct p9_stat *p9_clone_stat(struct p9_stat *st, int dotu) memmove(ret, st, sizeof(struct p9_stat)); p = ((char *) ret) + sizeof(struct p9_stat); memmove(p, st->name.str, st->name.len); + ret->name.str = p; p += st->name.len; memmove(p, st->uid.str, st->uid.len); + ret->uid.str = p; p += st->uid.len; memmove(p, st->gid.str, st->gid.len); + ret->gid.str = p; p += st->gid.len; memmove(p, st->muid.str, st->muid.len); + ret->muid.str = p; p += st->muid.len; if (dotu) { memmove(p, st->extension.str, st->extension.len); + ret->extension.str = p; p += st->extension.len; } diff --git a/net/9p/fcprint.c b/net/9p/fcprint.c index b1ae8ec57d54..40244fbd9b0d 100644 --- a/net/9p/fcprint.c +++ b/net/9p/fcprint.c @@ -347,12 +347,12 @@ p9_printfcall(char *buf, int buflen, struct p9_fcall *fc, int extended) return ret; } - #else int p9_printfcall(char *buf, int buflen, struct p9_fcall *fc, int extended) { return 0; } -EXPORT_SYMBOL(p9_printfcall); #endif /* CONFIG_NET_9P_DEBUG */ +EXPORT_SYMBOL(p9_printfcall); + diff --git a/net/9p/mod.c b/net/9p/mod.c index 8f9763a9dc12..c285aab2af04 100644 --- a/net/9p/mod.c +++ b/net/9p/mod.c @@ -106,15 +106,10 @@ EXPORT_SYMBOL(v9fs_default_trans); */ static int __init init_p9(void) { - int ret; + int ret = 0; p9_error_init(); printk(KERN_INFO "Installing 9P2000 support\n"); - ret = p9_mux_global_init(); - if (ret) { - printk(KERN_WARNING "9p: starting mux failed\n"); - return ret; - } return ret; } @@ -126,7 +121,7 @@ static int __init init_p9(void) static void __exit exit_p9(void) { - p9_mux_global_exit(); + printk(KERN_INFO "Unloading 9P2000 support\n"); } module_init(init_p9) diff --git a/net/9p/mux.c b/net/9p/mux.c deleted file mode 100644 index c9f0805048e4..000000000000 --- a/net/9p/mux.c +++ /dev/null @@ -1,1060 +0,0 @@ -/* - * net/9p/mux.c - * - * Protocol Multiplexer - * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> - * Copyright (C) 2004-2005 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/errno.h> -#include <linux/fs.h> -#include <linux/poll.h> -#include <linux/kthread.h> -#include <linux/idr.h> -#include <linux/mutex.h> -#include <net/9p/9p.h> -#include <linux/parser.h> -#include <net/9p/transport.h> -#include <net/9p/conn.h> - -#define ERREQFLUSH 1 -#define SCHED_TIMEOUT 10 -#define MAXPOLLWADDR 2 - -enum { - Rworksched = 1, /* read work scheduled or running */ - Rpending = 2, /* can read */ - Wworksched = 4, /* write work scheduled or running */ - Wpending = 8, /* can write */ -}; - -enum { - None, - Flushing, - Flushed, -}; - -struct p9_mux_poll_task; - -struct p9_req { - spinlock_t lock; /* protect request structure */ - int tag; - struct p9_fcall *tcall; - struct p9_fcall *rcall; - int err; - p9_conn_req_callback cb; - void *cba; - int flush; - struct list_head req_list; -}; - -struct p9_conn { - spinlock_t lock; /* protect lock structure */ - struct list_head mux_list; - struct p9_mux_poll_task *poll_task; - int msize; - unsigned char *extended; - struct p9_trans *trans; - struct p9_idpool *tagpool; - int err; - wait_queue_head_t equeue; - struct list_head req_list; - struct list_head unsent_req_list; - struct p9_fcall *rcall; - int rpos; - char *rbuf; - int wpos; - int wsize; - char *wbuf; - wait_queue_t poll_wait[MAXPOLLWADDR]; - wait_queue_head_t *poll_waddr[MAXPOLLWADDR]; - poll_table pt; - struct work_struct rq; - struct work_struct wq; - unsigned long wsched; -}; - -struct p9_mux_poll_task { - struct task_struct *task; - struct list_head mux_list; - int muxnum; -}; - -struct p9_mux_rpc { - struct p9_conn *m; - int err; - struct p9_fcall *tcall; - struct p9_fcall *rcall; - wait_queue_head_t wqueue; -}; - -static int p9_poll_proc(void *); -static void p9_read_work(struct work_struct *work); -static void p9_write_work(struct work_struct *work); -static void p9_pollwait(struct file *filp, wait_queue_head_t *wait_address, - poll_table * p); -static u16 p9_mux_get_tag(struct p9_conn *); -static void p9_mux_put_tag(struct p9_conn *, u16); - -static DEFINE_MUTEX(p9_mux_task_lock); -static struct workqueue_struct *p9_mux_wq; - -static int p9_mux_num; -static int p9_mux_poll_task_num; -static struct p9_mux_poll_task p9_mux_poll_tasks[100]; - -int p9_mux_global_init(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) - p9_mux_poll_tasks[i].task = NULL; - - p9_mux_wq = create_workqueue("v9fs"); - if (!p9_mux_wq) { - printk(KERN_WARNING "v9fs: mux: creating workqueue failed\n"); - return -ENOMEM; - } - - return 0; -} - -void p9_mux_global_exit(void) -{ - destroy_workqueue(p9_mux_wq); -} - -/** - * p9_mux_calc_poll_procs - calculates the number of polling procs - * based on the number of mounted v9fs filesystems. - * - * The current implementation returns sqrt of the number of mounts. - */ -static int p9_mux_calc_poll_procs(int muxnum) -{ - int n; - - if (p9_mux_poll_task_num) - n = muxnum / p9_mux_poll_task_num + - (muxnum % p9_mux_poll_task_num ? 1 : 0); - else - n = 1; - - if (n > ARRAY_SIZE(p9_mux_poll_tasks)) - n = ARRAY_SIZE(p9_mux_poll_tasks); - - return n; -} - -static int p9_mux_poll_start(struct p9_conn *m) -{ - int i, n; - struct p9_mux_poll_task *vpt, *vptlast; - struct task_struct *pproc; - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p muxnum %d procnum %d\n", m, p9_mux_num, - p9_mux_poll_task_num); - mutex_lock(&p9_mux_task_lock); - - n = p9_mux_calc_poll_procs(p9_mux_num + 1); - if (n > p9_mux_poll_task_num) { - for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) { - if (p9_mux_poll_tasks[i].task == NULL) { - vpt = &p9_mux_poll_tasks[i]; - P9_DPRINTK(P9_DEBUG_MUX, "create proc %p\n", - vpt); - pproc = kthread_create(p9_poll_proc, vpt, - "v9fs-poll"); - - if (!IS_ERR(pproc)) { - vpt->task = pproc; - INIT_LIST_HEAD(&vpt->mux_list); - vpt->muxnum = 0; - p9_mux_poll_task_num++; - wake_up_process(vpt->task); - } - break; - } - } - - if (i >= ARRAY_SIZE(p9_mux_poll_tasks)) - P9_DPRINTK(P9_DEBUG_ERROR, - "warning: no free poll slots\n"); - } - - n = (p9_mux_num + 1) / p9_mux_poll_task_num + - ((p9_mux_num + 1) % p9_mux_poll_task_num ? 1 : 0); - - vptlast = NULL; - for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) { - vpt = &p9_mux_poll_tasks[i]; - if (vpt->task != NULL) { - vptlast = vpt; - if (vpt->muxnum < n) { - P9_DPRINTK(P9_DEBUG_MUX, "put in proc %d\n", i); - list_add(&m->mux_list, &vpt->mux_list); - vpt->muxnum++; - m->poll_task = vpt; - memset(&m->poll_waddr, 0, - sizeof(m->poll_waddr)); - init_poll_funcptr(&m->pt, p9_pollwait); - break; - } - } - } - - if (i >= ARRAY_SIZE(p9_mux_poll_tasks)) { - if (vptlast == NULL) { - mutex_unlock(&p9_mux_task_lock); - return -ENOMEM; - } - - P9_DPRINTK(P9_DEBUG_MUX, "put in proc %d\n", i); - list_add(&m->mux_list, &vptlast->mux_list); - vptlast->muxnum++; - m->poll_task = vptlast; - memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); - init_poll_funcptr(&m->pt, p9_pollwait); - } - - p9_mux_num++; - mutex_unlock(&p9_mux_task_lock); - - return 0; -} - -static void p9_mux_poll_stop(struct p9_conn *m) -{ - int i; - struct p9_mux_poll_task *vpt; - - mutex_lock(&p9_mux_task_lock); - vpt = m->poll_task; - list_del(&m->mux_list); - for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { - if (m->poll_waddr[i] != NULL) { - remove_wait_queue(m->poll_waddr[i], &m->poll_wait[i]); - m->poll_waddr[i] = NULL; - } - } - vpt->muxnum--; - if (!vpt->muxnum) { - P9_DPRINTK(P9_DEBUG_MUX, "destroy proc %p\n", vpt); - kthread_stop(vpt->task); - vpt->task = NULL; - p9_mux_poll_task_num--; - } - p9_mux_num--; - mutex_unlock(&p9_mux_task_lock); -} - -/** - * p9_conn_create - allocate and initialize the per-session mux data - * Creates the polling task if this is the first session. - * - * @trans - transport structure - * @msize - maximum message size - * @extended - pointer to the extended flag - */ -struct p9_conn *p9_conn_create(struct p9_trans *trans, int msize, - unsigned char *extended) -{ - int i, n; - struct p9_conn *m, *mtmp; - - P9_DPRINTK(P9_DEBUG_MUX, "transport %p msize %d\n", trans, msize); - m = kmalloc(sizeof(struct p9_conn), GFP_KERNEL); - if (!m) - return ERR_PTR(-ENOMEM); - - spin_lock_init(&m->lock); - INIT_LIST_HEAD(&m->mux_list); - m->msize = msize; - m->extended = extended; - m->trans = trans; - m->tagpool = p9_idpool_create(); - if (IS_ERR(m->tagpool)) { - mtmp = ERR_PTR(-ENOMEM); - kfree(m); - return mtmp; - } - - m->err = 0; - init_waitqueue_head(&m->equeue); - INIT_LIST_HEAD(&m->req_list); - INIT_LIST_HEAD(&m->unsent_req_list); - m->rcall = NULL; - m->rpos = 0; - m->rbuf = NULL; - m->wpos = m->wsize = 0; - m->wbuf = NULL; - INIT_WORK(&m->rq, p9_read_work); - INIT_WORK(&m->wq, p9_write_work); - m->wsched = 0; - memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); - m->poll_task = NULL; - n = p9_mux_poll_start(m); - if (n) { - kfree(m); - return ERR_PTR(n); - } - - n = trans->poll(trans, &m->pt); - if (n & POLLIN) { - P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m); - set_bit(Rpending, &m->wsched); - } - - if (n & POLLOUT) { - P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m); - set_bit(Wpending, &m->wsched); - } - - for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { - if (IS_ERR(m->poll_waddr[i])) { - p9_mux_poll_stop(m); - mtmp = (void *)m->poll_waddr; /* the error code */ - kfree(m); - m = mtmp; - break; - } - } - - return m; -} -EXPORT_SYMBOL(p9_conn_create); - -/** - * p9_mux_destroy - cancels all pending requests and frees mux resources - */ -void p9_conn_destroy(struct p9_conn *m) -{ - P9_DPRINTK(P9_DEBUG_MUX, "mux %p prev %p next %p\n", m, - m->mux_list.prev, m->mux_list.next); - p9_conn_cancel(m, -ECONNRESET); - - if (!list_empty(&m->req_list)) { - /* wait until all processes waiting on this session exit */ - P9_DPRINTK(P9_DEBUG_MUX, - "mux %p waiting for empty request queue\n", m); - wait_event_timeout(m->equeue, (list_empty(&m->req_list)), 5000); - P9_DPRINTK(P9_DEBUG_MUX, "mux %p request queue empty: %d\n", m, - list_empty(&m->req_list)); - } - - p9_mux_poll_stop(m); - m->trans = NULL; - p9_idpool_destroy(m->tagpool); - kfree(m); -} -EXPORT_SYMBOL(p9_conn_destroy); - -/** - * p9_pollwait - called by files poll operation to add v9fs-poll task - * to files wait queue - */ -static void -p9_pollwait(struct file *filp, wait_queue_head_t *wait_address, - poll_table * p) -{ - int i; - struct p9_conn *m; - - m = container_of(p, struct p9_conn, pt); - for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) - if (m->poll_waddr[i] == NULL) - break; - - if (i >= ARRAY_SIZE(m->poll_waddr)) { - P9_DPRINTK(P9_DEBUG_ERROR, "not enough wait_address slots\n"); - return; - } - - m->poll_waddr[i] = wait_address; - - if (!wait_address) { - P9_DPRINTK(P9_DEBUG_ERROR, "no wait_address\n"); - m->poll_waddr[i] = ERR_PTR(-EIO); - return; - } - - init_waitqueue_entry(&m->poll_wait[i], m->poll_task->task); - add_wait_queue(wait_address, &m->poll_wait[i]); -} - -/** - * p9_poll_mux - polls a mux and schedules read or write works if necessary - */ -static void p9_poll_mux(struct p9_conn *m) -{ - int n; - - if (m->err < 0) - return; - - n = m->trans->poll(m->trans, NULL); - if (n < 0 || n & (POLLERR | POLLHUP | POLLNVAL)) { - P9_DPRINTK(P9_DEBUG_MUX, "error mux %p err %d\n", m, n); - if (n >= 0) - n = -ECONNRESET; - p9_conn_cancel(m, n); - } - - if (n & POLLIN) { - set_bit(Rpending, &m->wsched); - P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m); - if (!test_and_set_bit(Rworksched, &m->wsched)) { - P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m); - queue_work(p9_mux_wq, &m->rq); - } - } - - if (n & POLLOUT) { - set_bit(Wpending, &m->wsched); - P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m); - if ((m->wsize || !list_empty(&m->unsent_req_list)) - && !test_and_set_bit(Wworksched, &m->wsched)) { - P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m); - queue_work(p9_mux_wq, &m->wq); - } - } -} - -/** - * p9_poll_proc - polls all v9fs transports for new events and queues - * the appropriate work to the work queue - */ -static int p9_poll_proc(void *a) -{ - struct p9_conn *m, *mtmp; - struct p9_mux_poll_task *vpt; - - vpt = a; - P9_DPRINTK(P9_DEBUG_MUX, "start %p %p\n", current, vpt); - while (!kthread_should_stop()) { - set_current_state(TASK_INTERRUPTIBLE); - - list_for_each_entry_safe(m, mtmp, &vpt->mux_list, mux_list) { - p9_poll_mux(m); - } - - P9_DPRINTK(P9_DEBUG_MUX, "sleeping...\n"); - schedule_timeout(SCHED_TIMEOUT * HZ); - } - - __set_current_state(TASK_RUNNING); - P9_DPRINTK(P9_DEBUG_MUX, "finish\n"); - return 0; -} - -/** - * p9_write_work - called when a transport can send some data - */ -static void p9_write_work(struct work_struct *work) -{ - int n, err; - struct p9_conn *m; - struct p9_req *req; - - m = container_of(work, struct p9_conn, wq); - - if (m->err < 0) { - clear_bit(Wworksched, &m->wsched); - return; - } - - if (!m->wsize) { - if (list_empty(&m->unsent_req_list)) { - clear_bit(Wworksched, &m->wsched); - return; - } - - spin_lock(&m->lock); -again: - req = list_entry(m->unsent_req_list.next, struct p9_req, - req_list); - list_move_tail(&req->req_list, &m->req_list); - if (req->err == ERREQFLUSH) - goto again; - - m->wbuf = req->tcall->sdata; - m->wsize = req->tcall->size; - m->wpos = 0; - spin_unlock(&m->lock); - } - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p pos %d size %d\n", m, m->wpos, - m->wsize); - clear_bit(Wpending, &m->wsched); - err = m->trans->write(m->trans, m->wbuf + m->wpos, m->wsize - m->wpos); - P9_DPRINTK(P9_DEBUG_MUX, "mux %p sent %d bytes\n", m, err); - if (err == -EAGAIN) { - clear_bit(Wworksched, &m->wsched); - return; - } - - if (err < 0) - goto error; - else if (err == 0) { - err = -EREMOTEIO; - goto error; - } - - m->wpos += err; - if (m->wpos == m->wsize) - m->wpos = m->wsize = 0; - - if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) { - if (test_and_clear_bit(Wpending, &m->wsched)) - n = POLLOUT; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLOUT) { - P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m); - queue_work(p9_mux_wq, &m->wq); - } else - clear_bit(Wworksched, &m->wsched); - } else - clear_bit(Wworksched, &m->wsched); - - return; - -error: - p9_conn_cancel(m, err); - clear_bit(Wworksched, &m->wsched); -} - -static void process_request(struct p9_conn *m, struct p9_req *req) -{ - int ecode; - struct p9_str *ename; - - if (!req->err && req->rcall->id == P9_RERROR) { - ecode = req->rcall->params.rerror.errno; - ename = &req->rcall->params.rerror.error; - - P9_DPRINTK(P9_DEBUG_MUX, "Rerror %.*s\n", ename->len, - ename->str); - - if (*m->extended) - req->err = -ecode; - - if (!req->err) { - req->err = p9_errstr2errno(ename->str, ename->len); - - if (!req->err) { /* string match failed */ - PRINT_FCALL_ERROR("unknown error", req->rcall); - } - - if (!req->err) - req->err = -ESERVERFAULT; - } - } else if (req->tcall && req->rcall->id != req->tcall->id + 1) { - P9_DPRINTK(P9_DEBUG_ERROR, - "fcall mismatch: expected %d, got %d\n", - req->tcall->id + 1, req->rcall->id); - if (!req->err) - req->err = -EIO; - } -} - -/** - * p9_read_work - called when there is some data to be read from a transport - */ -static void p9_read_work(struct work_struct *work) -{ - int n, err; - struct p9_conn *m; - struct p9_req *req, *rptr, *rreq; - struct p9_fcall *rcall; - char *rbuf; - - m = container_of(work, struct p9_conn, rq); - - if (m->err < 0) - return; - - rcall = NULL; - P9_DPRINTK(P9_DEBUG_MUX, "start mux %p pos %d\n", m, m->rpos); - - if (!m->rcall) { - m->rcall = - kmalloc(sizeof(struct p9_fcall) + m->msize, GFP_KERNEL); - if (!m->rcall) { - err = -ENOMEM; - goto error; - } - - m->rbuf = (char *)m->rcall + sizeof(struct p9_fcall); - m->rpos = 0; - } - - clear_bit(Rpending, &m->wsched); - err = m->trans->read(m->trans, m->rbuf + m->rpos, m->msize - m->rpos); - P9_DPRINTK(P9_DEBUG_MUX, "mux %p got %d bytes\n", m, err); - if (err == -EAGAIN) { - clear_bit(Rworksched, &m->wsched); - return; - } - - if (err <= 0) - goto error; - - m->rpos += err; - while (m->rpos > 4) { - n = le32_to_cpu(*(__le32 *) m->rbuf); - if (n >= m->msize) { - P9_DPRINTK(P9_DEBUG_ERROR, - "requested packet size too big: %d\n", n); - err = -EIO; - goto error; - } - - if (m->rpos < n) - break; - - err = - p9_deserialize_fcall(m->rbuf, n, m->rcall, *m->extended); - if (err < 0) { - goto error; - } - -#ifdef CONFIG_NET_9P_DEBUG - if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { - char buf[150]; - - p9_printfcall(buf, sizeof(buf), m->rcall, - *m->extended); - printk(KERN_NOTICE ">>> %p %s\n", m, buf); - } -#endif - - rcall = m->rcall; - rbuf = m->rbuf; - if (m->rpos > n) { - m->rcall = kmalloc(sizeof(struct p9_fcall) + m->msize, - GFP_KERNEL); - if (!m->rcall) { - err = -ENOMEM; - goto error; - } - - m->rbuf = (char *)m->rcall + sizeof(struct p9_fcall); - memmove(m->rbuf, rbuf + n, m->rpos - n); - m->rpos -= n; - } else { - m->rcall = NULL; - m->rbuf = NULL; - m->rpos = 0; - } - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p fcall id %d tag %d\n", m, - rcall->id, rcall->tag); - - req = NULL; - spin_lock(&m->lock); - list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) { - if (rreq->tag == rcall->tag) { - req = rreq; - if (req->flush != Flushing) - list_del(&req->req_list); - break; - } - } - spin_unlock(&m->lock); - - if (req) { - req->rcall = rcall; - process_request(m, req); - - if (req->flush != Flushing) { - if (req->cb) - (*req->cb) (req, req->cba); - else - kfree(req->rcall); - - wake_up(&m->equeue); - } - } else { - if (err >= 0 && rcall->id != P9_RFLUSH) - P9_DPRINTK(P9_DEBUG_ERROR, - "unexpected response mux %p id %d tag %d\n", - m, rcall->id, rcall->tag); - kfree(rcall); - } - } - - if (!list_empty(&m->req_list)) { - if (test_and_clear_bit(Rpending, &m->wsched)) - n = POLLIN; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLIN) { - P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m); - queue_work(p9_mux_wq, &m->rq); - } else - clear_bit(Rworksched, &m->wsched); - } else - clear_bit(Rworksched, &m->wsched); - - return; - -error: - p9_conn_cancel(m, err); - clear_bit(Rworksched, &m->wsched); -} - -/** - * p9_send_request - send 9P request - * The function can sleep until the request is scheduled for sending. - * The function can be interrupted. Return from the function is not - * a guarantee that the request is sent successfully. Can return errors - * that can be retrieved by PTR_ERR macros. - * - * @m: mux data - * @tc: request to be sent - * @cb: callback function to call when response is received - * @cba: parameter to pass to the callback function - */ -static struct p9_req *p9_send_request(struct p9_conn *m, - struct p9_fcall *tc, - p9_conn_req_callback cb, void *cba) -{ - int n; - struct p9_req *req; - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p task %p tcall %p id %d\n", m, current, - tc, tc->id); - if (m->err < 0) - return ERR_PTR(m->err); - - req = kmalloc(sizeof(struct p9_req), GFP_KERNEL); - if (!req) - return ERR_PTR(-ENOMEM); - - if (tc->id == P9_TVERSION) - n = P9_NOTAG; - else - n = p9_mux_get_tag(m); - - if (n < 0) - return ERR_PTR(-ENOMEM); - - p9_set_tag(tc, n); - -#ifdef CONFIG_NET_9P_DEBUG - if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { - char buf[150]; - - p9_printfcall(buf, sizeof(buf), tc, *m->extended); - printk(KERN_NOTICE "<<< %p %s\n", m, buf); - } -#endif - - spin_lock_init(&req->lock); - req->tag = n; - req->tcall = tc; - req->rcall = NULL; - req->err = 0; - req->cb = cb; - req->cba = cba; - req->flush = None; - - spin_lock(&m->lock); - list_add_tail(&req->req_list, &m->unsent_req_list); - spin_unlock(&m->lock); - - if (test_and_clear_bit(Wpending, &m->wsched)) - n = POLLOUT; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched)) - queue_work(p9_mux_wq, &m->wq); - - return req; -} - -static void p9_mux_free_request(struct p9_conn *m, struct p9_req *req) -{ - p9_mux_put_tag(m, req->tag); - kfree(req); -} - -static void p9_mux_flush_cb(struct p9_req *freq, void *a) -{ - p9_conn_req_callback cb; - int tag; - struct p9_conn *m; - struct p9_req *req, *rreq, *rptr; - - m = a; - P9_DPRINTK(P9_DEBUG_MUX, "mux %p tc %p rc %p err %d oldtag %d\n", m, - freq->tcall, freq->rcall, freq->err, - freq->tcall->params.tflush.oldtag); - - spin_lock(&m->lock); - cb = NULL; - tag = freq->tcall->params.tflush.oldtag; - req = NULL; - list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) { - if (rreq->tag == tag) { - req = rreq; - list_del(&req->req_list); - break; - } - } - spin_unlock(&m->lock); - - if (req) { - spin_lock(&req->lock); - req->flush = Flushed; - spin_unlock(&req->lock); - - if (req->cb) - (*req->cb) (req, req->cba); - else - kfree(req->rcall); - - wake_up(&m->equeue); - } - - kfree(freq->tcall); - kfree(freq->rcall); - p9_mux_free_request(m, freq); -} - -static int -p9_mux_flush_request(struct p9_conn *m, struct p9_req *req) -{ - struct p9_fcall *fc; - struct p9_req *rreq, *rptr; - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p req %p tag %d\n", m, req, req->tag); - - /* if a response was received for a request, do nothing */ - spin_lock(&req->lock); - if (req->rcall || req->err) { - spin_unlock(&req->lock); - P9_DPRINTK(P9_DEBUG_MUX, - "mux %p req %p response already received\n", m, req); - return 0; - } - - req->flush = Flushing; - spin_unlock(&req->lock); - - spin_lock(&m->lock); - /* if the request is not sent yet, just remove it from the list */ - list_for_each_entry_safe(rreq, rptr, &m->unsent_req_list, req_list) { - if (rreq->tag == req->tag) { - P9_DPRINTK(P9_DEBUG_MUX, - "mux %p req %p request is not sent yet\n", m, req); - list_del(&rreq->req_list); - req->flush = Flushed; - spin_unlock(&m->lock); - if (req->cb) - (*req->cb) (req, req->cba); - return 0; - } - } - spin_unlock(&m->lock); - - clear_thread_flag(TIF_SIGPENDING); - fc = p9_create_tflush(req->tag); - p9_send_request(m, fc, p9_mux_flush_cb, m); - return 1; -} - -static void -p9_conn_rpc_cb(struct p9_req *req, void *a) -{ - struct p9_mux_rpc *r; - - P9_DPRINTK(P9_DEBUG_MUX, "req %p r %p\n", req, a); - r = a; - r->rcall = req->rcall; - r->err = req->err; - - if (req->flush != None && !req->err) - r->err = -ERESTARTSYS; - - wake_up(&r->wqueue); -} - -/** - * p9_mux_rpc - sends 9P request and waits until a response is available. - * The function can be interrupted. - * @m: mux data - * @tc: request to be sent - * @rc: pointer where a pointer to the response is stored - */ -int -p9_conn_rpc(struct p9_conn *m, struct p9_fcall *tc, - struct p9_fcall **rc) -{ - int err, sigpending; - unsigned long flags; - struct p9_req *req; - struct p9_mux_rpc r; - - r.err = 0; - r.tcall = tc; - r.rcall = NULL; - r.m = m; - init_waitqueue_head(&r.wqueue); - - if (rc) - *rc = NULL; - - sigpending = 0; - if (signal_pending(current)) { - sigpending = 1; - clear_thread_flag(TIF_SIGPENDING); - } - - req = p9_send_request(m, tc, p9_conn_rpc_cb, &r); - if (IS_ERR(req)) { - err = PTR_ERR(req); - P9_DPRINTK(P9_DEBUG_MUX, "error %d\n", err); - return err; - } - - err = wait_event_interruptible(r.wqueue, r.rcall != NULL || r.err < 0); - if (r.err < 0) - err = r.err; - - if (err == -ERESTARTSYS && m->trans->status == Connected - && m->err == 0) { - if (p9_mux_flush_request(m, req)) { - /* wait until we get response of the flush message */ - do { - clear_thread_flag(TIF_SIGPENDING); - err = wait_event_interruptible(r.wqueue, - r.rcall || r.err); - } while (!r.rcall && !r.err && err == -ERESTARTSYS && - m->trans->status == Connected && !m->err); - - err = -ERESTARTSYS; - } - sigpending = 1; - } - - if (sigpending) { - spin_lock_irqsave(¤t->sighand->siglock, flags); - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - } - - if (rc) - *rc = r.rcall; - else - kfree(r.rcall); - - p9_mux_free_request(m, req); - if (err > 0) - err = -EIO; - - return err; -} -EXPORT_SYMBOL(p9_conn_rpc); - -#ifdef P9_NONBLOCK -/** - * p9_conn_rpcnb - sends 9P request without waiting for response. - * @m: mux data - * @tc: request to be sent - * @cb: callback function to be called when response arrives - * @cba: value to pass to the callback function - */ -int p9_conn_rpcnb(struct p9_conn *m, struct p9_fcall *tc, - p9_conn_req_callback cb, void *a) -{ - int err; - struct p9_req *req; - - req = p9_send_request(m, tc, cb, a); - if (IS_ERR(req)) { - err = PTR_ERR(req); - P9_DPRINTK(P9_DEBUG_MUX, "error %d\n", err); - return PTR_ERR(req); - } - - P9_DPRINTK(P9_DEBUG_MUX, "mux %p tc %p tag %d\n", m, tc, req->tag); - return 0; -} -EXPORT_SYMBOL(p9_conn_rpcnb); -#endif /* P9_NONBLOCK */ - -/** - * p9_conn_cancel - cancel all pending requests with error - * @m: mux data - * @err: error code - */ -void p9_conn_cancel(struct p9_conn *m, int err) -{ - struct p9_req *req, *rtmp; - LIST_HEAD(cancel_list); - - P9_DPRINTK(P9_DEBUG_ERROR, "mux %p err %d\n", m, err); - m->err = err; - spin_lock(&m->lock); - list_for_each_entry_safe(req, rtmp, &m->req_list, req_list) { - list_move(&req->req_list, &cancel_list); - } - list_for_each_entry_safe(req, rtmp, &m->unsent_req_list, req_list) { - list_move(&req->req_list, &cancel_list); - } - spin_unlock(&m->lock); - - list_for_each_entry_safe(req, rtmp, &cancel_list, req_list) { - list_del(&req->req_list); - if (!req->err) - req->err = err; - - if (req->cb) - (*req->cb) (req, req->cba); - else - kfree(req->rcall); - } - - wake_up(&m->equeue); -} -EXPORT_SYMBOL(p9_conn_cancel); - -static u16 p9_mux_get_tag(struct p9_conn *m) -{ - int tag; - - tag = p9_idpool_get(m->tagpool); - if (tag < 0) - return P9_NOTAG; - else - return (u16) tag; -} - -static void p9_mux_put_tag(struct p9_conn *m, u16 tag) -{ - if (tag != P9_NOTAG && p9_idpool_check(tag, m->tagpool)) - p9_idpool_put(tag, m->tagpool); -} diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index 62332ed9da4a..1aa9d5175398 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -5,7 +5,7 @@ * * Copyright (C) 2006 by Russ Cox <rsc@swtch.com> * Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004-2007 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> * Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com> * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,7 @@ #include <linux/module.h> #include <linux/net.h> #include <linux/ipv6.h> +#include <linux/kthread.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/un.h> @@ -42,7 +43,9 @@ #define P9_PORT 564 #define MAX_SOCK_BUF (64*1024) - +#define ERREQFLUSH 1 +#define SCHED_TIMEOUT 10 +#define MAXPOLLWADDR 2 struct p9_fd_opts { int rfd; @@ -53,6 +56,7 @@ struct p9_fd_opts { struct p9_trans_fd { struct file *rd; struct file *wr; + struct p9_conn *conn; }; /* @@ -72,6 +76,1028 @@ static match_table_t tokens = { {Opt_err, NULL}, }; +enum { + Rworksched = 1, /* read work scheduled or running */ + Rpending = 2, /* can read */ + Wworksched = 4, /* write work scheduled or running */ + Wpending = 8, /* can write */ +}; + +enum { + None, + Flushing, + Flushed, +}; + +struct p9_req; + +typedef void (*p9_conn_req_callback)(struct p9_req *req, void *a); +struct p9_req { + spinlock_t lock; /* protect request structure */ + int tag; + struct p9_fcall *tcall; + struct p9_fcall *rcall; + int err; + p9_conn_req_callback cb; + void *cba; + int flush; + struct list_head req_list; +}; + +struct p9_mux_poll_task; + +struct p9_conn { + spinlock_t lock; /* protect lock structure */ + struct list_head mux_list; + struct p9_mux_poll_task *poll_task; + int msize; + unsigned char extended; + struct p9_trans *trans; + struct p9_idpool *tagpool; + int err; + wait_queue_head_t equeue; + struct list_head req_list; + struct list_head unsent_req_list; + struct p9_fcall *rcall; + int rpos; + char *rbuf; + int wpos; + int wsize; + char *wbuf; + wait_queue_t poll_wait[MAXPOLLWADDR]; + wait_queue_head_t *poll_waddr[MAXPOLLWADDR]; + poll_table pt; + struct work_struct rq; + struct work_struct wq; + unsigned long wsched; +}; + +struct p9_mux_poll_task { + struct task_struct *task; + struct list_head mux_list; + int muxnum; +}; + +struct p9_mux_rpc { + struct p9_conn *m; + int err; + struct p9_fcall *tcall; + struct p9_fcall *rcall; + wait_queue_head_t wqueue; +}; + +static int p9_poll_proc(void *); +static void p9_read_work(struct work_struct *work); +static void p9_write_work(struct work_struct *work); +static void p9_pollwait(struct file *filp, wait_queue_head_t *wait_address, + poll_table *p); +static int p9_fd_write(struct p9_trans *trans, void *v, int len); +static int p9_fd_read(struct p9_trans *trans, void *v, int len); + +static DEFINE_MUTEX(p9_mux_task_lock); +static struct workqueue_struct *p9_mux_wq; + +static int p9_mux_num; +static int p9_mux_poll_task_num; +static struct p9_mux_poll_task p9_mux_poll_tasks[100]; + +static void p9_conn_destroy(struct p9_conn *); +static unsigned int p9_fd_poll(struct p9_trans *trans, + struct poll_table_struct *pt); + +#ifdef P9_NONBLOCK +static int p9_conn_rpcnb(struct p9_conn *m, struct p9_fcall *tc, + p9_conn_req_callback cb, void *a); +#endif /* P9_NONBLOCK */ + +static void p9_conn_cancel(struct p9_conn *m, int err); + +static int p9_mux_global_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) + p9_mux_poll_tasks[i].task = NULL; + + p9_mux_wq = create_workqueue("v9fs"); + if (!p9_mux_wq) { + printk(KERN_WARNING "v9fs: mux: creating workqueue failed\n"); + return -ENOMEM; + } + + return 0; +} + +static u16 p9_mux_get_tag(struct p9_conn *m) +{ + int tag; + + tag = p9_idpool_get(m->tagpool); + if (tag < 0) + return P9_NOTAG; + else + return (u16) tag; +} + +static void p9_mux_put_tag(struct p9_conn *m, u16 tag) +{ + if (tag != P9_NOTAG && p9_idpool_check(tag, m->tagpool)) + p9_idpool_put(tag, m->tagpool); +} + +/** + * p9_mux_calc_poll_procs - calculates the number of polling procs + * based on the number of mounted v9fs filesystems. + * + * The current implementation returns sqrt of the number of mounts. + */ +static int p9_mux_calc_poll_procs(int muxnum) +{ + int n; + + if (p9_mux_poll_task_num) + n = muxnum / p9_mux_poll_task_num + + (muxnum % p9_mux_poll_task_num ? 1 : 0); + else + n = 1; + + if (n > ARRAY_SIZE(p9_mux_poll_tasks)) + n = ARRAY_SIZE(p9_mux_poll_tasks); + + return n; +} + +static int p9_mux_poll_start(struct p9_conn *m) +{ + int i, n; + struct p9_mux_poll_task *vpt, *vptlast; + struct task_struct *pproc; + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p muxnum %d procnum %d\n", m, p9_mux_num, + p9_mux_poll_task_num); + mutex_lock(&p9_mux_task_lock); + + n = p9_mux_calc_poll_procs(p9_mux_num + 1); + if (n > p9_mux_poll_task_num) { + for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) { + if (p9_mux_poll_tasks[i].task == NULL) { + vpt = &p9_mux_poll_tasks[i]; + P9_DPRINTK(P9_DEBUG_MUX, "create proc %p\n", + vpt); + pproc = kthread_create(p9_poll_proc, vpt, + "v9fs-poll"); + + if (!IS_ERR(pproc)) { + vpt->task = pproc; + INIT_LIST_HEAD(&vpt->mux_list); + vpt->muxnum = 0; + p9_mux_poll_task_num++; + wake_up_process(vpt->task); + } + break; + } + } + + if (i >= ARRAY_SIZE(p9_mux_poll_tasks)) + P9_DPRINTK(P9_DEBUG_ERROR, + "warning: no free poll slots\n"); + } + + n = (p9_mux_num + 1) / p9_mux_poll_task_num + + ((p9_mux_num + 1) % p9_mux_poll_task_num ? 1 : 0); + + vptlast = NULL; + for (i = 0; i < ARRAY_SIZE(p9_mux_poll_tasks); i++) { + vpt = &p9_mux_poll_tasks[i]; + if (vpt->task != NULL) { + vptlast = vpt; + if (vpt->muxnum < n) { + P9_DPRINTK(P9_DEBUG_MUX, "put in proc %d\n", i); + list_add(&m->mux_list, &vpt->mux_list); + vpt->muxnum++; + m->poll_task = vpt; + memset(&m->poll_waddr, 0, + sizeof(m->poll_waddr)); + init_poll_funcptr(&m->pt, p9_pollwait); + break; + } + } + } + + if (i >= ARRAY_SIZE(p9_mux_poll_tasks)) { + if (vptlast == NULL) { + mutex_unlock(&p9_mux_task_lock); + return -ENOMEM; + } + + P9_DPRINTK(P9_DEBUG_MUX, "put in proc %d\n", i); + list_add(&m->mux_list, &vptlast->mux_list); + vptlast->muxnum++; + m->poll_task = vptlast; + memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); + init_poll_funcptr(&m->pt, p9_pollwait); + } + + p9_mux_num++; + mutex_unlock(&p9_mux_task_lock); + + return 0; +} + +static void p9_mux_poll_stop(struct p9_conn *m) +{ + int i; + struct p9_mux_poll_task *vpt; + + mutex_lock(&p9_mux_task_lock); + vpt = m->poll_task; + list_del(&m->mux_list); + for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { + if (m->poll_waddr[i] != NULL) { + remove_wait_queue(m->poll_waddr[i], &m->poll_wait[i]); + m->poll_waddr[i] = NULL; + } + } + vpt->muxnum--; + if (!vpt->muxnum) { + P9_DPRINTK(P9_DEBUG_MUX, "destroy proc %p\n", vpt); + kthread_stop(vpt->task); + vpt->task = NULL; + p9_mux_poll_task_num--; + } + p9_mux_num--; + mutex_unlock(&p9_mux_task_lock); +} + +/** + * p9_conn_create - allocate and initialize the per-session mux data + * Creates the polling task if this is the first session. + * + * @trans - transport structure + * @msize - maximum message size + * @extended - extended flag + */ +static struct p9_conn *p9_conn_create(struct p9_trans *trans) +{ + int i, n; + struct p9_conn *m, *mtmp; + + P9_DPRINTK(P9_DEBUG_MUX, "transport %p msize %d\n", trans, + trans->msize); + m = kmalloc(sizeof(struct p9_conn), GFP_KERNEL); + if (!m) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&m->lock); + INIT_LIST_HEAD(&m->mux_list); + m->msize = trans->msize; + m->extended = trans->extended; + m->trans = trans; + m->tagpool = p9_idpool_create(); + if (IS_ERR(m->tagpool)) { + mtmp = ERR_PTR(-ENOMEM); + kfree(m); + return mtmp; + } + + m->err = 0; + init_waitqueue_head(&m->equeue); + INIT_LIST_HEAD(&m->req_list); + INIT_LIST_HEAD(&m->unsent_req_list); + m->rcall = NULL; + m->rpos = 0; + m->rbuf = NULL; + m->wpos = m->wsize = 0; + m->wbuf = NULL; + INIT_WORK(&m->rq, p9_read_work); + INIT_WORK(&m->wq, p9_write_work); + m->wsched = 0; + memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); + m->poll_task = NULL; + n = p9_mux_poll_start(m); + if (n) { + kfree(m); + return ERR_PTR(n); + } + + n = p9_fd_poll(trans, &m->pt); + if (n & POLLIN) { + P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m); + set_bit(Rpending, &m->wsched); + } + + if (n & POLLOUT) { + P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m); + set_bit(Wpending, &m->wsched); + } + + for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { + if (IS_ERR(m->poll_waddr[i])) { + p9_mux_poll_stop(m); + mtmp = (void *)m->poll_waddr; /* the error code */ + kfree(m); + m = mtmp; + break; + } + } + + return m; +} + +/** + * p9_mux_destroy - cancels all pending requests and frees mux resources + */ +static void p9_conn_destroy(struct p9_conn *m) +{ + P9_DPRINTK(P9_DEBUG_MUX, "mux %p prev %p next %p\n", m, + m->mux_list.prev, m->mux_list.next); + p9_conn_cancel(m, -ECONNRESET); + + if (!list_empty(&m->req_list)) { + /* wait until all processes waiting on this session exit */ + P9_DPRINTK(P9_DEBUG_MUX, + "mux %p waiting for empty request queue\n", m); + wait_event_timeout(m->equeue, (list_empty(&m->req_list)), 5000); + P9_DPRINTK(P9_DEBUG_MUX, "mux %p request queue empty: %d\n", m, + list_empty(&m->req_list)); + } + + p9_mux_poll_stop(m); + m->trans = NULL; + p9_idpool_destroy(m->tagpool); + kfree(m); +} + +/** + * p9_pollwait - called by files poll operation to add v9fs-poll task + * to files wait queue + */ +static void +p9_pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p) +{ + int i; + struct p9_conn *m; + + m = container_of(p, struct p9_conn, pt); + for (i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) + if (m->poll_waddr[i] == NULL) + break; + + if (i >= ARRAY_SIZE(m->poll_waddr)) { + P9_DPRINTK(P9_DEBUG_ERROR, "not enough wait_address slots\n"); + return; + } + + m->poll_waddr[i] = wait_address; + + if (!wait_address) { + P9_DPRINTK(P9_DEBUG_ERROR, "no wait_address\n"); + m->poll_waddr[i] = ERR_PTR(-EIO); + return; + } + + init_waitqueue_entry(&m->poll_wait[i], m->poll_task->task); + add_wait_queue(wait_address, &m->poll_wait[i]); +} + +/** + * p9_poll_mux - polls a mux and schedules read or write works if necessary + */ +static void p9_poll_mux(struct p9_conn *m) +{ + int n; + + if (m->err < 0) + return; + + n = p9_fd_poll(m->trans, NULL); + if (n < 0 || n & (POLLERR | POLLHUP | POLLNVAL)) { + P9_DPRINTK(P9_DEBUG_MUX, "error mux %p err %d\n", m, n); + if (n >= 0) + n = -ECONNRESET; + p9_conn_cancel(m, n); + } + + if (n & POLLIN) { + set_bit(Rpending, &m->wsched); + P9_DPRINTK(P9_DEBUG_MUX, "mux %p can read\n", m); + if (!test_and_set_bit(Rworksched, &m->wsched)) { + P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m); + queue_work(p9_mux_wq, &m->rq); + } + } + + if (n & POLLOUT) { + set_bit(Wpending, &m->wsched); + P9_DPRINTK(P9_DEBUG_MUX, "mux %p can write\n", m); + if ((m->wsize || !list_empty(&m->unsent_req_list)) + && !test_and_set_bit(Wworksched, &m->wsched)) { + P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m); + queue_work(p9_mux_wq, &m->wq); + } + } +} + +/** + * p9_poll_proc - polls all v9fs transports for new events and queues + * the appropriate work to the work queue + */ +static int p9_poll_proc(void *a) +{ + struct p9_conn *m, *mtmp; + struct p9_mux_poll_task *vpt; + + vpt = a; + P9_DPRINTK(P9_DEBUG_MUX, "start %p %p\n", current, vpt); + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + + list_for_each_entry_safe(m, mtmp, &vpt->mux_list, mux_list) { + p9_poll_mux(m); + } + + P9_DPRINTK(P9_DEBUG_MUX, "sleeping...\n"); + schedule_timeout(SCHED_TIMEOUT * HZ); + } + + __set_current_state(TASK_RUNNING); + P9_DPRINTK(P9_DEBUG_MUX, "finish\n"); + return 0; +} + +/** + * p9_write_work - called when a transport can send some data + */ +static void p9_write_work(struct work_struct *work) +{ + int n, err; + struct p9_conn *m; + struct p9_req *req; + + m = container_of(work, struct p9_conn, wq); + + if (m->err < 0) { + clear_bit(Wworksched, &m->wsched); + return; + } + + if (!m->wsize) { + if (list_empty(&m->unsent_req_list)) { + clear_bit(Wworksched, &m->wsched); + return; + } + + spin_lock(&m->lock); +again: + req = list_entry(m->unsent_req_list.next, struct p9_req, + req_list); + list_move_tail(&req->req_list, &m->req_list); + if (req->err == ERREQFLUSH) + goto again; + + m->wbuf = req->tcall->sdata; + m->wsize = req->tcall->size; + m->wpos = 0; + spin_unlock(&m->lock); + } + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p pos %d size %d\n", m, m->wpos, + m->wsize); + clear_bit(Wpending, &m->wsched); + err = p9_fd_write(m->trans, m->wbuf + m->wpos, m->wsize - m->wpos); + P9_DPRINTK(P9_DEBUG_MUX, "mux %p sent %d bytes\n", m, err); + if (err == -EAGAIN) { + clear_bit(Wworksched, &m->wsched); + return; + } + + if (err < 0) + goto error; + else if (err == 0) { + err = -EREMOTEIO; + goto error; + } + + m->wpos += err; + if (m->wpos == m->wsize) + m->wpos = m->wsize = 0; + + if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) { + if (test_and_clear_bit(Wpending, &m->wsched)) + n = POLLOUT; + else + n = p9_fd_poll(m->trans, NULL); + + if (n & POLLOUT) { + P9_DPRINTK(P9_DEBUG_MUX, "schedule write work %p\n", m); + queue_work(p9_mux_wq, &m->wq); + } else + clear_bit(Wworksched, &m->wsched); + } else + clear_bit(Wworksched, &m->wsched); + + return; + +error: + p9_conn_cancel(m, err); + clear_bit(Wworksched, &m->wsched); +} + +static void process_request(struct p9_conn *m, struct p9_req *req) +{ + int ecode; + struct p9_str *ename; + + if (!req->err && req->rcall->id == P9_RERROR) { + ecode = req->rcall->params.rerror.errno; + ename = &req->rcall->params.rerror.error; + + P9_DPRINTK(P9_DEBUG_MUX, "Rerror %.*s\n", ename->len, + ename->str); + + if (m->extended) + req->err = -ecode; + + if (!req->err) { + req->err = p9_errstr2errno(ename->str, ename->len); + + /* string match failed */ + if (!req->err) { + PRINT_FCALL_ERROR("unknown error", req->rcall); + req->err = -ESERVERFAULT; + } + } + } else if (req->tcall && req->rcall->id != req->tcall->id + 1) { + P9_DPRINTK(P9_DEBUG_ERROR, + "fcall mismatch: expected %d, got %d\n", + req->tcall->id + 1, req->rcall->id); + if (!req->err) + req->err = -EIO; + } +} + +/** + * p9_read_work - called when there is some data to be read from a transport + */ +static void p9_read_work(struct work_struct *work) +{ + int n, err; + struct p9_conn *m; + struct p9_req *req, *rptr, *rreq; + struct p9_fcall *rcall; + char *rbuf; + + m = container_of(work, struct p9_conn, rq); + + if (m->err < 0) + return; + + rcall = NULL; + P9_DPRINTK(P9_DEBUG_MUX, "start mux %p pos %d\n", m, m->rpos); + + if (!m->rcall) { + m->rcall = + kmalloc(sizeof(struct p9_fcall) + m->msize, GFP_KERNEL); + if (!m->rcall) { + err = -ENOMEM; + goto error; + } + + m->rbuf = (char *)m->rcall + sizeof(struct p9_fcall); + m->rpos = 0; + } + + clear_bit(Rpending, &m->wsched); + err = p9_fd_read(m->trans, m->rbuf + m->rpos, m->msize - m->rpos); + P9_DPRINTK(P9_DEBUG_MUX, "mux %p got %d bytes\n", m, err); + if (err == -EAGAIN) { + clear_bit(Rworksched, &m->wsched); + return; + } + + if (err <= 0) + goto error; + + m->rpos += err; + while (m->rpos > 4) { + n = le32_to_cpu(*(__le32 *) m->rbuf); + if (n >= m->msize) { + P9_DPRINTK(P9_DEBUG_ERROR, + "requested packet size too big: %d\n", n); + err = -EIO; + goto error; + } + + if (m->rpos < n) + break; + + err = + p9_deserialize_fcall(m->rbuf, n, m->rcall, m->extended); + if (err < 0) + goto error; + +#ifdef CONFIG_NET_9P_DEBUG + if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { + char buf[150]; + + p9_printfcall(buf, sizeof(buf), m->rcall, + m->extended); + printk(KERN_NOTICE ">>> %p %s\n", m, buf); + } +#endif + + rcall = m->rcall; + rbuf = m->rbuf; + if (m->rpos > n) { + m->rcall = kmalloc(sizeof(struct p9_fcall) + m->msize, + GFP_KERNEL); + if (!m->rcall) { + err = -ENOMEM; + goto error; + } + + m->rbuf = (char *)m->rcall + sizeof(struct p9_fcall); + memmove(m->rbuf, rbuf + n, m->rpos - n); + m->rpos -= n; + } else { + m->rcall = NULL; + m->rbuf = NULL; + m->rpos = 0; + } + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p fcall id %d tag %d\n", m, + rcall->id, rcall->tag); + + req = NULL; + spin_lock(&m->lock); + list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) { + if (rreq->tag == rcall->tag) { + req = rreq; + if (req->flush != Flushing) + list_del(&req->req_list); + break; + } + } + spin_unlock(&m->lock); + + if (req) { + req->rcall = rcall; + process_request(m, req); + + if (req->flush != Flushing) { + if (req->cb) + (*req->cb) (req, req->cba); + else + kfree(req->rcall); + + wake_up(&m->equeue); + } + } else { + if (err >= 0 && rcall->id != P9_RFLUSH) + P9_DPRINTK(P9_DEBUG_ERROR, + "unexpected response mux %p id %d tag %d\n", + m, rcall->id, rcall->tag); + kfree(rcall); + } + } + + if (!list_empty(&m->req_list)) { + if (test_and_clear_bit(Rpending, &m->wsched)) + n = POLLIN; + else + n = p9_fd_poll(m->trans, NULL); + + if (n & POLLIN) { + P9_DPRINTK(P9_DEBUG_MUX, "schedule read work %p\n", m); + queue_work(p9_mux_wq, &m->rq); + } else + clear_bit(Rworksched, &m->wsched); + } else + clear_bit(Rworksched, &m->wsched); + + return; + +error: + p9_conn_cancel(m, err); + clear_bit(Rworksched, &m->wsched); +} + +/** + * p9_send_request - send 9P request + * The function can sleep until the request is scheduled for sending. + * The function can be interrupted. Return from the function is not + * a guarantee that the request is sent successfully. Can return errors + * that can be retrieved by PTR_ERR macros. + * + * @m: mux data + * @tc: request to be sent + * @cb: callback function to call when response is received + * @cba: parameter to pass to the callback function + */ +static struct p9_req *p9_send_request(struct p9_conn *m, + struct p9_fcall *tc, + p9_conn_req_callback cb, void *cba) +{ + int n; + struct p9_req *req; + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p task %p tcall %p id %d\n", m, current, + tc, tc->id); + if (m->err < 0) + return ERR_PTR(m->err); + + req = kmalloc(sizeof(struct p9_req), GFP_KERNEL); + if (!req) + return ERR_PTR(-ENOMEM); + + if (tc->id == P9_TVERSION) + n = P9_NOTAG; + else + n = p9_mux_get_tag(m); + + if (n < 0) + return ERR_PTR(-ENOMEM); + + p9_set_tag(tc, n); + +#ifdef CONFIG_NET_9P_DEBUG + if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { + char buf[150]; + + p9_printfcall(buf, sizeof(buf), tc, m->extended); + printk(KERN_NOTICE "<<< %p %s\n", m, buf); + } +#endif + + spin_lock_init(&req->lock); + req->tag = n; + req->tcall = tc; + req->rcall = NULL; + req->err = 0; + req->cb = cb; + req->cba = cba; + req->flush = None; + + spin_lock(&m->lock); + list_add_tail(&req->req_list, &m->unsent_req_list); + spin_unlock(&m->lock); + + if (test_and_clear_bit(Wpending, &m->wsched)) + n = POLLOUT; + else + n = p9_fd_poll(m->trans, NULL); + + if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched)) + queue_work(p9_mux_wq, &m->wq); + + return req; +} + +static void p9_mux_free_request(struct p9_conn *m, struct p9_req *req) +{ + p9_mux_put_tag(m, req->tag); + kfree(req); +} + +static void p9_mux_flush_cb(struct p9_req *freq, void *a) +{ + p9_conn_req_callback cb; + int tag; + struct p9_conn *m; + struct p9_req *req, *rreq, *rptr; + + m = a; + P9_DPRINTK(P9_DEBUG_MUX, "mux %p tc %p rc %p err %d oldtag %d\n", m, + freq->tcall, freq->rcall, freq->err, + freq->tcall->params.tflush.oldtag); + + spin_lock(&m->lock); + cb = NULL; + tag = freq->tcall->params.tflush.oldtag; + req = NULL; + list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) { + if (rreq->tag == tag) { + req = rreq; + list_del(&req->req_list); + break; + } + } + spin_unlock(&m->lock); + + if (req) { + spin_lock(&req->lock); + req->flush = Flushed; + spin_unlock(&req->lock); + + if (req->cb) + (*req->cb) (req, req->cba); + else + kfree(req->rcall); + + wake_up(&m->equeue); + } + + kfree(freq->tcall); + kfree(freq->rcall); + p9_mux_free_request(m, freq); +} + +static int +p9_mux_flush_request(struct p9_conn *m, struct p9_req *req) +{ + struct p9_fcall *fc; + struct p9_req *rreq, *rptr; + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p req %p tag %d\n", m, req, req->tag); + + /* if a response was received for a request, do nothing */ + spin_lock(&req->lock); + if (req->rcall || req->err) { + spin_unlock(&req->lock); + P9_DPRINTK(P9_DEBUG_MUX, + "mux %p req %p response already received\n", m, req); + return 0; + } + + req->flush = Flushing; + spin_unlock(&req->lock); + + spin_lock(&m->lock); + /* if the request is not sent yet, just remove it from the list */ + list_for_each_entry_safe(rreq, rptr, &m->unsent_req_list, req_list) { + if (rreq->tag == req->tag) { + P9_DPRINTK(P9_DEBUG_MUX, + "mux %p req %p request is not sent yet\n", m, req); + list_del(&rreq->req_list); + req->flush = Flushed; + spin_unlock(&m->lock); + if (req->cb) + (*req->cb) (req, req->cba); + return 0; + } + } + spin_unlock(&m->lock); + + clear_thread_flag(TIF_SIGPENDING); + fc = p9_create_tflush(req->tag); + p9_send_request(m, fc, p9_mux_flush_cb, m); + return 1; +} + +static void +p9_conn_rpc_cb(struct p9_req *req, void *a) +{ + struct p9_mux_rpc *r; + + P9_DPRINTK(P9_DEBUG_MUX, "req %p r %p\n", req, a); + r = a; + r->rcall = req->rcall; + r->err = req->err; + + if (req->flush != None && !req->err) + r->err = -ERESTARTSYS; + + wake_up(&r->wqueue); +} + +/** + * p9_fd_rpc- sends 9P request and waits until a response is available. + * The function can be interrupted. + * @m: mux data + * @tc: request to be sent + * @rc: pointer where a pointer to the response is stored + */ +int +p9_fd_rpc(struct p9_trans *t, struct p9_fcall *tc, struct p9_fcall **rc) +{ + struct p9_trans_fd *p = t->priv; + struct p9_conn *m = p->conn; + int err, sigpending; + unsigned long flags; + struct p9_req *req; + struct p9_mux_rpc r; + + r.err = 0; + r.tcall = tc; + r.rcall = NULL; + r.m = m; + init_waitqueue_head(&r.wqueue); + + if (rc) + *rc = NULL; + + sigpending = 0; + if (signal_pending(current)) { + sigpending = 1; + clear_thread_flag(TIF_SIGPENDING); + } + + req = p9_send_request(m, tc, p9_conn_rpc_cb, &r); + if (IS_ERR(req)) { + err = PTR_ERR(req); + P9_DPRINTK(P9_DEBUG_MUX, "error %d\n", err); + return err; + } + + err = wait_event_interruptible(r.wqueue, r.rcall != NULL || r.err < 0); + if (r.err < 0) + err = r.err; + + if (err == -ERESTARTSYS && m->trans->status == Connected + && m->err == 0) { + if (p9_mux_flush_request(m, req)) { + /* wait until we get response of the flush message */ + do { + clear_thread_flag(TIF_SIGPENDING); + err = wait_event_interruptible(r.wqueue, + r.rcall || r.err); + } while (!r.rcall && !r.err && err == -ERESTARTSYS && + m->trans->status == Connected && !m->err); + + err = -ERESTARTSYS; + } + sigpending = 1; + } + + if (sigpending) { + spin_lock_irqsave(¤t->sighand->siglock, flags); + recalc_sigpending(); + spin_unlock_irqrestore(¤t->sighand->siglock, flags); + } + + if (rc) + *rc = r.rcall; + else + kfree(r.rcall); + + p9_mux_free_request(m, req); + if (err > 0) + err = -EIO; + + return err; +} + +#ifdef P9_NONBLOCK +/** + * p9_conn_rpcnb - sends 9P request without waiting for response. + * @m: mux data + * @tc: request to be sent + * @cb: callback function to be called when response arrives + * @cba: value to pass to the callback function + */ +int p9_conn_rpcnb(struct p9_conn *m, struct p9_fcall *tc, + p9_conn_req_callback cb, void *a) +{ + int err; + struct p9_req *req; + + req = p9_send_request(m, tc, cb, a); + if (IS_ERR(req)) { + err = PTR_ERR(req); + P9_DPRINTK(P9_DEBUG_MUX, "error %d\n", err); + return PTR_ERR(req); + } + + P9_DPRINTK(P9_DEBUG_MUX, "mux %p tc %p tag %d\n", m, tc, req->tag); + return 0; +} +#endif /* P9_NONBLOCK */ + +/** + * p9_conn_cancel - cancel all pending requests with error + * @m: mux data + * @err: error code + */ +void p9_conn_cancel(struct p9_conn *m, int err) +{ + struct p9_req *req, *rtmp; + LIST_HEAD(cancel_list); + + P9_DPRINTK(P9_DEBUG_ERROR, "mux %p err %d\n", m, err); + m->err = err; + spin_lock(&m->lock); + list_for_each_entry_safe(req, rtmp, &m->req_list, req_list) { + list_move(&req->req_list, &cancel_list); + } + list_for_each_entry_safe(req, rtmp, &m->unsent_req_list, req_list) { + list_move(&req->req_list, &cancel_list); + } + spin_unlock(&m->lock); + + list_for_each_entry_safe(req, rtmp, &cancel_list, req_list) { + list_del(&req->req_list); + if (!req->err) + req->err = err; + + if (req->cb) + (*req->cb) (req, req->cba); + else + kfree(req->rcall); + } + + wake_up(&m->equeue); +} + /** * v9fs_parse_options - parse mount options into session structure * @options: options string passed from mount @@ -268,7 +1294,7 @@ end: } /** - * p9_sock_close - shutdown socket + * p9_fd_close - shutdown socket * @trans: private socket structure * */ @@ -284,6 +1310,8 @@ static void p9_fd_close(struct p9_trans *trans) if (!ts) return; + p9_conn_destroy(ts->conn); + trans->status = Disconnected; if (ts->rd) fput(ts->rd); @@ -292,13 +1320,15 @@ static void p9_fd_close(struct p9_trans *trans) kfree(ts); } -static struct p9_trans *p9_trans_create_tcp(const char *addr, char *args) +static struct p9_trans * +p9_trans_create_tcp(const char *addr, char *args, int msize, unsigned char dotu) { int err; struct p9_trans *trans; struct socket *csocket; struct sockaddr_in sin_server; struct p9_fd_opts opts; + struct p9_trans_fd *p; parse_opts(args, &opts); @@ -306,11 +1336,10 @@ static struct p9_trans *p9_trans_create_tcp(const char *addr, char *args) trans = kmalloc(sizeof(struct p9_trans), GFP_KERNEL); if (!trans) return ERR_PTR(-ENOMEM); - - trans->write = p9_fd_write; - trans->read = p9_fd_read; + trans->msize = msize; + trans->extended = dotu; + trans->rpc = p9_fd_rpc; trans->close = p9_fd_close; - trans->poll = p9_fd_poll; sin_server.sin_family = AF_INET; sin_server.sin_addr.s_addr = in_aton(addr); @@ -337,6 +1366,14 @@ static struct p9_trans *p9_trans_create_tcp(const char *addr, char *args) if (err < 0) goto error; + p = (struct p9_trans_fd *) trans->priv; + p->conn = p9_conn_create(trans); + if (IS_ERR(p->conn)) { + err = PTR_ERR(p->conn); + p->conn = NULL; + goto error; + } + return trans; error: @@ -347,22 +1384,23 @@ error: return ERR_PTR(err); } -static struct p9_trans *p9_trans_create_unix(const char *addr, char *args) +static struct p9_trans * +p9_trans_create_unix(const char *addr, char *args, int msize, + unsigned char dotu) { int err; struct socket *csocket; struct sockaddr_un sun_server; struct p9_trans *trans; + struct p9_trans_fd *p; csocket = NULL; trans = kmalloc(sizeof(struct p9_trans), GFP_KERNEL); if (!trans) return ERR_PTR(-ENOMEM); - trans->write = p9_fd_write; - trans->read = p9_fd_read; + trans->rpc = p9_fd_rpc; trans->close = p9_fd_close; - trans->poll = p9_fd_poll; if (strlen(addr) > UNIX_PATH_MAX) { P9_EPRINTK(KERN_ERR, "p9_trans_unix: address too long: %s\n", @@ -387,6 +1425,16 @@ static struct p9_trans *p9_trans_create_unix(const char *addr, char *args) if (err < 0) goto error; + trans->msize = msize; + trans->extended = dotu; + p = (struct p9_trans_fd *) trans->priv; + p->conn = p9_conn_create(trans); + if (IS_ERR(p->conn)) { + err = PTR_ERR(p->conn); + p->conn = NULL; + goto error; + } + return trans; error: @@ -397,11 +1445,14 @@ error: return ERR_PTR(err); } -static struct p9_trans *p9_trans_create_fd(const char *name, char *args) +static struct p9_trans * +p9_trans_create_fd(const char *name, char *args, int msize, + unsigned char extended) { int err; struct p9_trans *trans; struct p9_fd_opts opts; + struct p9_trans_fd *p; parse_opts(args, &opts); @@ -414,15 +1465,23 @@ static struct p9_trans *p9_trans_create_fd(const char *name, char *args) if (!trans) return ERR_PTR(-ENOMEM); - trans->write = p9_fd_write; - trans->read = p9_fd_read; + trans->rpc = p9_fd_rpc; trans->close = p9_fd_close; - trans->poll = p9_fd_poll; err = p9_fd_open(trans, opts.rfd, opts.wfd); if (err < 0) goto error; + trans->msize = msize; + trans->extended = extended; + p = (struct p9_trans_fd *) trans->priv; + p->conn = p9_conn_create(trans); + if (IS_ERR(p->conn)) { + err = PTR_ERR(p->conn); + p->conn = NULL; + goto error; + } + return trans; error: @@ -453,6 +1512,12 @@ static struct p9_trans_module p9_fd_trans = { static int __init p9_trans_fd_init(void) { + int ret = p9_mux_global_init(); + if (ret) { + printk(KERN_WARNING "9p: starting mux failed\n"); + return ret; + } + v9fs_register_trans(&p9_tcp_trans); v9fs_register_trans(&p9_unix_trans); v9fs_register_trans(&p9_fd_trans); @@ -460,13 +1525,7 @@ static int __init p9_trans_fd_init(void) return 1; } -static void __exit p9_trans_fd_exit(void) { - printk(KERN_ERR "Removal of 9p transports not implemented\n"); - BUG(); -} - module_init(p9_trans_fd_init); -module_exit(p9_trans_fd_exit); MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>"); MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); diff --git a/net/9p/trans_virtio.c b/net/9p/trans_virtio.c index 42eea5fe2628..0117b9fb8480 100644 --- a/net/9p/trans_virtio.c +++ b/net/9p/trans_virtio.c @@ -1,17 +1,8 @@ /* * The Guest 9p transport driver * - * This is a trivial pipe-based transport driver based on the lguest console - * code: we use lguest's DMA mechanism to send bytes out, and register a - * DMA buffer to receive bytes in. It is assumed to be present and available - * from the very beginning of boot. - * - * This may be have been done by just instaniating another HVC console, - * but HVC's blocksize of 16 bytes is annoying and painful to performance. - * - * A more efficient transport could be built based on the virtio block driver - * but it requires some changes in the 9p transport model (which are in - * progress) + * This is a block based transport driver based on the lguest block driver + * code. * */ /* @@ -55,11 +46,25 @@ #include <linux/virtio.h> #include <linux/virtio_9p.h> +#define VIRTQUEUE_NUM 128 + /* a single mutex to manage channel initialization and attachment */ static DECLARE_MUTEX(virtio_9p_lock); /* global which tracks highest initialized channel */ static int chan_index; +#define P9_INIT_MAXTAG 16 + +#define REQ_STATUS_IDLE 0 +#define REQ_STATUS_SENT 1 +#define REQ_STATUS_RCVD 2 +#define REQ_STATUS_FLSH 3 + +struct p9_req_t { + int status; + wait_queue_head_t *wq; +}; + /* We keep all per-channel information in a structure. * This structure is allocated within the devices dev->mem space. * A pointer to the structure will get put in the transport private. @@ -68,146 +73,198 @@ static struct virtio_chan { bool initialized; /* channel is initialized */ bool inuse; /* channel is in use */ - struct virtqueue *in_vq, *out_vq; + spinlock_t lock; + struct virtio_device *vdev; + struct virtqueue *vq; - /* This is our input buffer, and how much data is left in it. */ - unsigned int in_len; - char *in, *inbuf; + struct p9_idpool *tagpool; + struct p9_req_t *reqs; + int max_tag; - wait_queue_head_t wq; /* waitq for buffer */ + /* Scatterlist: can be too big for stack. */ + struct scatterlist sg[VIRTQUEUE_NUM]; } channels[MAX_9P_CHAN]; +/* Lookup requests by tag */ +static struct p9_req_t *p9_lookup_tag(struct virtio_chan *c, u16 tag) +{ + /* This looks up the original request by tag so we know which + * buffer to read the data into */ + tag++; + + while (tag >= c->max_tag) { + int old_max = c->max_tag; + int count; + + if (c->max_tag) + c->max_tag *= 2; + else + c->max_tag = P9_INIT_MAXTAG; + + c->reqs = krealloc(c->reqs, sizeof(struct p9_req_t)*c->max_tag, + GFP_ATOMIC); + if (!c->reqs) { + printk(KERN_ERR "Couldn't grow tag array\n"); + BUG(); + } + for (count = old_max; count < c->max_tag; count++) { + c->reqs[count].status = REQ_STATUS_IDLE; + c->reqs[count].wq = kmalloc(sizeof(wait_queue_t), + GFP_ATOMIC); + if (!c->reqs[count].wq) { + printk(KERN_ERR "Couldn't grow tag array\n"); + BUG(); + } + init_waitqueue_head(c->reqs[count].wq); + } + } + + return &c->reqs[tag]; +} + + /* How many bytes left in this page. */ static unsigned int rest_of_page(void *data) { return PAGE_SIZE - ((unsigned long)data % PAGE_SIZE); } -static int p9_virtio_write(struct p9_trans *trans, void *buf, int count) +static void p9_virtio_close(struct p9_trans *trans) { - struct virtio_chan *chan = (struct virtio_chan *) trans->priv; - struct virtqueue *out_vq = chan->out_vq; - struct scatterlist sg[1]; - unsigned int len; + struct virtio_chan *chan = trans->priv; + int count; + unsigned int flags; - P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio write (%d)\n", count); + spin_lock_irqsave(&chan->lock, flags); + p9_idpool_destroy(chan->tagpool); + for (count = 0; count < chan->max_tag; count++) + kfree(chan->reqs[count].wq); + kfree(chan->reqs); + chan->max_tag = 0; + spin_unlock_irqrestore(&chan->lock, flags); - /* keep it simple - make sure we don't overflow a page */ - if (rest_of_page(buf) < count) - count = rest_of_page(buf); + down(&virtio_9p_lock); + chan->inuse = false; + up(&virtio_9p_lock); - sg_init_one(sg, buf, count); + kfree(trans); +} - /* add_buf wants a token to identify this buffer: we hand it any - * non-NULL pointer, since there's only ever one buffer. */ - if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) == 0) { - /* Tell Host to go! */ - out_vq->vq_ops->kick(out_vq); - /* Chill out until it's done with the buffer. */ - while (!out_vq->vq_ops->get_buf(out_vq, &len)) - cpu_relax(); +static void req_done(struct virtqueue *vq) +{ + struct virtio_chan *chan = vq->vdev->priv; + struct p9_fcall *rc; + unsigned int len; + unsigned long flags; + struct p9_req_t *req; + + spin_lock_irqsave(&chan->lock, flags); + while ((rc = chan->vq->vq_ops->get_buf(chan->vq, &len)) != NULL) { + req = p9_lookup_tag(chan, rc->tag); + req->status = REQ_STATUS_RCVD; + wake_up(req->wq); } - - P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio wrote (%d)\n", count); - - /* We're expected to return the amount of data we wrote: all of it. */ - return count; + /* In case queue is stopped waiting for more buffers. */ + spin_unlock_irqrestore(&chan->lock, flags); } -/* Create a scatter-gather list representing our input buffer and put it in the - * queue. */ -static void add_inbuf(struct virtio_chan *chan) +static int +pack_sg_list(struct scatterlist *sg, int start, int limit, char *data, + int count) { - struct scatterlist sg[1]; - - sg_init_one(sg, chan->inbuf, PAGE_SIZE); + int s; + int index = start; + + while (count) { + s = rest_of_page(data); + if (s > count) + s = count; + sg_set_buf(&sg[index++], data, s); + count -= s; + data += s; + if (index > limit) + BUG(); + } - /* We should always be able to add one buffer to an empty queue. */ - if (chan->in_vq->vq_ops->add_buf(chan->in_vq, sg, 0, 1, chan->inbuf)) - BUG(); - chan->in_vq->vq_ops->kick(chan->in_vq); + return index-start; } -static int p9_virtio_read(struct p9_trans *trans, void *buf, int count) +static int +p9_virtio_rpc(struct p9_trans *t, struct p9_fcall *tc, struct p9_fcall **rc) { - struct virtio_chan *chan = (struct virtio_chan *) trans->priv; - struct virtqueue *in_vq = chan->in_vq; - - P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio read (%d)\n", count); + int in, out; + int n, err, size; + struct virtio_chan *chan = t->priv; + char *rdata; + struct p9_req_t *req; + unsigned long flags; + + if (*rc == NULL) { + *rc = kmalloc(sizeof(struct p9_fcall) + t->msize, GFP_KERNEL); + if (!*rc) + return -ENOMEM; + } - /* If we don't have an input queue yet, we can't get input. */ - BUG_ON(!in_vq); + rdata = (char *)*rc+sizeof(struct p9_fcall); - /* No buffer? Try to get one. */ - if (!chan->in_len) { - chan->in = in_vq->vq_ops->get_buf(in_vq, &chan->in_len); - if (!chan->in) - return 0; + n = P9_NOTAG; + if (tc->id != P9_TVERSION) { + n = p9_idpool_get(chan->tagpool); + if (n < 0) + return -ENOMEM; } - /* You want more than we have to give? Well, try wanting less! */ - if (chan->in_len < count) - count = chan->in_len; + spin_lock_irqsave(&chan->lock, flags); + req = p9_lookup_tag(chan, n); + spin_unlock_irqrestore(&chan->lock, flags); - /* Copy across to their buffer and increment offset. */ - memcpy(buf, chan->in, count); - chan->in += count; - chan->in_len -= count; + p9_set_tag(tc, n); - /* Finished? Re-register buffer so Host will use it again. */ - if (chan->in_len == 0) - add_inbuf(chan); + P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio rpc tag %d\n", n); - P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio finished read (%d)\n", - count); + out = pack_sg_list(chan->sg, 0, VIRTQUEUE_NUM, tc->sdata, tc->size); + in = pack_sg_list(chan->sg, out, VIRTQUEUE_NUM-out, rdata, t->msize); - return count; -} + req->status = REQ_STATUS_SENT; -/* The poll function is used by 9p transports to determine if there - * is there is activity available on a particular channel. In our case - * we use it to wait for a callback from the input routines. - */ -static unsigned int -p9_virtio_poll(struct p9_trans *trans, struct poll_table_struct *pt) -{ - struct virtio_chan *chan = (struct virtio_chan *)trans->priv; - struct virtqueue *in_vq = chan->in_vq; - int ret = POLLOUT; /* we can always handle more output */ + if (chan->vq->vq_ops->add_buf(chan->vq, chan->sg, out, in, tc)) { + P9_DPRINTK(P9_DEBUG_TRANS, + "9p debug: virtio rpc add_buf returned failure"); + return -EIO; + } - poll_wait(NULL, &chan->wq, pt); + chan->vq->vq_ops->kick(chan->vq); - /* No buffer? Try to get one. */ - if (!chan->in_len) - chan->in = in_vq->vq_ops->get_buf(in_vq, &chan->in_len); + wait_event(*req->wq, req->status == REQ_STATUS_RCVD); - if (chan->in_len) - ret |= POLLIN; + size = le32_to_cpu(*(__le32 *) rdata); - return ret; -} + err = p9_deserialize_fcall(rdata, size, *rc, t->extended); + if (err < 0) { + P9_DPRINTK(P9_DEBUG_TRANS, + "9p debug: virtio rpc deserialize returned %d\n", err); + return err; + } -static void p9_virtio_close(struct p9_trans *trans) -{ - struct virtio_chan *chan = trans->priv; +#ifdef CONFIG_NET_9P_DEBUG + if ((p9_debug_level&P9_DEBUG_FCALL) == P9_DEBUG_FCALL) { + char buf[150]; - down(&virtio_9p_lock); - chan->inuse = false; - up(&virtio_9p_lock); + p9_printfcall(buf, sizeof(buf), *rc, t->extended); + printk(KERN_NOTICE ">>> %p %s\n", t, buf); + } +#endif - kfree(trans); -} + if (n != P9_NOTAG && p9_idpool_check(n, chan->tagpool)) + p9_idpool_put(n, chan->tagpool); -static void p9_virtio_intr(struct virtqueue *q) -{ - struct virtio_chan *chan = q->vdev->priv; + req->status = REQ_STATUS_IDLE; - P9_DPRINTK(P9_DEBUG_TRANS, "9p poll_wakeup: %p\n", &chan->wq); - wake_up_interruptible(&chan->wq); + return 0; } -static int p9_virtio_probe(struct virtio_device *dev) +static int p9_virtio_probe(struct virtio_device *vdev) { int err; struct virtio_chan *chan; @@ -221,44 +278,29 @@ static int p9_virtio_probe(struct virtio_device *dev) if (chan_index > MAX_9P_CHAN) { printk(KERN_ERR "9p: virtio: Maximum channels exceeded\n"); BUG(); - } - - chan->vdev = dev; - - /* This is the scratch page we use to receive console input */ - chan->inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!chan->inbuf) { err = -ENOMEM; goto fail; } - /* Find the input queue. */ - dev->priv = chan; - chan->in_vq = dev->config->find_vq(dev, 0, p9_virtio_intr); - if (IS_ERR(chan->in_vq)) { - err = PTR_ERR(chan->in_vq); - goto free; - } + chan->vdev = vdev; - chan->out_vq = dev->config->find_vq(dev, 1, NULL); - if (IS_ERR(chan->out_vq)) { - err = PTR_ERR(chan->out_vq); - goto free_in_vq; + /* We expect one virtqueue, for requests. */ + chan->vq = vdev->config->find_vq(vdev, 0, req_done); + if (IS_ERR(chan->vq)) { + err = PTR_ERR(chan->vq); + goto out_free_vq; } + chan->vq->vdev->priv = chan; + spin_lock_init(&chan->lock); - init_waitqueue_head(&chan->wq); + sg_init_table(chan->sg, VIRTQUEUE_NUM); - /* Register the input buffer the first time. */ - add_inbuf(chan); chan->inuse = false; chan->initialized = true; - return 0; -free_in_vq: - dev->config->del_vq(chan->in_vq); -free: - kfree(chan->inbuf); +out_free_vq: + vdev->config->del_vq(chan->vq); fail: down(&virtio_9p_lock); chan_index--; @@ -271,11 +313,13 @@ fail: * alternate channels by matching devname versus a virtio_config entry. * We use a simple reference count mechanism to ensure that only a single * mount has a channel open at a time. */ -static struct p9_trans *p9_virtio_create(const char *devname, char *args) +static struct p9_trans * +p9_virtio_create(const char *devname, char *args, int msize, + unsigned char extended) { struct p9_trans *trans; - int index = 0; struct virtio_chan *chan = channels; + int index = 0; down(&virtio_9p_lock); while (index < MAX_9P_CHAN) { @@ -290,25 +334,45 @@ static struct p9_trans *p9_virtio_create(const char *devname, char *args) up(&virtio_9p_lock); if (index >= MAX_9P_CHAN) { - printk(KERN_ERR "9p: virtio: couldn't find a free channel\n"); - return NULL; + printk(KERN_ERR "9p: no channels available\n"); + return ERR_PTR(-ENODEV); } + chan->tagpool = p9_idpool_create(); + if (IS_ERR(chan->tagpool)) { + printk(KERN_ERR "9p: couldn't allocate tagpool\n"); + return ERR_PTR(-ENOMEM); + } + p9_idpool_get(chan->tagpool); /* reserve tag 0 */ + chan->max_tag = 0; + chan->reqs = NULL; + trans = kmalloc(sizeof(struct p9_trans), GFP_KERNEL); if (!trans) { printk(KERN_ERR "9p: couldn't allocate transport\n"); return ERR_PTR(-ENOMEM); } - - trans->write = p9_virtio_write; - trans->read = p9_virtio_read; + trans->extended = extended; + trans->msize = msize; trans->close = p9_virtio_close; - trans->poll = p9_virtio_poll; + trans->rpc = p9_virtio_rpc; trans->priv = chan; return trans; } +static void p9_virtio_remove(struct virtio_device *vdev) +{ + struct virtio_chan *chan = vdev->priv; + + BUG_ON(chan->inuse); + + if (chan->initialized) { + vdev->config->del_vq(chan->vq); + chan->initialized = false; + } +} + #define VIRTIO_ID_9P 9 static struct virtio_device_id id_table[] = { @@ -322,12 +386,13 @@ static struct virtio_driver p9_virtio_drv = { .driver.owner = THIS_MODULE, .id_table = id_table, .probe = p9_virtio_probe, + .remove = p9_virtio_remove, }; static struct p9_trans_module p9_virtio_trans = { .name = "virtio", .create = p9_virtio_create, - .maxsize = PAGE_SIZE, + .maxsize = PAGE_SIZE*16, .def = 0, }; @@ -343,7 +408,13 @@ static int __init p9_virtio_init(void) return register_virtio_driver(&p9_virtio_drv); } +static void __exit p9_virtio_cleanup(void) +{ + unregister_virtio_driver(&p9_virtio_drv); +} + module_init(p9_virtio_init); +module_exit(p9_virtio_cleanup); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); diff --git a/net/9p/util.c b/net/9p/util.c index 22077b79395d..ef7215565d88 100644 --- a/net/9p/util.c +++ b/net/9p/util.c @@ -33,7 +33,7 @@ #include <net/9p/9p.h> struct p9_idpool { - struct semaphore lock; + spinlock_t lock; struct idr pool; }; @@ -45,7 +45,7 @@ struct p9_idpool *p9_idpool_create(void) if (!p) return ERR_PTR(-ENOMEM); - init_MUTEX(&p->lock); + spin_lock_init(&p->lock); idr_init(&p->pool); return p; @@ -71,19 +71,17 @@ int p9_idpool_get(struct p9_idpool *p) { int i = 0; int error; + unsigned int flags; retry: if (idr_pre_get(&p->pool, GFP_KERNEL) == 0) return 0; - if (down_interruptible(&p->lock) == -EINTR) { - P9_EPRINTK(KERN_WARNING, "Interrupted while locking\n"); - return -1; - } + spin_lock_irqsave(&p->lock, flags); /* no need to store exactly p, we just need something non-null */ error = idr_get_new(&p->pool, p, &i); - up(&p->lock); + spin_unlock_irqrestore(&p->lock, flags); if (error == -EAGAIN) goto retry; @@ -104,12 +102,10 @@ EXPORT_SYMBOL(p9_idpool_get); void p9_idpool_put(int id, struct p9_idpool *p) { - if (down_interruptible(&p->lock) == -EINTR) { - P9_EPRINTK(KERN_WARNING, "Interrupted while locking\n"); - return; - } + unsigned int flags; + spin_lock_irqsave(&p->lock, flags); idr_remove(&p->pool, id); - up(&p->lock); + spin_unlock_irqrestore(&p->lock, flags); } EXPORT_SYMBOL(p9_idpool_put); |