diff mbox series

[1/3] fs: Introduce buffered_write_operations

Message ID 20240130055414.2143959-2-willy@infradead.org
State New
Headers show
Series Start moving write_begin/write_end out of aops | expand

Commit Message

Matthew Wilcox Jan. 30, 2024, 5:54 a.m. UTC
Start the process of moving write_begin and write_end out from the
address_space_operations to their own struct.  Make them take a folio
instead of a page, and pass a pointer to the fsdata to write_end
(so that we don't need to initialise it).

Pass an optional buffered_write_operations pointer to various functions
in filemap.c.  The old names are available as macros for now, except
for generic_file_write_iter() which is used as a function pointer by
many filesystems.

Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
---
 fs/jfs/file.c           |  3 +-
 fs/ramfs/file-mmu.c     |  3 +-
 fs/ufs/file.c           |  2 +-
 include/linux/fs.h      |  3 --
 include/linux/pagemap.h | 22 +++++++++++++
 mm/filemap.c            | 70 +++++++++++++++++++++++++++--------------
 6 files changed, 71 insertions(+), 32 deletions(-)

Comments

Christoph Hellwig Jan. 30, 2024, 8:12 a.m. UTC | #1
> +struct buffered_write_operations {
> +	int (*write_begin)(struct file *, struct address_space *mapping,
> +			loff_t pos, size_t len, struct folio **foliop,
> +			void **fsdata);
> +	int (*write_end)(struct file *, struct address_space *mapping,
> +			loff_t pos, size_t len, size_t copied,
> +			struct folio *folio, void **fsdata);
> +};

Should write_begin simply return the folio or an ERR_PTR instead of
the return by reference?

I also wonder if the fsdata paramter should go away - if a fs needs
to pass forth and back fsdata, generic/filemap_perform_write is
probably the wrong abstraction for it.

Otherwise this looks sane to me.
kernel test robot Jan. 31, 2024, 3:14 a.m. UTC | #2
Hi Matthew,

kernel test robot noticed the following build warnings:

[auto build test WARNING on kleikamp-shaggy/jfs-next]
[also build test WARNING on akpm-mm/mm-everything linus/master v6.8-rc2 next-20240130]
[cannot apply to tytso-ext4/dev]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Matthew-Wilcox-Oracle/fs-Introduce-buffered_write_operations/20240130-135555
base:   https://github.com/kleikamp/linux-shaggy jfs-next
patch link:    https://lore.kernel.org/r/20240130055414.2143959-2-willy%40infradead.org
patch subject: [PATCH 1/3] fs: Introduce buffered_write_operations
config: x86_64-allnoconfig (https://download.01.org/0day-ci/archive/20240131/202401311058.81kC2rCv-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240131/202401311058.81kC2rCv-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202401311058.81kC2rCv-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> mm/filemap.c:4060: warning: Function parameter or struct member 'ops' not described in 'filemap_write_iter'


vim +4060 mm/filemap.c

^1da177e4c3f41 Linus Torvalds          2005-04-16  4043  
e4dd9de3c66bc7 Jan Kara                2009-08-17  4044  /**
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4045)  * filemap_write_iter - write data to a file
e4dd9de3c66bc7 Jan Kara                2009-08-17  4046   * @iocb:	IO state structure
8174202b34c30e Al Viro                 2014-04-03  4047   * @from:	iov_iter with data to write
e4dd9de3c66bc7 Jan Kara                2009-08-17  4048   *
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4049)  * This is a wrapper around __filemap_write_iter() to be used by most
e4dd9de3c66bc7 Jan Kara                2009-08-17  4050   * filesystems. It takes care of syncing the file in case of O_SYNC file
9608703e488cf7 Jan Kara                2021-04-12  4051   * and acquires i_rwsem as needed.
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4052)  *
a862f68a8b3600 Mike Rapoport           2019-03-05  4053   * Return:
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4054)  * * negative error code if no data has been written at all or if
a862f68a8b3600 Mike Rapoport           2019-03-05  4055   *   vfs_fsync_range() failed for a synchronous write
a862f68a8b3600 Mike Rapoport           2019-03-05  4056   * * number of bytes written, even for truncated writes
e4dd9de3c66bc7 Jan Kara                2009-08-17  4057   */
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4058) ssize_t filemap_write_iter(struct kiocb *iocb, struct iov_iter *from,
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4059) 		const struct buffered_write_operations *ops)
^1da177e4c3f41 Linus Torvalds          2005-04-16 @4060  {
^1da177e4c3f41 Linus Torvalds          2005-04-16  4061  	struct file *file = iocb->ki_filp;
148f948ba877f4 Jan Kara                2009-08-17  4062  	struct inode *inode = file->f_mapping->host;
^1da177e4c3f41 Linus Torvalds          2005-04-16  4063  	ssize_t ret;
^1da177e4c3f41 Linus Torvalds          2005-04-16  4064  
5955102c9984fa Al Viro                 2016-01-22  4065  	inode_lock(inode);
3309dd04cbcd2c Al Viro                 2015-04-09  4066  	ret = generic_write_checks(iocb, from);
3309dd04cbcd2c Al Viro                 2015-04-09  4067  	if (ret > 0)
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4068) 		ret = __filemap_write_iter(iocb, from, ops);
5955102c9984fa Al Viro                 2016-01-22  4069  	inode_unlock(inode);
^1da177e4c3f41 Linus Torvalds          2005-04-16  4070  
e259221763a404 Christoph Hellwig       2016-04-07  4071  	if (ret > 0)
e259221763a404 Christoph Hellwig       2016-04-07  4072  		ret = generic_write_sync(iocb, ret);
^1da177e4c3f41 Linus Torvalds          2005-04-16  4073  	return ret;
^1da177e4c3f41 Linus Torvalds          2005-04-16  4074  }
21878deb477823 Matthew Wilcox (Oracle  2024-01-30  4075)
Matthew Wilcox Feb. 1, 2024, 4:36 a.m. UTC | #3
On Tue, Jan 30, 2024 at 09:12:52AM +0100, Christoph Hellwig wrote:
> > +struct buffered_write_operations {
> > +	int (*write_begin)(struct file *, struct address_space *mapping,
> > +			loff_t pos, size_t len, struct folio **foliop,
> > +			void **fsdata);
> > +	int (*write_end)(struct file *, struct address_space *mapping,
> > +			loff_t pos, size_t len, size_t copied,
> > +			struct folio *folio, void **fsdata);
> > +};
> 
> Should write_begin simply return the folio or an ERR_PTR instead of
> the return by reference?

