ARM64: dts: rockchip: add rkvpu & vpu node in rk3399 android next dtsi
[firefly-linux-kernel-4.4.55.git] / net / sunrpc / xdr.c
index 1504bb11e4f351d1a79835fa990ac8f22751faf3..4439ac4c1b53fcaf12a8a06723b4a51330dddeae 100644 (file)
@@ -462,6 +462,7 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
        struct kvec *iov = buf->head;
        int scratch_len = buf->buflen - buf->page_len - buf->tail[0].iov_len;
 
+       xdr_set_scratch_buffer(xdr, NULL, 0);
        BUG_ON(scratch_len < 0);
        xdr->buf = buf;
        xdr->iov = iov;
@@ -481,6 +482,74 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
 }
 EXPORT_SYMBOL_GPL(xdr_init_encode);
 
+/**
+ * xdr_commit_encode - Ensure all data is written to buffer
+ * @xdr: pointer to xdr_stream
+ *
+ * We handle encoding across page boundaries by giving the caller a
+ * temporary location to write to, then later copying the data into
+ * place; xdr_commit_encode does that copying.
+ *
+ * Normally the caller doesn't need to call this directly, as the
+ * following xdr_reserve_space will do it.  But an explicit call may be
+ * required at the end of encoding, or any other time when the xdr_buf
+ * data might be read.
+ */
+void xdr_commit_encode(struct xdr_stream *xdr)
+{
+       int shift = xdr->scratch.iov_len;
+       void *page;
+
+       if (shift == 0)
+               return;
+       page = page_address(*xdr->page_ptr);
+       memcpy(xdr->scratch.iov_base, page, shift);
+       memmove(page, page + shift, (void *)xdr->p - page);
+       xdr->scratch.iov_len = 0;
+}
+EXPORT_SYMBOL_GPL(xdr_commit_encode);
+
+static __be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr,
+               size_t nbytes)
+{
+       static __be32 *p;
+       int space_left;
+       int frag1bytes, frag2bytes;
+
+       if (nbytes > PAGE_SIZE)
+               return NULL; /* Bigger buffers require special handling */
+       if (xdr->buf->len + nbytes > xdr->buf->buflen)
+               return NULL; /* Sorry, we're totally out of space */
+       frag1bytes = (xdr->end - xdr->p) << 2;
+       frag2bytes = nbytes - frag1bytes;
+       if (xdr->iov)
+               xdr->iov->iov_len += frag1bytes;
+       else
+               xdr->buf->page_len += frag1bytes;
+       xdr->page_ptr++;
+       xdr->iov = NULL;
+       /*
+        * If the last encode didn't end exactly on a page boundary, the
+        * next one will straddle boundaries.  Encode into the next
+        * page, then copy it back later in xdr_commit_encode.  We use
+        * the "scratch" iov to track any temporarily unused fragment of
+        * space at the end of the previous buffer:
+        */
+       xdr->scratch.iov_base = xdr->p;
+       xdr->scratch.iov_len = frag1bytes;
+       p = page_address(*xdr->page_ptr);
+       /*
+        * Note this is where the next encode will start after we've
+        * shifted this one back:
+        */
+       xdr->p = (void *)p + frag2bytes;
+       space_left = xdr->buf->buflen - xdr->buf->len;
+       xdr->end = (void *)p + min_t(int, space_left, PAGE_SIZE);
+       xdr->buf->page_len += frag2bytes;
+       xdr->buf->len += nbytes;
+       return p;
+}
+
 /**
  * xdr_reserve_space - Reserve buffer space for sending
  * @xdr: pointer to xdr_stream
@@ -495,19 +564,122 @@ __be32 * xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes)
        __be32 *p = xdr->p;
        __be32 *q;
 
+       xdr_commit_encode(xdr);
        /* align nbytes on the next 32-bit boundary */
        nbytes += 3;
        nbytes &= ~3;
        q = p + (nbytes >> 2);
        if (unlikely(q > xdr->end || q < p))
-               return NULL;
+               return xdr_get_next_encode_buffer(xdr, nbytes);
        xdr->p = q;
-       xdr->iov->iov_len += nbytes;
+       if (xdr->iov)
+               xdr->iov->iov_len += nbytes;
+       else
+               xdr->buf->page_len += nbytes;
        xdr->buf->len += nbytes;
        return p;
 }
 EXPORT_SYMBOL_GPL(xdr_reserve_space);
 
