diff mbox

[V9fs-developer,RFC] 9p/net: add zero copy support

Message ID 20100829162904.GA3691@valinor (mailing list archive)
State Changes Requested, archived
Headers show

Commit Message

Latchesar Ionkov Aug. 29, 2010, 4:29 p.m. UTC
None
diff mbox

Patch

diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h
index a8de812..d2f5597 100644
--- a/include/net/9p/9p.h
+++ b/include/net/9p/9p.h
@@ -27,6 +27,8 @@ 
 #ifndef NET_9P_H
 #define NET_9P_H
 
+#include <linux/scatterlist.h>
+
 /**
  * enum p9_debug_flags - bits for mount time debug parameter
  * @P9_DEBUG_ERROR: more verbose error messages including original error string
@@ -653,8 +655,17 @@  struct p9_fcall {
 	size_t capacity;
 
 	uint8_t *sdata;
+
+	uint8_t *pkt;			/* area that holds the fcall header */
+	int sgnum;			/* number of entries in sgpkt */
+	struct scatterlist *sgpkt;	/* NULL if no sg is used */
 };
 
+static inline int p9_fcall_has_sg(struct p9_fcall *fc)
+{
+	return fc->sgpkt != NULL;
+}
+
 struct p9_idpool;
 
 int p9_errstr2errno(char *errstr, int len);
diff --git a/include/net/9p/transport.h b/include/net/9p/transport.h
index 6d5886e..4912dc1 100644
--- a/include/net/9p/transport.h
+++ b/include/net/9p/transport.h
@@ -43,11 +43,16 @@ 
  * BUGS: the transport module list isn't protected.
  */
 
+enum {
+	P9_TRANS_SG = 1,	/* support for scatterlists */
+};
+
 struct p9_trans_module {
 	struct list_head list;
 	char *name;		/* name of transport */
 	int maxsize;		/* max message size of transport */
 	int def;		/* this transport should be default */
+	int flags;		/* P9_TRANS_* flags */
 	struct module *owner;
 	int (*create)(struct p9_client *, const char *, char *);
 	void (*close) (struct p9_client *);
@@ -60,4 +65,10 @@  void v9fs_unregister_trans(struct p9_trans_module *m);
 struct p9_trans_module *v9fs_get_trans_by_name(const substring_t *name);
 struct p9_trans_module *v9fs_get_default_trans(void);
 void v9fs_put_trans(struct p9_trans_module *m);
+
+static int inline p9_trans_sg_support(struct p9_trans_module *ts)
+{
+	return ts->flags & P9_TRANS_SG;
+}
+
 #endif /* NET_9P_TRANSPORT_H */
diff --git a/net/9p/client.c b/net/9p/client.c
index 9eb7250..2a464ad 100644
--- a/net/9p/client.c
+++ b/net/9p/client.c
@@ -93,7 +93,7 @@  static int get_protocol_version(const substring_t *name)
 }
 
 static struct p9_req_t *
