@@ -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
@@ -304,6 +306,8 @@ enum p9_qid_t {
/* ample room for Twrite/Rread header */
#define P9_IOHDRSZ 24
+#define P9_TWRITE_HDRSZ 23
+#define P9_RREAD_HDRSZ 11
/* Room for readdir header */
#define P9_READDIRHDRSZ 24
@@ -633,9 +637,12 @@ struct p9_rwstat {
* @size: prefixed length of the structure
* @id: protocol operating identifier of type &p9_msg_t
* @tag: transaction id of the request
- * @offset: used by marshalling routines to track currentposition in buffer
+ * @offset: used by marshalling routines to track current position in buffer
* @capacity: used by marshalling routines to track total capacity
- * @sdata: payload
+ * @sgcount: number of entries in the scatterlist array
+ * @sg: array of scatterlist structs that describe the payload. The first
+ * entry in the array is always mapped and pointer to it is stored in &buf
+ * @buf: payload (whole if sg is NULL, otherwise the first entry).
*
* &p9_fcall represents the structure for all 9P RPC
* transactions. Requests are packaged into fcalls, and reponses
@@ -645,14 +652,21 @@ struct p9_rwstat {
*/
struct p9_fcall {
+ /* 9P message header data */
u32 size;
u8 id;
u16 tag;
+ /* data related to marshalling */
size_t offset;
size_t capacity;
- uint8_t *sdata;
+ /* buffer description */
+ int sgcount;
+ struct scatterlist *sg;
+ uint8_t *buf;
+
+ struct list_head fclist;
};
struct p9_idpool;
@@ -95,6 +95,8 @@ enum p9_req_status_t {
* @wq: wait_queue for the client to block on for this request
* @tc: the request fcall structure
* @rc: the response fcall structure
+ * @tb: the request buffer
+ * @rb: the response buffer
* @aux: transport specific data (provided for trans_fd migration)
* @req_list: link for higher level objects to chain requests
*
@@ -110,6 +112,7 @@ enum p9_req_status_t {
*/
struct p9_req_t {
+ u16 tag;
int status;
int t_err;
wait_queue_head_t *wq;
@@ -134,6 +137,12 @@ struct p9_req_t {
* @tagpool - transaction id accounting for session
* @reqs - 2D array of requests
* @max_tag - current maximum tag id allocated
+ * @sblist - list of small pdu
+ * @sbcount - number of elements in sblist
+ * @lblist - list of large (->msize) pdu
+ * @lbcount - number of elements in lblist
+ * @sgblist - list of scatterlist pdu
+ * @sgbcount - number of elements in sgblist
*
* The client structure is used to keep track of various per-client
* state that has been instantiated.
@@ -146,12 +155,20 @@ struct p9_req_t {
* Each row is 256 requests and we'll support up to 256 rows for
* a total of 64k concurrent requests per session.
*
+ * The client structure keeps track of two classes of 9P message buffers:
+ * - large buffers (lblist, lbcount) that can hold up to &msize bytes
+ * messages.
+ * - small buffers (sblist, sbcount) that can hold up to P9_SMALL_SIZE
+ * byte header + scatterlist for up to &msize/PAGE_SIZE+2 pages.
+ * There are up to P9_MAX_BUFS messages kept in the lists.
+ *
* Bugs: duplicated data and potentially unnecessary elements.
*/
struct p9_client {
spinlock_t lock; /* protect client structure */
int msize;
+ int maxsgcount;
unsigned char proto_version;
struct p9_trans_module *trans_mod;
enum p9_trans_status status;
@@ -164,6 +181,11 @@ struct p9_client {
struct p9_idpool *tagpool;
struct p9_req_t *reqs[P9_ROW_MAXTAG];
int max_tag;
+
+ struct list_head sblist;
+ int sbcount;
+ struct list_head lblist;
+ int lbcount;
};
/**
@@ -257,6 +279,8 @@ void p9_client_cb(struct p9_client *c, struct p9_req_t *req);
int p9_parse_header(struct p9_fcall *, int32_t *, int8_t *, int16_t *, int);
int p9stat_read(char *, int, struct p9_wstat *, int);
void p9stat_free(struct p9_wstat *);
+struct p9_fcall *p9_fcall_alloc(struct p9_client *c, int size);
+void p9_fcall_free(struct p9_client *c, struct p9_fcall *fc);
int p9_is_proto_dotu(struct p9_client *clnt);
int p9_is_proto_dotl(struct p9_client *clnt);
@@ -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 */
@@ -32,12 +32,16 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
+#include <linux/highmem.h>
#include <net/9p/9p.h>
#include <linux/parser.h>
#include <net/9p/client.h>
#include <net/9p/transport.h>
#include "protocol.h"
+#define P9_FCALL_MINSZ 256
+#define P9_MAX_BUF 32
+
/*
* Client Option Parsing (code inspired by NFS code)
* - a little lazy - parse all client options
@@ -92,8 +96,8 @@ static int get_protocol_version(const substring_t *name)
return version;
}
-static struct p9_req_t *
-p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...);
+static struct p9_req_t *p9_client_rpc(struct p9_client *c, int8_t type,
+ struct p9_fcall *tc, struct p9_fcall *rc, const char *fmt, ...);
/**
* parse_options - parse mount options into client structure
@@ -229,29 +233,11 @@ static struct p9_req_t *p9_tag_alloc(struct p9_client *c, u16 tag)
return ERR_PTR(-ENOMEM);
}
init_waitqueue_head(req->wq);
- req->tc = kmalloc(sizeof(struct p9_fcall)+c->msize,
- GFP_KERNEL);
- req->rc = kmalloc(sizeof(struct p9_fcall)+c->msize,
- GFP_KERNEL);
- if ((!req->tc) || (!req->rc)) {
- printk(KERN_ERR "Couldn't grow tag array\n");
- kfree(req->tc);
- kfree(req->rc);
- kfree(req->wq);
- req->tc = req->rc = NULL;
- req->wq = NULL;
- 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;
+ req->tc = NULL;
+ req->rc = NULL;
}
- p9pdu_reset(req->tc);
- p9pdu_reset(req->rc);
-
- req->tc->tag = tag-1;
+ req->tag = tag - 1;
req->status = REQ_STATUS_ALLOC;
return &c->reqs[row][col];
@@ -357,14 +343,91 @@ static void p9_tag_cleanup(struct p9_client *c)
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);
+ int tag = r->tag;
+ P9_DPRINTK(P9_DEBUG_MUX, "clnt %p req %p tag: %d\n", c, r, tag);
+ p9_fcall_free(c, r->tc);
+ r->tc = NULL;
+ p9_fcall_free(c, r->rc);
+ r->rc = NULL;
r->status = REQ_STATUS_IDLE;
if (tag != P9_NOTAG && p9_idpool_check(tag, c->tagpool))
p9_idpool_put(tag, c->tagpool);
}
+struct p9_fcall *p9_fcall_alloc(struct p9_client *c, int size)
+{
+ unsigned long flags;
+ struct p9_fcall *fc;
+ int *count;
+ struct list_head *list;
+
+ fc = NULL;
+ if (size < P9_FCALL_MINSZ) {
+ size = P9_FCALL_MINSZ + c->maxsgcount*sizeof(struct scatterlist);
+ list = &c->sblist;
+ count = &c->sbcount;
+ } else {
+ size = c->msize;
+ list = &c->lblist;
+ count = &c->lbcount;
+ }
+
+ spin_lock_irqsave(&c->lock, flags);
+ if (*count > 0) {
+ fc = list_first_entry(list, struct p9_fcall, fclist);
+ list_del(&fc->fclist);
+ (*count)--;
+ }
+ spin_unlock_irqrestore(&c->lock, flags);
+ if (fc)
+ return fc;
+
+ /* no free fcalls, allocate one */
+ fc = kmalloc(sizeof(struct p9_fcall) + size, GFP_KERNEL);
+ if (!fc)
+ return NULL;
+
+ INIT_LIST_HEAD(&fc->fclist);
+ fc->capacity = size;
+ fc->buf = (uint8_t *) &fc[1];
+ fc->sgcount = 0;
+ fc->sg = NULL;
+
+ return fc;
+}
+EXPORT_SYMBOL(p9_fcall_alloc);
+
+void p9_fcall_free(struct p9_client *c, struct p9_fcall *fc)
+{
+ unsigned long flags;
+ int *count;
+ struct list_head *list;
+
+ if (!fc)
+ return;
+
+ fc->sgcount = 0;
+ fc->sg = NULL;
+ if (fc->capacity == c->msize) {
+ count = &c->lbcount;
+ list = &c->lblist;
+ } else {
+ count = &c->sbcount;
+ list = &c->sblist;
+ }
+
+ spin_lock_irqsave(&c->lock, flags);
+ if (*count < P9_MAX_BUF) {
+ list_add_tail(&fc->fclist, list);
+ fc = NULL;
+ (*count)++;
+ }
+ spin_unlock_irqrestore(&c->lock, flags);
+ kfree(fc);
+}
+EXPORT_SYMBOL(p9_fcall_free);
+
/**
* p9_client_cb - call back from transport to client
* c: client state
@@ -399,14 +462,12 @@ p9_parse_header(struct p9_fcall *pdu, int32_t *size, int8_t *type, int16_t *tag,
int err;
pdu->offset = 0;
- if (pdu->size == 0)
- pdu->size = 7;
-
err = p9pdu_readf(pdu, 0, "dbw", &r_size, &r_type, &r_tag);
if (err)
goto rewind_and_exit;
- pdu->size = r_size;
+/* we allow short reads
+ pdu->size = r_size; */
pdu->id = r_type;
pdu->tag = r_tag;
@@ -428,6 +489,44 @@ rewind_and_exit:
}
EXPORT_SYMBOL(p9_parse_header);
+static int p9_sg_read_error(struct p9_fcall *rc, int proto_version,
+ char **ename, int *ecode)
+{
+ int err, n, fecode;
+ 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;
+ fecode = (proto_version == p9_proto_2000u) ||
+ (proto_version == p9_proto_2000L);
+
+ if (fecode)
+ count += 4;
+
+ str = kmalloc(count + 1, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ n = sg_copy_to_buffer(rc->sg, rc->sgcount, str, count);
+ if (fecode) {
+ /* if we got short read, set ecode to -EINVAL */
+ if (n == count) {
+ *ecode = le32_to_cpu(*(__le32 *) &str[len]);
+ n -= 4;
+ } else
+ *ecode = -EINVAL;
+ }
+
+ str[n] = '\0';
+ *ename = str;
+ return 0;
+}
+
/**
* p9_check_errors - check 9p packet for error return and process it
* @c: current client instance
@@ -442,28 +541,50 @@ EXPORT_SYMBOL(p9_parse_header);
static int p9_check_errors(struct p9_client *c, struct p9_req_t *req)
{
int8_t type;
+ int32_t size;
int err;
+ struct p9_fcall *rc;
- err = p9_parse_header(req->rc, NULL, &type, NULL, 0);
+ rc = req->rc;
+ err = p9_parse_header(rc, &size, &type, NULL, 0);
if (err) {
P9_DPRINTK(P9_DEBUG_ERROR, "couldn't parse header %d\n", err);
return err;
}
- if (type == P9_RERROR) {
+ if (type == req->tc->id+1) {
+ if (size != rc->size) {
+ P9_DPRINTK(P9_DEBUG_ERROR, "short read: %d got %d\n",
+ rc->size, size);
+ err = -EINVAL;
+ } else
+ err = 0;
+ } else if (type == P9_RERROR) {
int ecode;
char *ename;
- err = p9pdu_readf(req->rc, c->proto_version, "s?d",
+ /* error handling is complicated because of the
+ * Tread/Rerror case if scatterlist is used */
+ if (rc->sg)
+ err = p9_sg_read_error(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);
- return err;
+ if (size != rc->size) {
+ ename = kstrdup("Error name too long",
+ GFP_KERNEL);
+ ecode = -EINVAL;
+ } else {
+ P9_DPRINTK(P9_DEBUG_ERROR,
+ "couldn't parse error%d\n", err);
+ return err;
+ }
}
- if (p9_is_proto_dotu(c) ||
- p9_is_proto_dotl(c))
+ if (p9_is_proto_dotu(c) || p9_is_proto_dotl(c))
err = -ecode;
if (!err || !IS_ERR_VALUE(err))
@@ -472,8 +593,11 @@ static int p9_check_errors(struct p9_client *c, struct p9_req_t *req)
P9_DPRINTK(P9_DEBUG_9P, "<<< RERROR (%d) %s\n", -ecode, ename);
kfree(ename);
- } else
- err = 0;
+ } else {
+ P9_DPRINTK(P9_DEBUG_ERROR, "mismatch: expected %d, got %d\n",
+ req->tc->id + 1, type);
+ err = -EINVAL;
+ }
return err;
}
@@ -502,7 +626,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, NULL, NULL, "w", oldtag);
if (IS_ERR(req))
return PTR_ERR(req);
@@ -522,13 +646,13 @@ 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
- * @fmt: protocol format string (see protocol.c)
+ * @tcfmt: request protocol format string (see protocol.c)
+ * @rcfmt: response protocol format string
*
- * 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, ...)
+static struct p9_req_t *p9_client_rpc(struct p9_client *c, int8_t type,
+ struct p9_fcall *tc, struct p9_fcall *rc, const char *fmt, ...)
{
va_list ap;
int tag, err;
@@ -563,13 +687,43 @@ p9_client_rpc(struct p9_client *c, int8_t type, const char *fmt, ...)
if (IS_ERR(req))
return req;
+ /* set up the outgoing buffer */
+ req->tc = tc;
+ if (!req->tc)
+ req->tc = p9_fcall_alloc(c, 0);
+
+ if (!req->tc) {
+ err = -ENOMEM;
+ goto reterr;
+ }
+ req->tc->tag = req->tag;
+
/* marshall the data */
- p9pdu_prepare(req->tc, tag, type);
va_start(ap, fmt);
+ p9pdu_prepare(req->tc, tag, type);
err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap);
- va_end(ap);
p9pdu_finalize(req->tc);
+ va_end(ap);
+ if (err<0 && !tc) {
+ /* the reason for the failure may be that the small buffer
+ was too small. Try with the large one */
+ p9_fcall_free(c, req->tc);
+ req->tc = p9_fcall_alloc(c, c->msize);
+ if (!req->tc) {
+ err = -ENOMEM;
+ goto reterr;
+ }
+ va_start(ap, fmt);
+ p9pdu_prepare(req->tc, tag, type);
+ err = p9pdu_vwritef(req->tc, c->proto_version, fmt, ap);
+ p9pdu_finalize(req->tc);
+ va_end(ap);
+ }
+
+ /* we'll let the transport to allocate the appropriate response buffer
+ in case it is not specified by the caller */
+ req->rc = rc;
err = c->trans_mod->request(c, req);
if (err < 0) {
c->status = Disconnected;
@@ -683,15 +837,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, NULL, NULL, "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, NULL, NULL, "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, NULL, NULL, "ds",
c->msize, "9P2000");
break;
default:
@@ -721,8 +875,10 @@ int p9_client_version(struct p9_client *c)
goto error;
}
- if (msize < c->msize)
+ if (msize < c->msize) {
c->msize = msize;
+ c->maxsgcount = c->msize/PAGE_SIZE + 2;
+ }
error:
kfree(version);
@@ -746,6 +902,10 @@ struct p9_client *p9_client_create(const char *dev_name, char *options)
clnt->trans = NULL;
spin_lock_init(&clnt->lock);
INIT_LIST_HEAD(&clnt->fidlist);
+ clnt->sbcount = 0;
+ INIT_LIST_HEAD(&clnt->sblist);
+ clnt->lbcount = 0;
+ INIT_LIST_HEAD(&clnt->lblist);
p9_tag_init(clnt);
@@ -780,6 +940,7 @@ struct p9_client *p9_client_create(const char *dev_name, char *options)
if ((clnt->msize+P9_IOHDRSZ) > clnt->trans_mod->maxsize)
clnt->msize = clnt->trans_mod->maxsize-P9_IOHDRSZ;
+ clnt->maxsgcount = clnt->msize/PAGE_SIZE + 2;
err = p9_client_version(clnt);
if (err)
goto close_trans;
@@ -856,7 +1017,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, NULL, NULL, "ddss?d", fid->fid,
afid ? afid->fid : P9_NOFID, uname, aname, n_uname);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -905,7 +1066,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, NULL, NULL, "dss?d",
afid ? afid->fid : P9_NOFID, uname, aname, n_uname);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -964,7 +1125,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, NULL, NULL, "ddT", oldfid->fid, fid->fid,
nwname, wnames);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1030,9 +1191,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, NULL, NULL, "dd", fid->fid, mode);
else
- req = p9_client_rpc(clnt, P9_TOPEN, "db", fid->fid, mode);
+ req = p9_client_rpc(clnt, P9_TOPEN, NULL, NULL, "db", fid->fid, mode);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1074,8 +1235,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, NULL, NULL, "dsddd", ofid->fid, name,
+ flags, mode, gid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1119,7 +1280,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, NULL, NULL, "dsdb?s", fid->fid, name, perm,
mode, extension);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1158,8 +1319,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, NULL, NULL, "dssd", dfid->fid,
+ name, symtgt, gid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1189,8 +1350,8 @@ 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,
- newname);
+ req = p9_client_rpc(clnt, P9_TLINK, NULL, NULL, "dds", dfid->fid,
+ oldfid->fid, newname);
if (IS_ERR(req))
return PTR_ERR(req);
@@ -1210,7 +1371,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, NULL, NULL, "d", fid->fid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1236,7 +1397,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, NULL, NULL, "d", fid->fid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1251,54 +1412,148 @@ error:
}
EXPORT_SYMBOL(p9_client_remove);
-int
-p9_client_read(struct p9_fid *fid, char *data, char __user *udata, u64 offset,
- u32 count)
+int p9_sg_prepare(struct p9_fcall *fc, int sz0, char *data,
+ const char __user *udata, u32 count, int rw)
{
- int err, rsize, total;
+ int i, m, err, off;
+ struct page *pages[64];
+
+ err = 0;
+ fc->sgcount = min(count/PAGE_SIZE + 3,
+ (fc->capacity - P9_FCALL_MINSZ) / sizeof(struct scatterlist));
+ fc->sg = (struct scatterlist *) &fc->buf[P9_FCALL_MINSZ];
+ sg_init_table(fc->sg, fc->sgcount);
+ sg_set_buf(&fc->sg[0], fc->buf, sz0);
+
+ if (udata) {
+ fc->sgcount = min(fc->sgcount, (int)(ARRAY_SIZE(pages) + 1));
+ err = get_user_pages_fast((unsigned long) udata, fc->sgcount-1,
+ rw, pages);
+ if (err < 0)
+ goto error;
+
+ off = ((unsigned long) udata) & ~PAGE_MASK;
+ for(i = 0; count > 0; off = 0, i++) {
+ m = min((int)(PAGE_SIZE - off), (int) count);
+ sg_set_page(&fc->sg[i+1], pages[i], m, off);
+ count -= m;
+ }
+ } else {
+ off = ((unsigned long) data) & ~PAGE_MASK;
+ for(i = 0; count > 0; off = 0, i++) {
+ m = min((int)(PAGE_SIZE - off), (int) count);
+ sg_set_buf(&fc->sg[i+1], data, m);
+ count -= m;
+ data += m;
+ }
+ }
+
+ sg_mark_end(&fc->sg[i]);
+ fc->sgcount = i+1;
+ fc->capacity = P9_FCALL_MINSZ;
+ return 0;
+
+error:
+ fc->sg = NULL;
+ fc->sgcount = 0;
+ return err;
+}
+
+void p9_sg_release(struct p9_client *c, struct p9_fcall *fc, char *data,
+ const char __user *udata)
+{
+ struct scatterlist *sg;
+ struct page *p;
+
+ if (!fc || !fc->sg)
+ return;
+
+ if (udata) {
+ /* fc->sg[0] is for the header and pointing to kernel space */
+ for(sg=&fc->sg[1]; sg != NULL; sg = sg_next(sg)) {
+ p = sg_page(sg);
+ kunmap(p);
+ put_page(p);
+ }
+ }
+
+ fc->sgcount = 0;
+ fc->sg = NULL;
+ fc->capacity = 256 + c->maxsgcount*sizeof(struct scatterlist);
+}
+
+int p9_client_read(struct p9_fid *fid, char *data, char __user *udata,
+ u64 offset, u32 count)
+{
+ int err, sgsupport;
struct p9_client *clnt;
struct p9_req_t *req;
+ struct p9_fcall *rc;
char *dataptr;
P9_DPRINTK(P9_DEBUG_9P, ">>> TREAD fid %d offset %llu %d\n", fid->fid,
(long long unsigned) offset, count);
+ rc = NULL;
err = 0;
clnt = fid->clnt;
- total = 0;
-
- rsize = fid->iounit;
- if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
- rsize = clnt->msize - P9_IOHDRSZ;
+ if (count > clnt->msize-P9_IOHDRSZ)
+ count = clnt->msize-P9_IOHDRSZ;
+
+ if (fid->iounit>0 && count>fid->iounit)
+ count = fid->iounit;
+
+ sgsupport = p9_trans_sg_support(clnt->trans_mod);
+ if (sgsupport) {
+ rc = p9_fcall_alloc(clnt, 0);
+ if (!rc)
+ return -ENOMEM;
+
+ /* don't use scatterlist for small buffers */
+ if (count >= 256) {
+ err = p9_sg_prepare(rc, P9_RREAD_HDRSZ, data, udata,
+ count, 1);
+ if (err < 0) {
+ p9_fcall_free(clnt, rc);
+ return err;
+ }
+ }
+ }
- if (count < rsize)
- rsize = count;
+ req = p9_client_rpc(clnt, P9_TREAD, NULL, rc, "dqd", fid->fid, offset,
+ count);
- req = p9_client_rpc(clnt, P9_TREAD, "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);
+ P9_DPRINTK(P9_DEBUG_9P, "<<< RREAD count %d\n", count);
+
+ rc = req->rc;
+ if (rc->sg)
+ 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;
}
- P9_DPRINTK(P9_DEBUG_9P, "<<< RREAD count %d\n", count);
-
- if (data) {
- memmove(data, dataptr, count);
- }
+ if (!rc->sg) {
+ 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;
+ }
}
}
+ p9_sg_release(clnt, rc, data, udata);
p9_free_req(clnt, req);
return count;
@@ -1313,28 +1568,54 @@ int
p9_client_write(struct p9_fid *fid, char *data, const char __user *udata,
u64 offset, u32 count)
{
- int err, rsize, total;
+ int err, sgsupport;
struct p9_client *clnt;
struct p9_req_t *req;
+ struct p9_fcall *tc;
P9_DPRINTK(P9_DEBUG_9P, ">>> TWRITE fid %d offset %llu count %d\n",
fid->fid, (long long unsigned) offset, count);
+ tc = NULL;
err = 0;
clnt = fid->clnt;
- total = 0;
- rsize = fid->iounit;
- if (!rsize || rsize > clnt->msize-P9_IOHDRSZ)
- rsize = clnt->msize - P9_IOHDRSZ;
+ if (count > clnt->msize-P9_IOHDRSZ)
+ count = clnt->msize-P9_IOHDRSZ;
+
+ if (fid->iounit>0 && count>fid->iounit)
+ count = fid->iounit;
+
+ sgsupport = p9_trans_sg_support(clnt->trans_mod);
+ if (sgsupport) {
+ tc = p9_fcall_alloc(clnt, 0);
+ if (!tc)
+ return -ENOMEM;
+
+ /* don't use scatterlist for small buffers */
+ if (count >= 256) {
+ err = p9_sg_prepare(tc, P9_TWRITE_HDRSZ, data, udata,
+ count, 0);
+ if (err < 0) {
+ p9_fcall_free(clnt, tc);
+ return err;
+ }
+ }
+ }
+
+ if (tc && tc->sg) {
+ /* the data is already put in the appropriate place in sg,
+ just put the fid, the offset and the count */
+ req = p9_client_rpc(clnt, P9_TWRITE, tc, NULL, "dqd", fid->fid,
+ offset, count);
+ } else {
+ if (data)
+ req = p9_client_rpc(clnt, P9_TWRITE, NULL, NULL, "dqD",
+ fid->fid, offset, count, data);
+ else
+ req = p9_client_rpc(clnt, P9_TWRITE, NULL, NULL, "dqU",
+ fid->fid, offset, count, udata);
+ }
- if (count < rsize)
- rsize = count;
- if (data)
- req = p9_client_rpc(clnt, P9_TWRITE, "dqD", fid->fid, offset,
- rsize, data);
- else
- req = p9_client_rpc(clnt, P9_TWRITE, "dqU", fid->fid, offset,
- rsize, udata);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1348,6 +1629,7 @@ p9_client_write(struct p9_fid *fid, char *data, const char __user *udata,
P9_DPRINTK(P9_DEBUG_9P, "<<< RWRITE count %d\n", count);
+ p9_sg_release(clnt, req->tc, data, udata);
p9_free_req(clnt, req);
return count;
@@ -1374,7 +1656,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, NULL, NULL, "d", fid->fid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1425,7 +1707,8 @@ 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, NULL, NULL, "dq", fid->fid,
+ request_mask);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1516,7 +1799,8 @@ 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, NULL, NULL, "dwS", fid->fid,
+ wst->size+2, wst);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1547,7 +1831,8 @@ 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, NULL, NULL, "dI", fid->fid,
+ p9attr);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1571,7 +1856,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, NULL, NULL, "d", fid->fid);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1611,7 +1896,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, NULL, NULL, "dds", fid->fid,
newdirfid->fid, name);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1649,7 +1934,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, NULL, NULL, "dds",
file_fid->fid, attr_fid->fid, attr_name);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1688,7 +1973,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, NULL, NULL, "dsqd",
fid->fid, name, attr_size, flags);
if (IS_ERR(req)) {
err = PTR_ERR(req);
@@ -1722,7 +2007,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, NULL, NULL, "dqd", fid->fid,
+ offset, rsize);
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto error;
@@ -1760,8 +2046,8 @@ 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,
- MAJOR(rdev), MINOR(rdev), gid);
+ req = p9_client_rpc(clnt, P9_TMKNOD, NULL, NULL, "dsdddd", fid->fid,
+ name, mode, MAJOR(rdev), MINOR(rdev), gid);
if (IS_ERR(req))
return PTR_ERR(req);
@@ -1791,8 +2077,8 @@ 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,
- gid);
+ req = p9_client_rpc(clnt, P9_TMKDIR, NULL, NULL, "dsdd", fid->fid,
+ name, mode, gid);
if (IS_ERR(req))
return PTR_ERR(req);
@@ -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->buf;
int datalen = pdu->size;
char buf[255];
int buflen = 255;
@@ -104,30 +105,38 @@ 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);
- pdu->offset += len;
- return size - len;
+ if (pdu->size-pdu->offset < size)
+ return 1;
+
+ memcpy(data, &pdu->buf[pdu->offset], size);
+ pdu->offset += size;
+ return 0;
}
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);
- pdu->size += len;
- return size - len;
+ if (pdu->capacity-pdu->offset < size)
+ return 1;
+
+ memcpy(&pdu->buf[pdu->offset], data, size);
+ pdu->offset += size;
+ return 0;
}
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;
+
+ if (pdu->capacity-pdu->offset < size)
+ return 1;
+
+ err = copy_from_user(&pdu->buf[pdu->offset], udata, size);
if (err)
- printk(KERN_WARNING "pdu_write_u returning: %d\n", err);
+ return 1;
- pdu->size += len;
- return size - len;
+ pdu->offset += size;
+ return 0;
}
/*
@@ -259,7 +268,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->buf[pdu->offset];
}
}
break;
@@ -582,7 +591,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.buf = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, proto_version, "S", st);
@@ -597,16 +606,25 @@ EXPORT_SYMBOL(p9stat_read);
int p9pdu_prepare(struct p9_fcall *pdu, int16_t tag, int8_t type)
{
+ pdu->id = type;
+ pdu->offset = 0;
return p9pdu_writef(pdu, 0, "dbw", 0, type, tag);
}
int p9pdu_finalize(struct p9_fcall *pdu)
{
- int size = pdu->size;
+ int size;
int err;
+ struct scatterlist *sg;
- pdu->size = 0;
+ size = pdu->offset;
+ if (pdu->sg) {
+ for(sg = &pdu->sg[1]; sg != NULL; sg = sg_next(sg))
+ size += sg->length;
+ }
+ pdu->offset = 0;
err = p9pdu_writef(pdu, 0, "d", size);
+ pdu->offset = size;
pdu->size = size;
#ifdef CONFIG_NET_9P_DEBUG
@@ -635,7 +653,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.buf = buf;
fake_pdu.offset = 0;
ret = p9pdu_readf(&fake_pdu, proto_version, "Qqbs", &dirent->qid,
@@ -32,3 +32,6 @@ 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);
+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);
@@ -347,16 +347,15 @@ static void p9_read_work(struct work_struct *work)
goto error;
}
- if (m->req->rc == NULL) {
- m->req->rc = kmalloc(sizeof(struct p9_fcall) +
- m->client->msize, GFP_KERNEL);
- if (!m->req->rc) {
- m->req = NULL;
- err = -ENOMEM;
- goto error;
- }
+ /* trans_fd doesn't support scatterlists, rc can't e non-NULL */
+ BUG_ON(m->req->rc != NULL);
+ m->req->rc = p9_fcall_alloc(m->client, n);
+ if (!m->req->rc) {
+ m->req = NULL;
+ err = -ENOMEM;
+ goto error;
}
- m->rbuf = (char *)m->req->rc + sizeof(struct p9_fcall);
+ m->rbuf = (char *)m->req->rc->buf;
memcpy(m->rbuf, m->tmp_buf, m->rsize);
m->rsize = n;
}
@@ -364,6 +363,7 @@ static void p9_read_work(struct work_struct *work)
/* not an else because some packets (like clunk) have no payload */
if ((m->req) && (m->rpos == m->rsize)) { /* packet is read in */
P9_DPRINTK(P9_DEBUG_TRANS, "got new packet\n");
+ m->req->rc->size = m->rsize;
spin_lock(&m->client->lock);
if (m->req->status != REQ_STATUS_ERROR)
m->req->status = REQ_STATUS_RCVD;
@@ -462,7 +462,7 @@ static void p9_write_work(struct work_struct *work)
P9_DPRINTK(P9_DEBUG_TRANS, "move req %p\n", req);
list_move_tail(&req->req_list, &m->req_list);
- m->wbuf = req->tc->sdata;
+ m->wbuf = req->tc->buf;
m->wsize = req->tc->size;
m->wpos = 0;
spin_unlock(&m->client->lock);
@@ -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
*/