Merge tag 'nfs-for-6.3-1' of git://git.linux-nfs.org/projects/anna/linux-nfs
[sfrench/cifs-2.6.git] / fs / nfs / pagelist.c
index 779bfc37233c8aaad6e902377bdebcbff36a2ed5..64fa8de199de1ede8cf8066c162da3e9a3a1fb1a 100644 (file)
 static struct kmem_cache *nfs_page_cachep;
 static const struct rpc_call_ops nfs_pgio_common_ops;
 
+struct nfs_page_iter_page {
+       const struct nfs_page *req;
+       size_t count;
+};
+
+static void nfs_page_iter_page_init(struct nfs_page_iter_page *i,
+                                   const struct nfs_page *req)
+{
+       i->req = req;
+       i->count = 0;
+}
+
+static void nfs_page_iter_page_advance(struct nfs_page_iter_page *i, size_t sz)
+{
+       const struct nfs_page *req = i->req;
+       size_t tmp = i->count + sz;
+
+       i->count = (tmp < req->wb_bytes) ? tmp : req->wb_bytes;
+}
+
+static struct page *nfs_page_iter_page_get(struct nfs_page_iter_page *i)
+{
+       const struct nfs_page *req = i->req;
+       struct page *page;
+
+       if (i->count != req->wb_bytes) {
+               size_t base = i->count + req->wb_pgbase;
+               size_t len = PAGE_SIZE - offset_in_page(base);
+
+               page = nfs_page_to_page(req, base);
+               nfs_page_iter_page_advance(i, len);
+               return page;
+       }
+       return NULL;
+}
+
 static struct nfs_pgio_mirror *
 nfs_pgio_get_mirror(struct nfs_pageio_descriptor *desc, u32 idx)
 {
@@ -391,7 +427,7 @@ nfs_page_group_init(struct nfs_page *req, struct nfs_page *prev)
                 * has extra ref from the write/commit path to handle handoff
                 * between write and commit lists. */
                if (test_bit(PG_INODE_REF, &prev->wb_head->wb_flags)) {
-                       inode = page_file_mapping(req->wb_page)->host;
+                       inode = nfs_page_to_inode(req);
                        set_bit(PG_INODE_REF, &req->wb_flags);
                        kref_get(&req->wb_kref);
                        atomic_long_inc(&NFS_I(inode)->nrequests);
@@ -431,10 +467,9 @@ out:
                nfs_release_request(head);
 }
 
-static struct nfs_page *
-__nfs_create_request(struct nfs_lock_context *l_ctx, struct page *page,
-                  unsigned int pgbase, unsigned int offset,
-                  unsigned int count)
+static struct nfs_page *nfs_page_create(struct nfs_lock_context *l_ctx,
+                                       unsigned int pgbase, pgoff_t index,
+                                       unsigned int offset, unsigned int count)
 {
        struct nfs_page         *req;
        struct nfs_open_context *ctx = l_ctx->open_context;
@@ -453,42 +488,90 @@ __nfs_create_request(struct nfs_lock_context *l_ctx, struct page *page,
        /* Initialize the request struct. Initially, we assume a
         * long write-back delay. This will be adjusted in
         * update_nfs_request below if the region is not locked. */
-       req->wb_page    = page;
-       if (page) {
-               req->wb_index = page_index(page);
-               get_page(page);
-       }
-       req->wb_offset  = offset;
-       req->wb_pgbase  = pgbase;
-       req->wb_bytes   = count;
+       req->wb_pgbase = pgbase;
+       req->wb_index = index;
+       req->wb_offset = offset;
+       req->wb_bytes = count;
        kref_init(&req->wb_kref);
        req->wb_nio = 0;
        return req;
 }
 
+static void nfs_page_assign_folio(struct nfs_page *req, struct folio *folio)
+{
+       if (folio != NULL) {
+               req->wb_folio = folio;
+               folio_get(folio);
+               set_bit(PG_FOLIO, &req->wb_flags);
+       }
+}
+
+static void nfs_page_assign_page(struct nfs_page *req, struct page *page)
+{
+       if (page != NULL) {
+               req->wb_page = page;
+               get_page(page);
+       }
+}
+
 /**
- * nfs_create_request - Create an NFS read/write request.
+ * nfs_page_create_from_page - Create an NFS read/write request.
  * @ctx: open context to use
  * @page: page to write
- * @offset: starting offset within the page for the write
+ * @pgbase: starting offset within the page for the write
+ * @offset: file offset for the write
  * @count: number of bytes to read/write
  *
  * The page must be locked by the caller. This makes sure we never
  * create two different requests for the same page.
  * User should ensure it is safe to sleep in this function.
  */
-struct nfs_page *
-nfs_create_request(struct nfs_open_context *ctx, struct page *page,
-                  unsigned int offset, unsigned int count)
+struct nfs_page *nfs_page_create_from_page(struct nfs_open_context *ctx,
+                                          struct page *page,
+                                          unsigned int pgbase, loff_t offset,
+                                          unsigned int count)
+{
+       struct nfs_lock_context *l_ctx = nfs_get_lock_context(ctx);
+       struct nfs_page *ret;
+
+       if (IS_ERR(l_ctx))
+               return ERR_CAST(l_ctx);
+       ret = nfs_page_create(l_ctx, pgbase, offset >> PAGE_SHIFT,
+                             offset_in_page(offset), count);
+       if (!IS_ERR(ret)) {
+               nfs_page_assign_page(ret, page);
+               nfs_page_group_init(ret, NULL);
+       }
+       nfs_put_lock_context(l_ctx);
+       return ret;
+}
+
+/**
+ * nfs_page_create_from_folio - Create an NFS read/write request.
+ * @ctx: open context to use
+ * @folio: folio to write
+ * @offset: starting offset within the folio for the write
+ * @count: number of bytes to read/write
+ *
+ * The page must be locked by the caller. This makes sure we never
+ * create two different requests for the same page.
+ * User should ensure it is safe to sleep in this function.
+ */
+struct nfs_page *nfs_page_create_from_folio(struct nfs_open_context *ctx,
+                                           struct folio *folio,
+                                           unsigned int offset,
+                                           unsigned int count)
 {
        struct nfs_lock_context *l_ctx = nfs_get_lock_context(ctx);
        struct nfs_page *ret;
 
        if (IS_ERR(l_ctx))
                return ERR_CAST(l_ctx);
-       ret = __nfs_create_request(l_ctx, page, offset, offset, count);
-       if (!IS_ERR(ret))
+       ret = nfs_page_create(l_ctx, offset, folio_index(folio), offset, count);
+       if (!IS_ERR(ret)) {
+               nfs_page_assign_folio(ret, folio);
                nfs_page_group_init(ret, NULL);
+       }
        nfs_put_lock_context(l_ctx);
        return ret;
 }
@@ -501,10 +584,16 @@ nfs_create_subreq(struct nfs_page *req,
 {
        struct nfs_page *last;
        struct nfs_page *ret;
+       struct folio *folio = nfs_page_to_folio(req);
+       struct page *page = nfs_page_to_page(req, pgbase);
 
-       ret = __nfs_create_request(req->wb_lock_context, req->wb_page,
-                       pgbase, offset, count);
+       ret = nfs_page_create(req->wb_lock_context, pgbase, req->wb_index,
+                             offset, count);
        if (!IS_ERR(ret)) {
+               if (folio)
+                       nfs_page_assign_folio(ret, folio);
+               else
+                       nfs_page_assign_page(ret, page);
                /* find the last request */
                for (last = req->wb_head;
                     last->wb_this_page != req->wb_head;
@@ -512,7 +601,6 @@ nfs_create_subreq(struct nfs_page *req,
                        ;
 
                nfs_lock_request(ret);
-               ret->wb_index = req->wb_index;
                nfs_page_group_init(ret, last);
                ret->wb_nio = req->wb_nio;
        }
@@ -551,11 +639,16 @@ void nfs_unlock_and_release_request(struct nfs_page *req)
  */
 static void nfs_clear_request(struct nfs_page *req)
 {
+       struct folio *folio = nfs_page_to_folio(req);
        struct page *page = req->wb_page;
        struct nfs_lock_context *l_ctx = req->wb_lock_context;
        struct nfs_open_context *ctx;
 
-       if (page != NULL) {
+       if (folio != NULL) {
+               folio_put(folio);
+               req->wb_folio = NULL;
+               clear_bit(PG_FOLIO, &req->wb_flags);
+       } else if (page != NULL) {
                put_page(page);
                req->wb_page = NULL;
        }
@@ -693,13 +786,14 @@ EXPORT_SYMBOL_GPL(nfs_pgio_header_free);
 /**
  * nfs_pgio_rpcsetup - Set up arguments for a pageio call
  * @hdr: The pageio hdr
+ * @pgbase: base
  * @count: Number of bytes to read
  * @how: How to commit data (writes only)
  * @cinfo: Commit information for the call (writes only)
  */
-static void nfs_pgio_rpcsetup(struct nfs_pgio_header *hdr,
-                             unsigned int count,
-                             int how, struct nfs_commit_info *cinfo)
+static void nfs_pgio_rpcsetup(struct nfs_pgio_header *hdr, unsigned int pgbase,
+                             unsigned int count, int how,
+                             struct nfs_commit_info *cinfo)
 {
        struct nfs_page *req = hdr->req;
 
@@ -710,7 +804,7 @@ static void nfs_pgio_rpcsetup(struct nfs_pgio_header *hdr,
        hdr->args.offset = req_offset(req);
        /* pnfs_set_layoutcommit needs this */
        hdr->mds_offset = hdr->args.offset;
-       hdr->args.pgbase = req->wb_pgbase;
+       hdr->args.pgbase = pgbase;
        hdr->args.pages  = hdr->page_array.pagevec;
        hdr->args.count  = count;
        hdr->args.context = get_nfs_open_context(nfs_req_openctx(req));
@@ -896,9 +990,10 @@ int nfs_generic_pgio(struct nfs_pageio_descriptor *desc,
        struct nfs_commit_info cinfo;
        struct nfs_page_array *pg_array = &hdr->page_array;
        unsigned int pagecount, pageused;
+       unsigned int pg_base = offset_in_page(mirror->pg_base);
        gfp_t gfp_flags = nfs_io_gfp_mask();
 
-       pagecount = nfs_page_array_len(mirror->pg_base, mirror->pg_count);
+       pagecount = nfs_page_array_len(pg_base, mirror->pg_count);
        pg_array->npages = pagecount;
 
        if (pagecount <= ARRAY_SIZE(pg_array->page_array))
@@ -918,16 +1013,26 @@ int nfs_generic_pgio(struct nfs_pageio_descriptor *desc,
        last_page = NULL;
        pageused = 0;
        while (!list_empty(head)) {
+               struct nfs_page_iter_page i;
+               struct page *page;
+
                req = nfs_list_entry(head->next);
                nfs_list_move_request(req, &hdr->pages);
 
-               if (!last_page || last_page != req->wb_page) {
-                       pageused++;
-                       if (pageused > pagecount)
-                               break;
-                       *pages++ = last_page = req->wb_page;
+               if (req->wb_pgbase == 0)
+                       last_page = NULL;
+
+               nfs_page_iter_page_init(&i, req);
+               while ((page = nfs_page_iter_page_get(&i)) != NULL) {
+                       if (last_page != page) {
+                               pageused++;
+                               if (pageused > pagecount)
+                                       goto full;
+                               *pages++ = last_page = page;
+                       }
                }
        }
+full:
        if (WARN_ON_ONCE(pageused != pagecount)) {
                nfs_pgio_error(hdr);
                desc->pg_error = -EINVAL;
@@ -939,7 +1044,8 @@ int nfs_generic_pgio(struct nfs_pageio_descriptor *desc,
                desc->pg_ioflags &= ~FLUSH_COND_STABLE;
 
        /* Set up the argument struct */
-       nfs_pgio_rpcsetup(hdr, mirror->pg_count, desc->pg_ioflags, &cinfo);
+       nfs_pgio_rpcsetup(hdr, pg_base, mirror->pg_count, desc->pg_ioflags,
+                         &cinfo);
        desc->pg_rpc_callops = &nfs_pgio_common_ops;
        return 0;
 }
@@ -1035,6 +1141,24 @@ static bool nfs_match_lock_context(const struct nfs_lock_context *l1,
        return l1->lockowner == l2->lockowner;
 }
 
+static bool nfs_page_is_contiguous(const struct nfs_page *prev,
+                                  const struct nfs_page *req)
+{
+       size_t prev_end = prev->wb_pgbase + prev->wb_bytes;
+
+       if (req_offset(req) != req_offset(prev) + prev->wb_bytes)
+               return false;
+       if (req->wb_pgbase == 0)
+               return prev_end == nfs_page_max_length(prev);
+       if (req->wb_pgbase == prev_end) {
+               struct folio *folio = nfs_page_to_folio(req);
+               if (folio)
+                       return folio == nfs_page_to_folio(prev);
+               return req->wb_page == prev->wb_page;
+       }
+       return false;
+}
+
 /**
  * nfs_coalesce_size - test two requests for compatibility
  * @prev: pointer to nfs_page
@@ -1063,16 +1187,8 @@ static unsigned int nfs_coalesce_size(struct nfs_page *prev,
                    !nfs_match_lock_context(req->wb_lock_context,
                                            prev->wb_lock_context))
                        return 0;
-               if (req_offset(req) != req_offset(prev) + prev->wb_bytes)
+               if (!nfs_page_is_contiguous(prev, req))
                        return 0;
-               if (req->wb_page == prev->wb_page) {
-                       if (req->wb_pgbase != prev->wb_pgbase + prev->wb_bytes)
-                               return 0;
-               } else {
-                       if (req->wb_pgbase != 0 ||
-                           prev->wb_pgbase + prev->wb_bytes != PAGE_SIZE)
-                               return 0;
-               }
        }
        return pgio->pg_ops->pg_test(pgio, prev, req);
 }
@@ -1412,16 +1528,21 @@ void nfs_pageio_cond_complete(struct nfs_pageio_descriptor *desc, pgoff_t index)
 {
        struct nfs_pgio_mirror *mirror;
        struct nfs_page *prev;
+       struct folio *folio;
        u32 midx;
 
        for (midx = 0; midx < desc->pg_mirror_count; midx++) {
                mirror = nfs_pgio_get_mirror(desc, midx);
                if (!list_empty(&mirror->pg_list)) {
                        prev = nfs_list_entry(mirror->pg_list.prev);
-                       if (index != prev->wb_index + 1) {
-                               nfs_pageio_complete(desc);
-                               break;
-                       }
+                       folio = nfs_page_to_folio(prev);
+                       if (folio) {
+                               if (index == folio_next_index(folio))
+                                       continue;
+                       } else if (index == prev->wb_index + 1)
+                               continue;
+                       nfs_pageio_complete(desc);
+                       break;
                }
        }
 }