OK, I've done that.  It's a _lot_ more intrusive for the ext4
conversion.  There's a higher risk of bugs.  BUT I think it does end up
looking a bit cleaner.  I also did the same conversion to iomap; ie

-static int iomap_write_begin(struct iomap_iter *iter, loff_t pos,
-               size_t len, struct folio **foliop)
+static struct folio *iomap_write_begin(struct iomap_iter *iter, loff_t pos,
+               size_t len)

with corresponding changes.  Again, ends up looking slightly cleaner.

> I also wonder if the fsdata paramter should go away - if a fs needs
> to pass forth and back fsdata, generic/filemap_perform_write is
> probably the wrong abstraction for it.

So I could get rid of fsdata in ext4; that works out fine.

We have three filesystems actually using fsdata (unless they call fsdata
something else ...)

fs/bcachefs/fs-io-buffered.c:   *fsdata = res;
fs/f2fs/compress.c:             *fsdata = cc->rpages;
fs/ocfs2/aops.c:        *fsdata = wc;

bcachefs seems to actually be using it for its intended purpose --
passing a reservation between write_begin and write_end.

f2fs also doesn't seem terribly objectional; passing rpages between
begin & end.

ocfs2 is passing a ocfs2_write_ctxt between the two.

I don't know that it's a win to remove fsdata from these callbacks,
only to duplicate __generic_file_write_iter() into ocfs2,
generic_perform_write() into f2fs and ... er, I don't think bcachefs
uses any of the functions which would call back through
write_begin/write_end.  So maybe that one's dead code?