+/**
+ * xdr_truncate_encode - truncate an encode buffer
+ * @xdr: pointer to xdr_stream
+ * @len: new length of buffer
+ *
+ * Truncates the xdr stream, so that xdr->buf->len == len,
+ * and xdr->p points at offset len from the start of the buffer, and
+ * head, tail, and page lengths are adjusted to correspond.
+ *
+ * If this means moving xdr->p to a different buffer, we assume that
+ * that the end pointer should be set to the end of the current page,
+ * except in the case of the head buffer when we assume the head
+ * buffer's current length represents the end of the available buffer.
+ *
+ * This is *not* safe to use on a buffer that already has inlined page
+ * cache pages (as in a zero-copy server read reply), except for the
+ * simple case of truncating from one position in the tail to another.
+ *
+ */
+void xdr_truncate_encode(struct xdr_stream *xdr, size_t len)
+{
+       struct xdr_buf *buf = xdr->buf;
+       struct kvec *head = buf->head;
+       struct kvec *tail = buf->tail;
+       int fraglen;
+       int new;
+
+       if (len > buf->len) {
+               WARN_ON_ONCE(1);
+               return;
+       }
+       xdr_commit_encode(xdr);
+
+       fraglen = min_t(int, buf->len - len, tail->iov_len);
+       tail->iov_len -= fraglen;
+       buf->len -= fraglen;
+       if (tail->iov_len) {
+               xdr->p = tail->iov_base + tail->iov_len;
+               WARN_ON_ONCE(!xdr->end);
+               WARN_ON_ONCE(!xdr->iov);
+               return;
+       }
+       WARN_ON_ONCE(fraglen);
+       fraglen = min_t(int, buf->len - len, buf->page_len);
+       buf->page_len -= fraglen;
+       buf->len -= fraglen;
+
+       new = buf->page_base + buf->page_len;
+
+       xdr->page_ptr = buf->pages + (new >> PAGE_SHIFT);
+
+       if (buf->page_len) {
+               xdr->p = page_address(*xdr->page_ptr);
+               xdr->end = (void *)xdr->p + PAGE_SIZE;
+               xdr->p = (void *)xdr->p + (new % PAGE_SIZE);
+               WARN_ON_ONCE(xdr->iov);
+               return;
+       }
+       if (fraglen) {
+               xdr->end = head->iov_base + head->iov_len;
+               xdr->page_ptr--;
+       }
+       /* (otherwise assume xdr->end is already set) */
+       head->iov_len = len;
+       buf->len = len;
+       xdr->p = head->iov_base + head->iov_len;
+       xdr->iov = buf->head;
+}
+EXPORT_SYMBOL(xdr_truncate_encode);
+
+/**
+ * xdr_restrict_buflen - decrease available buffer space
+ * @xdr: pointer to xdr_stream
+ * @newbuflen: new maximum number of bytes available
+ *
+ * Adjust our idea of how much space is available in the buffer.
+ * If we've already used too much space in the buffer, returns -1.
+ * If the available space is already smaller than newbuflen, returns 0
+ * and does nothing.  Otherwise, adjusts xdr->buf->buflen to newbuflen
+ * and ensures xdr->end is set at most offset newbuflen from the start
+ * of the buffer.
+ */
+int xdr_restrict_buflen(struct xdr_stream *xdr, int newbuflen)
+{
+       struct xdr_buf *buf = xdr->buf;
+       int left_in_this_buf = (void *)xdr->end - (void *)xdr->p;
+       int end_offset = buf->len + left_in_this_buf;
+
+       if (newbuflen < 0 || newbuflen < buf->len)
+               return -1;
+       if (newbuflen > buf->buflen)
+               return 0;
+       if (newbuflen < end_offset)
+               xdr->end = (void *)xdr->end + newbuflen - end_offset;
+       buf->buflen = newbuflen;
+       return 0;
+}
+EXPORT_SYMBOL(xdr_restrict_buflen);
+
 /**
  * xdr_write_pages - Insert a list of pages into an XDR buffer for sending
  * @xdr: pointer to xdr_stream
@@ -833,8 +1005,20 @@ xdr_buf_from_iov(struct kvec *iov, struct xdr_buf *buf)
 }
 EXPORT_SYMBOL_GPL(xdr_buf_from_iov);
 
-/* Sets subbuf to the portion of buf of length len beginning base bytes
- * from the start of buf. Returns -1 if base of length are out of bounds. */
+/**
+ * xdr_buf_subsegment - set subbuf to a portion of buf
+ * @buf: an xdr buffer
+ * @subbuf: the result buffer
+ * @base: beginning of range in bytes
+ * @len: length of range in bytes
+ *
+ * sets @subbuf to an xdr buffer representing the portion of @buf of
+ * length @len starting at offset @base.
+ *
+ * @buf and @subbuf may be pointers to the same struct xdr_buf.
+ *
+ * Returns -1 if base of length are out of bounds.
+ */
 int
 xdr_buf_subsegment(struct xdr_buf *buf, struct xdr_buf *subbuf,
                        unsigned int base, unsigned int len)
@@ -847,9 +1031,8 @@ xdr_buf_subsegment(struct xdr_buf *buf, struct xdr_buf *subbuf,
                len -= subbuf->head[0].iov_len;
                base = 0;
        } else {
-               subbuf->head[0].iov_base = NULL;
-               subbuf->head[0].iov_len = 0;
                base -= buf->head[0].iov_len;
+               subbuf->head[0].iov_len = 0;
        }
 
        if (base < buf->page_len) {
@@ -871,9 +1054,8 @@ xdr_buf_subsegment(struct xdr_buf *buf, struct xdr_buf *subbuf,
                len -= subbuf->tail[0].iov_len;
                base = 0;
        } else {
-               subbuf->tail[0].iov_base = NULL;
-               subbuf->tail[0].iov_len = 0;
                base -= buf->tail[0].iov_len;
+               subbuf->tail[0].iov_len = 0;
        }
 
        if (base || len)