-p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...);
+p9_client_rpc(struct p9_client *c, int8_t type, int rcsg, const char *fmt, ...);
 
 /**
  * parse_options - parse mount options into client structure
@@ -243,13 +243,11 @@  static struct p9_req_t *p9_tag_alloc(struct p9_client *c, u16 tag)
 			return ERR_PTR(-ENOMEM);
 		}
 		req->tc->sdata = (char *) req->tc + sizeof(struct p9_fcall);
-		req->tc->capacity = c->msize;
 		req->rc->sdata = (char *) req->rc + sizeof(struct p9_fcall);
-		req->rc->capacity = c->msize;
 	}
 
-	p9pdu_reset(req->tc);
-	p9pdu_reset(req->rc);
+	p9pdu_reset(req->tc, c->msize);
+	p9pdu_reset(req->rc, c->msize);
 
 	req->tc->tag = tag-1;
 	req->status = REQ_STATUS_ALLOC;
@@ -360,6 +358,11 @@  static void p9_free_req(struct p9_client *c, struct p9_req_t *r)
 	int tag = r->tc->tag;
 	P9_DPRINTK(P9_DEBUG_MUX, "clnt %p req %p tag: %d\n", c, r, tag);
 
+	if (p9_trans_sg_support(c->trans)) {
+		p9pdu_free_sg(r->tc);
+		p9pdu_free_sg(r->rc);
+	}
+
 	r->status = REQ_STATUS_IDLE;
 	if (tag != P9_NOTAG && p9_idpool_check(tag, c->tagpool))
 		p9_idpool_put(tag, c->tagpool);
@@ -428,6 +431,42 @@  rewind_and_exit:
 }
 EXPORT_SYMBOL(p9_parse_header);
 
+static int p9_read_error_sg(struct p9_fcall *rc, int proto_version,
+						char **ename, int *ecode)
+{
+	int err, n;
+	int16_t len, count;
+	char *str;
+
+	/* the string len is in the header */
+	err = p9pdu_readf(rc, proto_version, "w", &len);
+	if (err < 0)
+		return err;
+
+	count = len;
+	if ((proto_version == p9_proto_2000u) ||
+			(proto_version == p9_proto_2000L))
+		count += 4;
+
+	str = kmalloc(count + 1, GFP_KERNEL);
+	if (!str)
+		return -ENOMEM;
+
+	n = sg_copy_to_buffer(rc->sgpkt, rc->sgnum, str, count);
+	if (n != count) {
+		kfree(str);
+		return -EINVAL;
+	}
+
+	if ((proto_version == p9_proto_2000u) ||
+			(proto_version == p9_proto_2000L))
+		*ecode = le32_to_cpu(*(__le32 *) &str[len]);
+
+	str[len] = '\0';
+	*ename = str;
+	return 0;
+}
+
 /**
  * p9_check_errors - check 9p packet for error return and process it
  * @c: current client instance
@@ -453,9 +492,18 @@  static int p9_check_errors(struct p9_client *c, struct p9_req_t *req)
 	if (type == P9_RERROR) {
 		int ecode;
 		char *ename;
+		struct p9_fcall *rc;
 
-		err = p9pdu_readf(req->rc, c->proto_version, "s?d",
+		/* error handling is complicated because of the
+		 * Tread/Rerror case if scatterlist is used */
+		rc = req->rc;
+		if (rc->sgpkt != NULL)
+			err = p9_read_error_sg(req->rc, c->proto_version,
 							&ename, &ecode);
+		else
+			err = p9pdu_readf(req->rc, c->proto_version, "s?d",
+							&ename, &ecode);
+
 		if (err) {
 			P9_DPRINTK(P9_DEBUG_ERROR, "couldn't parse error%d\n",
 									err);
@@ -502,7 +550,7 @@  static int p9_client_flush(struct p9_client *c, struct p9_req_t *oldreq)
 
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TFLUSH tag %d\n", oldtag);
 
-	req = p9_client_rpc(c, P9_TFLUSH, "w", oldtag);
+	req = p9_client_rpc(c, P9_TFLUSH, 0, "w", oldtag);
 	if (IS_ERR(req))
 		return PTR_ERR(req);
 
@@ -522,13 +570,16 @@  static int p9_client_flush(struct p9_client *c, struct p9_req_t *oldreq)
  * p9_client_rpc - issue a request and wait for a response
  * @c: client session
  * @type: type of request
+ * @rcsg: flag if the response would be read in a scatterlist
+ *	  in that case there are three additional parameters at the end
+ *        of the va_list -- int32_t hdrsz, int32_t count, void __user *udata
  * @fmt: protocol format string (see protocol.c)
  *
  * Returns request structure (which client must free using p9_free_req)
  */
 
 static struct p9_req_t *
-p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...)
+p9_client_rpc(struct p9_client *c, int8_t type, int rcsg, const char *fmt, ...)
 {
 	va_list ap;
 	int tag, err;
@@ -563,13 +614,27 @@  p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...)
 	if (IS_ERR(req))
 		return req;
 
+	/* set up the outgoing and incoming buffers */
+	va_start(ap, fmt);
+
 	/* marshall the data */
 	p9pdu_prepare(req->tc, tag, type);
-	va_start(ap, fmt);
 	err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap);
-	va_end(ap);
 	p9pdu_finalize(req->tc);
 