Anyway, I'm most inclined to leave fsdata andling the way I had it
in v1, unless you have a better suggestion.
Christoph Hellwig Feb. 1, 2024, 4:42 a.m. UTC | #4
On Thu, Feb 01, 2024 at 04:36:09AM +0000, Matthew Wilcox wrote:
> +static struct folio *iomap_write_begin(struct iomap_iter *iter, loff_t pos,
> +               size_t len)
> 
> with corresponding changes.  Again, ends up looking slightly cleaner.

iomap also really needs some tweaks to the naming in the area as a
__foo function calling foo as the default is horrible.  I'll take a
look at your patches and can add that on top.

> f2fs also doesn't seem terribly objectional; passing rpages between
> begin & end.
> 
> ocfs2 is passing a ocfs2_write_ctxt between the two.

Well, it might be the intended purpose, but as-is it is horribly
inefficient - they need to do a dynamic allocation for every
page they iterate over.  So all of them are candidates for an
iterator model that does this allocation once per write.

But maybe this isn't the time to deal with that and we should just leave
it in place.
Matthew Wilcox Feb. 2, 2024, 7:54 p.m. UTC | #5
On Thu, Feb 01, 2024 at 05:42:46AM +0100, Christoph Hellwig wrote:
> On Thu, Feb 01, 2024 at 04:36:09AM +0000, Matthew Wilcox wrote:
> > +static struct folio *iomap_write_begin(struct iomap_iter *iter, loff_t pos,
> > +               size_t len)
> > 
> > with corresponding changes.  Again, ends up looking slightly cleaner.
> 
> iomap also really needs some tweaks to the naming in the area as a
> __foo function calling foo as the default is horrible.  I'll take a
> look at your patches and can add that on top.
> 
> > f2fs also doesn't seem terribly objectional; passing rpages between
> > begin & end.
> > 
> > ocfs2 is passing a ocfs2_write_ctxt between the two.
> 
> Well, it might be the intended purpose, but as-is it is horribly
> inefficient - they need to do a dynamic allocation for every
> page they iterate over.  So all of them are candidates for an
> iterator model that does this allocation once per write.

Oh, I see what you mean.  What I could do is pass in the fsdata,
like this.

commit 753c7d2d62e1
Author: Matthew Wilcox (Oracle) <willy@infradead.org>
Date:   Mon Jan 29 23:20:34 2024 -0500

    fs: Introduce buffered_write_operations
    
    Start the process of moving write_begin and write_end out from the
    address_space_operations to their own struct.
    
    The new write_begin returns the folio or an ERR_PTR instead of returning
    the folio by reference.  It also accepts len as a size_t and (as
    documented) the len may be larger than PAGE_SIZE.
    
    Pass an optional buffered_write_operations pointer to various functions
    in filemap.c.  The old names are available as macros for now, except
    for generic_file_write_iter() which is used as a function pointer by
    many filesystems.  If using the new functions, the filesystem can have
    per-operation fsdata instead of per-page fsdata.
    
    Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>

diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 2df35e65557d..a79c7f15ca9f 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -18,6 +18,27 @@
 
 struct folio_batch;
 
+struct buffered_write_operations {
+	struct folio *(*write_begin)(struct file *, struct address_space *,
+			loff_t pos, size_t len, void **fsdata);
+	size_t (*write_end)(struct file *, struct address_space *,
+			loff_t pos, size_t len, size_t copied,
+			struct folio *folio, void **fsdata);
+};
+
+ssize_t filemap_write_iter(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *, void *fsdata);
+ssize_t __filemap_write_iter(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *, void *fsdata);
+ssize_t filemap_perform_write(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *, void *fsdata);
+
+ssize_t generic_file_write_iter(struct kiocb *, struct iov_iter *);
+#define generic_perform_write(kiocb, iter)		\
+	filemap_perform_write(kiocb, iter, NULL, NULL)
+#define __generic_file_write_iter(kiocb, iter)		\
+	__filemap_write_iter(kiocb, iter, NULL, NULL)
+
 unsigned long invalidate_mapping_pages(struct address_space *mapping,
 					pgoff_t start, pgoff_t end);
 
