]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/commitdiff
Merge tag 'nfs-for-3.16-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 10 Jun 2014 22:02:42 +0000 (15:02 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 10 Jun 2014 22:02:42 +0000 (15:02 -0700)
Pull NFS client updates from Trond Myklebust:
 "Highlights include:

   - massive cleanup of the NFS read/write code by Anna and Dros
   - support multiple NFS read/write requests per page in order to deal
     with non-page aligned pNFS striping.  Also cleans up the r/wsize <
     page size code nicely.
   - stable fix for ensuring inode is declared uptodate only after all
     the attributes have been checked.
   - stable fix for a kernel Oops when remounting
   - NFS over RDMA client fixes
   - move the pNFS files layout driver into its own subdirectory"

* tag 'nfs-for-3.16-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs: (79 commits)
  NFS: populate ->net in mount data when remounting
  pnfs: fix lockup caused by pnfs_generic_pg_test
  NFSv4.1: Fix typo in dprintk
  NFSv4.1: Comment is now wrong and redundant to code
  NFS: Use raw_write_seqcount_begin/end int nfs4_reclaim_open_state
  xprtrdma: Disconnect on registration failure
  xprtrdma: Remove BUG_ON() call sites
  xprtrdma: Avoid deadlock when credit window is reset
  SUNRPC: Move congestion window constants to header file
  xprtrdma: Reset connection timeout after successful reconnect
  xprtrdma: Use macros for reconnection timeout constants
  xprtrdma: Allocate missing pagelist
  xprtrdma: Remove Tavor MTU setting
  xprtrdma: Ensure ia->ri_id->qp is not NULL when reconnecting
  xprtrdma: Reduce the number of hardway buffer allocations
  xprtrdma: Limit work done by completion handler
  xprtrmda: Reduce calls to ib_poll_cq() in completion handlers
  xprtrmda: Reduce lock contention in completion handlers
  xprtrdma: Split the completion queue
  xprtrdma: Make rpcrdma_ep_destroy() return void
  ...

1  2 
fs/nfs/filelayout/filelayoutdev.c
fs/nfs/inode.c
fs/nfs/nfs4proc.c
fs/nfs/nfs4state.c
fs/nfs/pagelist.c
fs/nfs/pnfs.c
fs/nfs/pnfs.h
fs/nfs/write.c
include/linux/sunrpc/xprt.h
net/sunrpc/xprt.c

index 0000000000000000000000000000000000000000,7c85390a46479daeee2f86d350149e52ab8a69d6..44bf0140a4c77e2d787f8ee154aa0436f6d5d615
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,843 +1,843 @@@
 -      smp_mb__before_clear_bit();
