SUNRPC: Add an xdr_align_data() function
authorAnna Schumaker <Anna.Schumaker@Netapp.com>
Tue, 21 Apr 2020 15:27:00 +0000 (11:27 -0400)
committerAnna Schumaker <Anna.Schumaker@Netapp.com>
Wed, 7 Oct 2020 18:28:40 +0000 (14:28 -0400)
For now, this function simply aligns the data at the beginning of the
pages. This can eventually be expanded to shift data to the correct
offsets when we're ready.

Signed-off-by: Anna Schumaker <Anna.Schumaker@Netapp.com>
include/linux/sunrpc/xdr.h
net/sunrpc/xdr.c

index f9636d2a6d54e1543267eb1b6edcb11406d0de46..fe7ff7f5b58400964713ca45f42684b034057f47 100644 (file)
@@ -250,6 +250,7 @@ extern __be32 *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes);
 extern unsigned int xdr_read_pages(struct xdr_stream *xdr, unsigned int len);
 extern void xdr_enter_page(struct xdr_stream *xdr, unsigned int len);
 extern int xdr_process_buf(struct xdr_buf *buf, unsigned int offset, unsigned int len, int (*actor)(struct scatterlist *, void *), void *data);
+extern uint64_t xdr_align_data(struct xdr_stream *, uint64_t, uint32_t);
 extern uint64_t xdr_expand_hole(struct xdr_stream *, uint64_t, uint64_t);
 
 /**
index 1052ccdb4e996720eb777ae1f4b88e85f5166b2a..3feff529a7646282f704e300ea1bfa73552fe14f 100644 (file)
@@ -19,6 +19,9 @@
 #include <linux/bvec.h>
 #include <trace/events/sunrpc.h>
 
+static void _copy_to_pages(struct page **, size_t, const char *, size_t);
+
+
 /*
  * XDR functions for basic NFS types
  */
@@ -201,6 +204,88 @@ EXPORT_SYMBOL_GPL(xdr_inline_pages);
  * Helper routines for doing 'memmove' like operations on a struct xdr_buf
  */
 
+/**
+ * _shift_data_left_pages
+ * @pages: vector of pages containing both the source and dest memory area.
+ * @pgto_base: page vector address of destination
+ * @pgfrom_base: page vector address of source
+ * @len: number of bytes to copy
+ *
+ * Note: the addresses pgto_base and pgfrom_base are both calculated in
+ *       the same way:
+ *            if a memory area starts at byte 'base' in page 'pages[i]',
+ *            then its address is given as (i << PAGE_CACHE_SHIFT) + base
+ * Alse note: pgto_base must be < pgfrom_base, but the memory areas
+ *     they point to may overlap.
+ */
+static void
+_shift_data_left_pages(struct page **pages, size_t pgto_base,
+                       size_t pgfrom_base, size_t len)
+{
+       struct page **pgfrom, **pgto;
+       char *vfrom, *vto;
+       size_t copy;
+
+       BUG_ON(pgfrom_base <= pgto_base);
+
+       pgto = pages + (pgto_base >> PAGE_SHIFT);
+       pgfrom = pages + (pgfrom_base >> PAGE_SHIFT);
+
+       pgto_base &= ~PAGE_MASK;
+       pgfrom_base &= ~PAGE_MASK;
+
+       do {
+               if (pgto_base >= PAGE_SIZE) {
+                       pgto_base = 0;
+                       pgto++;
+               }
+               if (pgfrom_base >= PAGE_SIZE){
+                       pgfrom_base = 0;
+                       pgfrom++;
+               }
+
+               copy = len;
+               if (copy > (PAGE_SIZE - pgto_base))
+                       copy = PAGE_SIZE - pgto_base;
+               if (copy > (PAGE_SIZE - pgfrom_base))
+                       copy = PAGE_SIZE - pgfrom_base;
+
+               vto = kmap_atomic(*pgto);
+               if (*pgto != *pgfrom) {
+                       vfrom = kmap_atomic(*pgfrom);
+                       memcpy(vto + pgto_base, vfrom + pgfrom_base, copy);
+                       kunmap_atomic(vfrom);
+               } else
+                       memmove(vto + pgto_base, vto + pgfrom_base, copy);
+               flush_dcache_page(*pgto);
+               kunmap_atomic(vto);
+
+               pgto_base += copy;
+               pgfrom_base += copy;
+
+       } while ((len -= copy) != 0);
+}
+
+static void
+_shift_data_left_tail(struct xdr_buf *buf, unsigned int pgto, size_t len)
+{
+       struct kvec *tail = buf->tail;
+
+       if (len > tail->iov_len)
+               len = tail->iov_len;
+
+       _copy_to_pages(buf->pages,
+                      buf->page_base + pgto,
+                      (char *)tail->iov_base,
+                      len);
+       tail->iov_len -= len;
+
+       if (tail->iov_len > 0)
+               memmove((char *)tail->iov_base,
+                               tail->iov_base + len,
+                               tail->iov_len);
+}
+
 /**
  * _shift_data_right_pages
  * @pages: vector of pages containing both the source and dest memory area.
@@ -1128,6 +1213,42 @@ unsigned int xdr_read_pages(struct xdr_stream *xdr, unsigned int len)
 }
 EXPORT_SYMBOL_GPL(xdr_read_pages);
 
+uint64_t xdr_align_data(struct xdr_stream *xdr, uint64_t offset, uint32_t length)
+{
+       struct xdr_buf *buf = xdr->buf;
+       unsigned int from, bytes;
+       unsigned int shift = 0;
+
+       if ((offset + length) < offset ||
+           (offset + length) > buf->page_len)
+               length = buf->page_len - offset;
+
+       xdr_realign_pages(xdr);
+       from = xdr_page_pos(xdr);
+       bytes = xdr->nwords << 2;
+       if (length < bytes)
+               bytes = length;
+
+       /* Move page data to the left */
+       if (from > offset) {
+               shift = min_t(unsigned int, bytes, buf->page_len - from);
+               _shift_data_left_pages(buf->pages,
+                                      buf->page_base + offset,
+                                      buf->page_base + from,
+                                      shift);
+               bytes -= shift;
+
+               /* Move tail data into the pages, if necessary */
+               if (bytes > 0)
+                       _shift_data_left_tail(buf, offset + shift, bytes);
+       }
+
+       xdr->nwords -= XDR_QUADLEN(length);
+       xdr_set_page(xdr, from + length, PAGE_SIZE);
+       return length;
+}
+EXPORT_SYMBOL_GPL(xdr_align_data);
+
 uint64_t xdr_expand_hole(struct xdr_stream *xdr, uint64_t offset, uint64_t length)
 {
        struct xdr_buf *buf = xdr->buf;