diff --git a/mm/filemap.c b/mm/filemap.c
index 750e779c23db..214266aeaca5 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -95,7 +95,7 @@
  *    ->invalidate_lock		(filemap_fault)
  *      ->lock_page		(filemap_fault, access_process_vm)
  *
- *  ->i_rwsem			(generic_perform_write)
+ *  ->i_rwsem			(filemap_perform_write)
  *    ->mmap_lock		(fault_in_readable->do_page_fault)
  *
  *  bdi->wb.list_lock
@@ -3890,7 +3890,8 @@ generic_file_direct_write(struct kiocb *iocb, struct iov_iter *from)
 }
 EXPORT_SYMBOL(generic_file_direct_write);
 
-ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
+ssize_t filemap_perform_write(struct kiocb *iocb, struct iov_iter *i,
+		const struct buffered_write_operations *ops, void *fsdata)
 {
 	struct file *file = iocb->ki_filp;
 	loff_t pos = iocb->ki_pos;
@@ -3900,11 +3901,10 @@ ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 	ssize_t written = 0;
 
 	do {
-		struct page *page;
-		unsigned long offset;	/* Offset into pagecache page */
-		unsigned long bytes;	/* Bytes to write to page */
+		struct folio *folio;
+		size_t offset;		/* Offset into pagecache folio */
+		size_t bytes;		/* Bytes to write to page */
 		size_t copied;		/* Bytes copied from user */
-		void *fsdata = NULL;
 
 		offset = (pos & (PAGE_SIZE - 1));
 		bytes = min_t(unsigned long, PAGE_SIZE - offset,
@@ -3927,19 +3927,33 @@ ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 			break;
 		}
 
-		status = a_ops->write_begin(file, mapping, pos, bytes,
+		if (ops) {
+			folio = ops->write_begin(file, mapping, pos, bytes, &fsdata);
+			if (IS_ERR(folio)) {
+				status = PTR_ERR(folio);
+				break;
+			}
+		} else {
+			struct page *page;
+			status = a_ops->write_begin(file, mapping, pos, bytes,
 						&page, &fsdata);
-		if (unlikely(status < 0))
-			break;
+			if (unlikely(status < 0))
+				break;
+			folio = page_folio(page);
+		}
 
 		if (mapping_writably_mapped(mapping))
-			flush_dcache_page(page);
+			flush_dcache_folio(folio);
 
-		copied = copy_page_from_iter_atomic(page, offset, bytes, i);
-		flush_dcache_page(page);
+		copied = copy_folio_from_iter_atomic(folio, offset, bytes, i);
+		flush_dcache_folio(folio);
 
-		status = a_ops->write_end(file, mapping, pos, bytes, copied,
-						page, fsdata);
+		if (ops)
+			status = ops->write_end(file, mapping, pos, bytes,
+					copied, folio, &fsdata);
+		else
+			status = a_ops->write_end(file, mapping, pos, bytes,
+					copied, &folio->page, fsdata);
 		if (unlikely(status != copied)) {
 			iov_iter_revert(i, copied - max(status, 0L));
 			if (unlikely(status < 0))
@@ -3969,12 +3983,13 @@ ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 	iocb->ki_pos += written;
 	return written;
 }
-EXPORT_SYMBOL(generic_perform_write);
+EXPORT_SYMBOL(filemap_perform_write);
 
 /**
- * __generic_file_write_iter - write data to a file
- * @iocb:	IO state structure (file, offset, etc.)
- * @from:	iov_iter with data to write
+ * __filemap_write_iter - write data to a file
+ * @iocb: IO state structure (file, offset, etc.)
+ * @from: iov_iter with data to write
+ * @ops: How to inform the filesystem that a write is starting/finishing.
  *
  * This function does all the work needed for actually writing data to a
  * file. It does all basic checks, removes SUID from the file, updates
@@ -3992,7 +4007,8 @@ EXPORT_SYMBOL(generic_perform_write);
  * * number of bytes written, even for truncated writes
  * * negative error code if no data has been written at all
  */
-ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+ssize_t __filemap_write_iter(struct kiocb *iocb, struct iov_iter *from,
+		const struct buffered_write_operations *ops, void *fsdata)
 {
 	struct file *file = iocb->ki_filp;
 	struct address_space *mapping = file->f_mapping;
@@ -4019,27 +4035,29 @@ ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 		if (ret < 0 || !iov_iter_count(from) || IS_DAX(inode))
 			return ret;
 		return direct_write_fallback(iocb, from, ret,
-				generic_perform_write(iocb, from));
+				filemap_perform_write(iocb, from, ops, fsdata));
 	}
 
-	return generic_perform_write(iocb, from);
+	return filemap_perform_write(iocb, from, ops, fsdata);
 }
-EXPORT_SYMBOL(__generic_file_write_iter);
+EXPORT_SYMBOL(__filemap_write_iter);
 
 /**
- * generic_file_write_iter - write data to a file
+ * filemap_write_iter - write data to a file
  * @iocb:	IO state structure
  * @from:	iov_iter with data to write
  *
- * This is a wrapper around __generic_file_write_iter() to be used by most
+ * This is a wrapper around __filemap_write_iter() to be used by most
  * filesystems. It takes care of syncing the file in case of O_SYNC file
  * and acquires i_rwsem as needed.
+ *
  * Return:
- * * negative error code if no data has been written at all of
+ * * negative error code if no data has been written at all or if
  *   vfs_fsync_range() failed for a synchronous write
  * * number of bytes written, even for truncated writes
  */
-ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+ssize_t filemap_write_iter(struct kiocb *iocb, struct iov_iter *from,
+		const struct buffered_write_operations *ops, void *fsdata)
 {
 	struct file *file = iocb->ki_filp;
 	struct inode *inode = file->f_mapping->host;
@@ -4048,13 +4066,18 @@ ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	inode_lock(inode);
 	ret = generic_write_checks(iocb, from);
 	if (ret > 0)
-		ret = __generic_file_write_iter(iocb, from);
+		ret = __filemap_write_iter(iocb, from, ops, fsdata);
 	inode_unlock(inode);
 
 	if (ret > 0)
 		ret = generic_write_sync(iocb, ret);
 	return ret;
 }
+
+ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	return filemap_write_iter(iocb, from, NULL, NULL);
+}
 EXPORT_SYMBOL(generic_file_write_iter);
 
 /**
diff mbox series

Patch

diff --git a/fs/jfs/file.c b/fs/jfs/file.c
index 01b6912e60f8..9c62445ea6be 100644
--- a/fs/jfs/file.c
+++ b/fs/jfs/file.c
@@ -4,8 +4,7 @@ 
  *   Portions Copyright (C) Christoph Hellwig, 2001-2002
  */
 