+ /*
+  *  Device operations for the pnfs nfs4 file layout driver.
+  *
+  *  Copyright (c) 2002
+  *  The Regents of the University of Michigan
+  *  All Rights Reserved
+  *
+  *  Dean Hildebrand <dhildebz@umich.edu>
+  *  Garth Goodson   <Garth.Goodson@netapp.com>
+  *
+  *  Permission is granted to use, copy, create derivative works, and
+  *  redistribute this software and such derivative works for any purpose,
+  *  so long as the name of the University of Michigan is not used in
+  *  any advertising or publicity pertaining to the use or distribution
+  *  of this software without specific, written prior authorization. If
+  *  the above copyright notice or any other identification of the
+  *  University of Michigan is included in any copy of any portion of
+  *  this software, then the disclaimer below must also be included.
+  *
+  *  This software is provided as is, without representation or warranty
+  *  of any kind either express or implied, including without limitation
+  *  the implied warranties of merchantability, fitness for a particular
+  *  purpose, or noninfringement.  The Regents of the University of
+  *  Michigan shall not be liable for any damages, including special,
+  *  indirect, incidental, or consequential damages, with respect to any
+  *  claim arising out of or in connection with the use of the software,
+  *  even if it has been or is hereafter advised of the possibility of
+  *  such damages.
+  */
+ #include <linux/nfs_fs.h>
+ #include <linux/vmalloc.h>
+ #include <linux/module.h>
+ #include <linux/sunrpc/addr.h>
+ #include "../internal.h"
+ #include "../nfs4session.h"
+ #include "filelayout.h"
+ #define NFSDBG_FACILITY               NFSDBG_PNFS_LD
+ static unsigned int dataserver_timeo = NFS4_DEF_DS_TIMEO;
+ static unsigned int dataserver_retrans = NFS4_DEF_DS_RETRANS;
+ /*
+  * Data server cache
+  *
+  * Data servers can be mapped to different device ids.
+  * nfs4_pnfs_ds reference counting
+  *   - set to 1 on allocation
+  *   - incremented when a device id maps a data server already in the cache.
+  *   - decremented when deviceid is removed from the cache.
+  */
+ static DEFINE_SPINLOCK(nfs4_ds_cache_lock);
+ static LIST_HEAD(nfs4_data_server_cache);
+ /* Debug routines */
+ void
+ print_ds(struct nfs4_pnfs_ds *ds)
+ {
+       if (ds == NULL) {
+               printk("%s NULL device\n", __func__);
+               return;
+       }
+       printk("        ds %s\n"
+               "        ref count %d\n"
+               "        client %p\n"
+               "        cl_exchange_flags %x\n",
+               ds->ds_remotestr,
+               atomic_read(&ds->ds_count), ds->ds_clp,
+               ds->ds_clp ? ds->ds_clp->cl_exchange_flags : 0);
+ }
+ static bool
+ same_sockaddr(struct sockaddr *addr1, struct sockaddr *addr2)
+ {
+       struct sockaddr_in *a, *b;
+       struct sockaddr_in6 *a6, *b6;
+       if (addr1->sa_family != addr2->sa_family)
+               return false;
+       switch (addr1->sa_family) {
+       case AF_INET:
+               a = (struct sockaddr_in *)addr1;
+               b = (struct sockaddr_in *)addr2;
+               if (a->sin_addr.s_addr == b->sin_addr.s_addr &&
+                   a->sin_port == b->sin_port)
+                       return true;
+               break;
+       case AF_INET6:
+               a6 = (struct sockaddr_in6 *)addr1;
+               b6 = (struct sockaddr_in6 *)addr2;
+               /* LINKLOCAL addresses must have matching scope_id */
+               if (ipv6_addr_src_scope(&a6->sin6_addr) ==
+                   IPV6_ADDR_SCOPE_LINKLOCAL &&
+                   a6->sin6_scope_id != b6->sin6_scope_id)
+                       return false;
+               if (ipv6_addr_equal(&a6->sin6_addr, &b6->sin6_addr) &&
+                   a6->sin6_port == b6->sin6_port)
+                       return true;
+               break;
+       default:
+               dprintk("%s: unhandled address family: %u\n",
+                       __func__, addr1->sa_family);
+               return false;
+       }
+       return false;
+ }
+ static bool
+ _same_data_server_addrs_locked(const struct list_head *dsaddrs1,
+                              const struct list_head *dsaddrs2)
+ {
+       struct nfs4_pnfs_ds_addr *da1, *da2;
+       /* step through both lists, comparing as we go */
+       for (da1 = list_first_entry(dsaddrs1, typeof(*da1), da_node),
+            da2 = list_first_entry(dsaddrs2, typeof(*da2), da_node);
+            da1 != NULL && da2 != NULL;
+            da1 = list_entry(da1->da_node.next, typeof(*da1), da_node),
+            da2 = list_entry(da2->da_node.next, typeof(*da2), da_node)) {
+               if (!same_sockaddr((struct sockaddr *)&da1->da_addr,
+                                  (struct sockaddr *)&da2->da_addr))
+                       return false;
+       }
+       if (da1 == NULL && da2 == NULL)
+               return true;
+       return false;
+ }
+ /*
+  * Lookup DS by addresses.  nfs4_ds_cache_lock is held
+  */
+ static struct nfs4_pnfs_ds *
+ _data_server_lookup_locked(const struct list_head *dsaddrs)
+ {
+       struct nfs4_pnfs_ds *ds;
+       list_for_each_entry(ds, &nfs4_data_server_cache, ds_node)
+               if (_same_data_server_addrs_locked(&ds->ds_addrs, dsaddrs))
+                       return ds;
+       return NULL;
+ }
+ /*
+  * Create an rpc connection to the nfs4_pnfs_ds data server
+  * Currently only supports IPv4 and IPv6 addresses
+  */
+ static int
+ nfs4_ds_connect(struct nfs_server *mds_srv, struct nfs4_pnfs_ds *ds)
+ {
+       struct nfs_client *clp = ERR_PTR(-EIO);
+       struct nfs4_pnfs_ds_addr *da;
+       int status = 0;
+       dprintk("--> %s DS %s au_flavor %d\n", __func__, ds->ds_remotestr,
+               mds_srv->nfs_client->cl_rpcclient->cl_auth->au_flavor);
+       list_for_each_entry(da, &ds->ds_addrs, da_node) {
+               dprintk("%s: DS %s: trying address %s\n",
+                       __func__, ds->ds_remotestr, da->da_remotestr);
+               clp = nfs4_set_ds_client(mds_srv->nfs_client,
+                                       (struct sockaddr *)&da->da_addr,
+                                       da->da_addrlen, IPPROTO_TCP,
+                                       dataserver_timeo, dataserver_retrans);
+               if (!IS_ERR(clp))
+                       break;
+       }
+       if (IS_ERR(clp)) {
+               status = PTR_ERR(clp);
+               goto out;
+       }
+       status = nfs4_init_ds_session(clp, mds_srv->nfs_client->cl_lease_time);
+       if (status)
+               goto out_put;
+       smp_wmb();
+       ds->ds_clp = clp;
+       dprintk("%s [new] addr: %s\n", __func__, ds->ds_remotestr);
+ out:
+       return status;
+ out_put:
+       nfs_put_client(clp);
+       goto out;
+ }
+ static void
+ destroy_ds(struct nfs4_pnfs_ds *ds)
+ {
+       struct nfs4_pnfs_ds_addr *da;
+       dprintk("--> %s\n", __func__);
+       ifdebug(FACILITY)
+               print_ds(ds);
+       if (ds->ds_clp)
+               nfs_put_client(ds->ds_clp);
+       while (!list_empty(&ds->ds_addrs)) {
+               da = list_first_entry(&ds->ds_addrs,
+                                     struct nfs4_pnfs_ds_addr,
+                                     da_node);
+               list_del_init(&da->da_node);
+               kfree(da->da_remotestr);
+               kfree(da);
+       }
+       kfree(ds->ds_remotestr);
+       kfree(ds);
+ }
+ void
+ nfs4_fl_free_deviceid(struct nfs4_file_layout_dsaddr *dsaddr)
+ {
+       struct nfs4_pnfs_ds *ds;
+       int i;
+       nfs4_print_deviceid(&dsaddr->id_node.deviceid);
+       for (i = 0; i < dsaddr->ds_num; i++) {
+               ds = dsaddr->ds_list[i];
+               if (ds != NULL) {
+                       if (atomic_dec_and_lock(&ds->ds_count,
+                                               &nfs4_ds_cache_lock)) {
+                               list_del_init(&ds->ds_node);
+                               spin_unlock(&nfs4_ds_cache_lock);
+                               destroy_ds(ds);
+                       }
+               }
+       }
+       kfree(dsaddr->stripe_indices);
+       kfree(dsaddr);
+ }
+ /*
+  * Create a string with a human readable address and port to avoid
+  * complicated setup around many dprinks.
+  */
+ static char *
+ nfs4_pnfs_remotestr(struct list_head *dsaddrs, gfp_t gfp_flags)
+ {
+       struct nfs4_pnfs_ds_addr *da;
+       char *remotestr;
+       size_t len;
+       char *p;
+       len = 3;        /* '{', '}' and eol */
+       list_for_each_entry(da, dsaddrs, da_node) {
+               len += strlen(da->da_remotestr) + 1;    /* string plus comma */
+       }
+       remotestr = kzalloc(len, gfp_flags);
+       if (!remotestr)
+               return NULL;
+       p = remotestr;
+       *(p++) = '{';
+       len--;
+       list_for_each_entry(da, dsaddrs, da_node) {
+               size_t ll = strlen(da->da_remotestr);
+               if (ll > len)
+                       goto out_err;
+               memcpy(p, da->da_remotestr, ll);
+               p += ll;
+               len -= ll;
+               if (len < 1)
+                       goto out_err;
+               (*p++) = ',';
+               len--;
+       }
+       if (len < 2)
+               goto out_err;
+       *(p++) = '}';
+       *p = '\0';
+       return remotestr;
+ out_err:
+       kfree(remotestr);
+       return NULL;
+ }
+ static struct nfs4_pnfs_ds *
+ nfs4_pnfs_ds_add(struct list_head *dsaddrs, gfp_t gfp_flags)
+ {
+       struct nfs4_pnfs_ds *tmp_ds, *ds = NULL;
+       char *remotestr;
+       if (list_empty(dsaddrs)) {
+               dprintk("%s: no addresses defined\n", __func__);
+               goto out;
+       }
+       ds = kzalloc(sizeof(*ds), gfp_flags);
+       if (!ds)
+               goto out;
+       /* this is only used for debugging, so it's ok if its NULL */
+       remotestr = nfs4_pnfs_remotestr(dsaddrs, gfp_flags);
+       spin_lock(&nfs4_ds_cache_lock);
+       tmp_ds = _data_server_lookup_locked(dsaddrs);
+       if (tmp_ds == NULL) {
+               INIT_LIST_HEAD(&ds->ds_addrs);
+               list_splice_init(dsaddrs, &ds->ds_addrs);
+               ds->ds_remotestr = remotestr;
+               atomic_set(&ds->ds_count, 1);
+               INIT_LIST_HEAD(&ds->ds_node);
+               ds->ds_clp = NULL;
+               list_add(&ds->ds_node, &nfs4_data_server_cache);
+               dprintk("%s add new data server %s\n", __func__,
+                       ds->ds_remotestr);
+       } else {
+               kfree(remotestr);
+               kfree(ds);
+               atomic_inc(&tmp_ds->ds_count);
+               dprintk("%s data server %s found, inc'ed ds_count to %d\n",
+                       __func__, tmp_ds->ds_remotestr,
+                       atomic_read(&tmp_ds->ds_count));
+               ds = tmp_ds;
+       }
+       spin_unlock(&nfs4_ds_cache_lock);
+ out:
+       return ds;
+ }
+ /*
+  * Currently only supports ipv4, ipv6 and one multi-path address.
+  */
+ static struct nfs4_pnfs_ds_addr *
+ decode_ds_addr(struct net *net, struct xdr_stream *streamp, gfp_t gfp_flags)
+ {
+       struct nfs4_pnfs_ds_addr *da = NULL;
+       char *buf, *portstr;
+       __be16 port;
+       int nlen, rlen;
+       int tmp[2];
+       __be32 *p;
+       char *netid, *match_netid;
+       size_t len, match_netid_len;
+       char *startsep = "";
+       char *endsep = "";
+       /* r_netid */
+       p = xdr_inline_decode(streamp, 4);
+       if (unlikely(!p))
+               goto out_err;
+       nlen = be32_to_cpup(p++);
+       p = xdr_inline_decode(streamp, nlen);
+       if (unlikely(!p))
+               goto out_err;
+       netid = kmalloc(nlen+1, gfp_flags);
+       if (unlikely(!netid))
+               goto out_err;
+       netid[nlen] = '\0';
+       memcpy(netid, p, nlen);
+       /* r_addr: ip/ip6addr with port in dec octets - see RFC 5665 */
+       p = xdr_inline_decode(streamp, 4);
+       if (unlikely(!p))
+               goto out_free_netid;
+       rlen = be32_to_cpup(p);
+       p = xdr_inline_decode(streamp, rlen);
+       if (unlikely(!p))
+               goto out_free_netid;
+       /* port is ".ABC.DEF", 8 chars max */
+       if (rlen > INET6_ADDRSTRLEN + IPV6_SCOPE_ID_LEN + 8) {
+               dprintk("%s: Invalid address, length %d\n", __func__,
+                       rlen);
+               goto out_free_netid;
+       }
+       buf = kmalloc(rlen + 1, gfp_flags);
+       if (!buf) {
+               dprintk("%s: Not enough memory\n", __func__);
+               goto out_free_netid;
+       }
+       buf[rlen] = '\0';
+       memcpy(buf, p, rlen);
+       /* replace port '.' with '-' */
+       portstr = strrchr(buf, '.');
+       if (!portstr) {
+               dprintk("%s: Failed finding expected dot in port\n",
+                       __func__);
+               goto out_free_buf;
+       }
+       *portstr = '-';
+       /* find '.' between address and port */
+       portstr = strrchr(buf, '.');
+       if (!portstr) {
+               dprintk("%s: Failed finding expected dot between address and "
+                       "port\n", __func__);
+               goto out_free_buf;
+       }
+       *portstr = '\0';
+       da = kzalloc(sizeof(*da), gfp_flags);
+       if (unlikely(!da))
+               goto out_free_buf;
+       INIT_LIST_HEAD(&da->da_node);
+       if (!rpc_pton(net, buf, portstr-buf, (struct sockaddr *)&da->da_addr,
+                     sizeof(da->da_addr))) {
+               dprintk("%s: error parsing address %s\n", __func__, buf);
+               goto out_free_da;
+       }
+       portstr++;
+       sscanf(portstr, "%d-%d", &tmp[0], &tmp[1]);
+       port = htons((tmp[0] << 8) | (tmp[1]));
+       switch (da->da_addr.ss_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)&da->da_addr)->sin_port = port;
+               da->da_addrlen = sizeof(struct sockaddr_in);
+               match_netid = "tcp";
+               match_netid_len = 3;
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)&da->da_addr)->sin6_port = port;
+               da->da_addrlen = sizeof(struct sockaddr_in6);
+               match_netid = "tcp6";
+               match_netid_len = 4;
+               startsep = "[";
+               endsep = "]";
+               break;
+       default:
+               dprintk("%s: unsupported address family: %u\n",
+                       __func__, da->da_addr.ss_family);
+               goto out_free_da;
+       }
+       if (nlen != match_netid_len || strncmp(netid, match_netid, nlen)) {
+               dprintk("%s: ERROR: r_netid \"%s\" != \"%s\"\n",
+                       __func__, netid, match_netid);
+               goto out_free_da;
+       }
+       /* save human readable address */
+       len = strlen(startsep) + strlen(buf) + strlen(endsep) + 7;
+       da->da_remotestr = kzalloc(len, gfp_flags);
+       /* NULL is ok, only used for dprintk */
+       if (da->da_remotestr)
+               snprintf(da->da_remotestr, len, "%s%s%s:%u", startsep,
+                        buf, endsep, ntohs(port));
+       dprintk("%s: Parsed DS addr %s\n", __func__, da->da_remotestr);
+       kfree(buf);
+       kfree(netid);
+       return da;
+ out_free_da:
+       kfree(da);
+ out_free_buf:
+       dprintk("%s: Error parsing DS addr: %s\n", __func__, buf);
+       kfree(buf);
+ out_free_netid:
+       kfree(netid);
+ out_err:
+       return NULL;
+ }
+ /* Decode opaque device data and return the result */
+ static struct nfs4_file_layout_dsaddr*
+ decode_device(struct inode *ino, struct pnfs_device *pdev, gfp_t gfp_flags)
+ {
+       int i;
+       u32 cnt, num;
+       u8 *indexp;
+       __be32 *p;
+       u8 *stripe_indices;
+       u8 max_stripe_index;
+       struct nfs4_file_layout_dsaddr *dsaddr = NULL;
+       struct xdr_stream stream;
+       struct xdr_buf buf;
+       struct page *scratch;
+       struct list_head dsaddrs;
+       struct nfs4_pnfs_ds_addr *da;
+       /* set up xdr stream */
+       scratch = alloc_page(gfp_flags);
+       if (!scratch)
+               goto out_err;
+       xdr_init_decode_pages(&stream, &buf, pdev->pages, pdev->pglen);
+       xdr_set_scratch_buffer(&stream, page_address(scratch), PAGE_SIZE);
+       /* Get the stripe count (number of stripe index) */
+       p = xdr_inline_decode(&stream, 4);
+       if (unlikely(!p))
+               goto out_err_free_scratch;
+       cnt = be32_to_cpup(p);
+       dprintk("%s stripe count  %d\n", __func__, cnt);
+       if (cnt > NFS4_PNFS_MAX_STRIPE_CNT) {
+               printk(KERN_WARNING "NFS: %s: stripe count %d greater than "
+                      "supported maximum %d\n", __func__,
+                       cnt, NFS4_PNFS_MAX_STRIPE_CNT);
+               goto out_err_free_scratch;
+       }
+       /* read stripe indices */
+       stripe_indices = kcalloc(cnt, sizeof(u8), gfp_flags);
+       if (!stripe_indices)
+               goto out_err_free_scratch;
+       p = xdr_inline_decode(&stream, cnt << 2);
+       if (unlikely(!p))
+               goto out_err_free_stripe_indices;
+       indexp = &stripe_indices[0];
+       max_stripe_index = 0;
+       for (i = 0; i < cnt; i++) {
+               *indexp = be32_to_cpup(p++);
+               max_stripe_index = max(max_stripe_index, *indexp);
+               indexp++;
+       }
+       /* Check the multipath list count */
+       p = xdr_inline_decode(&stream, 4);
+       if (unlikely(!p))
+               goto out_err_free_stripe_indices;
+       num = be32_to_cpup(p);
+       dprintk("%s ds_num %u\n", __func__, num);
+       if (num > NFS4_PNFS_MAX_MULTI_CNT) {
+               printk(KERN_WARNING "NFS: %s: multipath count %d greater than "
+                       "supported maximum %d\n", __func__,
+                       num, NFS4_PNFS_MAX_MULTI_CNT);
+               goto out_err_free_stripe_indices;
+       }
+       /* validate stripe indices are all < num */
+       if (max_stripe_index >= num) {
+               printk(KERN_WARNING "NFS: %s: stripe index %u >= num ds %u\n",
+                       __func__, max_stripe_index, num);
+               goto out_err_free_stripe_indices;
+       }
+       dsaddr = kzalloc(sizeof(*dsaddr) +
+                       (sizeof(struct nfs4_pnfs_ds *) * (num - 1)),
+                       gfp_flags);
+       if (!dsaddr)
+               goto out_err_free_stripe_indices;
+       dsaddr->stripe_count = cnt;
+       dsaddr->stripe_indices = stripe_indices;
+       stripe_indices = NULL;
+       dsaddr->ds_num = num;
+       nfs4_init_deviceid_node(&dsaddr->id_node,
+                               NFS_SERVER(ino)->pnfs_curr_ld,
+                               NFS_SERVER(ino)->nfs_client,
+                               &pdev->dev_id);
+       INIT_LIST_HEAD(&dsaddrs);
+       for (i = 0; i < dsaddr->ds_num; i++) {
+               int j;
+               u32 mp_count;
+               p = xdr_inline_decode(&stream, 4);
+               if (unlikely(!p))
+                       goto out_err_free_deviceid;
+               mp_count = be32_to_cpup(p); /* multipath count */
+               for (j = 0; j < mp_count; j++) {
+                       da = decode_ds_addr(NFS_SERVER(ino)->nfs_client->cl_net,
+                                           &stream, gfp_flags);
+                       if (da)
+                               list_add_tail(&da->da_node, &dsaddrs);
+               }
+               if (list_empty(&dsaddrs)) {
+                       dprintk("%s: no suitable DS addresses found\n",
+                               __func__);
+                       goto out_err_free_deviceid;
+               }
+               dsaddr->ds_list[i] = nfs4_pnfs_ds_add(&dsaddrs, gfp_flags);
+               if (!dsaddr->ds_list[i])
+                       goto out_err_drain_dsaddrs;
+               /* If DS was already in cache, free ds addrs */
+               while (!list_empty(&dsaddrs)) {
+                       da = list_first_entry(&dsaddrs,
+                                             struct nfs4_pnfs_ds_addr,
+                                             da_node);
+                       list_del_init(&da->da_node);
+                       kfree(da->da_remotestr);
+                       kfree(da);
+               }
+       }
+       __free_page(scratch);
+       return dsaddr;
+ out_err_drain_dsaddrs:
+       while (!list_empty(&dsaddrs)) {
+               da = list_first_entry(&dsaddrs, struct nfs4_pnfs_ds_addr,
+                                     da_node);
+               list_del_init(&da->da_node);
+               kfree(da->da_remotestr);
+               kfree(da);
+       }
+ out_err_free_deviceid:
+       nfs4_fl_free_deviceid(dsaddr);
+       /* stripe_indicies was part of dsaddr */
+       goto out_err_free_scratch;
+ out_err_free_stripe_indices:
+       kfree(stripe_indices);
+ out_err_free_scratch:
+       __free_page(scratch);
+ out_err:
+       dprintk("%s ERROR: returning NULL\n", __func__);
+       return NULL;
+ }
+ /*
+  * Decode the opaque device specified in 'dev' and add it to the cache of
+  * available devices.
+  */
+ static struct nfs4_file_layout_dsaddr *
+ decode_and_add_device(struct inode *inode, struct pnfs_device *dev, gfp_t gfp_flags)
+ {
+       struct nfs4_deviceid_node *d;
+       struct nfs4_file_layout_dsaddr *n, *new;
+       new = decode_device(inode, dev, gfp_flags);
+       if (!new) {
+               printk(KERN_WARNING "NFS: %s: Could not decode or add device\n",
+                       __func__);
+               return NULL;
+       }
+       d = nfs4_insert_deviceid_node(&new->id_node);
+       n = container_of(d, struct nfs4_file_layout_dsaddr, id_node);
+       if (n != new) {
+               nfs4_fl_free_deviceid(new);
+               return n;
+       }
+       return new;
+ }
+ /*
+  * Retrieve the information for dev_id, add it to the list
+  * of available devices, and return it.
+  */
+ struct nfs4_file_layout_dsaddr *
+ filelayout_get_device_info(struct inode *inode,
+               struct nfs4_deviceid *dev_id,
+               struct rpc_cred *cred,
+               gfp_t gfp_flags)
+ {
+       struct pnfs_device *pdev = NULL;
+       u32 max_resp_sz;
+       int max_pages;
+       struct page **pages = NULL;
+       struct nfs4_file_layout_dsaddr *dsaddr = NULL;
+       int rc, i;
+       struct nfs_server *server = NFS_SERVER(inode);
+       /*
+        * Use the session max response size as the basis for setting
+        * GETDEVICEINFO's maxcount
+        */
+       max_resp_sz = server->nfs_client->cl_session->fc_attrs.max_resp_sz;
+       max_pages = nfs_page_array_len(0, max_resp_sz);
+       dprintk("%s inode %p max_resp_sz %u max_pages %d\n",
+               __func__, inode, max_resp_sz, max_pages);
+       pdev = kzalloc(sizeof(struct pnfs_device), gfp_flags);
+       if (pdev == NULL)
+               return NULL;
+       pages = kzalloc(max_pages * sizeof(struct page *), gfp_flags);
+       if (pages == NULL) {
+               kfree(pdev);
+               return NULL;
+       }
+       for (i = 0; i < max_pages; i++) {
+               pages[i] = alloc_page(gfp_flags);
+               if (!pages[i])
+                       goto out_free;
+       }
+       memcpy(&pdev->dev_id, dev_id, sizeof(*dev_id));
+       pdev->layout_type = LAYOUT_NFSV4_1_FILES;
+       pdev->pages = pages;
+       pdev->pgbase = 0;
+       pdev->pglen = max_resp_sz;
+       pdev->mincount = 0;
+       pdev->maxcount = max_resp_sz - nfs41_maxgetdevinfo_overhead;
+       rc = nfs4_proc_getdeviceinfo(server, pdev, cred);
+       dprintk("%s getdevice info returns %d\n", __func__, rc);
+       if (rc)
+               goto out_free;
+       /*
+        * Found new device, need to decode it and then add it to the
+        * list of known devices for this mountpoint.
+        */
+       dsaddr = decode_and_add_device(inode, pdev, gfp_flags);
+ out_free:
+       for (i = 0; i < max_pages; i++)
+               __free_page(pages[i]);
+       kfree(pages);
+       kfree(pdev);
+       dprintk("<-- %s dsaddr %p\n", __func__, dsaddr);
+       return dsaddr;
+ }
+ void
+ nfs4_fl_put_deviceid(struct nfs4_file_layout_dsaddr *dsaddr)
+ {
+       nfs4_put_deviceid_node(&dsaddr->id_node);
+ }
+ /*
+  * Want res = (offset - layout->pattern_offset)/ layout->stripe_unit
+  * Then: ((res + fsi) % dsaddr->stripe_count)
+  */
+ u32
+ nfs4_fl_calc_j_index(struct pnfs_layout_segment *lseg, loff_t offset)
+ {
+       struct nfs4_filelayout_segment *flseg = FILELAYOUT_LSEG(lseg);
+       u64 tmp;
+       tmp = offset - flseg->pattern_offset;
+       do_div(tmp, flseg->stripe_unit);
+       tmp += flseg->first_stripe_index;
+       return do_div(tmp, flseg->dsaddr->stripe_count);
+ }
+ u32
+ nfs4_fl_calc_ds_index(struct pnfs_layout_segment *lseg, u32 j)
+ {
+       return FILELAYOUT_LSEG(lseg)->dsaddr->stripe_indices[j];
+ }
+ struct nfs_fh *
+ nfs4_fl_select_ds_fh(struct pnfs_layout_segment *lseg, u32 j)
+ {
+       struct nfs4_filelayout_segment *flseg = FILELAYOUT_LSEG(lseg);
+       u32 i;
+       if (flseg->stripe_type == STRIPE_SPARSE) {
+               if (flseg->num_fh == 1)
+                       i = 0;
+               else if (flseg->num_fh == 0)
+                       /* Use the MDS OPEN fh set in nfs_read_rpcsetup */
+                       return NULL;
+               else
+                       i = nfs4_fl_calc_ds_index(lseg, j);
+       } else
+               i = j;
+       return flseg->fh_array[i];
+ }
+ static void nfs4_wait_ds_connect(struct nfs4_pnfs_ds *ds)
+ {
+       might_sleep();
+       wait_on_bit(&ds->ds_state, NFS4DS_CONNECTING,
+                       nfs_wait_bit_killable, TASK_KILLABLE);
+ }
+ static void nfs4_clear_ds_conn_bit(struct nfs4_pnfs_ds *ds)
+ {
 -      smp_mb__after_clear_bit();
++      smp_mb__before_atomic();
+       clear_bit(NFS4DS_CONNECTING, &ds->ds_state);
++      smp_mb__after_atomic();
+       wake_up_bit(&ds->ds_state, NFS4DS_CONNECTING);
+ }
+ struct nfs4_pnfs_ds *
+ nfs4_fl_prepare_ds(struct pnfs_layout_segment *lseg, u32 ds_idx)
+ {
+       struct nfs4_file_layout_dsaddr *dsaddr = FILELAYOUT_LSEG(lseg)->dsaddr;
+       struct nfs4_pnfs_ds *ds = dsaddr->ds_list[ds_idx];
+       struct nfs4_deviceid_node *devid = FILELAYOUT_DEVID_NODE(lseg);
+       struct nfs4_pnfs_ds *ret = ds;
+       if (ds == NULL) {
+               printk(KERN_ERR "NFS: %s: No data server for offset index %d\n",
+                       __func__, ds_idx);
+               filelayout_mark_devid_invalid(devid);
+               goto out;
+       }
+       smp_rmb();
+       if (ds->ds_clp)
+               goto out_test_devid;
+       if (test_and_set_bit(NFS4DS_CONNECTING, &ds->ds_state) == 0) {
+               struct nfs_server *s = NFS_SERVER(lseg->pls_layout->plh_inode);
+               int err;
+               err = nfs4_ds_connect(s, ds);
+               if (err)
+                       nfs4_mark_deviceid_unavailable(devid);
+               nfs4_clear_ds_conn_bit(ds);
+       } else {
+               /* Either ds is connected, or ds is NULL */
+               nfs4_wait_ds_connect(ds);
+       }
+ out_test_devid:
+       if (filelayout_test_devid_unavailable(devid))
+               ret = NULL;
+ out:
+       return ret;
+ }
+ module_param(dataserver_retrans, uint, 0644);
+ MODULE_PARM_DESC(dataserver_retrans, "The  number of times the NFSv4.1 client "
+                       "retries a request before it attempts further "
+                       " recovery  action.");
+ module_param(dataserver_timeo, uint, 0644);
+ MODULE_PARM_DESC(dataserver_timeo, "The time (in tenths of a second) the "
+                       "NFSv4.1  client  waits for a response from a "
+                       " data server before it retries an NFS request.");
diff --combined fs/nfs/inode.c
index e6f7398d2b3cac22b30c9515985b07a1caff9bc1,c79f3e767c8ce10631b7f0c09c8982301253cde1..c496f8a746393f5720df5d8255b661e85feb6710
@@@ -1085,7 -1085,7 +1085,7 @@@ int nfs_revalidate_mapping(struct inod
        trace_nfs_invalidate_mapping_exit(inode, ret);
  
        clear_bit_unlock(NFS_INO_INVALIDATING, bitlock);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(bitlock, NFS_INO_INVALIDATING);
  out:
        return ret;
@@@ -1575,18 -1575,20 +1575,20 @@@ static int nfs_update_inode(struct inod
                        inode->i_version = fattr->change_attr;
                }
        } else if (server->caps & NFS_CAP_CHANGE_ATTR)