+	/* prepare the incoming buffer */
+	if (rcsg && p9_trans_sg_support(c->trans_mod)) {
+		int32_t hdrsz, count;
+		const void __user *udata;
+
+		hdrsz = va_arg(ap, int32_t);
+		count = va_arg(ap, int32_t);
+		udata = va_arg(ap, const void __user *);
+
+		p9pdu_prepare_sg(req->rc, hdrsz, count, 0, udata);
+	}
+	va_end(ap);
+
 	err = c->trans_mod->request(c, req);
 	if (err < 0) {
 		c->status = Disconnected;
@@ -683,15 +748,15 @@  int p9_client_version(struct p9_client *c)
 
 	switch (c->proto_version) {
 	case p9_proto_2000L:
-		req = p9_client_rpc(c, P9_TVERSION, "ds",
+		req = p9_client_rpc(c, P9_TVERSION, 0, "ds",
 					c->msize, "9P2000.L");
 		break;
 	case p9_proto_2000u:
-		req = p9_client_rpc(c, P9_TVERSION, "ds",
+		req = p9_client_rpc(c, P9_TVERSION, 0, "ds",
 					c->msize, "9P2000.u");
 		break;
 	case p9_proto_legacy:
-		req = p9_client_rpc(c, P9_TVERSION, "ds",
+		req = p9_client_rpc(c, P9_TVERSION, 0, "ds",
 					c->msize, "9P2000");
 		break;
 	default:
@@ -856,7 +921,7 @@  struct p9_fid *p9_client_attach(struct p9_client *clnt, struct p9_fid *afid,
 		goto error;
 	}
 
-	req = p9_client_rpc(clnt, P9_TATTACH, "ddss?d", fid->fid,
+	req = p9_client_rpc(clnt, P9_TATTACH, 0, "ddss?d", fid->fid,
 			afid ? afid->fid : P9_NOFID, uname, aname, n_uname);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -905,7 +970,7 @@  p9_client_auth(struct p9_client *clnt, char *uname, u32 n_uname, char *aname)
 		goto error;
 	}
 
-	req = p9_client_rpc(clnt, P9_TAUTH, "dss?d",
+	req = p9_client_rpc(clnt, P9_TAUTH, 0, "dss?d",
 			afid ? afid->fid : P9_NOFID, uname, aname, n_uname);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -964,7 +1029,7 @@  struct p9_fid *p9_client_walk(struct p9_fid *oldfid, int nwname, char **wnames,
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TWALK fids %d,%d nwname %d wname[0] %s\n",
 		oldfid->fid, fid->fid, nwname, wnames ? wnames[0] : NULL);
 
-	req = p9_client_rpc(clnt, P9_TWALK, "ddT", oldfid->fid, fid->fid,
+	req = p9_client_rpc(clnt, P9_TWALK, 0, "ddT", oldfid->fid, fid->fid,
 								nwname, wnames);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1030,9 +1095,9 @@  int p9_client_open(struct p9_fid *fid, int mode)
 		return -EINVAL;
 
 	if (p9_is_proto_dotl(clnt))
-		req = p9_client_rpc(clnt, P9_TLOPEN, "dd", fid->fid, mode);
+		req = p9_client_rpc(clnt, P9_TLOPEN, 0, "dd", fid->fid, mode);
 	else
-		req = p9_client_rpc(clnt, P9_TOPEN, "db", fid->fid, mode);
+		req = p9_client_rpc(clnt, P9_TOPEN, 0, "db", fid->fid, mode);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1074,8 +1139,8 @@  int p9_client_create_dotl(struct p9_fid *ofid, char *name, u32 flags, u32 mode,
 	if (ofid->mode != -1)
 		return -EINVAL;
 
-	req = p9_client_rpc(clnt, P9_TLCREATE, "dsddd", ofid->fid, name, flags,
-			mode, gid);
+	req = p9_client_rpc(clnt, P9_TLCREATE, 0, "dsddd", ofid->fid, name,
+			flags, mode, gid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1119,7 +1184,7 @@  int p9_client_fcreate(struct p9_fid *fid, char *name, u32 perm, int mode,
 	if (fid->mode != -1)
 		return -EINVAL;
 
-	req = p9_client_rpc(clnt, P9_TCREATE, "dsdb?s", fid->fid, name, perm,
+	req = p9_client_rpc(clnt, P9_TCREATE, 0, "dsdb?s", fid->fid, name, perm,
 				mode, extension);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1158,8 +1223,8 @@  int p9_client_symlink(struct p9_fid *dfid, char *name, char *symtgt, gid_t gid,
 			dfid->fid, name, symtgt);
 	clnt = dfid->clnt;
 
-	req = p9_client_rpc(clnt, P9_TSYMLINK, "dssd", dfid->fid, name, symtgt,
-			gid);
+	req = p9_client_rpc(clnt, P9_TSYMLINK, 0, "dssd", dfid->fid, name,
+			symtgt, gid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1189,7 +1254,7 @@  int p9_client_link(struct p9_fid *dfid, struct p9_fid *oldfid, char *newname)
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TLINK dfid %d oldfid %d newname %s\n",
 			dfid->fid, oldfid->fid, newname);
 	clnt = dfid->clnt;
-	req = p9_client_rpc(clnt, P9_TLINK, "dds", dfid->fid, oldfid->fid,
+	req = p9_client_rpc(clnt, P9_TLINK, 0, "dds", dfid->fid, oldfid->fid,
 			newname);
 	if (IS_ERR(req))
 		return PTR_ERR(req);
@@ -1210,7 +1275,7 @@  int p9_client_clunk(struct p9_fid *fid)
 	err = 0;
 	clnt = fid->clnt;
 
-	req = p9_client_rpc(clnt, P9_TCLUNK, "d", fid->fid);
+	req = p9_client_rpc(clnt, P9_TCLUNK, 0, "d", fid->fid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1236,7 +1301,7 @@  int p9_client_remove(struct p9_fid *fid)
 	err = 0;
 	clnt = fid->clnt;
 
-	req = p9_client_rpc(clnt, P9_TREMOVE, "d", fid->fid);
+	req = p9_client_rpc(clnt, P9_TREMOVE, 0, "d", fid->fid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1255,7 +1320,7 @@  int
 p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset,
 								u32 count)
 {
-	int err, rsize, total;
+	int err, rsize, total, sgsupport;
 	struct p9_client *clnt;
 	struct p9_req_t *req;
 	char *dataptr;
@@ -1273,13 +1338,24 @@  p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset,
 	if (count < rsize)
 		rsize = count;
 
-	req = p9_client_rpc(clnt, P9_TREAD, "dqd", fid->fid, offset, rsize);
+	sgsupport = udata && p9_trans_sg_support(clnt->trans_mod);
+	if (sgsupport)
+		req = p9_client_rpc(clnt, P9_TREAD, 1, "dqd", fid->fid, offset,
+				rsize, 11, rsize, udata);
+	else
+		req = p9_client_rpc(clnt, P9_TREAD, 0, "dqd", fid->fid, offset,
+				rsize);
+
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
 	}
 
-	err = p9pdu_readf(req->rc, clnt->proto_version, "D", &count, &dataptr);
+	if (sgsupport)
+		err = p9pdu_readf(req->rc, clnt->proto_version, "d", &count);
+	else
+		err = p9pdu_readf(req->rc, clnt->proto_version, "D", &count, &dataptr);
+
 	if (err) {
 		p9pdu_dump(1, req->rc);
 		goto free_and_error;
@@ -1287,15 +1363,16 @@  p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset,
 
 	P9_DPRINTK(P9_DEBUG_9P, "<<< RREAD count %d\n", count);
 
-	if (data) {
-		memmove(data, dataptr, count);
-	}
+	if (!sgsupport) {
+		if (data)
+			memmove(data, dataptr, count);
 
-	if (udata) {
-		err = copy_to_user(udata, dataptr, count);
-		if (err) {
-			err = -EFAULT;
-			goto free_and_error;
+		if (udata) {
+			err = copy_to_user(udata, dataptr, count);
+			if (err) {
+				err = -EFAULT;
+				goto free_and_error;
+			}
 		}
 	}
 
@@ -1330,10 +1407,13 @@  p9_client_write(struct p9_fid *fid, char *data, const char __user *udata,
 	if (count < rsize)
 		rsize = count;
 	if (data)
-		req = p9_client_rpc(clnt, P9_TWRITE, "dqD", fid->fid, offset,
+		req = p9_client_rpc(clnt, P9_TWRITE, 0, "dqD", fid->fid, offset,
 								rsize, data);
+	else if (p9_trans_sg_support(clnt->trans_mod))
+		req = p9_client_rpc(clnt, P9_TWRITE, 0, "dqu", fid->fid, offset,
+								rsize, udata);
 	else
-		req = p9_client_rpc(clnt, P9_TWRITE, "dqU", fid->fid, offset,
+		req = p9_client_rpc(clnt, P9_TWRITE, 0, "dqU", fid->fid, offset,
 								rsize, udata);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1374,7 +1454,7 @@  struct p9_wstat *p9_client_stat(struct p9_fid *fid)
 	err = 0;
 	clnt = fid->clnt;
 
-	req = p9_client_rpc(clnt, P9_TSTAT, "d", fid->fid);
+	req = p9_client_rpc(clnt, P9_TSTAT, 0, "d", fid->fid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1425,7 +1505,7 @@  struct p9_stat_dotl *p9_client_getattr_dotl(struct p9_fid *fid,
 	err = 0;
 	clnt = fid->clnt;
 
-	req = p9_client_rpc(clnt, P9_TGETATTR, "dq", fid->fid, request_mask);
+	req = p9_client_rpc(clnt, P9_TGETATTR, 0, "dq", fid->fid, request_mask);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1516,7 +1596,7 @@  int p9_client_wstat(struct p9_fid *fid, struct p9_wstat *wst)
 		wst->name, wst->uid, wst->gid, wst->muid, wst->extension,
 		wst->n_uid, wst->n_gid, wst->n_muid);
 
-	req = p9_client_rpc(clnt, P9_TWSTAT, "dwS", fid->fid, wst->size+2, wst);
+	req = p9_client_rpc(clnt, P9_TWSTAT, 0, "dwS", fid->fid, wst->size+2, wst);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1547,7 +1627,7 @@  int p9_client_setattr(struct p9_fid *fid, struct p9_iattr_dotl *p9attr)
 		p9attr->size, p9attr->atime_sec, p9attr->atime_nsec,
 		p9attr->mtime_sec, p9attr->mtime_nsec);
 
-	req = p9_client_rpc(clnt, P9_TSETATTR, "dI", fid->fid, p9attr);
+	req = p9_client_rpc(clnt, P9_TSETATTR, 0, "dI", fid->fid, p9attr);
 
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1571,7 +1651,7 @@  int p9_client_statfs(struct p9_fid *fid, struct p9_rstatfs *sb)
 
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TSTATFS fid %d\n", fid->fid);
 
-	req = p9_client_rpc(clnt, P9_TSTATFS, "d", fid->fid);
+	req = p9_client_rpc(clnt, P9_TSTATFS, 0, "d", fid->fid);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1611,7 +1691,7 @@  int p9_client_rename(struct p9_fid *fid, struct p9_fid *newdirfid, char *name)
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TRENAME fid %d newdirfid %d name %s\n",
 			fid->fid, newdirfid->fid, name);
 
-	req = p9_client_rpc(clnt, P9_TRENAME, "dds", fid->fid,
+	req = p9_client_rpc(clnt, P9_TRENAME, 0, "dds", fid->fid,
 			newdirfid->fid, name);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1649,7 +1729,7 @@  struct p9_fid *p9_client_xattrwalk(struct p9_fid *file_fid,
 		">>> TXATTRWALK file_fid %d, attr_fid %d name %s\n",
 		file_fid->fid, attr_fid->fid, attr_name);
 
-	req = p9_client_rpc(clnt, P9_TXATTRWALK, "dds",
+	req = p9_client_rpc(clnt, P9_TXATTRWALK, 0, "dds",
 			file_fid->fid, attr_fid->fid, attr_name);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1688,7 +1768,7 @@  int p9_client_xattrcreate(struct p9_fid *fid, const char *name,
 		fid->fid, name, (long long)attr_size, flags);
 	err = 0;
 	clnt = fid->clnt;
-	req = p9_client_rpc(clnt, P9_TXATTRCREATE, "dsqd",
+	req = p9_client_rpc(clnt, P9_TXATTRCREATE, 0, "dsqd",
 			fid->fid, name, attr_size, flags);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
@@ -1722,7 +1802,8 @@  int p9_client_readdir(struct p9_fid *fid, char *data, u32 count, u64 offset)
 	if (count < rsize)
 		rsize = count;
 
-	req = p9_client_rpc(clnt, P9_TREADDIR, "dqd", fid->fid, offset, rsize);
+	req = p9_client_rpc(clnt, P9_TREADDIR, 0, "dqd", fid->fid, offset,
+				rsize);
 	if (IS_ERR(req)) {
 		err = PTR_ERR(req);
 		goto error;
@@ -1760,7 +1841,7 @@  int p9_client_mknod_dotl(struct p9_fid *fid, char *name, int mode,
 	clnt = fid->clnt;
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TMKNOD fid %d name %s mode %d major %d "
 		"minor %d\n", fid->fid, name, mode, MAJOR(rdev), MINOR(rdev));
-	req = p9_client_rpc(clnt, P9_TMKNOD, "dsdddd", fid->fid, name, mode,
+	req = p9_client_rpc(clnt, P9_TMKNOD, 0, "dsdddd", fid->fid, name, mode,
 		MAJOR(rdev), MINOR(rdev), gid);
 	if (IS_ERR(req))
 		return PTR_ERR(req);
@@ -1791,7 +1872,7 @@  int p9_client_mkdir_dotl(struct p9_fid *fid, char *name, int mode,
 	clnt = fid->clnt;
 	P9_DPRINTK(P9_DEBUG_9P, ">>> TMKDIR fid %d name %s mode %d gid %d\n",
 		 fid->fid, name, mode, gid);
-	req = p9_client_rpc(clnt, P9_TMKDIR, "dsdd", fid->fid, name, mode,
+	req = p9_client_rpc(clnt, P9_TMKDIR, 0, "dsdd", fid->fid, name, mode,
 		gid);
 	if (IS_ERR(req))
 		return PTR_ERR(req);
diff --git a/net/9p/protocol.c b/net/9p/protocol.c
index 3acd3af..72291e8 100644
--- a/net/9p/protocol.c
+++ b/net/9p/protocol.c
@@ -31,6 +31,7 @@ 
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/types.h>
+#include <linux/highmem.h>
 #include <net/9p/9p.h>
 #include <net/9p/client.h>
 #include "protocol.h"
@@ -60,7 +61,7 @@  void
 p9pdu_dump(int way, struct p9_fcall *pdu)
 {
 	int i, n;
-	u8 *data = pdu->sdata;
+	u8 *data = pdu->pkt;
 	int datalen = pdu->size;
 	char buf[255];
 	int buflen = 255;
@@ -105,7 +106,7 @@  EXPORT_SYMBOL(p9stat_free);
 static size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size)
 {
 	size_t len = MIN(pdu->size - pdu->offset, size);
-	memcpy(data, &pdu->sdata[pdu->offset], len);
+	memcpy(data, &pdu->pkt[pdu->offset], len);
 	pdu->offset += len;
 	return size - len;
 }
@@ -113,7 +114,7 @@  static size_t pdu_read(struct p9_fcall *pdu, void *data, size_t size)
 static size_t pdu_write(struct p9_fcall *pdu, const void *data, size_t size)
 {
 	size_t len = MIN(pdu->capacity - pdu->size, size);
-	memcpy(&pdu->sdata[pdu->size], data, len);
+	memcpy(&pdu->pkt[pdu->size], data, len);
 	pdu->size += len;
 	return size - len;
 }
@@ -122,7 +123,7 @@  static size_t
 pdu_write_u(struct p9_fcall *pdu, const char __user *udata, size_t size)
 {
 	size_t len = MIN(pdu->capacity - pdu->size, size);
-	int err = copy_from_user(&pdu->sdata[pdu->size], udata, len);
+	int err = copy_from_user(&pdu->pkt[pdu->size], udata, len);
 	if (err)
 		printk(KERN_WARNING "pdu_write_u returning: %d\n", err);
 
@@ -130,6 +131,26 @@  pdu_write_u(struct p9_fcall *pdu, const char __user *udata, size_t size)
 	return size - len;
 }
 
+static size_t
+pdu_wmap_u(struct p9_fcall *pdu, const char __user *udata, size_t size)
+{
+	int err;
+	size_t count;
+
+	count = MIN(pdu->capacity - pdu->size, size);
+	err = p9pdu_prepare_sg(pdu, pdu->size, count, 1, udata);
+	if (err < 0)
+		return err;
+
+	/* if we adjusted pkt so the header can fit in a page (unlikely),
+	 * we need to move the header data itself */
+	if (pdu->pkt != pdu->sdata)
+		memmove(pdu->pkt, pdu->sdata, pdu->size);
+
+	pdu->size += count;
+	return size - count;
+}
+
 /*
 	b - int8_t
 	w - int16_t
@@ -259,7 +280,7 @@  p9pdu_vreadf(struct p9_fcall *pdu, int proto_version, const char *fmt,
 					*count =
 					    MIN(*count,
 						pdu->size - pdu->offset);
-					*data = &pdu->sdata[pdu->offset];
+					*data = &pdu->pkt[pdu->offset];
 				}
 			}
 			break;
@@ -473,6 +494,16 @@  p9pdu_vwritef(struct p9_fcall *pdu, int proto_version, const char *fmt,
 					errcode = -EFAULT;
 			}
 			break;
+		case 'u':{
+				int32_t count = va_arg(ap, int32_t);
+				const char __user *udata =
+						va_arg(ap, const void __user *);
+				errcode = p9pdu_writef(pdu, proto_version, "d",
+									count);
+				if (!errcode && pdu_wmap_u(pdu, udata, count))
+					errcode = -EFAULT;
+			}
+			break;
 		case 'T':{
 				int16_t nwname = va_arg(ap, int);
 				const char **wnames = va_arg(ap, const char **);
@@ -583,6 +614,7 @@  int p9stat_read(char *buf, int len, struct p9_wstat *st, int proto_version)
 	fake_pdu.size = len;
 	fake_pdu.capacity = len;
 	fake_pdu.sdata = buf;
+	fake_pdu.pkt = buf;
 	fake_pdu.offset = 0;
 
 	ret = p9pdu_readf(&fake_pdu, proto_version, "S", st);
@@ -602,9 +634,10 @@  int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type)
 
 int p9pdu_finalize(struct p9_fcall *pdu)
 {
-	int size = pdu->size;
+	int size;
 	int err;
 
+	size = pdu->size;
 	pdu->size = 0;
 	err = p9pdu_writef(pdu, 0, "d", size);
 	pdu->size = size;
@@ -620,10 +653,102 @@  int p9pdu_finalize(struct p9_fcall *pdu)
 	return err;
 }
 
-void p9pdu_reset(struct p9_fcall *pdu)
+void p9pdu_reset(struct p9_fcall *pdu, size_t capacity)
 {
 	pdu->offset = 0;
 	pdu->size = 0;
+	pdu->pkt = pdu->sdata;
+	pdu->capacity = capacity;
+	pdu->sgpkt = NULL;
+	pdu->sgnum = 0;
+}
+
+int p9pdu_prepare_sg(struct p9_fcall *pdu, int32_t hdrsz, int32_t count,
+					int rw, const void __user *data)
+{
+	int i, m, nent, off, err;
+	uint8_t *p, *edata;
+	struct page *pages[32];
+
+	nent = min(count/PAGE_SIZE + 2, ARRAY_SIZE(pages));
+	edata = pdu->pkt + pdu->capacity;
+
+	/* The pdu->sdata pkt area looks like:
+	 *	-- padding to ensure the header fits in a page --
+	 *	-- header --
+	 *	-- array of scatterlist structs --
+	 *	-- trailer (rest of the space) --
+	 *
+	 * The trailer is going to be used only if Tread gets Rerror
+	 * response and the user data buffer (+ header) is not big
+	 * enough to hold the all of the response data */
+
+	/* fcall header */
+	BUG_ON(hdrsz > PAGE_SIZE);
+	off = ((unsigned long) pdu->pkt) & ~PAGE_MASK;
+	if (off+hdrsz > PAGE_SIZE) {
+		pdu->pkt += PAGE_SIZE - off;
+		off = 0;
+	}
+
+	p = pdu->pkt + hdrsz;
+	p += 8 - ((unsigned long) p)%8;	/* align */
+	pdu->sgpkt = (struct scatterlist *) p;
+	if ((uint8_t *)(&pdu->sgpkt[nent+1]) > edata) {
+		/* no enough space for the scatterlist array */
+		err = -ENOMEM;
+		goto error;
+	}
+
+	sg_init_table(pdu->sgpkt, nent + 2);
+	sg_set_buf(&pdu->sgpkt[0], pdu->pkt, hdrsz);
+
+	/* udata */
+	err = get_user_pages_fast((unsigned long) data, nent, rw, pages);
+	if (err < 0)
+		goto error;
+
+	off = ((unsigned long) data) & ~PAGE_MASK;
+	for(i = 0; count > 0; off = 0, i++) {
+		m = min((int)(PAGE_SIZE - off), count);
+		sg_set_page(&pdu->sgpkt[i+1], pages[i], m, off);
+		count -= m;
+	}
+
+	/* fcall trailer (in case of Tread/Rerror) */
+	p = (uint8_t *) &pdu->sgpkt[nent+1];
+	sg_set_buf(&pdu->sgpkt[i+1], p, pdu->capacity - (p - pdu->sdata));
+	sg_mark_end(&pdu->sgpkt[i+1]);
+	pdu->sgnum = i+1;
+
+	/* make sure the header doesn't go over the scatterlist array */
+	pdu->capacity = hdrsz;
+	return 0;
+
+error:
+	pdu->sgpkt = NULL;
+	return err;
+}
+
+void p9pdu_free_sg(struct p9_fcall *pdu)
+{
+	int i;
+	struct scatterlist *sg;
+	struct page *p;
+
+	if (!pdu->sgpkt)
+		return;
+
+	/* pdu->sgpkt[0] and the last one point to pdu->pkt and
+	 * shouldn't be unmapped */
+	for(i=1, sg=&pdu->sgpkt[1]; i < pdu->sgnum; i++, sg = sg_next(sg)) {
+		p = sg_page(sg);
+		kunmap(p);
+		put_page(p);
+	}
+
+	pdu->sgnum = 0;
+	pdu->sgpkt = NULL;
 }
 
 int p9dirent_read(char *buf, int len, struct p9_dirent *dirent,
@@ -636,6 +761,7 @@  int p9dirent_read(char *buf, int len, struct p9_dirent *dirent,
 	fake_pdu.size = len;
 	fake_pdu.capacity = len;
 	fake_pdu.sdata = buf;
+	fake_pdu.pkt = buf;
 	fake_pdu.offset = 0;
 
 	ret = p9pdu_readf(&fake_pdu, proto_version, "Qqbs", &dirent->qid,
diff --git a/net/9p/protocol.h b/net/9p/protocol.h
index 2431c0f..a711b56 100644
--- a/net/9p/protocol.h
+++ b/net/9p/protocol.h
@@ -31,4 +31,7 @@  int p9pdu_readf(struct p9_fcall *pdu, int proto_version, const char *fmt, ...);
 int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type);
 int p9pdu_finalize(struct p9_fcall *pdu);
 void p9pdu_dump(int, struct p9_fcall *);
-void p9pdu_reset(struct p9_fcall *pdu);
+void p9pdu_reset(struct p9_fcall *pdu, size_t msize);
+int p9pdu_prepare_sg(struct p9_fcall *pdu, int32_t hdrsz, int32_t count,
+					int rw, const void __user *data);
+void p9pdu_free_sg(struct p9_fcall *pdu);
diff --git a/net/9p/util.c b/net/9p/util.c
index e048701..f8c35f8 100644
--- a/net/9p/util.c
+++ b/net/9p/util.c
@@ -66,7 +66,7 @@  struct p9_idpool *p9_idpool_create(void)
 EXPORT_SYMBOL(p9_idpool_create);
 
 /**
- * p9_idpool_destroy - create a new per-connection id pool
+ * p9_idpool_destroy - destroy the per-connection id pool
  * @p: idpool to destory
  */