-#include <linux/mm.h>
-#include <linux/fs.h>
+#include <linux/pagemap.h>
 #include <linux/posix_acl.h>
 #include <linux/quotaops.h>
 #include "jfs_incore.h"
diff --git a/fs/ramfs/file-mmu.c b/fs/ramfs/file-mmu.c
index c7a1aa3c882b..a621b08b0235 100644
--- a/fs/ramfs/file-mmu.c
+++ b/fs/ramfs/file-mmu.c
@@ -24,8 +24,7 @@ 
  * caches is sufficient.
  */
 
-#include <linux/fs.h>
-#include <linux/mm.h>
+#include <linux/pagemap.h>
 #include <linux/ramfs.h>
 #include <linux/sched.h>
 
diff --git a/fs/ufs/file.c b/fs/ufs/file.c
index 6558882a89ef..b557d4a14143 100644
--- a/fs/ufs/file.c
+++ b/fs/ufs/file.c
@@ -24,7 +24,7 @@ 
  *  ext2 fs regular file handling primitives
  */
 
-#include <linux/fs.h>
+#include <linux/pagemap.h>
 
 #include "ufs_fs.h"
 #include "ufs.h"
diff --git a/include/linux/fs.h b/include/linux/fs.h
index ed5966a70495..92dc0cf08b1f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3048,10 +3048,7 @@  extern int generic_file_rw_checks(struct file *file_in, struct file *file_out);
 ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *to,
 		ssize_t already_read);
 extern ssize_t generic_file_read_iter(struct kiocb *, struct iov_iter *);
