]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - net/sunrpc/xdr.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / net / sunrpc / xdr.c
index cd9e841e7492ceecdd23859a7ec532733371293c..679cd674b81dcd878362c89262a6b4949d04a364 100644 (file)
@@ -552,6 +552,74 @@ void xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int b
 }
 EXPORT_SYMBOL_GPL(xdr_write_pages);
 
+static void xdr_set_iov(struct xdr_stream *xdr, struct kvec *iov,
+               __be32 *p, unsigned int len)
+{
+       if (len > iov->iov_len)
+               len = iov->iov_len;
+       if (p == NULL)
+               p = (__be32*)iov->iov_base;
+       xdr->p = p;
+       xdr->end = (__be32*)(iov->iov_base + len);
+       xdr->iov = iov;
+       xdr->page_ptr = NULL;
+}
+
+static int xdr_set_page_base(struct xdr_stream *xdr,
+               unsigned int base, unsigned int len)
+{
+       unsigned int pgnr;
+       unsigned int maxlen;
+       unsigned int pgoff;
+       unsigned int pgend;
+       void *kaddr;
+
+       maxlen = xdr->buf->page_len;
+       if (base >= maxlen)
+               return -EINVAL;
+       maxlen -= base;
+       if (len > maxlen)
+               len = maxlen;
+
+       base += xdr->buf->page_base;
+
+       pgnr = base >> PAGE_SHIFT;
+       xdr->page_ptr = &xdr->buf->pages[pgnr];
+       kaddr = page_address(*xdr->page_ptr);
+
+       pgoff = base & ~PAGE_MASK;
+       xdr->p = (__be32*)(kaddr + pgoff);
+
+       pgend = pgoff + len;
+       if (pgend > PAGE_SIZE)
+               pgend = PAGE_SIZE;
+       xdr->end = (__be32*)(kaddr + pgend);
+       xdr->iov = NULL;
+       return 0;
+}
+
+static void xdr_set_next_page(struct xdr_stream *xdr)
+{
+       unsigned int newbase;
+
+       newbase = (1 + xdr->page_ptr - xdr->buf->pages) << PAGE_SHIFT;
+       newbase -= xdr->buf->page_base;
+
+       if (xdr_set_page_base(xdr, newbase, PAGE_SIZE) < 0)
+               xdr_set_iov(xdr, xdr->buf->tail, NULL, xdr->buf->len);
+}
+
+static bool xdr_set_next_buffer(struct xdr_stream *xdr)
+{
+       if (xdr->page_ptr != NULL)
+               xdr_set_next_page(xdr);
+       else if (xdr->iov == xdr->buf->head) {
+               if (xdr_set_page_base(xdr, 0, PAGE_SIZE) < 0)
+                       xdr_set_iov(xdr, xdr->buf->tail, NULL, xdr->buf->len);
+       }
+       return xdr->p != xdr->end;
+}
+
 /**
  * xdr_init_decode - Initialize an xdr_stream for decoding data.
  * @xdr: pointer to xdr_stream struct
@@ -560,41 +628,67 @@ EXPORT_SYMBOL_GPL(xdr_write_pages);
  */
 void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
 {
-       struct kvec *iov = buf->head;
-       unsigned int len = iov->iov_len;
-
-       if (len > buf->len)
-               len = buf->len;
        xdr->buf = buf;
-       xdr->iov = iov;
-       xdr->p = p;
-       xdr->end = (__be32 *)((char *)iov->iov_base + len);
+       xdr->scratch.iov_base = NULL;
+       xdr->scratch.iov_len = 0;
+       if (buf->head[0].iov_len != 0)
+               xdr_set_iov(xdr, buf->head, p, buf->len);
+       else if (buf->page_len != 0)
+               xdr_set_page_base(xdr, 0, buf->len);
 }
 EXPORT_SYMBOL_GPL(xdr_init_decode);
 