-               invalid |= save_cache_validity;
+               nfsi->cache_validity |= save_cache_validity;
  
        if (fattr->valid & NFS_ATTR_FATTR_MTIME) {
                memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
        } else if (server->caps & NFS_CAP_MTIME)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_REVAL_FORCED);
  
        if (fattr->valid & NFS_ATTR_FATTR_CTIME) {
                memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
        } else if (server->caps & NFS_CAP_CTIME)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_REVAL_FORCED);
  
        /* Check if our cached file size is stale */
                                        (long long)new_isize);
                }
        } else
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_REVAL_PAGECACHE
                                | NFS_INO_REVAL_FORCED);
  
        if (fattr->valid & NFS_ATTR_FATTR_ATIME)
                memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime));
        else if (server->caps & NFS_CAP_ATIME)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATIME
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATIME
                                | NFS_INO_REVAL_FORCED);
  
        if (fattr->valid & NFS_ATTR_FATTR_MODE) {
                        invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
                }
        } else if (server->caps & NFS_CAP_MODE)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_INVALID_ACCESS
                                | NFS_INO_INVALID_ACL
                                | NFS_INO_REVAL_FORCED);
                        inode->i_uid = fattr->uid;
                }
        } else if (server->caps & NFS_CAP_OWNER)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_INVALID_ACCESS
                                | NFS_INO_INVALID_ACL
                                | NFS_INO_REVAL_FORCED);
                        inode->i_gid = fattr->gid;
                }
        } else if (server->caps & NFS_CAP_OWNER_GROUP)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_INVALID_ACCESS
                                | NFS_INO_INVALID_ACL
                                | NFS_INO_REVAL_FORCED);
                        set_nlink(inode, fattr->nlink);
                }
        } else if (server->caps & NFS_CAP_NLINK)
-               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+               nfsi->cache_validity |= save_cache_validity &
+                               (NFS_INO_INVALID_ATTR
                                | NFS_INO_REVAL_FORCED);
  
        if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {
diff --combined fs/nfs/nfs4proc.c
index 7f55fed8dc649cf9f0f84ceea493319c58c4b169,68dd81e5b58a9a8d069b1f705a10b840c5bb1acb..285ad53340186c883ff8a7b7c214ba45a94f3ef9
@@@ -2027,7 -2027,7 +2027,7 @@@ static int _nfs4_proc_open(struct nfs4_
                        return status;
        }
        if (!(o_res->f_attr->valid & NFS_ATTR_FATTR))
-               _nfs4_proc_getattr(server, &o_res->fh, o_res->f_attr, o_res->f_label);
+               nfs4_proc_getattr(server, &o_res->fh, o_res->f_attr, o_res->f_label);
        return 0;
  }
  
@@@ -2750,7 -2750,7 +2750,7 @@@ static void nfs4_close_context(struct n
  
  #define FATTR4_WORD1_NFS40_MASK (2*FATTR4_WORD1_MOUNTED_ON_FILEID - 1UL)
  #define FATTR4_WORD2_NFS41_MASK (2*FATTR4_WORD2_SUPPATTR_EXCLCREAT - 1UL)
 -#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_CHANGE_SECURITY_LABEL - 1UL)
 +#define FATTR4_WORD2_NFS42_MASK (2*FATTR4_WORD2_SECURITY_LABEL - 1UL)
  
  static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)
  {
@@@ -4033,12 -4033,12 +4033,12 @@@ static bool nfs4_error_stateid_expired(
        return false;
  }
  
- void __nfs4_read_done_cb(struct nfs_read_data *data)
+ void __nfs4_read_done_cb(struct nfs_pgio_data *data)
  {
        nfs_invalidate_atime(data->header->inode);
  }
  
- static int nfs4_read_done_cb(struct rpc_task *task, struct nfs_read_data *data)
+ static int nfs4_read_done_cb(struct rpc_task *task, struct nfs_pgio_data *data)
  {
        struct nfs_server *server = NFS_SERVER(data->header->inode);
  
  }
  
  static bool nfs4_read_stateid_changed(struct rpc_task *task,
-               struct nfs_readargs *args)
+               struct nfs_pgio_args *args)
  {
  
        if (!nfs4_error_stateid_expired(task->tk_status) ||
        return true;
  }
  
- static int nfs4_read_done(struct rpc_task *task, struct nfs_read_data *data)
+ static int nfs4_read_done(struct rpc_task *task, struct nfs_pgio_data *data)
  {
  
        dprintk("--> %s\n", __func__);
                return -EAGAIN;
        if (nfs4_read_stateid_changed(task, &data->args))
                return -EAGAIN;
-       return data->read_done_cb ? data->read_done_cb(task, data) :
+       return data->pgio_done_cb ? data->pgio_done_cb(task, data) :
                                    nfs4_read_done_cb(task, data);
  }
  
- static void nfs4_proc_read_setup(struct nfs_read_data *data, struct rpc_message *msg)
+ static void nfs4_proc_read_setup(struct nfs_pgio_data *data, struct rpc_message *msg)
  {
        data->timestamp   = jiffies;
-       data->read_done_cb = nfs4_read_done_cb;
+       data->pgio_done_cb = nfs4_read_done_cb;
        msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_READ];
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 0);
  }
  
- static int nfs4_proc_read_rpc_prepare(struct rpc_task *task, struct nfs_read_data *data)
+ static int nfs4_proc_pgio_rpc_prepare(struct rpc_task *task, struct nfs_pgio_data *data)
  {
        if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
                        &data->args.seq_args,
                        task))
                return 0;
        if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                               data->args.lock_context, FMODE_READ) == -EIO)
+                               data->args.lock_context, data->header->rw_ops->rw_mode) == -EIO)
                return -EIO;
        if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
                return -EIO;
        return 0;
  }
  
- static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_write_data *data)
+ static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_pgio_data *data)
  {
        struct inode *inode = data->header->inode;
        
  }
  
  static bool nfs4_write_stateid_changed(struct rpc_task *task,
-               struct nfs_writeargs *args)
+               struct nfs_pgio_args *args)
  {
  
        if (!nfs4_error_stateid_expired(task->tk_status) ||
        return true;
  }
  
- static int nfs4_write_done(struct rpc_task *task, struct nfs_write_data *data)
+ static int nfs4_write_done(struct rpc_task *task, struct nfs_pgio_data *data)
  {
        if (!nfs4_sequence_done(task, &data->res.seq_res))
                return -EAGAIN;
        if (nfs4_write_stateid_changed(task, &data->args))
                return -EAGAIN;
-       return data->write_done_cb ? data->write_done_cb(task, data) :
+       return data->pgio_done_cb ? data->pgio_done_cb(task, data) :
                nfs4_write_done_cb(task, data);
  }
  
  static
- bool nfs4_write_need_cache_consistency_data(const struct nfs_write_data *data)
+ bool nfs4_write_need_cache_consistency_data(const struct nfs_pgio_data *data)
  {
        const struct nfs_pgio_header *hdr = data->header;
  
        return nfs4_have_delegation(hdr->inode, FMODE_READ) == 0;
  }
  
- static void nfs4_proc_write_setup(struct nfs_write_data *data, struct rpc_message *msg)
+ static void nfs4_proc_write_setup(struct nfs_pgio_data *data, struct rpc_message *msg)
  {
        struct nfs_server *server = NFS_SERVER(data->header->inode);
  
        } else
                data->args.bitmask = server->cache_consistency_bitmask;
  
-       if (!data->write_done_cb)
-               data->write_done_cb = nfs4_write_done_cb;
+       if (!data->pgio_done_cb)
+               data->pgio_done_cb = nfs4_write_done_cb;
        data->res.server = server;
        data->timestamp   = jiffies;
  
        nfs4_init_sequence(&data->args.seq_args, &data->res.seq_res, 1);
  }
  