-extern ssize_t __generic_file_write_iter(struct kiocb *, struct iov_iter *);
-extern ssize_t generic_file_write_iter(struct kiocb *, struct iov_iter *);
 extern ssize_t generic_file_direct_write(struct kiocb *, struct iov_iter *);
-ssize_t generic_perform_write(struct kiocb *, struct iov_iter *);
 ssize_t direct_write_fallback(struct kiocb *iocb, struct iov_iter *iter,
 		ssize_t direct_written, ssize_t buffered_written);
 
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 2df35e65557d..a5c474ad230e 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -18,6 +18,28 @@ 
 
 struct folio_batch;
 
+struct buffered_write_operations {
+	int (*write_begin)(struct file *, struct address_space *mapping,
+			loff_t pos, size_t len, struct folio **foliop,
+			void **fsdata);
+	int (*write_end)(struct file *, struct address_space *mapping,
+			loff_t pos, size_t len, size_t copied,
+			struct folio *folio, void **fsdata);
+};
+
+ssize_t filemap_write_iter(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *);
+ssize_t __filemap_write_iter(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *);
+ssize_t filemap_perform_write(struct kiocb *, struct iov_iter *,
+		const struct buffered_write_operations *);
+
+ssize_t generic_file_write_iter(struct kiocb *, struct iov_iter *);
+#define generic_perform_write(kiocb, iter)		\
+	filemap_perform_write(kiocb, iter, NULL)
+#define __generic_file_write_iter(kiocb, iter)		\
+	__filemap_write_iter(kiocb, iter, NULL)
+
 unsigned long invalidate_mapping_pages(struct address_space *mapping,
 					pgoff_t start, pgoff_t end);
 
diff --git a/mm/filemap.c b/mm/filemap.c
index 750e779c23db..66d779b787c8 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -95,7 +95,7 @@ 
  *    ->invalidate_lock		(filemap_fault)
  *      ->lock_page		(filemap_fault, access_process_vm)
  *
- *  ->i_rwsem			(generic_perform_write)
+ *  ->i_rwsem			(filemap_perform_write)
  *    ->mmap_lock		(fault_in_readable->do_page_fault)
  *
  *  bdi->wb.list_lock
@@ -3890,7 +3890,8 @@  generic_file_direct_write(struct kiocb *iocb, struct iov_iter *from)
 }
 EXPORT_SYMBOL(generic_file_direct_write);
 
-ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
+ssize_t filemap_perform_write(struct kiocb *iocb, struct iov_iter *i,
+		const struct buffered_write_operations *ops)
 {
 	struct file *file = iocb->ki_filp;
 	loff_t pos = iocb->ki_pos;
@@ -3900,9 +3901,9 @@  ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 	ssize_t written = 0;
 
 	do {
-		struct page *page;
-		unsigned long offset;	/* Offset into pagecache page */
-		unsigned long bytes;	/* Bytes to write to page */
+		struct folio *folio;
+		size_t offset;		/* Offset into pagecache folio */
+		size_t bytes;		/* Bytes to write to page */
 		size_t copied;		/* Bytes copied from user */
 		void *fsdata = NULL;
 
@@ -3927,19 +3928,31 @@  ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 			break;
 		}
 
-		status = a_ops->write_begin(file, mapping, pos, bytes,
+		if (ops)
+			status = ops->write_begin(file, mapping, pos, bytes,
+						&folio, &fsdata);
+		else {
+			struct page *page;
+			status = a_ops->write_begin(file, mapping, pos, bytes,
 						&page, &fsdata);
+			if (status >= 0)
+				folio = page_folio(page);
+		}
 		if (unlikely(status < 0))
 			break;
 
 		if (mapping_writably_mapped(mapping))
-			flush_dcache_page(page);
+			flush_dcache_folio(folio);
 
-		copied = copy_page_from_iter_atomic(page, offset, bytes, i);
-		flush_dcache_page(page);
+		copied = copy_folio_from_iter_atomic(folio, offset, bytes, i);
+		flush_dcache_folio(folio);
 
-		status = a_ops->write_end(file, mapping, pos, bytes, copied,
-						page, fsdata);
+		if (ops)
+			status = ops->write_end(file, mapping, pos, bytes,
+					copied, folio, &fsdata);
+		else
+			status = a_ops->write_end(file, mapping, pos, bytes,
+					copied, &folio->page, fsdata);
 		if (unlikely(status != copied)) {
 			iov_iter_revert(i, copied - max(status, 0L));
 			if (unlikely(status < 0))
@@ -3969,12 +3982,13 @@  ssize_t generic_perform_write(struct kiocb *iocb, struct iov_iter *i)
 	iocb->ki_pos += written;
 	return written;
 }
-EXPORT_SYMBOL(generic_perform_write);
+EXPORT_SYMBOL(filemap_perform_write);
 
 /**
- * __generic_file_write_iter - write data to a file
- * @iocb:	IO state structure (file, offset, etc.)
- * @from:	iov_iter with data to write
+ * __filemap_write_iter - write data to a file
+ * @iocb: IO state structure (file, offset, etc.)
+ * @from: iov_iter with data to write
+ * @ops: How to inform the filesystem that a write is starting/finishing.
  *
  * This function does all the work needed for actually writing data to a
  * file. It does all basic checks, removes SUID from the file, updates
@@ -3992,7 +4006,8 @@  EXPORT_SYMBOL(generic_perform_write);
  * * number of bytes written, even for truncated writes
  * * negative error code if no data has been written at all
  */
-ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+ssize_t __filemap_write_iter(struct kiocb *iocb, struct iov_iter *from,
+		const struct buffered_write_operations *ops)
 {
 	struct file *file = iocb->ki_filp;
 	struct address_space *mapping = file->f_mapping;
@@ -4019,27 +4034,29 @@  ssize_t __generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 		if (ret < 0 || !iov_iter_count(from) || IS_DAX(inode))
 			return ret;
 		return direct_write_fallback(iocb, from, ret,
-				generic_perform_write(iocb, from));
+				filemap_perform_write(iocb, from, ops));
 	}
 
-	return generic_perform_write(iocb, from);
+	return filemap_perform_write(iocb, from, ops);
 }
-EXPORT_SYMBOL(__generic_file_write_iter);
+EXPORT_SYMBOL(__filemap_write_iter);
 
 /**
- * generic_file_write_iter - write data to a file
+ * filemap_write_iter - write data to a file
  * @iocb:	IO state structure
  * @from:	iov_iter with data to write
  *
- * This is a wrapper around __generic_file_write_iter() to be used by most
+ * This is a wrapper around __filemap_write_iter() to be used by most
  * filesystems. It takes care of syncing the file in case of O_SYNC file
  * and acquires i_rwsem as needed.
+ *
  * Return:
- * * negative error code if no data has been written at all of
+ * * negative error code if no data has been written at all or if
  *   vfs_fsync_range() failed for a synchronous write
  * * number of bytes written, even for truncated writes
  */
-ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+ssize_t filemap_write_iter(struct kiocb *iocb, struct iov_iter *from,
+		const struct buffered_write_operations *ops)
 {
 	struct file *file = iocb->ki_filp;
 	struct inode *inode = file->f_mapping->host;
@@ -4048,13 +4065,18 @@  ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
 	inode_lock(inode);
 	ret = generic_write_checks(iocb, from);
 	if (ret > 0)
-		ret = __generic_file_write_iter(iocb, from);
+		ret = __filemap_write_iter(iocb, from, ops);
 	inode_unlock(inode);
 
 	if (ret > 0)
 		ret = generic_write_sync(iocb, ret);
 	return ret;
 }
+
+ssize_t generic_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
+{
+	return filemap_write_iter(iocb, from, NULL);
+}
 EXPORT_SYMBOL(generic_file_write_iter);
 
 /**