-/**
- * xdr_inline_peek - Allow read-ahead in the XDR data stream
- * @xdr: pointer to xdr_stream struct
- * @nbytes: number of bytes of data to decode
- *
- * Check if the input buffer is long enough to enable us to decode
- * 'nbytes' more bytes of data starting at the current position.
- * If so return the current pointer without updating the current
- * pointer position.
- */
-__be32 * xdr_inline_peek(struct xdr_stream *xdr, size_t nbytes)
+static __be32 * __xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
 {
        __be32 *p = xdr->p;
        __be32 *q = p + XDR_QUADLEN(nbytes);
 
        if (unlikely(q > xdr->end || q < p))
                return NULL;
+       xdr->p = q;
        return p;
 }
-EXPORT_SYMBOL_GPL(xdr_inline_peek);
 
 /**
- * xdr_inline_decode - Retrieve non-page XDR data to decode
+ * xdr_set_scratch_buffer - Attach a scratch buffer for decoding data.
+ * @xdr: pointer to xdr_stream struct
+ * @buf: pointer to an empty buffer
+ * @buflen: size of 'buf'
+ *
+ * The scratch buffer is used when decoding from an array of pages.
+ * If an xdr_inline_decode() call spans across page boundaries, then
+ * we copy the data into the scratch buffer in order to allow linear
+ * access.
+ */
+void xdr_set_scratch_buffer(struct xdr_stream *xdr, void *buf, size_t buflen)
+{
+       xdr->scratch.iov_base = buf;
+       xdr->scratch.iov_len = buflen;
+}
+EXPORT_SYMBOL_GPL(xdr_set_scratch_buffer);
+
+static __be32 *xdr_copy_to_scratch(struct xdr_stream *xdr, size_t nbytes)
+{
+       __be32 *p;
+       void *cpdest = xdr->scratch.iov_base;
+       size_t cplen = (char *)xdr->end - (char *)xdr->p;
+
+       if (nbytes > xdr->scratch.iov_len)
+               return NULL;
+       memcpy(cpdest, xdr->p, cplen);
+       cpdest += cplen;
+       nbytes -= cplen;
+       if (!xdr_set_next_buffer(xdr))
+               return NULL;
+       p = __xdr_inline_decode(xdr, nbytes);
+       if (p == NULL)
+               return NULL;
+       memcpy(cpdest, p, nbytes);
+       return xdr->scratch.iov_base;
+}
+
+/**
+ * xdr_inline_decode - Retrieve XDR data to decode
  * @xdr: pointer to xdr_stream struct
  * @nbytes: number of bytes of data to decode
  *
@@ -605,13 +699,16 @@ EXPORT_SYMBOL_GPL(xdr_inline_peek);
  */
 __be32 * xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)
 {
-       __be32 *p = xdr->p;
-       __be32 *q = p + XDR_QUADLEN(nbytes);
+       __be32 *p;
 
-       if (unlikely(q > xdr->end || q < p))
+       if (nbytes == 0)
+               return xdr->p;
+       if (xdr->p == xdr->end && !xdr_set_next_buffer(xdr))
                return NULL;
-       xdr->p = q;
-       return p;
+       p = __xdr_inline_decode(xdr, nbytes);
+       if (p != NULL)
+               return p;
+       return xdr_copy_to_scratch(xdr, nbytes);
 }
 EXPORT_SYMBOL_GPL(xdr_inline_decode);
 
@@ -671,16 +768,12 @@ EXPORT_SYMBOL_GPL(xdr_read_pages);
  */
 void xdr_enter_page(struct xdr_stream *xdr, unsigned int len)
 {
-       char * kaddr = page_address(xdr->buf->pages[0]);
        xdr_read_pages(xdr, len);
        /*
         * Position current pointer at beginning of tail, and
         * set remaining message length.
         */
-       if (len > PAGE_CACHE_SIZE - xdr->buf->page_base)
-               len = PAGE_CACHE_SIZE - xdr->buf->page_base;
-       xdr->p = (__be32 *)(kaddr + xdr->buf->page_base);
-       xdr->end = (__be32 *)((char *)xdr->p + len);
+       xdr_set_page_base(xdr, 0, len);
 }
 EXPORT_SYMBOL_GPL(xdr_enter_page);