Adds support for scatterlists for the transports. If the transport indicates that it can handle scatterlists (P9_TRANS_SG in the flags field), it should check the sg field in the p9_fcall structs. If sg field is non-NULL, the transport should write/read the 9P message into the memory pointed by the scatterlist. The protocol and client code prepares the transmitting/receiving buffers so the data is directly written from/read to the user data buffer. Signed-off-by: Latchesar Ionkov <lucho@xxxxxxxxxx> --- include/net/9p/9p.h | 20 ++- include/net/9p/client.h | 24 ++ include/net/9p/transport.h | 11 + net/9p/client.c | 512 ++++++++++++++++++++++++++++++++++---------- net/9p/protocol.c | 56 ++++-- net/9p/protocol.h | 3 + net/9p/trans_fd.c | 20 +- net/9p/util.c | 2 +- 8 files changed, 502 insertions(+), 146 deletions(-) diff --git a/include/net/9p/9p.h b/include/net/9p/9p.h index a8de812..871e90d 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 @@ -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; diff --git a/include/net/9p/client.h b/include/net/9p/client.h index d1aa2cf..ccc3ab7 100644 --- a/include/net/9p/client.h +++ b/include/net/9p/client.h @@ -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); 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..8536411 100644 --- a/net/9p/client.c +++ b/net/9p/client.c @@ -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); diff --git a/net/9p/protocol.c b/net/9p/protocol.c index 3acd3af..36ab7bf 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->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, diff --git a/net/9p/protocol.h b/net/9p/protocol.h index 2431c0f..09da1a7 100644 --- a/net/9p/protocol.h +++ b/net/9p/protocol.h @@ -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); diff --git a/net/9p/trans_fd.c b/net/9p/trans_fd.c index c85109d..9843376 100644 --- a/net/9p/trans_fd.c +++ b/net/9p/trans_fd.c @@ -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); 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 */ -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html