- static int nfs4_proc_write_rpc_prepare(struct rpc_task *task, struct nfs_write_data *data)
- {
-       if (nfs4_setup_sequence(NFS_SERVER(data->header->inode),
-                       &data->args.seq_args,
-                       &data->res.seq_res,
-                       task))
-               return 0;
-       if (nfs4_set_rw_stateid(&data->args.stateid, data->args.context,
-                               data->args.lock_context, FMODE_WRITE) == -EIO)
-               return -EIO;
-       if (unlikely(test_bit(NFS_CONTEXT_BAD, &data->args.context->flags)))
-               return -EIO;
-       return 0;
- }
  static void nfs4_proc_commit_rpc_prepare(struct rpc_task *task, struct nfs_commit_data *data)
  {
        nfs4_setup_sequence(NFS_SERVER(data->inode),
@@@ -8432,13 -8417,10 +8417,10 @@@ const struct nfs_rpc_ops nfs_v4_cliento
        .pathconf       = nfs4_proc_pathconf,
        .set_capabilities = nfs4_server_capabilities,
        .decode_dirent  = nfs4_decode_dirent,
+       .pgio_rpc_prepare = nfs4_proc_pgio_rpc_prepare,
        .read_setup     = nfs4_proc_read_setup,
-       .read_pageio_init = pnfs_pageio_init_read,
-       .read_rpc_prepare = nfs4_proc_read_rpc_prepare,
        .read_done      = nfs4_read_done,
        .write_setup    = nfs4_proc_write_setup,
-       .write_pageio_init = pnfs_pageio_init_write,
-       .write_rpc_prepare = nfs4_proc_write_rpc_prepare,
        .write_done     = nfs4_write_done,
        .commit_setup   = nfs4_proc_commit_setup,
        .commit_rpc_prepare = nfs4_proc_commit_rpc_prepare,
diff --combined fs/nfs/nfs4state.c
index c0583b9bef716a316c7f2554786345a958c6427c,21275148fc1319db85254153b17903c9b1afcec0..848f6853c59e49c8b3b570ddc99626bfab935212
@@@ -1140,9 -1140,9 +1140,9 @@@ static int nfs4_run_state_manager(void 
  
  static void nfs4_clear_state_manager_bit(struct nfs_client *clp)
  {
 -      smp_mb__before_clear_bit();
 +      smp_mb__before_atomic();
        clear_bit(NFS4CLNT_MANAGER_RUNNING, &clp->cl_state);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(&clp->cl_state, NFS4CLNT_MANAGER_RUNNING);
        rpc_wake_up(&clp->cl_rpcwaitq);
  }
@@@ -1456,7 -1456,7 +1456,7 @@@ static int nfs4_reclaim_open_state(stru
         * server that doesn't support a grace period.
         */
        spin_lock(&sp->so_lock);
-       write_seqcount_begin(&sp->so_reclaim_seqcount);
+       raw_write_seqcount_begin(&sp->so_reclaim_seqcount);
  restart:
        list_for_each_entry(state, &sp->so_states, open_states) {
                if (!test_and_clear_bit(ops->state_flag_bit, &state->flags))
                spin_lock(&sp->so_lock);
                goto restart;
        }
-       write_seqcount_end(&sp->so_reclaim_seqcount);
+       raw_write_seqcount_end(&sp->so_reclaim_seqcount);
        spin_unlock(&sp->so_lock);
        return 0;
  out_err:
        nfs4_put_open_state(state);
        spin_lock(&sp->so_lock);
-       write_seqcount_end(&sp->so_reclaim_seqcount);
+       raw_write_seqcount_end(&sp->so_reclaim_seqcount);
        spin_unlock(&sp->so_lock);
        return status;
  }
diff --combined fs/nfs/pagelist.c
index 03ed984ab4d8016e3e062a7fc95c2b3268e6287c,a8759825ac764343afdc172d0fb6800a2d43fd46..b6ee3a6ee96dd2b06df61a022fadc0841da8d0b4
  #include "internal.h"
  #include "pnfs.h"
  
+ #define NFSDBG_FACILITY               NFSDBG_PAGECACHE
  static struct kmem_cache *nfs_page_cachep;
+ static const struct rpc_call_ops nfs_pgio_common_ops;
+ static void nfs_free_request(struct nfs_page *);
  
- bool nfs_pgarray_set(struct nfs_page_array *p, unsigned int pagecount)
static bool nfs_pgarray_set(struct nfs_page_array *p, unsigned int pagecount)
  {
        p->npages = pagecount;
        if (pagecount <= ARRAY_SIZE(p->page_array))
@@@ -95,7 -100,7 +100,7 @@@ nfs_iocounter_dec(struct nfs_io_counte
  {
        if (atomic_dec_and_test(&c->io_count)) {
                clear_bit(NFS_IO_INPROGRESS, &c->flags);
 -              smp_mb__after_clear_bit();
 +              smp_mb__after_atomic();
                wake_up_bit(&c->flags, NFS_IO_INPROGRESS);
        }
  }
@@@ -133,11 -138,156 +138,156 @@@ nfs_iocounter_wait(struct nfs_io_counte
        return __nfs_iocounter_wait(c);
  }
  
 -      smp_mb__before_clear_bit();
+ static int nfs_wait_bit_uninterruptible(void *word)
+ {
+       io_schedule();
+       return 0;
+ }
+ /*
+  * nfs_page_group_lock - lock the head of the page group
+  * @req - request in group that is to be locked
+  *
+  * this lock must be held if modifying the page group list
+  */
+ void
+ nfs_page_group_lock(struct nfs_page *req)
+ {
+       struct nfs_page *head = req->wb_head;
+       WARN_ON_ONCE(head != head->wb_head);
+       wait_on_bit_lock(&head->wb_flags, PG_HEADLOCK,
+                       nfs_wait_bit_uninterruptible,
+                       TASK_UNINTERRUPTIBLE);
+ }
+ /*
+  * nfs_page_group_unlock - unlock the head of the page group
+  * @req - request in group that is to be unlocked
+  */
+ void
+ nfs_page_group_unlock(struct nfs_page *req)
+ {
+       struct nfs_page *head = req->wb_head;
+       WARN_ON_ONCE(head != head->wb_head);
 -      smp_mb__after_clear_bit();
++      smp_mb__before_atomic();
+       clear_bit(PG_HEADLOCK, &head->wb_flags);
++      smp_mb__after_atomic();
+       wake_up_bit(&head->wb_flags, PG_HEADLOCK);
+ }
+ /*
+  * nfs_page_group_sync_on_bit_locked
+  *
+  * must be called with page group lock held
+  */
+ static bool
+ nfs_page_group_sync_on_bit_locked(struct nfs_page *req, unsigned int bit)
+ {
+       struct nfs_page *head = req->wb_head;
+       struct nfs_page *tmp;
+       WARN_ON_ONCE(!test_bit(PG_HEADLOCK, &head->wb_flags));
+       WARN_ON_ONCE(test_and_set_bit(bit, &req->wb_flags));
+       tmp = req->wb_this_page;
+       while (tmp != req) {
+               if (!test_bit(bit, &tmp->wb_flags))
+                       return false;
+               tmp = tmp->wb_this_page;
+       }
+       /* true! reset all bits */
+       tmp = req;
+       do {
+               clear_bit(bit, &tmp->wb_flags);
+               tmp = tmp->wb_this_page;
+       } while (tmp != req);
+       return true;
+ }
+ /*
+  * nfs_page_group_sync_on_bit - set bit on current request, but only
+  *   return true if the bit is set for all requests in page group
+  * @req - request in page group
+  * @bit - PG_* bit that is used to sync page group
+  */
+ bool nfs_page_group_sync_on_bit(struct nfs_page *req, unsigned int bit)
+ {
+       bool ret;
+       nfs_page_group_lock(req);
+       ret = nfs_page_group_sync_on_bit_locked(req, bit);
+       nfs_page_group_unlock(req);
+       return ret;
+ }
+ /*
+  * nfs_page_group_init - Initialize the page group linkage for @req
+  * @req - a new nfs request
+  * @prev - the previous request in page group, or NULL if @req is the first
+  *         or only request in the group (the head).
+  */
+ static inline void
+ nfs_page_group_init(struct nfs_page *req, struct nfs_page *prev)
+ {
+       WARN_ON_ONCE(prev == req);
+       if (!prev) {
+               req->wb_head = req;
+               req->wb_this_page = req;
+       } else {
+               WARN_ON_ONCE(prev->wb_this_page != prev->wb_head);
+               WARN_ON_ONCE(!test_bit(PG_HEADLOCK, &prev->wb_head->wb_flags));
+               req->wb_head = prev->wb_head;
+               req->wb_this_page = prev->wb_this_page;
+               prev->wb_this_page = req;
+               /* grab extra ref if head request 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))
+                       kref_get(&req->wb_kref);
+       }
+ }
+ /*
+  * nfs_page_group_destroy - sync the destruction of page groups
+  * @req - request that no longer needs the page group
+  *
+  * releases the page group reference from each member once all
+  * members have called this function.
+  */
+ static void
+ nfs_page_group_destroy(struct kref *kref)
+ {
+       struct nfs_page *req = container_of(kref, struct nfs_page, wb_kref);
+       struct nfs_page *tmp, *next;
+       if (!nfs_page_group_sync_on_bit(req, PG_TEARDOWN))
+               return;
+       tmp = req;
+       do {
+               next = tmp->wb_this_page;
+               /* unlink and free */
+               tmp->wb_this_page = tmp;
+               tmp->wb_head = tmp;
+               nfs_free_request(tmp);
+               tmp = next;
+       } while (tmp != req);
+ }
  /**
   * nfs_create_request - Create an NFS read/write request.
   * @ctx: open context to use
-  * @inode: inode to which the request is attached
   * @page: page to write
+  * @last: last nfs request created for this page group or NULL if head
   * @offset: starting offset within the page for the write
   * @count: number of bytes to read/write
   *
   * User should ensure it is safe to sleep in this function.
   */
  struct nfs_page *
- nfs_create_request(struct nfs_open_context *ctx, struct inode *inode,
-                  struct page *page,
-                  unsigned int offset, unsigned int count)
+ nfs_create_request(struct nfs_open_context *ctx, struct page *page,
+                  struct nfs_page *last, unsigned int offset,
+                  unsigned int count)
  {
        struct nfs_page         *req;
        struct nfs_lock_context *l_ctx;
        req->wb_bytes   = count;
        req->wb_context = get_nfs_open_context(ctx);
        kref_init(&req->wb_kref);
+       nfs_page_group_init(req, last);
        return req;
  }
  
@@@ -193,9 -344,9 +344,9 @@@ void nfs_unlock_request(struct nfs_pag
                printk(KERN_ERR "NFS: Invalid unlock attempted\n");
                BUG();
        }
 -      smp_mb__before_clear_bit();
 +      smp_mb__before_atomic();
        clear_bit(PG_BUSY, &req->wb_flags);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(&req->wb_flags, PG_BUSY);
  }
  
@@@ -237,16 -388,22 +388,22 @@@ static void nfs_clear_request(struct nf
        }
  }
  
  /**
   * nfs_release_request - Release the count on an NFS read/write request
   * @req: request to release
   *
   * Note: Should never be called with the spinlock held!
   */
- static void nfs_free_request(struct kref *kref)
+ static void nfs_free_request(struct nfs_page *req)
  {
-       struct nfs_page *req = container_of(kref, struct nfs_page, wb_kref);
+       WARN_ON_ONCE(req->wb_this_page != req);
+       /* extra debug: make sure no sync bits are still set */
+       WARN_ON_ONCE(test_bit(PG_TEARDOWN, &req->wb_flags));
+       WARN_ON_ONCE(test_bit(PG_UNLOCKPAGE, &req->wb_flags));
+       WARN_ON_ONCE(test_bit(PG_UPTODATE, &req->wb_flags));
+       WARN_ON_ONCE(test_bit(PG_WB_END, &req->wb_flags));
+       WARN_ON_ONCE(test_bit(PG_REMOVE, &req->wb_flags));
  
        /* Release struct file and open context */
        nfs_clear_request(req);
  
  void nfs_release_request(struct nfs_page *req)
  {
-       kref_put(&req->wb_kref, nfs_free_request);
- }
- static int nfs_wait_bit_uninterruptible(void *word)
- {
-       io_schedule();
-       return 0;
+       kref_put(&req->wb_kref, nfs_page_group_destroy);
  }
  
  /**
@@@ -279,22 -430,249 +430,249 @@@ nfs_wait_on_request(struct nfs_page *re
                        TASK_UNINTERRUPTIBLE);
  }
  
- bool nfs_generic_pg_test(struct nfs_pageio_descriptor *desc, struct nfs_page *prev, struct nfs_page *req)
+ /*
+  * nfs_generic_pg_test - determine if requests can be coalesced
+  * @desc: pointer to descriptor
+  * @prev: previous request in desc, or NULL
+  * @req: this request
+  *
+  * Returns zero if @req can be coalesced into @desc, otherwise it returns
+  * the size of the request.
+  */
+ size_t nfs_generic_pg_test(struct nfs_pageio_descriptor *desc,
+                          struct nfs_page *prev, struct nfs_page *req)
  {
-       /*
-        * FIXME: ideally we should be able to coalesce all requests
-        * that are not block boundary aligned, but currently this
-        * is problematic for the case of bsize < PAGE_CACHE_SIZE,
-        * since nfs_flush_multi and nfs_pagein_multi assume you
-        * can have only one struct nfs_page.
-        */
-       if (desc->pg_bsize < PAGE_SIZE)
+       if (desc->pg_count > desc->pg_bsize) {
+               /* should never happen */
+               WARN_ON_ONCE(1);
                return 0;
+       }
  
-       return desc->pg_count + req->wb_bytes <= desc->pg_bsize;
+       return min(desc->pg_bsize - desc->pg_count, (size_t)req->wb_bytes);
  }
  EXPORT_SYMBOL_GPL(nfs_generic_pg_test);
  
+ static inline struct nfs_rw_header *NFS_RW_HEADER(struct nfs_pgio_header *hdr)
+ {
+       return container_of(hdr, struct nfs_rw_header, header);
+ }
+ /**
+  * nfs_rw_header_alloc - Allocate a header for a read or write
+  * @ops: Read or write function vector
+  */
+ struct nfs_rw_header *nfs_rw_header_alloc(const struct nfs_rw_ops *ops)
+ {
+       struct nfs_rw_header *header = ops->rw_alloc_header();
+       if (header) {
+               struct nfs_pgio_header *hdr = &header->header;
+               INIT_LIST_HEAD(&hdr->pages);
+               spin_lock_init(&hdr->lock);
+               atomic_set(&hdr->refcnt, 0);
+               hdr->rw_ops = ops;
+       }
+       return header;
+ }
+ EXPORT_SYMBOL_GPL(nfs_rw_header_alloc);
+ /*
+  * nfs_rw_header_free - Free a read or write header
+  * @hdr: The header to free
+  */
+ void nfs_rw_header_free(struct nfs_pgio_header *hdr)
+ {
+       hdr->rw_ops->rw_free_header(NFS_RW_HEADER(hdr));
+ }
+ EXPORT_SYMBOL_GPL(nfs_rw_header_free);
+ /**
+  * nfs_pgio_data_alloc - Allocate pageio data
+  * @hdr: The header making a request
+  * @pagecount: Number of pages to create
+  */
+ static struct nfs_pgio_data *nfs_pgio_data_alloc(struct nfs_pgio_header *hdr,
+                                                unsigned int pagecount)
+ {
+       struct nfs_pgio_data *data, *prealloc;
+       prealloc = &NFS_RW_HEADER(hdr)->rpc_data;
+       if (prealloc->header == NULL)
+               data = prealloc;
+       else
+               data = kzalloc(sizeof(*data), GFP_KERNEL);
+       if (!data)
+               goto out;
+       if (nfs_pgarray_set(&data->pages, pagecount)) {
+               data->header = hdr;
+               atomic_inc(&hdr->refcnt);
+       } else {
+               if (data != prealloc)
+                       kfree(data);
+               data = NULL;
+       }
+ out:
+       return data;
+ }
+ /**
+  * nfs_pgio_data_release - Properly free pageio data
+  * @data: The data to release
+  */
+ void nfs_pgio_data_release(struct nfs_pgio_data *data)
+ {
+       struct nfs_pgio_header *hdr = data->header;
+       struct nfs_rw_header *pageio_header = NFS_RW_HEADER(hdr);
+       put_nfs_open_context(data->args.context);
+       if (data->pages.pagevec != data->pages.page_array)
+               kfree(data->pages.pagevec);
+       if (data == &pageio_header->rpc_data) {
+               data->header = NULL;
+               data = NULL;
+       }
+       if (atomic_dec_and_test(&hdr->refcnt))
+               hdr->completion_ops->completion(hdr);
+       /* Note: we only free the rpc_task after callbacks are done.
+        * See the comment in rpc_free_task() for why
+        */
+       kfree(data);
+ }
+ EXPORT_SYMBOL_GPL(nfs_pgio_data_release);
+ /**
+  * nfs_pgio_rpcsetup - Set up arguments for a pageio call
+  * @data: The pageio data
+  * @count: Number of bytes to read
+  * @offset: Initial offset
+  * @how: How to commit data (writes only)
+  * @cinfo: Commit information for the call (writes only)
+  */
+ static void nfs_pgio_rpcsetup(struct nfs_pgio_data *data,
+                             unsigned int count, unsigned int offset,
+                             int how, struct nfs_commit_info *cinfo)
+ {
+       struct nfs_page *req = data->header->req;
+       /* Set up the RPC argument and reply structs
+        * NB: take care not to mess about with data->commit et al. */
+       data->args.fh     = NFS_FH(data->header->inode);
+       data->args.offset = req_offset(req) + offset;
+       /* pnfs_set_layoutcommit needs this */
+       data->mds_offset = data->args.offset;
+       data->args.pgbase = req->wb_pgbase + offset;
+       data->args.pages  = data->pages.pagevec;
+       data->args.count  = count;
+       data->args.context = get_nfs_open_context(req->wb_context);
+       data->args.lock_context = req->wb_lock_context;
+       data->args.stable  = NFS_UNSTABLE;
+       switch (how & (FLUSH_STABLE | FLUSH_COND_STABLE)) {
+       case 0:
+               break;
+       case FLUSH_COND_STABLE:
+               if (nfs_reqs_to_commit(cinfo))
+                       break;
+       default:
+               data->args.stable = NFS_FILE_SYNC;
+       }
+       data->res.fattr   = &data->fattr;
+       data->res.count   = count;
+       data->res.eof     = 0;
+       data->res.verf    = &data->verf;
+       nfs_fattr_init(&data->fattr);
+ }
+ /**
+  * nfs_pgio_prepare - Prepare pageio data to go over the wire
+  * @task: The current task
+  * @calldata: pageio data to prepare
+  */
+ static void nfs_pgio_prepare(struct rpc_task *task, void *calldata)
+ {
+       struct nfs_pgio_data *data = calldata;
+       int err;
+       err = NFS_PROTO(data->header->inode)->pgio_rpc_prepare(task, data);
+       if (err)
+               rpc_exit(task, err);
+ }
+ int nfs_initiate_pgio(struct rpc_clnt *clnt, struct nfs_pgio_data *data,
+                     const struct rpc_call_ops *call_ops, int how, int flags)
+ {
+       struct rpc_task *task;
+       struct rpc_message msg = {
+               .rpc_argp = &data->args,
+               .rpc_resp = &data->res,
+               .rpc_cred = data->header->cred,
+       };
+       struct rpc_task_setup task_setup_data = {
+               .rpc_client = clnt,
+               .task = &data->task,
+               .rpc_message = &msg,
+               .callback_ops = call_ops,
+               .callback_data = data,
+               .workqueue = nfsiod_workqueue,
+               .flags = RPC_TASK_ASYNC | flags,
+       };
+       int ret = 0;
+       data->header->rw_ops->rw_initiate(data, &msg, &task_setup_data, how);
+       dprintk("NFS: %5u initiated pgio call "
+               "(req %s/%llu, %u bytes @ offset %llu)\n",
+               data->task.tk_pid,
+               data->header->inode->i_sb->s_id,
+               (unsigned long long)NFS_FILEID(data->header->inode),
+               data->args.count,
+               (unsigned long long)data->args.offset);
+       task = rpc_run_task(&task_setup_data);
+       if (IS_ERR(task)) {
+               ret = PTR_ERR(task);
+               goto out;
+       }
+       if (how & FLUSH_SYNC) {
+               ret = rpc_wait_for_completion_task(task);
+               if (ret == 0)
+                       ret = task->tk_status;
+       }
+       rpc_put_task(task);
+ out:
+       return ret;
+ }
+ EXPORT_SYMBOL_GPL(nfs_initiate_pgio);
+ /**
+  * nfs_pgio_error - Clean up from a pageio error
+  * @desc: IO descriptor
+  * @hdr: pageio header
+  */
+ static int nfs_pgio_error(struct nfs_pageio_descriptor *desc,
+                         struct nfs_pgio_header *hdr)
+ {
+       set_bit(NFS_IOHDR_REDO, &hdr->flags);
+       nfs_pgio_data_release(hdr->data);
+       hdr->data = NULL;
+       desc->pg_completion_ops->error_cleanup(&desc->pg_list);
+       return -ENOMEM;
+ }
+ /**
+  * nfs_pgio_release - Release pageio data
+  * @calldata: The pageio data to release
+  */
+ static void nfs_pgio_release(void *calldata)
+ {
+       struct nfs_pgio_data *data = calldata;
+       if (data->header->rw_ops->rw_release)
+               data->header->rw_ops->rw_release(data);
+       nfs_pgio_data_release(data);
+ }
  /**
   * nfs_pageio_init - initialise a page io descriptor
   * @desc: pointer to descriptor
@@@ -307,6 -685,7 +685,7 @@@ void nfs_pageio_init(struct nfs_pageio_
                     struct inode *inode,
                     const struct nfs_pageio_ops *pg_ops,
                     const struct nfs_pgio_completion_ops *compl_ops,
+                    const struct nfs_rw_ops *rw_ops,
                     size_t bsize,
                     int io_flags)
  {
        desc->pg_inode = inode;
        desc->pg_ops = pg_ops;
        desc->pg_completion_ops = compl_ops;
+       desc->pg_rw_ops = rw_ops;
        desc->pg_ioflags = io_flags;
        desc->pg_error = 0;
        desc->pg_lseg = NULL;
  }
  EXPORT_SYMBOL_GPL(nfs_pageio_init);
  
+ /**
+  * nfs_pgio_result - Basic pageio error handling
+  * @task: The task that ran
+  * @calldata: Pageio data to check
+  */
+ static void nfs_pgio_result(struct rpc_task *task, void *calldata)
+ {
+       struct nfs_pgio_data *data = calldata;
+       struct inode *inode = data->header->inode;
+       dprintk("NFS: %s: %5u, (status %d)\n", __func__,
+               task->tk_pid, task->tk_status);
+       if (data->header->rw_ops->rw_done(task, data, inode) != 0)
+               return;
+       if (task->tk_status < 0)
+               nfs_set_pgio_error(data->header, task->tk_status, data->args.offset);
+       else
+               data->header->rw_ops->rw_result(task, data);
+ }
+ /*
+  * Create an RPC task for the given read or write request and kick it.
+  * The page must have been locked by the caller.
+  *
+  * It may happen that the page we're passed is not marked dirty.
+  * This is the case if nfs_updatepage detects a conflicting request
+  * that has been written but not committed.
+  */
+ int nfs_generic_pgio(struct nfs_pageio_descriptor *desc,
+                    struct nfs_pgio_header *hdr)
+ {
+       struct nfs_page         *req;
+       struct page             **pages;
+       struct nfs_pgio_data    *data;
+       struct list_head *head = &desc->pg_list;
+       struct nfs_commit_info cinfo;
+       data = nfs_pgio_data_alloc(hdr, nfs_page_array_len(desc->pg_base,
+                                                          desc->pg_count));
+       if (!data)
+               return nfs_pgio_error(desc, hdr);
+       nfs_init_cinfo(&cinfo, desc->pg_inode, desc->pg_dreq);
+       pages = data->pages.pagevec;
+       while (!list_empty(head)) {
+               req = nfs_list_entry(head->next);
+               nfs_list_remove_request(req);
+               nfs_list_add_request(req, &hdr->pages);
+               *pages++ = req->wb_page;
+       }
+       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
+           (desc->pg_moreio || nfs_reqs_to_commit(&cinfo)))
+               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
+       /* Set up the argument struct */
+       nfs_pgio_rpcsetup(data, desc->pg_count, 0, desc->pg_ioflags, &cinfo);
+       hdr->data = data;
+       desc->pg_rpc_callops = &nfs_pgio_common_ops;
+       return 0;
+ }
+ EXPORT_SYMBOL_GPL(nfs_generic_pgio);
+ static int nfs_generic_pg_pgios(struct nfs_pageio_descriptor *desc)
+ {
+       struct nfs_rw_header *rw_hdr;
+       struct nfs_pgio_header *hdr;
+       int ret;
+       rw_hdr = nfs_rw_header_alloc(desc->pg_rw_ops);
+       if (!rw_hdr) {
+               desc->pg_completion_ops->error_cleanup(&desc->pg_list);
+               return -ENOMEM;
+       }
+       hdr = &rw_hdr->header;
+       nfs_pgheader_init(desc, hdr, nfs_rw_header_free);
+       atomic_inc(&hdr->refcnt);
+       ret = nfs_generic_pgio(desc, hdr);
+       if (ret == 0)
+               ret = nfs_initiate_pgio(NFS_CLIENT(hdr->inode),
+                                       hdr->data, desc->pg_rpc_callops,
+                                       desc->pg_ioflags, 0);
+       if (atomic_dec_and_test(&hdr->refcnt))
+               hdr->completion_ops->completion(hdr);
+       return ret;
+ }
  static bool nfs_match_open_context(const struct nfs_open_context *ctx1,
                const struct nfs_open_context *ctx2)
  {
@@@ -356,18 -824,23 +824,23 @@@ static bool nfs_can_coalesce_requests(s
                                      struct nfs_page *req,
                                      struct nfs_pageio_descriptor *pgio)
  {
-       if (!nfs_match_open_context(req->wb_context, prev->wb_context))
-               return false;
-       if (req->wb_context->dentry->d_inode->i_flock != NULL &&
-           !nfs_match_lock_context(req->wb_lock_context, prev->wb_lock_context))
-               return false;
-       if (req->wb_pgbase != 0)
-               return false;
-       if (prev->wb_pgbase + prev->wb_bytes != PAGE_CACHE_SIZE)
-               return false;
-       if (req_offset(req) != req_offset(prev) + prev->wb_bytes)
-               return false;
-       return pgio->pg_ops->pg_test(pgio, prev, req);
+       size_t size;
+       if (prev) {
+               if (!nfs_match_open_context(req->wb_context, prev->wb_context))
+                       return false;
+               if (req->wb_context->dentry->d_inode->i_flock != NULL &&
+                   !nfs_match_lock_context(req->wb_lock_context,
+                                           prev->wb_lock_context))
+                       return false;
+               if (req_offset(req) != req_offset(prev) + prev->wb_bytes)
+                       return false;
+       }
+       size = pgio->pg_ops->pg_test(pgio, prev, req);
+       WARN_ON_ONCE(size > req->wb_bytes);
+       if (size && size < req->wb_bytes)
+               req->wb_bytes = size;
+       return size > 0;
  }
  
  /**
  static int nfs_pageio_do_add_request(struct nfs_pageio_descriptor *desc,
                                     struct nfs_page *req)
  {
+       struct nfs_page *prev = NULL;
        if (desc->pg_count != 0) {
-               struct nfs_page *prev;
                prev = nfs_list_entry(desc->pg_list.prev);
-               if (!nfs_can_coalesce_requests(prev, req, desc))
-                       return 0;
        } else {
                if (desc->pg_ops->pg_init)
                        desc->pg_ops->pg_init(desc, req);
                desc->pg_base = req->wb_pgbase;
        }
+       if (!nfs_can_coalesce_requests(prev, req, desc))
+               return 0;
        nfs_list_remove_request(req);
        nfs_list_add_request(req, &desc->pg_list);
        desc->pg_count += req->wb_bytes;
@@@ -421,22 -893,73 +893,73 @@@ static void nfs_pageio_doio(struct nfs_
   * @desc: destination io descriptor
   * @req: request
   *
+  * This may split a request into subrequests which are all part of the
+  * same page group.
+  *
   * Returns true if the request 'req' was successfully coalesced into the
   * existing list of pages 'desc'.
   */
  static int __nfs_pageio_add_request(struct nfs_pageio_descriptor *desc,
                           struct nfs_page *req)
  {
-       while (!nfs_pageio_do_add_request(desc, req)) {
-               desc->pg_moreio = 1;
-               nfs_pageio_doio(desc);
-               if (desc->pg_error < 0)
-                       return 0;
-               desc->pg_moreio = 0;
-               if (desc->pg_recoalesce)
-                       return 0;
-       }
+       struct nfs_page *subreq;
+       unsigned int bytes_left = 0;
+       unsigned int offset, pgbase;
+       nfs_page_group_lock(req);
+       subreq = req;
+       bytes_left = subreq->wb_bytes;
+       offset = subreq->wb_offset;
+       pgbase = subreq->wb_pgbase;
+       do {
+               if (!nfs_pageio_do_add_request(desc, subreq)) {
+                       /* make sure pg_test call(s) did nothing */
+                       WARN_ON_ONCE(subreq->wb_bytes != bytes_left);
+                       WARN_ON_ONCE(subreq->wb_offset != offset);
+                       WARN_ON_ONCE(subreq->wb_pgbase != pgbase);
+                       nfs_page_group_unlock(req);
+                       desc->pg_moreio = 1;
+                       nfs_pageio_doio(desc);
+                       if (desc->pg_error < 0)
+                               return 0;
+                       desc->pg_moreio = 0;
+                       if (desc->pg_recoalesce)
+                               return 0;
+                       /* retry add_request for this subreq */
+                       nfs_page_group_lock(req);
+                       continue;
+               }
+               /* check for buggy pg_test call(s) */
+               WARN_ON_ONCE(subreq->wb_bytes + subreq->wb_pgbase > PAGE_SIZE);
+               WARN_ON_ONCE(subreq->wb_bytes > bytes_left);
+               WARN_ON_ONCE(subreq->wb_bytes == 0);
+               bytes_left -= subreq->wb_bytes;
+               offset += subreq->wb_bytes;
+               pgbase += subreq->wb_bytes;
+               if (bytes_left) {
+                       subreq = nfs_create_request(req->wb_context,
+                                       req->wb_page,
+                                       subreq, pgbase, bytes_left);
+                       if (IS_ERR(subreq))
+                               goto err_ptr;
+                       nfs_lock_request(subreq);
+                       subreq->wb_offset  = offset;
+                       subreq->wb_index = req->wb_index;
+               }
+       } while (bytes_left > 0);
+       nfs_page_group_unlock(req);
        return 1;
+ err_ptr:
+       desc->pg_error = PTR_ERR(subreq);
+       nfs_page_group_unlock(req);
+       return 0;
  }
  
  static int nfs_do_recoalesce(struct nfs_pageio_descriptor *desc)
@@@ -535,3 -1058,13 +1058,13 @@@ void nfs_destroy_nfspagecache(void
        kmem_cache_destroy(nfs_page_cachep);
  }
  
+ static const struct rpc_call_ops nfs_pgio_common_ops = {
+       .rpc_call_prepare = nfs_pgio_prepare,
+       .rpc_call_done = nfs_pgio_result,
+       .rpc_release = nfs_pgio_release,
+ };
+ const struct nfs_pageio_ops nfs_pgio_rw_ops = {
+       .pg_test = nfs_generic_pg_test,
+       .pg_doio = nfs_generic_pg_pgios,
+ };
diff --combined fs/nfs/pnfs.c
index fd9536e494bc202184178449b5316c780a1b8e8f,ee60c42b72b3bda3d935ee4fe7f90f50b0c86f01..6fdcd233d6f7b2bd7cf254016af820b997b0e6ed
@@@ -1388,11 -1388,6 +1388,6 @@@ pnfs_generic_pg_init_read(struct nfs_pa
  
        WARN_ON_ONCE(pgio->pg_lseg != NULL);
  
-       if (req->wb_offset != req->wb_pgbase) {
-               nfs_pageio_reset_read_mds(pgio);
-               return;
-       }
        if (pgio->pg_dreq == NULL)
                rd_size = i_size_read(pgio->pg_inode) - req_offset(req);
        else
@@@ -1417,11 -1412,6 +1412,6 @@@ pnfs_generic_pg_init_write(struct nfs_p
  {
        WARN_ON_ONCE(pgio->pg_lseg != NULL);
  
-       if (req->wb_offset != req->wb_pgbase) {
-               nfs_pageio_reset_write_mds(pgio);
-               return;
-       }
        pgio->pg_lseg = pnfs_update_layout(pgio->pg_inode,
                                           req->wb_context,
                                           req_offset(req),
  }
  EXPORT_SYMBOL_GPL(pnfs_generic_pg_init_write);
  
- void
- pnfs_pageio_init_read(struct nfs_pageio_descriptor *pgio, struct inode *inode,
-                     const struct nfs_pgio_completion_ops *compl_ops)
- {
-       struct nfs_server *server = NFS_SERVER(inode);
-       struct pnfs_layoutdriver_type *ld = server->pnfs_curr_ld;
-       if (ld == NULL)
-               nfs_pageio_init_read(pgio, inode, compl_ops);
-       else
-               nfs_pageio_init(pgio, inode, ld->pg_read_ops, compl_ops, server->rsize, 0);
- }
- void
- pnfs_pageio_init_write(struct nfs_pageio_descriptor *pgio, struct inode *inode,
-                      int ioflags,
-                      const struct nfs_pgio_completion_ops *compl_ops)
- {
-       struct nfs_server *server = NFS_SERVER(inode);
-       struct pnfs_layoutdriver_type *ld = server->pnfs_curr_ld;
-       if (ld == NULL)
-               nfs_pageio_init_write(pgio, inode, ioflags, compl_ops);
-       else
-               nfs_pageio_init(pgio, inode, ld->pg_write_ops, compl_ops, server->wsize, ioflags);
- }
- bool
+ /*
+  * Return 0 if @req cannot be coalesced into @pgio, otherwise return the number
+  * of bytes (maximum @req->wb_bytes) that can be coalesced.
+  */
+ size_t
  pnfs_generic_pg_test(struct nfs_pageio_descriptor *pgio, struct nfs_page *prev,
                     struct nfs_page *req)
  {
-       if (pgio->pg_lseg == NULL)
-               return nfs_generic_pg_test(pgio, prev, req);
+       unsigned int size;
+       u64 seg_end, req_start, seg_left;
+       size = nfs_generic_pg_test(pgio, prev, req);
+       if (!size)
+               return 0;
  
        /*
-        * Test if a nfs_page is fully contained in the pnfs_layout_range.
-        * Note that this test makes several assumptions:
-        * - that the previous nfs_page in the struct nfs_pageio_descriptor
-        *   is known to lie within the range.
-        *   - that the nfs_page being tested is known to be contiguous with the
-        *   previous nfs_page.
-        *   - Layout ranges are page aligned, so we only have to test the
-        *   start offset of the request.
+        * 'size' contains the number of bytes left in the current page (up
+        * to the original size asked for in @req->wb_bytes).
+        *
+        * Calculate how many bytes are left in the layout segment
+        * and if there are less bytes than 'size', return that instead.
         *
         * Please also note that 'end_offset' is actually the offset of the
         * first byte that lies outside the pnfs_layout_range. FIXME?
         *
         */
-       return req_offset(req) < end_offset(pgio->pg_lseg->pls_range.offset,
-                                        pgio->pg_lseg->pls_range.length);
+       if (pgio->pg_lseg) {
+               seg_end = end_offset(pgio->pg_lseg->pls_range.offset,
+                                    pgio->pg_lseg->pls_range.length);
+               req_start = req_offset(req);
+               WARN_ON_ONCE(req_start > seg_end);
+               /* start of request is past the last byte of this segment */
+               if (req_start >= seg_end)
+                       return 0;
+               /* adjust 'size' iff there are fewer bytes left in the
+                * segment than what nfs_generic_pg_test returned */
+               seg_left = seg_end - req_start;
+               if (seg_left < size)
+                       size = (unsigned int)seg_left;
+       }
+       return size;
  }
  EXPORT_SYMBOL_GPL(pnfs_generic_pg_test);
  
@@@ -1496,7 -1479,7 +1479,7 @@@ int pnfs_write_done_resend_to_mds(struc
        LIST_HEAD(failed);
  
        /* Resend all requests through the MDS */
-       nfs_pageio_init_write(&pgio, inode, FLUSH_STABLE, compl_ops);
+       nfs_pageio_init_write(&pgio, inode, FLUSH_STABLE, true, compl_ops);
        pgio.pg_dreq = dreq;
        while (!list_empty(head)) {
                struct nfs_page *req = nfs_list_entry(head->next);
  }
  EXPORT_SYMBOL_GPL(pnfs_write_done_resend_to_mds);
  
- static void pnfs_ld_handle_write_error(struct nfs_write_data *data)
+ static void pnfs_ld_handle_write_error(struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
  /*
   * Called by non rpc-based layout drivers
   */
- void pnfs_ld_write_done(struct nfs_write_data *data)
+ void pnfs_ld_write_done(struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
@@@ -1554,7 -1537,7 +1537,7 @@@ EXPORT_SYMBOL_GPL(pnfs_ld_write_done)
  
  static void
  pnfs_write_through_mds(struct nfs_pageio_descriptor *desc,
-               struct nfs_write_data *data)
+               struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
                nfs_pageio_reset_write_mds(desc);
                desc->pg_recoalesce = 1;
        }
-       nfs_writedata_release(data);
+       nfs_pgio_data_release(data);
  }
  
  static enum pnfs_try_status
- pnfs_try_to_write_data(struct nfs_write_data *wdata,
+ pnfs_try_to_write_data(struct nfs_pgio_data *wdata,
                        const struct rpc_call_ops *call_ops,
                        struct pnfs_layout_segment *lseg,
                        int how)
  }
  
  static void
- pnfs_do_multiple_writes(struct nfs_pageio_descriptor *desc, struct list_head *head, int how)
+ pnfs_do_write(struct nfs_pageio_descriptor *desc,
+             struct nfs_pgio_header *hdr, int how)
  {
-       struct nfs_write_data *data;
+       struct nfs_pgio_data *data = hdr->data;
        const struct rpc_call_ops *call_ops = desc->pg_rpc_callops;
        struct pnfs_layout_segment *lseg = desc->pg_lseg;
+       enum pnfs_try_status trypnfs;
  
        desc->pg_lseg = NULL;
-       while (!list_empty(head)) {
-               enum pnfs_try_status trypnfs;
-               data = list_first_entry(head, struct nfs_write_data, list);
-               list_del_init(&data->list);
-               trypnfs = pnfs_try_to_write_data(data, call_ops, lseg, how);
-               if (trypnfs == PNFS_NOT_ATTEMPTED)
-                       pnfs_write_through_mds(desc, data);
-       }
+       trypnfs = pnfs_try_to_write_data(data, call_ops, lseg, how);
+       if (trypnfs == PNFS_NOT_ATTEMPTED)
+               pnfs_write_through_mds(desc, data);
        pnfs_put_lseg(lseg);
  }
  
  static void pnfs_writehdr_free(struct nfs_pgio_header *hdr)
  {
        pnfs_put_lseg(hdr->lseg);
-       nfs_writehdr_free(hdr);
+       nfs_rw_header_free(hdr);
  }
  EXPORT_SYMBOL_GPL(pnfs_writehdr_free);
  
  int
  pnfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc)
  {
-       struct nfs_write_header *whdr;
+       struct nfs_rw_header *whdr;
        struct nfs_pgio_header *hdr;
        int ret;
  
-       whdr = nfs_writehdr_alloc();
+       whdr = nfs_rw_header_alloc(desc->pg_rw_ops);
        if (!whdr) {
                desc->pg_completion_ops->error_cleanup(&desc->pg_list);
                pnfs_put_lseg(desc->pg_lseg);
        nfs_pgheader_init(desc, hdr, pnfs_writehdr_free);
        hdr->lseg = pnfs_get_lseg(desc->pg_lseg);
        atomic_inc(&hdr->refcnt);
-       ret = nfs_generic_flush(desc, hdr);
+       ret = nfs_generic_pgio(desc, hdr);
        if (ret != 0) {
                pnfs_put_lseg(desc->pg_lseg);
                desc->pg_lseg = NULL;
        } else
-               pnfs_do_multiple_writes(desc, &hdr->rpc_list, desc->pg_ioflags);
+               pnfs_do_write(desc, hdr, desc->pg_ioflags);
        if (atomic_dec_and_test(&hdr->refcnt))
                hdr->completion_ops->completion(hdr);
        return ret;
@@@ -1655,7 -1633,7 +1633,7 @@@ int pnfs_read_done_resend_to_mds(struc
        LIST_HEAD(failed);
  
        /* Resend all requests through the MDS */
-       nfs_pageio_init_read(&pgio, inode, compl_ops);
+       nfs_pageio_init_read(&pgio, inode, true, compl_ops);
        pgio.pg_dreq = dreq;
        while (!list_empty(head)) {
                struct nfs_page *req = nfs_list_entry(head->next);
  }
  EXPORT_SYMBOL_GPL(pnfs_read_done_resend_to_mds);
  
- static void pnfs_ld_handle_read_error(struct nfs_read_data *data)
+ static void pnfs_ld_handle_read_error(struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
  /*
   * Called by non rpc-based layout drivers
   */
- void pnfs_ld_read_done(struct nfs_read_data *data)
+ void pnfs_ld_read_done(struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
@@@ -1709,7 -1687,7 +1687,7 @@@ EXPORT_SYMBOL_GPL(pnfs_ld_read_done)
  
  static void
  pnfs_read_through_mds(struct nfs_pageio_descriptor *desc,
-               struct nfs_read_data *data)
+               struct nfs_pgio_data *data)
  {
        struct nfs_pgio_header *hdr = data->header;
  
                nfs_pageio_reset_read_mds(desc);
                desc->pg_recoalesce = 1;
        }
-       nfs_readdata_release(data);
+       nfs_pgio_data_release(data);
  }
  
  /*
   * Call the appropriate parallel I/O subsystem read function.
   */
  static enum pnfs_try_status
- pnfs_try_to_read_data(struct nfs_read_data *rdata,
+ pnfs_try_to_read_data(struct nfs_pgio_data *rdata,
                       const struct rpc_call_ops *call_ops,
                       struct pnfs_layout_segment *lseg)
  {
  }
  
  static void
- pnfs_do_multiple_reads(struct nfs_pageio_descriptor *desc, struct list_head *head)
+ pnfs_do_read(struct nfs_pageio_descriptor *desc, struct nfs_pgio_header *hdr)
  {
-       struct nfs_read_data *data;
+       struct nfs_pgio_data *data = hdr->data;
        const struct rpc_call_ops *call_ops = desc->pg_rpc_callops;
        struct pnfs_layout_segment *lseg = desc->pg_lseg;
+       enum pnfs_try_status trypnfs;
  
        desc->pg_lseg = NULL;
-       while (!list_empty(head)) {
-               enum pnfs_try_status trypnfs;
-               data = list_first_entry(head, struct nfs_read_data, list);
-               list_del_init(&data->list);
-               trypnfs = pnfs_try_to_read_data(data, call_ops, lseg);
-               if (trypnfs == PNFS_NOT_ATTEMPTED)
-                       pnfs_read_through_mds(desc, data);
-       }
+       trypnfs = pnfs_try_to_read_data(data, call_ops, lseg);
+       if (trypnfs == PNFS_NOT_ATTEMPTED)
+               pnfs_read_through_mds(desc, data);
        pnfs_put_lseg(lseg);
  }
  
  static void pnfs_readhdr_free(struct nfs_pgio_header *hdr)
  {
        pnfs_put_lseg(hdr->lseg);
-       nfs_readhdr_free(hdr);
+       nfs_rw_header_free(hdr);
  }
  EXPORT_SYMBOL_GPL(pnfs_readhdr_free);
  
  int
  pnfs_generic_pg_readpages(struct nfs_pageio_descriptor *desc)
  {
-       struct nfs_read_header *rhdr;
+       struct nfs_rw_header *rhdr;
        struct nfs_pgio_header *hdr;
        int ret;
  
-       rhdr = nfs_readhdr_alloc();
+       rhdr = nfs_rw_header_alloc(desc->pg_rw_ops);
        if (!rhdr) {
                desc->pg_completion_ops->error_cleanup(&desc->pg_list);
                ret = -ENOMEM;
        nfs_pgheader_init(desc, hdr, pnfs_readhdr_free);
        hdr->lseg = pnfs_get_lseg(desc->pg_lseg);
        atomic_inc(&hdr->refcnt);
-       ret = nfs_generic_pagein(desc, hdr);
+       ret = nfs_generic_pgio(desc, hdr);
        if (ret != 0) {
                pnfs_put_lseg(desc->pg_lseg);
                desc->pg_lseg = NULL;
        } else
-               pnfs_do_multiple_reads(desc, &hdr->rpc_list);
+               pnfs_do_read(desc, hdr);
        if (atomic_dec_and_test(&hdr->refcnt))
                hdr->completion_ops->completion(hdr);
        return ret;
@@@ -1810,7 -1782,7 +1782,7 @@@ static void pnfs_clear_layoutcommitting
        unsigned long *bitlock = &NFS_I(inode)->flags;
  
        clear_bit_unlock(NFS_INO_LAYOUTCOMMITTING, bitlock);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(bitlock, NFS_INO_LAYOUTCOMMITTING);
  }
  
@@@ -1848,7 -1820,7 +1820,7 @@@ void pnfs_set_lo_fail(struct pnfs_layou
  EXPORT_SYMBOL_GPL(pnfs_set_lo_fail);
  
  void
- pnfs_set_layoutcommit(struct nfs_write_data *wdata)
+ pnfs_set_layoutcommit(struct nfs_pgio_data *wdata)
  {
        struct nfs_pgio_header *hdr = wdata->header;
        struct inode *inode = hdr->inode;
diff --combined fs/nfs/pnfs.h
index c3058a076596f954f4a22379e71a69600b67c834,dccf182ec4d8451c2869d8c03a845971c9464974..4fb309a2b4c48e871de3a13a0b60c8ce66e08c7e
@@@ -113,8 -113,8 +113,8 @@@ struct pnfs_layoutdriver_type 
         * Return PNFS_ATTEMPTED to indicate the layout code has attempted
         * I/O, else return PNFS_NOT_ATTEMPTED to fall back to normal NFS
         */
-       enum pnfs_try_status (*read_pagelist) (struct nfs_read_data *nfs_data);
-       enum pnfs_try_status (*write_pagelist) (struct nfs_write_data *nfs_data, int how);
+       enum pnfs_try_status (*read_pagelist) (struct nfs_pgio_data *nfs_data);
+       enum pnfs_try_status (*write_pagelist) (struct nfs_pgio_data *nfs_data, int how);
  
        void (*free_deviceid_node) (struct nfs4_deviceid_node *);
  
@@@ -180,11 -180,6 +180,6 @@@ extern int nfs4_proc_layoutreturn(struc
  void pnfs_get_layout_hdr(struct pnfs_layout_hdr *lo);
  void pnfs_put_lseg(struct pnfs_layout_segment *lseg);
  
- void pnfs_pageio_init_read(struct nfs_pageio_descriptor *, struct inode *,
-                          const struct nfs_pgio_completion_ops *);
- void pnfs_pageio_init_write(struct nfs_pageio_descriptor *, struct inode *,
-                           int, const struct nfs_pgio_completion_ops *);
  void set_pnfs_layoutdriver(struct nfs_server *, const struct nfs_fh *, u32);
  void unset_pnfs_layoutdriver(struct nfs_server *);
  void pnfs_generic_pg_init_read(struct nfs_pageio_descriptor *, struct nfs_page *);
@@@ -192,7 -187,8 +187,8 @@@ int pnfs_generic_pg_readpages(struct nf
  void pnfs_generic_pg_init_write(struct nfs_pageio_descriptor *pgio,
                                struct nfs_page *req, u64 wb_size);
  int pnfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc);
- bool pnfs_generic_pg_test(struct nfs_pageio_descriptor *pgio, struct nfs_page *prev, struct nfs_page *req);
+ size_t pnfs_generic_pg_test(struct nfs_pageio_descriptor *pgio,
+                           struct nfs_page *prev, struct nfs_page *req);
  void pnfs_set_lo_fail(struct pnfs_layout_segment *lseg);
  struct pnfs_layout_segment *pnfs_layout_process(struct nfs4_layoutget *lgp);
  void pnfs_free_lseg_list(struct list_head *tmp_list);
@@@ -217,13 -213,13 +213,13 @@@ bool pnfs_roc(struct inode *ino)
  void pnfs_roc_release(struct inode *ino);
  void pnfs_roc_set_barrier(struct inode *ino, u32 barrier);
  bool pnfs_roc_drain(struct inode *ino, u32 *barrier, struct rpc_task *task);
- void pnfs_set_layoutcommit(struct nfs_write_data *wdata);
+ void pnfs_set_layoutcommit(struct nfs_pgio_data *wdata);
  void pnfs_cleanup_layoutcommit(struct nfs4_layoutcommit_data *data);
  int pnfs_layoutcommit_inode(struct inode *inode, bool sync);
  int _pnfs_return_layout(struct inode *);
  int pnfs_commit_and_return_layout(struct inode *);
- void pnfs_ld_write_done(struct nfs_write_data *);
- void pnfs_ld_read_done(struct nfs_read_data *);
+ void pnfs_ld_write_done(struct nfs_pgio_data *);
+ void pnfs_ld_read_done(struct nfs_pgio_data *);
  struct pnfs_layout_segment *pnfs_update_layout(struct inode *ino,
                                               struct nfs_open_context *ctx,
                                               loff_t pos,
@@@ -275,7 -271,7 +271,7 @@@ pnfs_get_lseg(struct pnfs_layout_segmen
  {
        if (lseg) {
                atomic_inc(&lseg->pls_refcount);
 -              smp_mb__after_atomic_inc();
 +              smp_mb__after_atomic();
        }
        return lseg;
  }
@@@ -461,18 -457,6 +457,6 @@@ static inline void unset_pnfs_layoutdri
  {
  }
  
- static inline void pnfs_pageio_init_read(struct nfs_pageio_descriptor *pgio, struct inode *inode,
-                                        const struct nfs_pgio_completion_ops *compl_ops)
- {
-       nfs_pageio_init_read(pgio, inode, compl_ops);
- }
- static inline void pnfs_pageio_init_write(struct nfs_pageio_descriptor *pgio, struct inode *inode, int ioflags,
-                                         const struct nfs_pgio_completion_ops *compl_ops)
- {
-       nfs_pageio_init_write(pgio, inode, ioflags, compl_ops);
- }
  static inline int
  pnfs_commit_list(struct inode *inode, struct list_head *mds_pages, int how,
                 struct nfs_commit_info *cinfo)
diff --combined fs/nfs/write.c
index ffb9459f180bc73f3d7d499c14f5ba4c0b219968,17b98952f7bdda895434844c0c93b39583be358f..3ee5af4e738efb0f014d5b9dbd9963a179de5dc5
   * Local function declarations
   */
  static void nfs_redirty_request(struct nfs_page *req);
- static const struct rpc_call_ops nfs_write_common_ops;
  static const struct rpc_call_ops nfs_commit_ops;
  static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops;
  static const struct nfs_commit_completion_ops nfs_commit_completion_ops;
+ static const struct nfs_rw_ops nfs_rw_write_ops;
  
  static struct kmem_cache *nfs_wdata_cachep;
  static mempool_t *nfs_wdata_mempool;
@@@ -70,76 -70,19 +70,19 @@@ void nfs_commit_free(struct nfs_commit_
  }
  EXPORT_SYMBOL_GPL(nfs_commit_free);
  
- struct nfs_write_header *nfs_writehdr_alloc(void)
+ static struct nfs_rw_header *nfs_writehdr_alloc(void)
  {
-       struct nfs_write_header *p = mempool_alloc(nfs_wdata_mempool, GFP_NOIO);
-       if (p) {
-               struct nfs_pgio_header *hdr = &p->header;
+       struct nfs_rw_header *p = mempool_alloc(nfs_wdata_mempool, GFP_NOIO);
  
+       if (p)
                memset(p, 0, sizeof(*p));
-               INIT_LIST_HEAD(&hdr->pages);
-               INIT_LIST_HEAD(&hdr->rpc_list);
-               spin_lock_init(&hdr->lock);
-               atomic_set(&hdr->refcnt, 0);
-               hdr->verf = &p->verf;
-       }
        return p;
  }
- EXPORT_SYMBOL_GPL(nfs_writehdr_alloc);
- static struct nfs_write_data *nfs_writedata_alloc(struct nfs_pgio_header *hdr,
-                                                 unsigned int pagecount)
- {
-       struct nfs_write_data *data, *prealloc;
-       prealloc = &container_of(hdr, struct nfs_write_header, header)->rpc_data;
-       if (prealloc->header == NULL)
-               data = prealloc;
-       else
-               data = kzalloc(sizeof(*data), GFP_KERNEL);
-       if (!data)
-               goto out;
-       if (nfs_pgarray_set(&data->pages, pagecount)) {
-               data->header = hdr;
-               atomic_inc(&hdr->refcnt);
-       } else {
-               if (data != prealloc)
-                       kfree(data);
-               data = NULL;
-       }
- out:
-       return data;
- }
  
void nfs_writehdr_free(struct nfs_pgio_header *hdr)
static void nfs_writehdr_free(struct nfs_rw_header *whdr)
  {
-       struct nfs_write_header *whdr = container_of(hdr, struct nfs_write_header, header);
        mempool_free(whdr, nfs_wdata_mempool);
  }
- EXPORT_SYMBOL_GPL(nfs_writehdr_free);
- void nfs_writedata_release(struct nfs_write_data *wdata)
- {
-       struct nfs_pgio_header *hdr = wdata->header;
-       struct nfs_write_header *write_header = container_of(hdr, struct nfs_write_header, header);
-       put_nfs_open_context(wdata->args.context);
-       if (wdata->pages.pagevec != wdata->pages.page_array)
-               kfree(wdata->pages.pagevec);
-       if (wdata == &write_header->rpc_data) {
-               wdata->header = NULL;
-               wdata = NULL;
-       }
-       if (atomic_dec_and_test(&hdr->refcnt))
-               hdr->completion_ops->completion(hdr);
-       /* Note: we only free the rpc_task after callbacks are done.
-        * See the comment in rpc_free_task() for why
-        */
-       kfree(wdata);
- }
- EXPORT_SYMBOL_GPL(nfs_writedata_release);
  
  static void nfs_context_set_write_error(struct nfs_open_context *ctx, int error)
  {
@@@ -211,18 -154,78 +154,78 @@@ static void nfs_set_pageerror(struct pa
        nfs_zap_mapping(page_file_mapping(page)->host, page_file_mapping(page));
  }
  
+ /*
+  * nfs_page_group_search_locked
+  * @head - head request of page group
+  * @page_offset - offset into page
+  *
+  * Search page group with head @head to find a request that contains the
+  * page offset @page_offset.
+  *
+  * Returns a pointer to the first matching nfs request, or NULL if no
+  * match is found.
+  *
+  * Must be called with the page group lock held
+  */
+ static struct nfs_page *
+ nfs_page_group_search_locked(struct nfs_page *head, unsigned int page_offset)
+ {
+       struct nfs_page *req;
+       WARN_ON_ONCE(head != head->wb_head);
+       WARN_ON_ONCE(!test_bit(PG_HEADLOCK, &head->wb_head->wb_flags));
+       req = head;
+       do {
+               if (page_offset >= req->wb_pgbase &&
+                   page_offset < (req->wb_pgbase + req->wb_bytes))
+                       return req;
+               req = req->wb_this_page;
+       } while (req != head);
+       return NULL;
+ }
+ /*
+  * nfs_page_group_covers_page
+  * @head - head request of page group
+  *
+  * Return true if the page group with head @head covers the whole page,
+  * returns false otherwise
+  */
+ static bool nfs_page_group_covers_page(struct nfs_page *req)
+ {
+       struct nfs_page *tmp;
+       unsigned int pos = 0;
+       unsigned int len = nfs_page_length(req->wb_page);
+       nfs_page_group_lock(req);
+       do {
+               tmp = nfs_page_group_search_locked(req->wb_head, pos);
+               if (tmp) {
+                       /* no way this should happen */
+                       WARN_ON_ONCE(tmp->wb_pgbase != pos);
+                       pos += tmp->wb_bytes - (pos - tmp->wb_pgbase);
+               }
+       } while (tmp && pos < len);
+       nfs_page_group_unlock(req);
+       WARN_ON_ONCE(pos > len);
+       return pos == len;
+ }
  /* We can set the PG_uptodate flag if we see that a write request
   * covers the full page.
   */
- static void nfs_mark_uptodate(struct page *page, unsigned int base, unsigned int count)
+ static void nfs_mark_uptodate(struct nfs_page *req)
  {
-       if (PageUptodate(page))
-               return;
-       if (base != 0)
+       if (PageUptodate(req->wb_page))
                return;
-       if (count != nfs_page_length(page))
+       if (!nfs_page_group_covers_page(req))
                return;
-       SetPageUptodate(page);
+       SetPageUptodate(req->wb_page);
  }
  
  static int wb_priority(struct writeback_control *wbc)
@@@ -258,12 -261,15 +261,15 @@@ static void nfs_set_page_writeback(stru
        }
  }
  
- static void nfs_end_page_writeback(struct page *page)
+ static void nfs_end_page_writeback(struct nfs_page *req)
  {
-       struct inode *inode = page_file_mapping(page)->host;
+       struct inode *inode = page_file_mapping(req->wb_page)->host;
        struct nfs_server *nfss = NFS_SERVER(inode);
  
-       end_page_writeback(page);
+       if (!nfs_page_group_sync_on_bit(req, PG_WB_END))
+               return;
+       end_page_writeback(req->wb_page);
        if (atomic_long_dec_return(&nfss->writeback) < NFS_CONGESTION_OFF_THRESH)
                clear_bdi_congested(&nfss->backing_dev_info, BLK_RW_ASYNC);
  }
@@@ -354,10 -360,8 +360,8 @@@ static int nfs_writepage_locked(struct 
        struct nfs_pageio_descriptor pgio;
        int err;
  
-       NFS_PROTO(page_file_mapping(page)->host)->write_pageio_init(&pgio,
-                                                         page->mapping->host,
-                                                         wb_priority(wbc),
-                                                         &nfs_async_write_completion_ops);
+       nfs_pageio_init_write(&pgio, page->mapping->host, wb_priority(wbc),
+                               false, &nfs_async_write_completion_ops);
        err = nfs_do_writepage(page, wbc, &pgio);
        nfs_pageio_complete(&pgio);
        if (err < 0)
@@@ -400,12 -404,13 +404,13 @@@ int nfs_writepages(struct address_spac
  
        nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);
  
-       NFS_PROTO(inode)->write_pageio_init(&pgio, inode, wb_priority(wbc), &nfs_async_write_completion_ops);
+       nfs_pageio_init_write(&pgio, inode, wb_priority(wbc), false,
+                               &nfs_async_write_completion_ops);
        err = write_cache_pages(mapping, wbc, nfs_writepages_callback, &pgio);
        nfs_pageio_complete(&pgio);
  
        clear_bit_unlock(NFS_INO_FLUSHING, bitlock);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(bitlock, NFS_INO_FLUSHING);
  
        if (err < 0)
@@@ -425,6 -430,8 +430,8 @@@ static void nfs_inode_add_request(struc
  {
        struct nfs_inode *nfsi = NFS_I(inode);
  
+       WARN_ON_ONCE(req->wb_this_page != req);
        /* Lock the request! */
        nfs_lock_request(req);
  
                set_page_private(req->wb_page, (unsigned long)req);
        }
        nfsi->npages++;
+       set_bit(PG_INODE_REF, &req->wb_flags);
        kref_get(&req->wb_kref);
        spin_unlock(&inode->i_lock);
  }
@@@ -452,15 -460,20 +460,20 @@@ static void nfs_inode_remove_request(st
  {
        struct inode *inode = req->wb_context->dentry->d_inode;
        struct nfs_inode *nfsi = NFS_I(inode);
+       struct nfs_page *head;
  
-       spin_lock(&inode->i_lock);
-       if (likely(!PageSwapCache(req->wb_page))) {
-               set_page_private(req->wb_page, 0);
-               ClearPagePrivate(req->wb_page);
-               clear_bit(PG_MAPPED, &req->wb_flags);
+       if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
+               head = req->wb_head;
+               spin_lock(&inode->i_lock);
+               if (likely(!PageSwapCache(head->wb_page))) {
+                       set_page_private(head->wb_page, 0);
+                       ClearPagePrivate(head->wb_page);
+                       clear_bit(PG_MAPPED, &head->wb_flags);
+               }
+               nfsi->npages--;
+               spin_unlock(&inode->i_lock);
        }
-       nfsi->npages--;
-       spin_unlock(&inode->i_lock);
        nfs_release_request(req);
  }
  
@@@ -583,7 -596,7 +596,7 @@@ nfs_clear_request_commit(struct nfs_pag
  }
  
  static inline
- int nfs_write_need_commit(struct nfs_write_data *data)
+ int nfs_write_need_commit(struct nfs_pgio_data *data)
  {
        if (data->verf.committed == NFS_DATA_SYNC)
                return data->header->lseg == NULL;
@@@ -614,7 -627,7 +627,7 @@@ nfs_clear_request_commit(struct nfs_pag
  }
  
  static inline
- int nfs_write_need_commit(struct nfs_write_data *data)
+ int nfs_write_need_commit(struct nfs_pgio_data *data)
  {
        return 0;
  }
@@@ -625,6 -638,7 +638,7 @@@ static void nfs_write_completion(struc
  {
        struct nfs_commit_info cinfo;
        unsigned long bytes = 0;
+       bool do_destroy;
  
        if (test_bit(NFS_IOHDR_REDO, &hdr->flags))
                goto out;
                        goto next;
                }
                if (test_bit(NFS_IOHDR_NEED_COMMIT, &hdr->flags)) {
-                       memcpy(&req->wb_verf, &hdr->verf->verifier, sizeof(req->wb_verf));
+                       memcpy(&req->wb_verf, &hdr->verf.verifier, sizeof(req->wb_verf));
                        nfs_mark_request_commit(req, hdr->lseg, &cinfo);
                        goto next;
                }
@@@ -653,7 -667,8 +667,8 @@@ remove_req
                nfs_inode_remove_request(req);
  next:
                nfs_unlock_request(req);
-               nfs_end_page_writeback(req->wb_page);
+               nfs_end_page_writeback(req);
+               do_destroy = !test_bit(NFS_IOHDR_NEED_COMMIT, &hdr->flags);
                nfs_release_request(req);
        }
  out:
  }
  
  #if  IS_ENABLED(CONFIG_NFS_V3) || IS_ENABLED(CONFIG_NFS_V4)
static unsigned long
+ unsigned long
  nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
  {
        return cinfo->mds->ncommit;
@@@ -718,7 -733,7 +733,7 @@@ nfs_scan_commit(struct inode *inode, st
  }
  
  #else
static unsigned long nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
+ unsigned long nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
  {
        return 0;
  }
@@@ -758,6 -773,10 +773,10 @@@ static struct nfs_page *nfs_try_to_upda
                if (req == NULL)
                        goto out_unlock;
  
+               /* should be handled by nfs_flush_incompatible */
+               WARN_ON_ONCE(req->wb_head != req);
+               WARN_ON_ONCE(req->wb_this_page != req);
                rqend = req->wb_offset + req->wb_bytes;
                /*
                 * Tell the caller to flush out the request if
@@@ -819,7 -838,7 +838,7 @@@ static struct nfs_page * nfs_setup_writ
        req = nfs_try_to_update_request(inode, page, offset, bytes);
        if (req != NULL)
                goto out;
-       req = nfs_create_request(ctx, inode, page, offset, bytes);
+       req = nfs_create_request(ctx, page, NULL, offset, bytes);
        if (IS_ERR(req))
                goto out;
        nfs_inode_add_request(inode, req);
@@@ -837,7 -856,7 +856,7 @@@ static int nfs_writepage_setup(struct n
                return PTR_ERR(req);
        /* Update file length */
        nfs_grow_file(page, offset, count);
-       nfs_mark_uptodate(page, req->wb_pgbase, req->wb_bytes);
+       nfs_mark_uptodate(req);
        nfs_mark_request_dirty(req);
        nfs_unlock_and_release_request(req);
        return 0;
@@@ -863,6 -882,8 +882,8 @@@ int nfs_flush_incompatible(struct file 
                        return 0;
                l_ctx = req->wb_lock_context;
                do_flush = req->wb_page != page || req->wb_context != ctx;
+               /* for now, flush if more than 1 request in page_group */
+               do_flush |= req->wb_this_page != req;
                if (l_ctx && ctx->dentry->d_inode->i_flock != NULL) {
                        do_flush |= l_ctx->lockowner.l_owner != current->files
                                || l_ctx->lockowner.l_pid != current->tgid;
@@@ -990,126 -1011,17 +1011,17 @@@ static int flush_task_priority(int how
        return RPC_PRIORITY_NORMAL;
  }
  
- int nfs_initiate_write(struct rpc_clnt *clnt,
-                      struct nfs_write_data *data,
-                      const struct rpc_call_ops *call_ops,
-                      int how, int flags)
+ static void nfs_initiate_write(struct nfs_pgio_data *data, struct rpc_message *msg,
+                              struct rpc_task_setup *task_setup_data, int how)
  {
        struct inode *inode = data->header->inode;
        int priority = flush_task_priority(how);
-       struct rpc_task *task;
-       struct rpc_message msg = {
-               .rpc_argp = &data->args,
-               .rpc_resp = &data->res,
-               .rpc_cred = data->header->cred,
-       };
-       struct rpc_task_setup task_setup_data = {
-               .rpc_client = clnt,
-               .task = &data->task,
-               .rpc_message = &msg,
-               .callback_ops = call_ops,
-               .callback_data = data,
-               .workqueue = nfsiod_workqueue,
-               .flags = RPC_TASK_ASYNC | flags,
-               .priority = priority,
-       };
-       int ret = 0;
-       /* Set up the initial task struct.  */
-       NFS_PROTO(inode)->write_setup(data, &msg);
  
-       dprintk("NFS: %5u initiated write call "
-               "(req %s/%llu, %u bytes @ offset %llu)\n",
-               data->task.tk_pid,
-               inode->i_sb->s_id,
-               (unsigned long long)NFS_FILEID(inode),
-               data->args.count,
-               (unsigned long long)data->args.offset);
+       task_setup_data->priority = priority;
+       NFS_PROTO(inode)->write_setup(data, msg);
  
        nfs4_state_protect_write(NFS_SERVER(inode)->nfs_client,
-                                &task_setup_data.rpc_client, &msg, data);
-       task = rpc_run_task(&task_setup_data);
-       if (IS_ERR(task)) {
-               ret = PTR_ERR(task);
-               goto out;
-       }
-       if (how & FLUSH_SYNC) {
-               ret = rpc_wait_for_completion_task(task);
-               if (ret == 0)
-                       ret = task->tk_status;
-       }
-       rpc_put_task(task);
- out:
-       return ret;
- }
- EXPORT_SYMBOL_GPL(nfs_initiate_write);
- /*
-  * Set up the argument/result storage required for the RPC call.
-  */
- static void nfs_write_rpcsetup(struct nfs_write_data *data,
-               unsigned int count, unsigned int offset,
-               int how, struct nfs_commit_info *cinfo)
- {
-       struct nfs_page *req = data->header->req;
-       /* Set up the RPC argument and reply structs
-        * NB: take care not to mess about with data->commit et al. */
-       data->args.fh     = NFS_FH(data->header->inode);
-       data->args.offset = req_offset(req) + offset;
-       /* pnfs_set_layoutcommit needs this */
-       data->mds_offset = data->args.offset;
-       data->args.pgbase = req->wb_pgbase + offset;
-       data->args.pages  = data->pages.pagevec;
-       data->args.count  = count;
-       data->args.context = get_nfs_open_context(req->wb_context);
-       data->args.lock_context = req->wb_lock_context;
-       data->args.stable  = NFS_UNSTABLE;
-       switch (how & (FLUSH_STABLE | FLUSH_COND_STABLE)) {
-       case 0:
-               break;
-       case FLUSH_COND_STABLE:
-               if (nfs_reqs_to_commit(cinfo))
-                       break;
-       default:
-               data->args.stable = NFS_FILE_SYNC;
-       }
-       data->res.fattr   = &data->fattr;
-       data->res.count   = count;
-       data->res.verf    = &data->verf;
-       nfs_fattr_init(&data->fattr);
- }
- static int nfs_do_write(struct nfs_write_data *data,
-               const struct rpc_call_ops *call_ops,
-               int how)
- {
-       struct inode *inode = data->header->inode;
-       return nfs_initiate_write(NFS_CLIENT(inode), data, call_ops, how, 0);
- }
- static int nfs_do_multiple_writes(struct list_head *head,
-               const struct rpc_call_ops *call_ops,
-               int how)
- {
-       struct nfs_write_data *data;
-       int ret = 0;
-       while (!list_empty(head)) {
-               int ret2;
-               data = list_first_entry(head, struct nfs_write_data, list);
-               list_del_init(&data->list);
-               
-               ret2 = nfs_do_write(data, call_ops, how);
-                if (ret == 0)
-                        ret = ret2;
-       }
-       return ret;
+                                &task_setup_data->rpc_client, msg, data);
  }
  
  /* If a nfs_flush_* function fails, it should remove reqs from @head and
@@@ -1120,7 -1032,7 +1032,7 @@@ static void nfs_redirty_request(struct 
  {
        nfs_mark_request_dirty(req);
        nfs_unlock_request(req);
-       nfs_end_page_writeback(req->wb_page);
+       nfs_end_page_writeback(req);
        nfs_release_request(req);
  }
  
@@@ -1140,173 -1052,30 +1052,30 @@@ static const struct nfs_pgio_completion
        .completion = nfs_write_completion,
  };
  
- static void nfs_flush_error(struct nfs_pageio_descriptor *desc,
-               struct nfs_pgio_header *hdr)
- {
-       set_bit(NFS_IOHDR_REDO, &hdr->flags);
-       while (!list_empty(&hdr->rpc_list)) {
-               struct nfs_write_data *data = list_first_entry(&hdr->rpc_list,
-                               struct nfs_write_data, list);
-               list_del(&data->list);
-               nfs_writedata_release(data);
-       }
-       desc->pg_completion_ops->error_cleanup(&desc->pg_list);
- }
- /*
-  * Generate multiple small requests to write out a single
-  * contiguous dirty area on one page.
-  */
- static int nfs_flush_multi(struct nfs_pageio_descriptor *desc,
-                          struct nfs_pgio_header *hdr)
- {
-       struct nfs_page *req = hdr->req;
-       struct page *page = req->wb_page;
-       struct nfs_write_data *data;
-       size_t wsize = desc->pg_bsize, nbytes;
-       unsigned int offset;
-       int requests = 0;
-       struct nfs_commit_info cinfo;
-       nfs_init_cinfo(&cinfo, desc->pg_inode, desc->pg_dreq);
-       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
-           (desc->pg_moreio || nfs_reqs_to_commit(&cinfo) ||
-            desc->pg_count > wsize))
-               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
-       offset = 0;
-       nbytes = desc->pg_count;
-       do {
-               size_t len = min(nbytes, wsize);
-               data = nfs_writedata_alloc(hdr, 1);
-               if (!data) {
-                       nfs_flush_error(desc, hdr);
-                       return -ENOMEM;
-               }
-               data->pages.pagevec[0] = page;
-               nfs_write_rpcsetup(data, len, offset, desc->pg_ioflags, &cinfo);
-               list_add(&data->list, &hdr->rpc_list);
-               requests++;
-               nbytes -= len;
-               offset += len;
-       } while (nbytes != 0);
-       nfs_list_remove_request(req);
-       nfs_list_add_request(req, &hdr->pages);
-       desc->pg_rpc_callops = &nfs_write_common_ops;
-       return 0;
- }
- /*
-  * Create an RPC task for the given write request and kick it.
-  * The page must have been locked by the caller.
-  *
-  * It may happen that the page we're passed is not marked dirty.
-  * This is the case if nfs_updatepage detects a conflicting request
-  * that has been written but not committed.
-  */
- static int nfs_flush_one(struct nfs_pageio_descriptor *desc,
-                        struct nfs_pgio_header *hdr)
- {
-       struct nfs_page         *req;
-       struct page             **pages;
-       struct nfs_write_data   *data;
-       struct list_head *head = &desc->pg_list;
-       struct nfs_commit_info cinfo;
-       data = nfs_writedata_alloc(hdr, nfs_page_array_len(desc->pg_base,
-                                                          desc->pg_count));
-       if (!data) {
-               nfs_flush_error(desc, hdr);
-               return -ENOMEM;
-       }
-       nfs_init_cinfo(&cinfo, desc->pg_inode, desc->pg_dreq);
-       pages = data->pages.pagevec;
-       while (!list_empty(head)) {
-               req = nfs_list_entry(head->next);
-               nfs_list_remove_request(req);
-               nfs_list_add_request(req, &hdr->pages);
-               *pages++ = req->wb_page;
-       }
-       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
-           (desc->pg_moreio || nfs_reqs_to_commit(&cinfo)))
-               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
-       /* Set up the argument struct */
-       nfs_write_rpcsetup(data, desc->pg_count, 0, desc->pg_ioflags, &cinfo);
-       list_add(&data->list, &hdr->rpc_list);
-       desc->pg_rpc_callops = &nfs_write_common_ops;
-       return 0;
- }
- int nfs_generic_flush(struct nfs_pageio_descriptor *desc,
-                     struct nfs_pgio_header *hdr)
- {
-       if (desc->pg_bsize < PAGE_CACHE_SIZE)
-               return nfs_flush_multi(desc, hdr);
-       return nfs_flush_one(desc, hdr);
- }
- EXPORT_SYMBOL_GPL(nfs_generic_flush);
- static int nfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc)
- {
-       struct nfs_write_header *whdr;
-       struct nfs_pgio_header *hdr;
-       int ret;
-       whdr = nfs_writehdr_alloc();
-       if (!whdr) {
-               desc->pg_completion_ops->error_cleanup(&desc->pg_list);
-               return -ENOMEM;
-       }
-       hdr = &whdr->header;
-       nfs_pgheader_init(desc, hdr, nfs_writehdr_free);
-       atomic_inc(&hdr->refcnt);
-       ret = nfs_generic_flush(desc, hdr);
-       if (ret == 0)
-               ret = nfs_do_multiple_writes(&hdr->rpc_list,
-                                            desc->pg_rpc_callops,
-                                            desc->pg_ioflags);
-       if (atomic_dec_and_test(&hdr->refcnt))
-               hdr->completion_ops->completion(hdr);
-       return ret;
- }
- static const struct nfs_pageio_ops nfs_pageio_write_ops = {
-       .pg_test = nfs_generic_pg_test,
-       .pg_doio = nfs_generic_pg_writepages,
- };
  void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
-                              struct inode *inode, int ioflags,
+                              struct inode *inode, int ioflags, bool force_mds,
                               const struct nfs_pgio_completion_ops *compl_ops)
  {
-       nfs_pageio_init(pgio, inode, &nfs_pageio_write_ops, compl_ops,
-                               NFS_SERVER(inode)->wsize, ioflags);
+       struct nfs_server *server = NFS_SERVER(inode);
+       const struct nfs_pageio_ops *pg_ops = &nfs_pgio_rw_ops;
+ #ifdef CONFIG_NFS_V4_1
+       if (server->pnfs_curr_ld && !force_mds)
+               pg_ops = server->pnfs_curr_ld->pg_write_ops;
+ #endif
+       nfs_pageio_init(pgio, inode, pg_ops, compl_ops, &nfs_rw_write_ops,
+                       server->wsize, ioflags);
  }
  EXPORT_SYMBOL_GPL(nfs_pageio_init_write);
  
  void nfs_pageio_reset_write_mds(struct nfs_pageio_descriptor *pgio)
  {
-       pgio->pg_ops = &nfs_pageio_write_ops;
+       pgio->pg_ops = &nfs_pgio_rw_ops;
        pgio->pg_bsize = NFS_SERVER(pgio->pg_inode)->wsize;
  }
  EXPORT_SYMBOL_GPL(nfs_pageio_reset_write_mds);
  
  
- void nfs_write_prepare(struct rpc_task *task, void *calldata)
- {
-       struct nfs_write_data *data = calldata;
-       int err;
-       err = NFS_PROTO(data->header->inode)->write_rpc_prepare(task, data);
-       if (err)
-               rpc_exit(task, err);
- }
  void nfs_commit_prepare(struct rpc_task *task, void *calldata)
  {
        struct nfs_commit_data *data = calldata;
        NFS_PROTO(data->inode)->commit_rpc_prepare(task, data);
  }
  
- /*
-  * Handle a write reply that flushes a whole page.
-  *
-  * FIXME: There is an inherent race with invalidate_inode_pages and
-  *      writebacks since the page->count is kept > 1 for as long
-  *      as the page has a write request pending.
-  */
- static void nfs_writeback_done_common(struct rpc_task *task, void *calldata)
- {
-       struct nfs_write_data   *data = calldata;
-       nfs_writeback_done(task, data);
- }
- static void nfs_writeback_release_common(void *calldata)
+ static void nfs_writeback_release_common(struct nfs_pgio_data *data)
  {
-       struct nfs_write_data   *data = calldata;
        struct nfs_pgio_header *hdr = data->header;
        int status = data->task.tk_status;
  
                if (test_bit(NFS_IOHDR_NEED_RESCHED, &hdr->flags))
                        ; /* Do nothing */
                else if (!test_and_set_bit(NFS_IOHDR_NEED_COMMIT, &hdr->flags))
-                       memcpy(hdr->verf, &data->verf, sizeof(*hdr->verf));
-               else if (memcmp(hdr->verf, &data->verf, sizeof(*hdr->verf)))
+                       memcpy(&hdr->verf, &data->verf, sizeof(hdr->verf));
+               else if (memcmp(&hdr->verf, &data->verf, sizeof(hdr->verf)))
                        set_bit(NFS_IOHDR_NEED_RESCHED, &hdr->flags);
                spin_unlock(&hdr->lock);
        }
-       nfs_writedata_release(data);
  }
  
- static const struct rpc_call_ops nfs_write_common_ops = {
-       .rpc_call_prepare = nfs_write_prepare,
-       .rpc_call_done = nfs_writeback_done_common,
-       .rpc_release = nfs_writeback_release_common,
- };
+ /*
+  * Special version of should_remove_suid() that ignores capabilities.
+  */
+ static int nfs_should_remove_suid(const struct inode *inode)
+ {
+       umode_t mode = inode->i_mode;
+       int kill = 0;
+       /* suid always must be killed */
+       if (unlikely(mode & S_ISUID))
+               kill = ATTR_KILL_SUID;
  
+       /*
+        * sgid without any exec bits is just a mandatory locking mark; leave
+        * it alone.  If some exec bits are set, it's a real sgid; kill it.
+        */
+       if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
+               kill |= ATTR_KILL_SGID;
+       if (unlikely(kill && S_ISREG(mode)))
+               return kill;
+       return 0;
+ }
  
  /*
   * This function is called when the WRITE call is complete.
   */
- void nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
+ static int nfs_writeback_done(struct rpc_task *task, struct nfs_pgio_data *data,
+                             struct inode *inode)
  {
-       struct nfs_writeargs    *argp = &data->args;
-       struct nfs_writeres     *resp = &data->res;
-       struct inode            *inode = data->header->inode;
        int status;
  
-       dprintk("NFS: %5u nfs_writeback_done (status %d)\n",
-               task->tk_pid, task->tk_status);
        /*
         * ->write_done will attempt to use post-op attributes to detect
         * conflicting writes by other clients.  A strict interpretation
         */
        status = NFS_PROTO(inode)->write_done(task, data);
        if (status != 0)
-               return;
-       nfs_add_stats(inode, NFSIOS_SERVERWRITTENBYTES, resp->count);
+               return status;
+       nfs_add_stats(inode, NFSIOS_SERVERWRITTENBYTES, data->res.count);
  
  #if IS_ENABLED(CONFIG_NFS_V3) || IS_ENABLED(CONFIG_NFS_V4)
-       if (resp->verf->committed < argp->stable && task->tk_status >= 0) {
+       if (data->res.verf->committed < data->args.stable && task->tk_status >= 0) {
                /* We tried a write call, but the server did not
                 * commit data to stable storage even though we
                 * requested it.
                        dprintk("NFS:       faulty NFS server %s:"
                                " (committed = %d) != (stable = %d)\n",
                                NFS_SERVER(inode)->nfs_client->cl_hostname,
-                               resp->verf->committed, argp->stable);
+                               data->res.verf->committed, data->args.stable);
                        complain = jiffies + 300 * HZ;
                }
        }
  #endif
-       if (task->tk_status < 0)
-               nfs_set_pgio_error(data->header, task->tk_status, argp->offset);
-       else if (resp->count < argp->count) {
+       /* Deal with the suid/sgid bit corner case */
+       if (nfs_should_remove_suid(inode))
+               nfs_mark_for_revalidate(inode);
+       return 0;
+ }
+ /*
+  * This function is called when the WRITE call is complete.
+  */
+ static void nfs_writeback_result(struct rpc_task *task, struct nfs_pgio_data *data)
+ {
+       struct nfs_pgio_args    *argp = &data->args;
+       struct nfs_pgio_res     *resp = &data->res;
+       if (resp->count < argp->count) {
                static unsigned long    complain;
  
                /* This a short write! */
-               nfs_inc_stats(inode, NFSIOS_SHORTWRITE);
+               nfs_inc_stats(data->header->inode, NFSIOS_SHORTWRITE);
  
                /* Has the server at least made some progress? */
                if (resp->count == 0) {
@@@ -1458,7 -1237,7 +1237,7 @@@ static int nfs_commit_set_lock(struct n
  static void nfs_commit_clear_lock(struct nfs_inode *nfsi)
  {
        clear_bit(NFS_INO_COMMIT, &nfsi->flags);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
        wake_up_bit(&nfsi->flags, NFS_INO_COMMIT);
  }
  
@@@ -1874,7 -1653,7 +1653,7 @@@ int nfs_migrate_page(struct address_spa
  int __init nfs_init_writepagecache(void)
  {
        nfs_wdata_cachep = kmem_cache_create("nfs_write_data",
-                                            sizeof(struct nfs_write_header),
+                                            sizeof(struct nfs_rw_header),
                                             0, SLAB_HWCACHE_ALIGN,
                                             NULL);
        if (nfs_wdata_cachep == NULL)
@@@ -1936,3 -1715,12 +1715,12 @@@ void nfs_destroy_writepagecache(void
        kmem_cache_destroy(nfs_wdata_cachep);
  }
  
+ static const struct nfs_rw_ops nfs_rw_write_ops = {
+       .rw_mode                = FMODE_WRITE,
+       .rw_alloc_header        = nfs_writehdr_alloc,
+       .rw_free_header         = nfs_writehdr_free,
+       .rw_release             = nfs_writeback_release_common,
+       .rw_done                = nfs_writeback_done,
+       .rw_result              = nfs_writeback_result,
+       .rw_initiate            = nfs_initiate_write,
+ };
index 3876f0f1dfd38115a1ac981d11f1aea641c03041,5903d2c0ab4d4b8279031c6ea7c698b7424a5dac..fcbfe8783243bb66c65c440a02104a0041ccec4b
  #define RPC_MAX_SLOT_TABLE_LIMIT      (65536U)
  #define RPC_MAX_SLOT_TABLE    RPC_MAX_SLOT_TABLE_LIMIT
  
+ #define RPC_CWNDSHIFT         (8U)
+ #define RPC_CWNDSCALE         (1U << RPC_CWNDSHIFT)
+ #define RPC_INITCWND          RPC_CWNDSCALE
+ #define RPC_MAXCWND(xprt)     ((xprt)->max_reqs << RPC_CWNDSHIFT)
+ #define RPCXPRT_CONGESTED(xprt) ((xprt)->cong >= (xprt)->cwnd)
  /*
   * This describes a timeout strategy
   */
@@@ -379,9 -385,9 +385,9 @@@ static inline int xprt_test_and_clear_c
  
  static inline void xprt_clear_connecting(struct rpc_xprt *xprt)
  {
 -      smp_mb__before_clear_bit();
 +      smp_mb__before_atomic();
        clear_bit(XPRT_CONNECTING, &xprt->state);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
  }
  
  static inline int xprt_connecting(struct rpc_xprt *xprt)
@@@ -411,9 -417,9 +417,9 @@@ static inline void xprt_clear_bound(str
  
  static inline void xprt_clear_binding(struct rpc_xprt *xprt)
  {
 -      smp_mb__before_clear_bit();
 +      smp_mb__before_atomic();
        clear_bit(XPRT_BINDING, &xprt->state);
 -      smp_mb__after_clear_bit();
 +      smp_mb__after_atomic();
  }
  
  static inline int xprt_test_and_set_binding(struct rpc_xprt *xprt)
diff --combined net/sunrpc/xprt.c
index 89d051de6b3e8da4fb439e9e2380cba71d5c716e,2d1d5a643b950c42b068b97f8f8db9cf5d14ce19..c3b2b3369e52ad48bca94f44e3d95695e7bd147d
@@@ -71,24 -71,6 +71,6 @@@ static void   xprt_destroy(struct rpc_xp
  static DEFINE_SPINLOCK(xprt_list_lock);
  static LIST_HEAD(xprt_list);
  
- /*
-  * The transport code maintains an estimate on the maximum number of out-
-  * standing RPC requests, using a smoothed version of the congestion
-  * avoidance implemented in 44BSD. This is basically the Van Jacobson
-  * congestion algorithm: If a retransmit occurs, the congestion window is
-  * halved; otherwise, it is incremented by 1/cwnd when
-  *
-  *    -       a reply is received and
-  *    -       a full number of requests are outstanding and
-  *    -       the congestion window hasn't been updated recently.
-  */
- #define RPC_CWNDSHIFT         (8U)
- #define RPC_CWNDSCALE         (1U << RPC_CWNDSHIFT)
- #define RPC_INITCWND          RPC_CWNDSCALE
- #define RPC_MAXCWND(xprt)     ((xprt)->max_reqs << RPC_CWNDSHIFT)
- #define RPCXPRT_CONGESTED(xprt) ((xprt)->cong >= (xprt)->cwnd)
  /**
   * xprt_register_transport - register a transport implementation
   * @transport: transport to register
@@@ -230,9 -212,9 +212,9 @@@ static void xprt_clear_locked(struct rp
  {
        xprt->snd_task = NULL;
        if (!test_bit(XPRT_CLOSE_WAIT, &xprt->state)) {
 -              smp_mb__before_clear_bit();
 +              smp_mb__before_atomic();
                clear_bit(XPRT_LOCKED, &xprt->state);
 -              smp_mb__after_clear_bit();
 +              smp_mb__after_atomic();
        } else
                queue_work(rpciod_workqueue, &xprt->task_cleanup);
  }
@@@ -446,7 -428,15 +428,15 @@@ EXPORT_SYMBOL_GPL(xprt_release_rqst_con
   * @task: recently completed RPC request used to adjust window
   * @result: result code of completed RPC request
   *
-  * We use a time-smoothed congestion estimator to avoid heavy oscillation.
+  * The transport code maintains an estimate on the maximum number of out-
+  * standing RPC requests, using a smoothed version of the congestion
+  * avoidance implemented in 44BSD. This is basically the Van Jacobson
+  * congestion algorithm: If a retransmit occurs, the congestion window is
+  * halved; otherwise, it is incremented by 1/cwnd when
+  *
+  *    -       a reply is received and
+  *    -       a full number of requests are outstanding and
+  *    -       the congestion window hasn't been updated recently.
   */
  void xprt_adjust_cwnd(struct rpc_xprt *xprt, struct rpc_task *task, int result)
  {