* [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction
@ 2026-10-05 7:12 David Howells
2026-10-05 7:12 ` [PATCH v12 1/8] mm: Make readahead store folio count in readahead_control David Howells
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Hi Christian,
Could you pull these patches please into your vfs-7.4.netfs branch? This
is the fourth of five batches. The primary purpose of this batch is to add
an abstraction for a position in a bvecq chain and make use of it.
With that in mind, this patchset does the following:
(1) Add a struct, bvecq_pos, to refer to a position in a bvecq chain.
This is a tuple of ( bvecq pointer, slot and offset } and the position
struct holds (for now) a ref on the bvecq pointed to.
(2) Change unbuffered/DIO to extract the supplied user buffers into a
bvecq chain rather than a bio_vec[] up front. This means that
netfslib then uses bvecq chains for everything.
(3) Add a folio count to the readahead_control struct and then makes
readahead pre-create its buffer using that count to determine how many
slots are required.
(4) Change netfslib to use the bvecq_pos struct to keep track of the whole
request buffer and various points in it where loading, dispatch and
collection are taking place and to also keep track of the slice given
to each subrequest.
This was split from v11 of a larger netfslib series[1].
The patches can also be found here:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git/log/?h=netfs-next-4
Thanks,
David
Changes
=======
ver #12)
- Split from v11 of "netfs: Keep track of folios in a segmented bio_vec[]
chain"[1]
[1] https://lore.kernel.org/r/20260902173350.3468672-1-dhowells@redhat.com/
David Howells (8):
mm: Make readahead store folio count in readahead_control
netfs: Add a function to extract from an iter into a bvecq
netfs: Make unbuffered/DIO read and write use bvecq
netfs: Add some tools for managing a position in a bvecq chain
netfs: Provide a func to load the readahead buffers into a bvecq chain
netfs: Use bvecq_pos to hold the buffer positions
netfs: Remove the rolling_buffer implementation
netfs: Remove netfs_extract_user_iter()
fs/afs/dir.c | 5 +-
fs/afs/symlink.c | 6 +-
fs/cachefiles/io.c | 12 -
fs/netfs/Makefile | 1 -
fs/netfs/buffered_read.c | 169 ++++++++------
fs/netfs/bvecq.c | 310 ++++++++++++++++++++++++++
fs/netfs/direct_read.c | 77 +++----
fs/netfs/direct_write.c | 67 +++---
fs/netfs/internal.h | 4 +-
fs/netfs/iterator.c | 392 ++++++++++-----------------------
fs/netfs/misc.c | 18 --
fs/netfs/objects.c | 16 +-
fs/netfs/read_collect.c | 92 ++++----
fs/netfs/read_pgpriv2.c | 87 +++++---
fs/netfs/read_retry.c | 86 ++++----
fs/netfs/read_single.c | 16 +-
fs/netfs/rolling_buffer.c | 182 ---------------
fs/netfs/write_collect.c | 55 ++---
fs/netfs/write_issue.c | 166 +++++++++-----
fs/netfs/write_retry.c | 61 +++--
include/linux/bvecq.h | 184 ++++++++++++++++
include/linux/netfs.h | 25 ++-
include/linux/pagemap.h | 10 +
include/linux/rolling_buffer.h | 47 ----
include/trace/events/netfs.h | 32 ++-
mm/readahead.c | 5 +
26 files changed, 1195 insertions(+), 930 deletions(-)
delete mode 100644 fs/netfs/rolling_buffer.c
delete mode 100644 include/linux/rolling_buffer.h
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 1/8] mm: Make readahead store folio count in readahead_control
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 2/8] netfs: Add a function to extract from an iter into a bvecq David Howells
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel,
linux-mm
Make readahead store folio count in readahead_control so that the
filesystem can know in advance how many folios it needs to keep track of.
This is cleared by read_pages() in case it is called from a loop.
The count is accessed by the filesystem with readahead_folio_count().
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-mm@kvack.org
cc: linux-fsdevel@vger.kernel.org
---
include/linux/pagemap.h | 10 ++++++++++
mm/readahead.c | 5 +++++
2 files changed, 15 insertions(+)
diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h
index 0adfa6605653..b7154b81d0d7 100644
--- a/include/linux/pagemap.h
+++ b/include/linux/pagemap.h
@@ -1411,6 +1411,7 @@ struct readahead_control {
struct file_ra_state *ra;
/* private: use the readahead_* accessors instead */
pgoff_t _index;
+ unsigned int _nr_folios;
unsigned int _nr_pages;
unsigned int _batch_count;
bool dropbehind;
@@ -1590,6 +1591,15 @@ static inline size_t readahead_batch_length(const struct readahead_control *rac)
return rac->_batch_count * PAGE_SIZE;
}
+/**
+ * readahead_folio_count - Get the number of folios in this readahead request.
+ * @rac: The readahead request.
+ */
+static inline unsigned int readahead_folio_count(const struct readahead_control *rac)
+{
+ return rac->_nr_folios;
+}
+
static inline unsigned long dir_pages(const struct inode *inode)
{
return (unsigned long)(inode->i_size + PAGE_SIZE - 1) >>
diff --git a/mm/readahead.c b/mm/readahead.c
index 6e5563290287..196542fb1c93 100644
--- a/mm/readahead.c
+++ b/mm/readahead.c
@@ -188,6 +188,7 @@ static void read_pages(struct readahead_control *rac)
if (unlikely(rac->_workingset))
psi_memstall_leave(&rac->_pflags);
rac->_workingset = false;
+ rac->_nr_folios = 0;
BUG_ON(readahead_count(rac));
}
@@ -303,6 +304,7 @@ void page_cache_ra_unbounded(struct readahead_control *ractl,
if (i == mark)
folio_set_readahead(folio);
ractl->_workingset |= folio_test_workingset(folio);
+ ractl->_nr_folios++;
ractl->_nr_pages += min_nrpages;
i += min_nrpages;
}
@@ -473,6 +475,7 @@ static inline int ra_alloc_folio(struct readahead_control *ractl, pgoff_t index,
return err;
}
+ ractl->_nr_folios++;
ractl->_nr_pages += 1UL << order;
ractl->_workingset |= folio_test_workingset(folio);
return 0;
@@ -822,6 +825,7 @@ void readahead_expand(struct readahead_control *ractl,
ractl->_workingset = true;
psi_memstall_enter(&ractl->_pflags);
}
+ ractl->_nr_folios++;
ractl->_nr_pages += min_nrpages;
ractl->_index = folio->index;
}
@@ -851,6 +855,7 @@ void readahead_expand(struct readahead_control *ractl,
ractl->_workingset = true;
psi_memstall_enter(&ractl->_pflags);
}
+ ractl->_nr_folios++;
ractl->_nr_pages += min_nrpages;
if (ra) {
ra->size += min_nrpages;
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 2/8] netfs: Add a function to extract from an iter into a bvecq
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
2026-10-05 7:12 ` [PATCH v12 1/8] mm: Make readahead store folio count in readahead_control David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 3/8] netfs: Make unbuffered/DIO read and write use bvecq David Howells
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Add a function to extract a slice of data from an iterator of any type into
a bvec queue chain.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/iterator.c | 143 ++++++++++++++++++++++++++++++++++++++++++
include/linux/netfs.h | 3 +
2 files changed, 146 insertions(+)
diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c
index 31748526d568..55fd2d3b3b29 100644
--- a/fs/netfs/iterator.c
+++ b/fs/netfs/iterator.c
@@ -13,6 +13,149 @@
#include <linux/netfs.h>
#include "internal.h"
+/**
+ * netfs_extract_iter - Extract virtually contiguous pages from an iterator into a bvecq
+ * @orig: The original iterator
+ * @max_len: Maximum number of bytes to extract
+ * @max_pages: Maximum number of pages to extract
+ * @_bvecq_head: Where to cache the bvec queue
+ * @extraction_flags: Flags to qualify the request
+ * @gfp: Allocation mode for bvecq structs.
+ *
+ * Extract virtually contiguous page fragments from the source iterator up to
+ * the given maxima and build bvec queue that refers to all of those bits.
+ * This allows the original iterator to disposed of.
+ *
+ * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-peer DMA
+ * be allowed on the pages extracted.
+ *
+ * On success or partial success, the amount of data in the bvec is returned,
+ * the original iterator will have been advanced by the amount extracted.
+ *
+ * If an error occurs and no pages are extracted, an error will be returned and
+ * any allocated bvecq will be freed. If there is no data to be extracted (or
+ * @max_len or @max_pages are zero), a single empty bvecq will be returned.
+ *
+ * The bvecq segments are marked with indications on how to get clean up the
+ * extracted fragments.
+ */
+ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t max_pages,
+ struct bvecq **_bvecq_head,
+ iov_iter_extraction_t extraction_flags, gfp_t gfp)
+{
+ struct bvecq *bq_tail = NULL, *bq;
+ ssize_t ret = 0;
+ size_t extracted = 0;
+
+ _enter("{%u,%zx},%zx", orig->iter_type, orig->count, max_len);
+
+ *_bvecq_head = NULL;
+ if (max_len > orig->count)
+ max_len = orig->count;
+ if (!max_len || !max_pages)
+ goto alloc_empty;
+ if (WARN_ON_ONCE(max_pages > INT_MAX))
+ max_pages = INT_MAX; /* Protect iov_iter_npages(). */
+
+ max_pages = iov_iter_npages(orig, max_pages);
+ if (!max_pages)
+ goto alloc_empty;
+
+ do {
+ bq = bvecq_alloc_one(max_pages, gfp, false);
+ if (!bq) {
+ ret = -ENOMEM;
+ break;
+ }
+ if (user_backed_iter(orig))
+ bq->mem_type = iov_iter_extract_will_pin(orig) ?
+ BVECQ_MEM_GUP : BVECQ_MEM_PAGECACHE;
+
+ if (bq_tail)
+ bvecq_append(bq_tail, bq);
+ else
+ *_bvecq_head = bq;
+ bq_tail = bq;
+
+ if (max_len == 0)
+ break;
+
+ struct bio_vec *bv = bq->bv;
+ unsigned int slot = 0;
+ do {
+ struct page **pages;
+ ssize_t got;
+ size_t offset;
+ size_t space = bq->max_slots - slot;
+ size_t bv_size = array_size(bq->max_slots, sizeof(*bv));
+ size_t pg_size = array_size(space, sizeof(*pages));
+
+ /* Put the page list at the end of the bvec list
+ * storage. bvec elements are larger than page
+ * pointers, so as long as we work 0->last, we should
+ * be fine.
+ */
+ pages = (void *)bv + bv_size - pg_size;
+
+ got = iov_iter_extract_pages(orig, &pages, max_len,
+ min(space, max_pages),
+ extraction_flags, &offset);
+ if (got < 0) {
+ ret = got;
+ goto out;
+ }
+
+ if (got == 0) {
+ pr_err("extract_pages gave nothing from %zx, %zx\n",
+ extracted, max_len);
+ ret = -EIO;
+ goto out;
+ }
+
+ if (WARN(got > max_len,
+ "%s: extract_pages overrun %zx > %zx bytes\n",
+ __func__, got, max_len)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ extracted += got;
+ max_len -= got;
+
+ do {
+ size_t len = umin(got, PAGE_SIZE - offset);
+
+ BUG_ON(slot >= bq->max_slots);
+
+ bvec_set_page(&bq->bv[slot], *pages++, len, offset);
+ slot++;
+ max_pages--;
+ got -= len;
+ offset = 0;
+ } while (got > 0);
+
+ bvecq_filled_to(bq, slot);
+ } while (max_len > 0 && max_pages > 0 && !bvecq_is_full(bq));
+
+ } while (max_len > 0 && max_pages > 0);
+
+out:
+ if (extracted || ret == 0)
+ return extracted;
+ bvecq_put(*_bvecq_head);
+ *_bvecq_head = NULL;
+ return ret;
+
+alloc_empty:
+ bq = bvecq_alloc_one(1, gfp, false);
+ if (!bq)
+ return -ENOMEM;
+ *_bvecq_head = bq;
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(netfs_extract_iter);
+
/**
* netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
* @orig: The original iterator
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index f1f85473b0ee..de34c485c099 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -452,6 +452,9 @@ void netfs_get_subrequest(struct netfs_io_subrequest *subreq,
enum netfs_sreq_ref_trace what);
void netfs_put_subrequest(struct netfs_io_subrequest *subreq,
enum netfs_sreq_ref_trace what);
+ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t max_pages,
+ struct bvecq **_bvecq_head,
+ iov_iter_extraction_t extraction_flags, gfp_t gfp);
ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
struct iov_iter *new,
iov_iter_extraction_t extraction_flags);
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 3/8] netfs: Make unbuffered/DIO read and write use bvecq
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
2026-10-05 7:12 ` [PATCH v12 1/8] mm: Make readahead store folio count in readahead_control David Howells
2026-10-05 7:12 ` [PATCH v12 2/8] netfs: Add a function to extract from an iter into a bvecq David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 4/8] netfs: Add some tools for managing a position in a bvecq chain David Howells
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Make unbuffered and DIO read and write in netfslib use bvecq. The idea is
to only use bvecq in talking to the filesystem and not pass iterators at
all as the filesystem may just end up extracting them.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/direct_read.c | 31 +++++++++++++------------------
fs/netfs/direct_write.c | 33 ++++++++++++++-------------------
fs/netfs/objects.c | 11 +----------
fs/netfs/read_collect.c | 15 +++++++++------
include/linux/netfs.h | 4 +---
5 files changed, 38 insertions(+), 56 deletions(-)
diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c
index 8c15f3079723..2b8eeb2d3c06 100644
--- a/fs/netfs/direct_read.c
+++ b/fs/netfs/direct_read.c
@@ -177,25 +177,20 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_iter *i
* buffer for ourselves as the caller's iterator will be trashed when
* we return.
*
- * In such a case, extract an iterator to represent as much of the the
- * output buffer as we can manage. Note that the extraction might not
- * be able to allocate a sufficiently large bvec array and may shorten
- * the request.
+ * Extract a buffer queue to represent as much of the output buffer as
+ * we can manage. The fragments are extracted into a bvecq which will
+ * have sufficient nodes allocated to hold all the data, though this
+ * may end up truncated if ENOMEM is encountered.
*/
- if (user_backed_iter(iter)) {
- ret = netfs_extract_user_iter(iter, rreq->len, &rreq->buffer.iter, 0);
- if (ret < 0)
- goto error_put;
- rreq->direct_bv = (struct bio_vec *)rreq->buffer.iter.bvec;
- rreq->direct_bv_count = ret;
- rreq->direct_bv_unpin = iov_iter_extract_will_pin(iter);
- rreq->len = iov_iter_count(&rreq->buffer.iter);
- } else {
- rreq->buffer.iter = *iter;
- rreq->len = orig_count;
- rreq->direct_bv_unpin = false;
- iov_iter_advance(iter, orig_count);
- }
+ ret = netfs_extract_iter(iter, rreq->len, INT_MAX,
+ &rreq->direct_bq, 0, rreq->gfp);
+ if (ret < 0)
+ goto error_put;
+
+ rreq->len = ret;
+
+ iov_iter_bvec_queue(&rreq->buffer.iter, ITER_DEST, rreq->direct_bq,
+ 0, 0, rreq->len);
// TODO: Set up bounce buffer if needed
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index 32200c10d2a4..ff4e027e736a 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -222,10 +222,10 @@ static void netfs_unbuffered_write_async(struct work_struct *work)
* encrypted file. This can also be used for direct I/O writes.
*/
ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *iter,
- struct netfs_group *netfs_group)
+ struct netfs_group *netfs_group)
{
struct netfs_io_request *wreq;
- ssize_t ret, n;
+ ssize_t ret;
uoff_t start = iocb->ki_pos;
uoff_t end = start + iov_iter_count(iter);
size_t len = iov_iter_count(iter);
@@ -261,25 +261,20 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *
* allocate a sufficiently large bvec array and may shorten the
* request.
*/
- if (user_backed_iter(iter)) {
- n = netfs_extract_user_iter(iter, len, &wreq->buffer.iter, 0);
- if (n < 0) {
- ret = n;
- goto error_put;
- }
- wreq->direct_bv = (struct bio_vec *)wreq->buffer.iter.bvec;
- wreq->direct_bv_count = n;
- wreq->direct_bv_unpin = iov_iter_extract_will_pin(iter);
- } else {
- /* If this is a kernel-generated async DIO request,
- * assume that any resources the iterator points to
- * (eg. a bio_vec array) will persist till the end of
- * the op.
- */
- wreq->buffer.iter = *iter;
+ ssize_t n = netfs_extract_iter(iter, len, INT_MAX,
+ &wreq->direct_bq, 0, wreq->gfp);
+
+ if (n < 0) {
+ ret = n;
+ goto error_put;
}
+ wreq->len = n;
+ _debug("dio-write %zx/%zx %u/%u",
+ n, len, wreq->direct_bq->nr_slots,
+ wreq->direct_bq->max_slots);
- wreq->len = iov_iter_count(&wreq->buffer.iter);
+ iov_iter_bvec_queue(&wreq->buffer.iter, ITER_SOURCE,
+ wreq->direct_bq, 0, 0, wreq->len);
}
__set_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags);
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index 4b8d20559b0e..f8a5b6a7a2a9 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -133,7 +133,6 @@ static void netfs_free_request_rcu(struct rcu_head *rcu)
static void netfs_deinit_request(struct netfs_io_request *rreq)
{
struct netfs_inode *ictx = netfs_inode(rreq->inode);
- unsigned int i;
trace_netfs_rreq(rreq, netfs_rreq_trace_free);
@@ -148,15 +147,7 @@ static void netfs_deinit_request(struct netfs_io_request *rreq)
rreq->netfs_ops->free_request(rreq);
if (rreq->cache_resources.ops)
rreq->cache_resources.ops->end_operation(&rreq->cache_resources);
- if (rreq->direct_bv) {
- for (i = 0; i < rreq->direct_bv_count; i++) {
- if (rreq->direct_bv[i].bv_page) {
- if (rreq->direct_bv_unpin)
- unpin_user_page(rreq->direct_bv[i].bv_page);
- }
- }
- kvfree(rreq->direct_bv);
- }
+ bvecq_put(rreq->direct_bq);
rolling_buffer_clear(&rreq->buffer);
if (atomic_dec_and_test(&ictx->io_count))
diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c
index 961b76e37a02..7736a8d670aa 100644
--- a/fs/netfs/read_collect.c
+++ b/fs/netfs/read_collect.c
@@ -398,12 +398,15 @@ static void netfs_rreq_assess_dio(struct netfs_io_request *rreq)
if (rreq->origin == NETFS_UNBUFFERED_READ ||
rreq->origin == NETFS_DIO_READ) {
- for (i = 0; i < rreq->direct_bv_count; i++) {
- flush_dcache_page(rreq->direct_bv[i].bv_page);
- // TODO: cifs marks pages in the destination buffer
- // dirty under some circumstances after a read. Do we
- // need to do that too?
- set_page_dirty(rreq->direct_bv[i].bv_page);
+ for (struct bvecq *bq = rreq->direct_bq; bq; bq = bvecq_next(bq)) {
+ unsigned int nr_slots = bvecq_nr_slots_acquire(bq);
+ /* Read the slot count before the slots. */
+
+ /* Mark the target buffers dirty. */
+ for (i = 0; i < nr_slots; i++) {
+ flush_dcache_page(bq->bv[i].bv_page);
+ set_page_dirty(bq->bv[i].bv_page);
+ }
}
}
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index de34c485c099..94f155517f5a 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -251,7 +251,7 @@ struct netfs_io_request {
wait_queue_head_t waitq; /* Processor waiter */
void *netfs_priv; /* Private data for the netfs */
void *netfs_priv2; /* Private data for the netfs */
- struct bio_vec *direct_bv; /* DIO buffer list (when handling iovec-iter) */
+ struct bvecq *direct_bq; /* DIO buffer list (when handling iovec-iter) */
uoff_t submitted; /* Amount submitted for I/O so far */
uoff_t len; /* Length of the request */
size_t transferred; /* Amount to be indicated as transferred */
@@ -266,7 +266,6 @@ struct netfs_io_request {
uoff_t abandon_to; /* Position to abandon folios to */
const struct folio *no_unlock_folio; /* Don't unlock this folio after read */
gfp_t gfp; /* GFP flags to use */
- unsigned int direct_bv_count; /* Number of elements in direct_bv[] */
unsigned int debug_id;
unsigned int rsize; /* Maximum read size (0 for none) */
unsigned int wsize; /* Maximum write size (0 for none) */
@@ -274,7 +273,6 @@ struct netfs_io_request {
unsigned int nr_group_rel; /* Number of refs to release on ->group */
spinlock_t lock; /* Lock for queuing subreqs */
enum netfs_io_origin origin; /* Origin of the request */
- bool direct_bv_unpin; /* T if direct_bv[] must be unpinned */
refcount_t ref;
unsigned long flags;
#define NETFS_RREQ_IN_PROGRESS 0 /* Unlocked when the request completes (has ref) */
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 4/8] netfs: Add some tools for managing a position in a bvecq chain
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
` (2 preceding siblings ...)
2026-10-05 7:12 ` [PATCH v12 3/8] netfs: Make unbuffered/DIO read and write use bvecq David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 5/8] netfs: Provide a func to load the readahead buffers into " David Howells
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Provide a selection of tools for holding and managing a position within a
bvec queue chain.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/bvecq.c | 250 ++++++++++++++++++++++++++++++++++++++++++
include/linux/bvecq.h | 183 +++++++++++++++++++++++++++++++
2 files changed, 433 insertions(+)
diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c
index 5b747f6b5938..1493e028bb17 100644
--- a/fs/netfs/bvecq.c
+++ b/fs/netfs/bvecq.c
@@ -342,3 +342,253 @@ int bvecq_expand_buffer(struct bvecq **_buffer, size_t *_cur_size, size_t size,
return 0;
}
EXPORT_SYMBOL(bvecq_expand_buffer);
+
+/**
+ * bvecq_buffer_init - Initialise a buffer and set position
+ * @pos: The position to point at the new buffer.
+ * @gfp: The allocation constraints.
+ * @for_writeback: True if allocating for writeback
+ *
+ * Initialise a rolling buffer. We allocate an unpopulated bvecq node to so
+ * that the pointers can be independently driven by the producer and the
+ * consumer.
+ *
+ * Return 0 if successful; -ENOMEM on allocation failure.
+ */
+int bvecq_buffer_init(struct bvecq_pos *pos, gfp_t gfp, bool for_writeback)
+{
+ struct bvecq *bq;
+
+ bq = bvecq_alloc_one(BVECQ_POOL_SLOTS, gfp, for_writeback);
+ if (!bq)
+ return -ENOMEM;
+
+ pos->bvecq = bq; /* Comes with a ref. */
+ pos->slot = 0;
+ pos->offset = 0;
+ return 0;
+}
+
+/**
+ * bvecq_buffer_append - Append a new bvecq node to a buffer
+ * @pos: The position of the last node.
+ * @bq: The buffer to add.
+ *
+ * Add a new node on to the buffer chain at the specified position, either
+ * because the previous one is full or because we have a discontiguity to
+ * contend with, and update @pos to point to it.
+ */
+void bvecq_buffer_append(struct bvecq_pos *pos, struct bvecq *bq)
+{
+ struct bvecq *head = pos->bvecq;
+
+ pos->bvecq = bvecq_get(bq);
+ pos->slot = 0;
+ pos->offset = 0;
+
+ /* [!] NOTE: After we set head->next, the consumer is at liberty to
+ * immediately delete the old head.
+ */
+ bvecq_append(head, bq);
+ bvecq_put(head);
+}
+
+/**
+ * bvecq_pos_advance - Advance a bvecq position
+ * @pos: The position to advance.
+ * @amount: The amount of bytes to advance by.
+ *
+ * Advance the specified bvecq position by @amount bytes. @pos is updated and
+ * bvecq ref counts may have been manipulated. If the position hits the end of
+ * the queue, then it is left pointing beyond the last slot of the last bvecq
+ * so that it doesn't break the chain.
+ */
+void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount)
+{
+ struct bvecq *bq = pos->bvecq, *next;
+ unsigned int slot = pos->slot;
+ size_t offset = pos->offset;
+
+ while (amount) {
+ size_t part;
+
+ if (!bvecq_acquire_slot(bq, slot)) {
+ next = bvecq_next(bq);
+ if (!next) {
+ WARN_ON_ONCE(amount > 0);
+ break;
+ }
+ if (bvecq_acquire_slot(bq, slot))
+ continue; /* More slots got added. */
+ bq = next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ part = bq->bv[slot].bv_len - offset;
+
+ if (part > amount) {
+ offset += amount;
+ break;
+ }
+ amount -= part;
+ offset = 0;
+ slot++;
+ }
+
+ pos->slot = slot;
+ pos->offset = offset;
+ bvecq_pos_move(pos, bq);
+}
+
+/*
+ * Clear part of the memory pointed to by a bio_vec.
+ */
+static void bvec_zero(const struct bio_vec *bv, size_t offset, size_t len)
+{
+ struct page *page = bv->bv_page;
+
+ offset += bv->bv_offset;
+
+ page += offset / PAGE_SIZE;
+ offset = offset % PAGE_SIZE;
+
+ while (len) {
+ size_t part = min(len, PAGE_SIZE - offset);
+ char *p = kmap_local_page(page);
+
+ memset(p + offset, 0, part);
+ kunmap_local(p);
+
+ len -= part;
+ offset = 0;
+ page++;
+ }
+}
+
+/**
+ * bvecq_zero - Clear memory starting at the bvecq position.
+ * @pos: The position in the bvecq chain to start clearing.
+ * @amount: The number of bytes to clear.
+ *
+ * Clear memory fragments pointed to by a bvec queue. @pos is updated and
+ * bvecq ref counts may have been manipulated. If the position hits the end of
+ * the queue, then it is left pointing beyond the last slot of the last bvecq
+ * so that it doesn't break the chain.
+ *
+ * Return: The number of bytes cleared.
+ */
+ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount)
+{
+ struct bvecq *bq = pos->bvecq, *next;
+ unsigned int slot = pos->slot;
+ ssize_t cleared = 0;
+ size_t offset = pos->offset;
+
+ while (amount) {
+ const struct bio_vec *bv;
+ size_t part;
+
+ if (!bvecq_acquire_slot(bq, slot)) {
+ next = bvecq_next(bq);
+ if (!next) {
+ WARN_ON_ONCE(amount > 0);
+ break;
+ }
+ if (bvecq_acquire_slot(bq, slot))
+ continue; /* More slots got added. */
+ bq = next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ bv = &bq->bv[slot];
+ if (offset >= bv->bv_len) {
+ slot++;
+ offset = 0;
+ continue;
+ }
+
+ part = min(bv->bv_len - offset, amount);
+ bvec_zero(bv, offset, part);
+ cleared += part;
+ offset += part;
+ amount -= part;
+ }
+
+ pos->slot = slot;
+ pos->offset = offset;
+ bvecq_pos_move(pos, bq);
+ return cleared;
+}
+
+/**
+ * bvecq_slice - Find a slice of a bvecq queue
+ * @pos: The position to start at.
+ * @max_size: The maximum size of the slice (or ULONG_MAX).
+ * @max_slots: The maximum number of slots in the slice (or INT_MAX).
+ * @_nr_slots: Where to put the number of slots (updated).
+ *
+ * Determine the size and number of slots that can be obtained the next slice
+ * of bvec queue up to the maximum size and slot count specified.
+ *
+ * @pos is updated to the end of the slice. If the position hits the end of
+ * the queue, then it is left pointing beyond the last slot of the last bvecq
+ * so that it doesn't break the chain.
+ *
+ * Return: The number of bytes in the slice.
+ */
+size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size,
+ unsigned int max_slots, unsigned int *_nr_slots)
+{
+ struct bvecq *bq, *next;
+ unsigned int slot = pos->slot, nslots = 0;
+ size_t size = 0, offset = pos->offset;
+
+ bq = pos->bvecq;
+ for (;;) {
+ for (; slot < bvecq_nr_slots_acquire(bq); slot++) {
+ const struct bio_vec *bvec = &bq->bv[slot];
+
+ if (offset < bvec->bv_len && bvec->bv_page) {
+ size_t part = min(bvec->bv_len - offset, max_size);
+
+ size += part;
+ offset += part;
+ max_size -= part;
+ nslots++;
+ if (!max_size || nslots >= max_slots)
+ goto out;
+ }
+ offset = 0;
+ }
+
+ /* pos->bvecq isn't allowed to go NULL as the queue may get
+ * extended and we would lose our place.
+ */
+ next = bvecq_next(bq);
+ if (!next)
+ break;
+ if (bvecq_acquire_slot(bq, slot))
+ continue; /* More slots got added. */
+ slot = 0;
+ bq = next;
+ }
+
+out:
+ *_nr_slots = nslots;
+ if (slot == bvecq_nr_slots_acquire(bq)) {
+ next = bvecq_next(bq);
+ if (next) {
+ bq = next;
+ slot = 0;
+ offset = 0;
+ }
+ }
+ bvecq_pos_move(pos, bq);
+ pos->slot = slot;
+ pos->offset = offset;
+ return size;
+}
diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h
index b984aaa44908..f1513f507cfb 100644
--- a/include/linux/bvecq.h
+++ b/include/linux/bvecq.h
@@ -54,6 +54,16 @@ struct bvecq {
/* Number of slots in a 4K bvecq. */
#define BVECQ_4KB_SLOTS ((4096 - sizeof(struct bvecq)) / sizeof(struct bio_vec))
+/*
+ * Position in a bio_vec queue. The bvecq holds a ref on the queue segment it
+ * points to.
+ */
+struct bvecq_pos {
+ struct bvecq *bvecq; /* The first bvecq */
+ unsigned int offset; /* The offset within the starting slot */
+ u16 slot; /* The starting slot */
+};
+
void bvecq_dump(const struct bvecq *bq);
struct bvecq *bvecq_alloc_one(size_t nr_slots, gfp_t gfp, bool for_writeback);
struct bvecq *bvecq_alloc_chain(size_t nr_slots, gfp_t gfp, bool for_writeback);
@@ -61,6 +71,12 @@ struct bvecq *bvecq_alloc_buffer2(size_t size, unsigned int pre_slots, gfp_t gfp
bool for_writeback);
void bvecq_put(struct bvecq *bq);
int bvecq_expand_buffer(struct bvecq **_buffer, size_t *_cur_size, size_t size, gfp_t gfp);
+int bvecq_buffer_init(struct bvecq_pos *pos, gfp_t gfp, bool for_writeback);
+void bvecq_buffer_append(struct bvecq_pos *pos, struct bvecq *bq);
+void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount);
+ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount);
+size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size,
+ unsigned int max_slots, unsigned int *_nr_slots);
/**
* bvecq_alloc_buffer - Allocate a bvecq chain and populate with buffers
@@ -163,4 +179,171 @@ static inline struct bvecq *bvecq_next(const struct bvecq *bq)
return smp_load_acquire(&bq->next);
}
+/**
+ * bvecq_pos_set - Set one position to be the same as another
+ * @pos: The position object to set
+ * @at: The source position.
+ *
+ * Set @pos to have the same position as @at. This may take a ref on the
+ * bvecq pointed to.
+ */
+static inline void bvecq_pos_set(struct bvecq_pos *pos, const struct bvecq_pos *at)
+{
+ *pos = *at;
+ bvecq_get(pos->bvecq);
+}
+
+/**
+ * bvecq_pos_unset - Unset a position
+ * @pos: The position object to unset
+ *
+ * Unset @pos. This does any needed ref cleanup.
+ */
+static inline void bvecq_pos_unset(struct bvecq_pos *pos)
+{
+ bvecq_put(pos->bvecq);
+ pos->bvecq = NULL;
+ pos->slot = 0;
+ pos->offset = 0;
+}
+
+/**
+ * bvecq_pos_transfer - Transfer one position to another, clearing the first
+ * @pos: The position object to set
+ * @from: The source position to clear.
+ *
+ * Set @pos to have the same position as @from and then clear @from. This may
+ * transfer a ref on the bvecq pointed to.
+ */
+static inline void bvecq_pos_transfer(struct bvecq_pos *pos, struct bvecq_pos *from)
+{
+ *pos = *from;
+ from->bvecq = NULL;
+ from->slot = 0;
+ from->offset = 0;
+}
+
+/**
+ * bvecq_pos_move - Update a position to a new bvecq
+ * @pos: The position object to update.
+ * @to: The new bvecq to point at.
+ *
+ * Update @pos to point to @to if it doesn't already do so. This may
+ * manipulate refs on the bvecqs pointed to.
+ */
+static inline void bvecq_pos_move(struct bvecq_pos *pos, struct bvecq *to)
+{
+ struct bvecq *old = pos->bvecq;
+
+ if (old != to) {
+ pos->bvecq = bvecq_get(to);
+ bvecq_put(old);
+ }
+}
+
+/**
+ * bvecq_pos_nudge - Nudge a position onto the next segment if current used up
+ * @pos: The position object to nudge.
+ *
+ * Update @pos to point to the next segment in the chain if we've used up the
+ * current segment. This may manipulate refs on the bvecqs pointed to.
+ *
+ * Return: true if found a new segment, false if hit the end.
+ */
+static inline bool bvecq_pos_nudge(struct bvecq_pos *pos)
+{
+ struct bvecq *bq = pos->bvecq;
+
+ for (;;) {
+ if (!bvecq_acquire_slot(bq, pos->slot)) {
+ bq = bvecq_next(bq);
+ if (!bq)
+ return false;
+ if (bvecq_acquire_slot(bq, pos->slot))
+ continue; /* More slots got added. */
+ bvecq_pos_move(pos, bq);
+ pos->slot = 0;
+ pos->offset = 0;
+ continue;
+ }
+ if (pos->offset >= bq->bv[pos->slot].bv_len) {
+ pos->slot++;
+ pos->offset = 0;
+ continue;
+ }
+ return true;
+ }
+}
+
+/**
+ * bvecq_pos_step - Step a position to the next slot if possible
+ * @pos: The position object to step.
+ *
+ * Update @pos to point to the next slot in the queue if not at the end. This
+ * may manipulate refs on the bvecqs pointed to.
+ *
+ * Return: true if successful, false if was at the end.
+ */
+static inline bool bvecq_pos_step(struct bvecq_pos *pos)
+{
+ struct bvecq *bq = pos->bvecq, *next;
+
+ pos->slot++;
+ pos->offset = 0;
+ if (bvecq_acquire_slot(bq, pos->slot))
+ return true;
+ next = bvecq_next(bq);
+ if (!next)
+ return false;
+ if (bvecq_acquire_slot(bq, pos->slot))
+ return true;
+ bvecq_pos_move(pos, next);
+ pos->slot = 0;
+ return true;
+}
+
+/**
+ * bvecq_delete_spent - Delete the bvecq at the front if possible
+ * @pos: The position object to update.
+ *
+ * Delete the used up bvecq at the front of the queue that @pos points to if it
+ * is not the last node in the queue; if it is the last node in the queue, it
+ * is kept so that the queue doesn't become detached from the other end. This
+ * may manipulate refs on the bvecqs pointed to. It is also possible that the
+ * producer will fill more slots in the current bvecq.
+ *
+ * Also, we have to be very careful: the consumer can catch the producer, which
+ * could lead to us having nothing left in the queue, causing the front and
+ * back pointers to end up on different tracks. To avoid this, we must always
+ * keep at least one segment in the queue.
+ *
+ * The caller must reload from @pos after calling this.
+ *
+ * Return: true if there's more available; false if not.
+ */
+static inline bool bvecq_delete_spent(struct bvecq_pos *pos)
+{
+ struct bvecq *spent = pos->bvecq;
+ struct bvecq *next;
+ unsigned int slot = pos->slot;
+
+again:
+ /* Read the contents of the queue node after the pointer to it. */
+ next = bvecq_next(spent);
+ if (!next)
+ return false; /* Nothing more to consume at the moment. */
+ if (slot < bvecq_nr_slots_acquire(spent))
+ return true; /* The producer added more. */
+ next->prev = NULL;
+ bvecq_pos_move(pos, next);
+ pos->slot = 0;
+ pos->offset = 0;
+ if (!bvecq_acquire_slot(next, 0)) {
+ spent = next;
+ slot = 0;
+ goto again;
+ }
+ return true;
+}
+
#endif /* _LINUX_BVECQ_H */
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 5/8] netfs: Provide a func to load the readahead buffers into a bvecq chain
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
` (3 preceding siblings ...)
2026-10-05 7:12 ` [PATCH v12 4/8] netfs: Add some tools for managing a position in a bvecq chain David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 6/8] netfs: Use bvecq_pos to hold the buffer positions David Howells
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
In netfslib, provide a function to load the readahead buffers into a bvecq
chain by preallocating the bvecq chain and then iterating directly over the
pagecache to extract all the folios. This builds on the preceding patch to
note the folio count in the readahead_control struct.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/bvecq.c | 60 +++++++++++++++++++++++++++++++++++++++++++
include/linux/bvecq.h | 1 +
2 files changed, 61 insertions(+)
diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c
index 1493e028bb17..2edc0045c245 100644
--- a/fs/netfs/bvecq.c
+++ b/fs/netfs/bvecq.c
@@ -592,3 +592,63 @@ size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size,
pos->offset = offset;
return size;
}
+
+/**
+ * bvecq_load_from_ra - Allocate a bvecq chain and load from readahead
+ * @pos: Blank position object to attach the new chain to.
+ * @ractl: The readahead control context.
+ *
+ * Decant the set of folios to be read from the readahead context into a bvecq
+ * chain. Each folio occupies one bio_vec element.
+ *
+ * Return: Amount of data loaded or -ENOMEM on allocation failure.
+ */
+ssize_t bvecq_load_from_ra(struct bvecq_pos *pos, struct readahead_control *ractl)
+{
+ XA_STATE(xas, &ractl->mapping->i_pages, ractl->_index);
+ struct folio *folio;
+ struct bvecq *bq;
+ unsigned int slot = 0;
+ size_t loaded = 0;
+
+ bq = bvecq_alloc_chain(ractl->_nr_folios, GFP_KERNEL, false);
+ if (!bq)
+ return -ENOMEM;
+
+ pos->bvecq = bq;
+ pos->slot = 0;
+ pos->offset = 0;
+
+ rcu_read_lock();
+
+ xas_for_each(&xas, folio, ractl->_index + ractl->_nr_pages - 1) {
+ size_t len;
+
+ if (xas_retry(&xas, folio))
+ continue;
+ VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
+
+ len = folio_size(folio);
+ bvec_set_folio(&bq->bv[slot], folio, len, 0);
+ loaded += len;
+ slot++;
+ trace_netfs_folio(folio, netfs_folio_trace_read);
+
+ if (slot >= bq->max_slots) {
+ bvecq_filled_to(bq, slot);
+ bq = bq->next;
+ if (!bq)
+ break;
+ slot = 0;
+ }
+ }
+
+ rcu_read_unlock();
+
+ if (bq)
+ bvecq_filled_to(bq, slot);
+
+ ractl->_index += ractl->_nr_pages;
+ ractl->_nr_pages = 0;
+ return loaded;
+}
diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h
index f1513f507cfb..389f6407cb84 100644
--- a/include/linux/bvecq.h
+++ b/include/linux/bvecq.h
@@ -77,6 +77,7 @@ void bvecq_pos_advance(struct bvecq_pos *pos, size_t amount);
ssize_t bvecq_zero(struct bvecq_pos *pos, size_t amount);
size_t bvecq_slice(struct bvecq_pos *pos, size_t max_size,
unsigned int max_slots, unsigned int *_nr_slots);
+ssize_t bvecq_load_from_ra(struct bvecq_pos *pos, struct readahead_control *ractl);
/**
* bvecq_alloc_buffer - Allocate a bvecq chain and populate with buffers
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 6/8] netfs: Use bvecq_pos to hold the buffer positions
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
` (4 preceding siblings ...)
2026-10-05 7:12 ` [PATCH v12 5/8] netfs: Provide a func to load the readahead buffers into " David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 7/8] netfs: Remove the rolling_buffer implementation David Howells
2026-10-05 7:12 ` [PATCH v12 8/8] netfs: Remove netfs_extract_user_iter() David Howells
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel,
Shyam Prasad N, Tom Talpey
Use struct bvecq_pos to hold the position in a buffer for various things,
including the point at which memory segments are loaded into a rolling
buffer or are cleaned up from that buffer and the point at which a
subrequest's slice of buffer begins.
The position recorded by a bvecq_pos is a tuple of { bvecq, slot, offset }.
This is lighter weight than using a full iov_iter, though that would also
suffice. If not NULL, the position also holds a reference on the bvecq it
is pointing to.
For unbuffered/DIO reads and writes and various buffered reads including
readahead, the iterator is extracted into the queue up front. For buffered
writeback, the folios are added to the queue as the operation proceeds,
much as it does now with folio_queues. For buffered reads and writes
through the pagecache, bv_len is set to the length of the entire folio and
then slices of the relevant[*] parts are passed to subrequests.
[*] What's relevant for writing to the cache isn't necessarily the same as
what's relevant for uploading to the server.
When setting up a subrequest, netfslib will furnish it with a slice of the
main buffer queue as a bvecq_pos, and, for the moment, an ITER_BVECQ
iterator is set to cover the slice in subreq->io_iter - though this will be
moved down into the filesystem in a later patch.
Notes on the implementation:
(1) There are three positions on the netfs_io_request struct. Not all are
used by every request type.
Firstly, there's ->load_cursor, which is used by buffered write and
pgpriv2-copy to point to the next slot to have a folio inserted into
it (e.g. when loading from writeback_iter()).
Secondly, there's ->dispatch_cursor, which is used to provide the
position in the buffer from which we start dispatching a subrequest.
Thirdly, there's the ->collect_cursor, which is used by the collection
routines to point to the next memory segment to be cleaned up.
(2) There's one position on the netfs_io_subrequest struct, ->io_buffer,
that records the position from which a subrequest's buffer begins.
This is also used as the base of the position from which to retry
(advanced by ->transfer). For that reason, the filesystem is not
allowed to change ->io_buffer for the moment.
(3) Maintenance of the positions is done with helper functions, such as
bvecq_pos_attach(), to abstract the refcounting.
(4) When sending a read or a write to the cache, the ends of the slice
will be rounded up/down to the backing file's DIO block alignment.
(5) bvecq_slice() is used to select a slice of the source buffer and
assign it to a subrequest. The source buffer position is advanced.
(6) netfs_extract_iter() is used by unbuffered/direct I/O API functions to
decant a chunk of the iov_iter supplied by the VFS into a bvecq chain
- and to label the bvecqs with appropriate disposal information
(e.g. unpin, free, nothing).
There are further options that can be explored in the future:
(1) Allow the provision of a duplicated bvecq chain for a specific slice
so that the filesystem can add bits on either end (such as adding
protocol headers and trailers and gluing several things together into
a compound operation).
(2) If a filesystem supports vectored/sparse read and write ops, it can be
given a chain with discontiguities in it to perform in a single op
(Ceph, for example, can do this).
(3) Make MSG_SPLICE_PAGES handling read the disposal info in the bvecq and
use that to indicate how it should get rid of the stuff it pasted into
a sk_buff.
(4) If a bounce buffer is needed (encryption, for example), the bounce
buffer can be held in a bvecq and sliced up instead of the main buffer
queue.
(5) Get rid of subreq->io_iter and move the iov_iter stuff down into the
filesystem. The I/O iterators are normally only needed transitorily,
and the one currently in netfs_io_subrequest is unnecessary most of
the time.
rolling_buffer will be removed in a follow up patch.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Namjae Jeon <linkinjeon@kernel.org>
cc: Shyam Prasad N <sprasad@microsoft.com>
cc: Tom Talpey <tom@talpey.com>
cc: linux-cifs@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/afs/dir.c | 5 +-
fs/afs/symlink.c | 6 +-
fs/cachefiles/io.c | 12 ---
fs/netfs/Makefile | 1 -
fs/netfs/buffered_read.c | 169 ++++++++++++++++++++---------------
fs/netfs/direct_read.c | 54 ++++-------
fs/netfs/direct_write.c | 44 +++++----
fs/netfs/internal.h | 4 +-
fs/netfs/iterator.c | 2 +
fs/netfs/misc.c | 18 ----
fs/netfs/objects.c | 7 +-
fs/netfs/read_collect.c | 79 ++++++++--------
fs/netfs/read_pgpriv2.c | 87 ++++++++++++------
fs/netfs/read_retry.c | 86 ++++++++++--------
fs/netfs/read_single.c | 16 +++-
fs/netfs/write_collect.c | 55 ++++++------
fs/netfs/write_issue.c | 166 +++++++++++++++++++++++-----------
fs/netfs/write_retry.c | 61 ++++++++-----
include/linux/netfs.h | 15 ++--
include/trace/events/netfs.h | 32 ++++++-
20 files changed, 539 insertions(+), 380 deletions(-)
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 5fac02d2d281..39cba3f37ecd 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -2229,8 +2229,9 @@ static int afs_dir_writepages(struct address_space *mapping,
if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags)) {
iov_iter_bvec_queue(&iter, ITER_SOURCE, dvnode->directory, 0, 0,
- i_size_read(&dvnode->netfs.inode));
- ret = netfs_writeback_single(mapping, wbc, &iter);
+ dvnode->directory_size);
+ ret = netfs_writeback_single(mapping, wbc, &iter,
+ i_size_read(&dvnode->netfs.inode));
if (ret == 1)
ret = 0; /* Skipped write due to lock conflict. */
}
diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c
index 9a611efe6b26..ae03ceff42b8 100644
--- a/fs/afs/symlink.c
+++ b/fs/afs/symlink.c
@@ -248,9 +248,9 @@ int afs_symlink_writepages(struct address_space *mapping,
if (vnode->directory &&
atomic64_read(&vnode->cb_expires_at) != AFS_NO_CB_PROMISE) {
- iov_iter_bvec_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0,
- i_size_read(&vnode->netfs.inode));
- ret = netfs_writeback_single(mapping, wbc, &iter);
+ iov_iter_bvec_queue(&iter, ITER_SOURCE, vnode->directory, 0, 0, PAGE_SIZE);
+ ret = netfs_writeback_single(mapping, wbc, &iter,
+ i_size_read(&vnode->netfs.inode));
}
if (ret == 0) {
diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c
index d05059822288..788439ea6e1c 100644
--- a/fs/cachefiles/io.c
+++ b/fs/cachefiles/io.c
@@ -546,7 +546,6 @@ static void cachefiles_issue_write(struct netfs_io_subrequest *subreq)
struct netfs_cache_resources *cres = &wreq->cache_resources;
struct cachefiles_object *object = cachefiles_cres_object(cres);
struct cachefiles_cache *cache = object->volume->cache;
- struct netfs_io_stream *stream = &wreq->io_streams[subreq->stream_nr];
const struct cred *saved_cred;
size_t off, pre, post, len = subreq->len;
uoff_t start = subreq->start;
@@ -571,17 +570,6 @@ static void cachefiles_issue_write(struct netfs_io_subrequest *subreq)
}
/* We also need to end on the cache granularity boundary */
- if (start + len == wreq->i_size) {
- size_t part = len & (cache->bsize - 1);
- size_t need = cache->bsize - part;
-
- if (part && stream->submit_extendable_to >= need) {
- len += need;
- subreq->len += need;
- subreq->io_iter.count += need;
- }
- }
-
post = len & (cache->bsize - 1);
if (post) {
len -= post;
diff --git a/fs/netfs/Makefile b/fs/netfs/Makefile
index b1ea4439c1bb..421dd0be413b 100644
--- a/fs/netfs/Makefile
+++ b/fs/netfs/Makefile
@@ -14,7 +14,6 @@ netfs-y := \
read_collect.o \
read_retry.o \
read_single.o \
- rolling_buffer.o \
write_collect.o \
write_issue.o \
write_retry.o
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index aa1e4f8d46ab..945bfa6eed3f 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -114,26 +114,21 @@ static int netfs_begin_cache_read(struct netfs_io_request *rreq, struct netfs_in
static ssize_t netfs_prepare_read_iterator(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
+ struct netfs_io_stream *stream = &rreq->io_streams[0];
+ ssize_t extracted;
size_t rsize = subreq->len;
if (subreq->source == NETFS_DOWNLOAD_FROM_SERVER)
- rsize = umin(rsize, rreq->io_streams[0].sreq_max_len);
-
- subreq->len = rsize;
- if (unlikely(rreq->io_streams[0].sreq_max_segs)) {
- size_t limit = netfs_limit_iter(&rreq->buffer.iter, 0, rsize,
- rreq->io_streams[0].sreq_max_segs);
-
- if (limit < rsize) {
- subreq->len = limit;
- trace_netfs_sreq(subreq, netfs_sreq_trace_limited);
- }
+ rsize = umin(rsize, stream->sreq_max_len);
+
+ bvecq_pos_set(&subreq->io_buffer, &rreq->dispatch_cursor);
+ extracted = bvecq_slice(&rreq->dispatch_cursor, rsize,
+ stream->sreq_max_segs, &subreq->nr_segs);
+ if (extracted < rsize) {
+ subreq->len = extracted;
+ trace_netfs_sreq(subreq, netfs_sreq_trace_limited);
}
- subreq->io_iter = rreq->buffer.iter;
-
- iov_iter_truncate(&subreq->io_iter, subreq->len);
- rolling_buffer_advance(&rreq->buffer, subreq->len);
return subreq->len;
}
@@ -192,6 +187,9 @@ void netfs_queue_read(struct netfs_io_request *rreq,
static void netfs_issue_read(struct netfs_io_request *rreq,
struct netfs_io_subrequest *subreq)
{
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->io_buffer.bvecq,
+ subreq->io_buffer.slot, subreq->io_buffer.offset, subreq->len);
+
switch (subreq->source) {
case NETFS_DOWNLOAD_FROM_SERVER:
rreq->netfs_ops->issue_read(subreq);
@@ -200,10 +198,9 @@ static void netfs_issue_read(struct netfs_io_request *rreq,
netfs_read_cache_to_pagecache(rreq, subreq);
break;
default:
- __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags);
- subreq->error = 0;
- iov_iter_zero(subreq->len, &subreq->io_iter);
+ bvecq_zero(&subreq->io_buffer, subreq->len);
subreq->transferred = subreq->len;
+ subreq->error = 0;
netfs_read_subreq_terminated(subreq);
break;
}
@@ -215,31 +212,31 @@ static void netfs_issue_read(struct netfs_io_request *rreq,
* otherwise we set the deprecated PG_private_2.
*/
static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq,
- struct bvecq **bq,
- unsigned int *offset,
- int *slot,
- size_t len,
- bool copy)
+ struct bvecq_pos *mark, size_t len, bool copy)
{
+ struct bvecq *bq = mark->bvecq;
+ unsigned int offset = mark->offset;
+ int slot = mark->slot;
+
while (len > 0) {
- struct folio *folio;
size_t fsize, overlap;
- if (!*bq)
+ if (!bq)
break;
- if (!bvecq_acquire_slot(*bq, *slot)) {
- *bq = bvecq_next(*bq);
- *slot = 0;
- *offset = 0;
+ if (!bvecq_acquire_slot(bq, slot)) {
+ bq = bq->next;
+ slot = 0;
+ offset = 0;
continue;
}
/* Determine how much the subreq overlaps the folio, if at all. */
- fsize = (*bq)->bv[*slot].bv_len;
- overlap = min(len, fsize - *offset);
+ fsize = bq->bv[slot].bv_len;
+ overlap = min(len, fsize - offset);
if (overlap > 0 && copy) {
- folio = bvec_folio(&(*bq)->bv[*slot]);
+ struct folio *folio = bvec_folio(&bq->bv[slot]);
+
if (netfs_using_pgpriv2(rreq)) {
if (!folio_test_private_2(folio))
folio_start_private_2(folio);
@@ -251,12 +248,20 @@ static void netfs_mark_copy_to_cache(struct netfs_io_request *rreq,
}
len -= overlap;
- *offset += overlap;
- if (*offset >= fsize) {
- *slot += 1;
- *offset = 0;
+ offset += overlap;
+ if (offset >= fsize) {
+ slot += 1;
+ offset = 0;
}
}
+
+ if (bq) {
+ bvecq_pos_move(mark, bq);
+ mark->offset = offset;
+ mark->slot = slot;
+ } else {
+ bvecq_pos_unset(mark);
+ }
}
/*
@@ -275,11 +280,14 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
.cached_to[1] = ULLONG_MAX,
};
struct fscache_occupancy *occ = &_occ;
- struct bvecq *bq = rreq->buffer.tail;
- unsigned int offset = 0;
+ struct bvecq_pos mark_cursor;
ssize_t size = rreq->len;
uoff_t start = rreq->start;
- int ret = 0, slot = 0;
+ int ret = 0;
+
+ _enter("R=%08x", rreq->debug_id);
+
+ bvecq_pos_set(&mark_cursor, &rreq->dispatch_cursor);
do {
int (*prepare_read)(struct netfs_io_subrequest *subreq) = NULL;
@@ -408,10 +416,10 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
if (size <= 0)
netfs_all_subreqs_queued(rreq);
- if (bq) {
+ if (mark_cursor.bvecq) {
/* See if the cache indicated this should be cached. */
copy = test_bit(NETFS_SREQ_COPY_TO_CACHE, &subreq->flags);
- netfs_mark_copy_to_cache(rreq, &bq, &slot, &offset, slice, copy);
+ netfs_mark_copy_to_cache(rreq, &mark_cursor, slice, copy);
}
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
@@ -432,6 +440,9 @@ static void netfs_read_to_pagecache(struct netfs_io_request *rreq)
/* Defer error return as we may need to wait for outstanding I/O. */
cmpxchg(&rreq->error, 0, ret);
+
+ bvecq_pos_unset(&mark_cursor);
+ bvecq_pos_unset(&rreq->dispatch_cursor);
}
/**
@@ -479,7 +490,7 @@ void netfs_readahead(struct readahead_control *ractl)
* acquires a ref on each folio that we will need to release later -
* but we don't want to do that until after we've started the I/O.
*/
- added = rolling_buffer_bulk_load_from_ra(&rreq->buffer, ractl, rreq->gfp);
+ added = bvecq_load_from_ra(&rreq->dispatch_cursor, ractl);
if (added < 0) {
ret = added;
goto cleanup_free;
@@ -488,6 +499,7 @@ void netfs_readahead(struct readahead_control *ractl)
rreq->submitted = rreq->start + added;
rreq->cleaned_to = rreq->start;
+ bvecq_pos_set(&rreq->collect_cursor, &rreq->dispatch_cursor);
netfs_read_set_unlock_at(rreq);
netfs_read_to_pagecache(rreq);
@@ -500,20 +512,26 @@ void netfs_readahead(struct readahead_control *ractl)
EXPORT_SYMBOL(netfs_readahead);
/*
- * Create a rolling buffer with a single occupying folio.
+ * Create a buffer queue with a single occupying folio.
*/
static int netfs_create_singular_buffer(struct netfs_io_request *rreq, struct folio *folio)
{
- ssize_t added;
+ struct bvecq *bq;
+ size_t fsize = folio_size(folio);
- if (rolling_buffer_init(&rreq->buffer, ITER_DEST, rreq->gfp, false) < 0)
+ bq = bvecq_alloc_one(1, rreq->gfp, false);
+ if (!bq)
return -ENOMEM;
- added = rolling_buffer_append(&rreq->buffer, folio, rreq->gfp);
- if (added < 0)
- return added;
- rreq->submitted = rreq->start + added;
- rreq->progress_at = added;
+ rreq->dispatch_cursor.bvecq = bq;
+ rreq->dispatch_cursor.slot = 0;
+ rreq->dispatch_cursor.offset = 0;
+
+ bvec_set_folio(&bq->bv[0], folio, fsize, 0);
+ bvecq_filled_to(bq, 1);
+ bvecq_pos_set(&rreq->collect_cursor, &rreq->dispatch_cursor);
+ rreq->submitted = rreq->start + fsize;
+ rreq->progress_at = fsize;
return 0;
}
@@ -527,14 +545,14 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
struct netfs_group *group = netfs_folio_group(folio);
struct netfs_folio *finfo = netfs_folio_info(folio);
struct netfs_inode *ctx = netfs_inode(mapping->host);
- struct bio_vec *bvec = NULL;
+ struct bvecq *bq = NULL;
unsigned int from = finfo->dirty_offset;
unsigned int to = from + finfo->dirty_len;
unsigned int off = 0;
size_t flen = folio_size(folio);
size_t nr_bvec = flen / PAGE_SIZE + 2;
size_t part;
- int ret, i = 0, sink_from = -1, sink_to = -1;
+ int ret, i = 0;
_enter("%lx", folio->index);
@@ -555,31 +573,46 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
* end get copied to, but the middle is discarded.
*/
ret = -ENOMEM;
- bvec = kmalloc_objs(*bvec, nr_bvec);
- if (!bvec)
+ bq = bvecq_alloc_chain(nr_bvec, rreq->gfp, false);
+ if (!bq)
goto discard;
+ rreq->dispatch_cursor.bvecq = bq;
trace_netfs_folio(folio, netfs_folio_trace_read_gaps);
+ for (struct bvecq *p = bq; p; p = p->next)
+ p->mem_type = BVECQ_MEM_PAGECACHE;
+
if (from > 0) {
- bvec_set_folio(&bvec[i++], folio, from, 0);
+ folio_get(folio);
+ bvec_set_folio(&bq->bv[i++], folio, from, 0);
off = from;
}
- sink_from = i;
while (off < to) {
struct folio *sink = folio_alloc(GFP_KERNEL, 0);
if (!sink)
goto discard;
- part = min_t(size_t, to - off, PAGE_SIZE);
- bvec_set_folio(&bvec[i], sink, part, 0);
+ if (i >= bq->max_slots) {
+ bvecq_filled_to(bq, i);
+ bq = bq->next;
+ i = 0;
+ }
+ part = min(to - off, PAGE_SIZE);
+ bvec_set_folio(&bq->bv[i++], sink, part, 0);
off += part;
- sink_to = i;
- i++;
}
- if (to < flen)
- bvec_set_folio(&bvec[i++], folio, flen - to, to);
- iov_iter_bvec(&rreq->buffer.iter, ITER_DEST, bvec, i, rreq->len);
+ if (to < flen) {
+ if (i >= bq->max_slots) {
+ bvecq_filled_to(bq, i);
+ bq = bq->next;
+ i = 0;
+ }
+ folio_get(folio);
+ bvec_set_folio(&bq->bv[i++], folio, flen - to, to);
+ }
+ bvecq_filled_to(bq, i);
+
rreq->submitted = rreq->start + flen;
netfs_read_to_pagecache(rreq);
@@ -596,22 +629,16 @@ static int netfs_read_gaps(struct file *file, struct folio *folio)
folio_mark_uptodate(folio);
}
- if (sink_to >= 0)
- for (; sink_from <= sink_to; sink_from++)
- folio_put(bvec_folio(&bvec[sink_from]));
- kfree(bvec);
+ bvecq_pos_unset(&rreq->dispatch_cursor);
folio_unlock(folio);
netfs_put_request(rreq, netfs_rreq_trace_put_return);
return ret < 0 ? ret : 0;
discard:
+ bvecq_pos_unset(&rreq->dispatch_cursor);
netfs_put_failed_request(rreq);
alloc_error:
folio_unlock(folio);
- if (sink_to >= 0)
- for (; sink_from <= sink_to; sink_from++)
- folio_put(bvec_folio(&bvec[sink_from]));
- kfree(bvec);
return ret;
}
diff --git a/fs/netfs/direct_read.c b/fs/netfs/direct_read.c
index 2b8eeb2d3c06..dae890e8df28 100644
--- a/fs/netfs/direct_read.c
+++ b/fs/netfs/direct_read.c
@@ -16,44 +16,21 @@
#include <linux/netfs.h>
#include "internal.h"
-static void netfs_prepare_dio_read_iterator(struct netfs_io_subrequest *subreq)
-{
- struct netfs_io_request *rreq = subreq->rreq;
- size_t rsize;
-
- rsize = umin(subreq->len, rreq->io_streams[0].sreq_max_len);
- subreq->len = rsize;
-
- if (unlikely(rreq->io_streams[0].sreq_max_segs)) {
- size_t limit = netfs_limit_iter(&rreq->buffer.iter, 0, rsize,
- rreq->io_streams[0].sreq_max_segs);
-
- if (limit < rsize) {
- subreq->len = limit;
- trace_netfs_sreq(subreq, netfs_sreq_trace_limited);
- }
- }
-
- trace_netfs_sreq(subreq, netfs_sreq_trace_prepare);
-
- subreq->io_iter = rreq->buffer.iter;
- iov_iter_truncate(&subreq->io_iter, subreq->len);
- iov_iter_advance(&rreq->buffer.iter, subreq->len);
-}
-
/*
* Perform a read to a buffer from the server, slicing up the region to be read
* according to the network rsize.
*/
static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq)
{
+ struct netfs_io_stream *stream = &rreq->io_streams[0];
ssize_t size = rreq->len;
uoff_t start = rreq->start;
int ret;
+ bvecq_pos_set(&rreq->collect_cursor, &rreq->dispatch_cursor);
+
do {
struct netfs_io_subrequest *subreq;
- ssize_t slice;
subreq = netfs_alloc_subrequest(rreq, NETFS_DOWNLOAD_FROM_SERVER);
if (!subreq) {
@@ -78,14 +55,22 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq)
}
}
- netfs_prepare_dio_read_iterator(subreq);
- slice = subreq->len;
- size -= slice;
- start += slice;
- rreq->submitted += slice;
+ bvecq_pos_set(&subreq->io_buffer, &rreq->dispatch_cursor);
+ subreq->len = bvecq_slice(&rreq->dispatch_cursor,
+ umin(size, stream->sreq_max_len),
+ stream->sreq_max_segs,
+ &subreq->nr_segs);
+
+ size -= subreq->len;
+ start += subreq->len;
+ rreq->submitted += subreq->len;
if (size <= 0)
netfs_all_subreqs_queued(rreq);
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->io_buffer.bvecq,
+ subreq->io_buffer.slot, subreq->io_buffer.offset,
+ subreq->len);
+
rreq->netfs_ops->issue_read(subreq);
if (test_bit(NETFS_RREQ_PAUSE, &rreq->flags))
@@ -99,6 +84,8 @@ static void netfs_dispatch_unbuffered_reads(struct netfs_io_request *rreq)
netfs_all_subreqs_queued(rreq);
netfs_wake_collector(rreq);
}
+
+ bvecq_pos_unset(&rreq->dispatch_cursor);
}
/*
@@ -183,15 +170,12 @@ ssize_t netfs_unbuffered_read_iter_locked(struct kiocb *iocb, struct iov_iter *i
* may end up truncated if ENOMEM is encountered.
*/
ret = netfs_extract_iter(iter, rreq->len, INT_MAX,
- &rreq->direct_bq, 0, rreq->gfp);
+ &rreq->dispatch_cursor.bvecq, 0, rreq->gfp);
if (ret < 0)
goto error_put;
rreq->len = ret;
- iov_iter_bvec_queue(&rreq->buffer.iter, ITER_DEST, rreq->direct_bq,
- 0, 0, rreq->len);
-
// TODO: Set up bounce buffer if needed
if (!sync) {
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index ff4e027e736a..e78340515827 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -73,7 +73,11 @@ static void netfs_unbuffered_write_collect(struct netfs_io_request *wreq,
spin_unlock(&wreq->lock);
wreq->transferred += subreq->transferred;
- iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
+ if (subreq->transferred < subreq->len) {
+ bvecq_pos_unset(&wreq->dispatch_cursor);
+ bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->io_buffer);
+ bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred);
+ }
stream->collected_to = subreq->start + subreq->transferred;
wreq->collected_to = stream->collected_to;
@@ -99,6 +103,8 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
_enter("%llx", wreq->len);
+ bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor);
+
if (wreq->origin == NETFS_DIO_WRITE)
inode_dio_begin(wreq->inode);
@@ -116,6 +122,8 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
break;
}
stream->construct = NULL;
+ } else {
+ bvecq_pos_set(&subreq->io_buffer, &wreq->dispatch_cursor);
}
/* Check if (re-)preparation failed. */
@@ -125,9 +133,16 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
break;
}
- iov_iter_truncate(&subreq->io_iter, wreq->len - wreq->transferred);
+ subreq->len = bvecq_slice(&wreq->dispatch_cursor, stream->sreq_max_len,
+ stream->sreq_max_segs, &subreq->nr_segs);
+
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE,
+ subreq->io_buffer.bvecq, subreq->io_buffer.slot,
+ subreq->io_buffer.offset,
+ subreq->len);
+
if (!iov_iter_count(&subreq->io_iter)) {
- pr_warn("netfs: Unexpected zero-length iterator R=%08x\n",
+ pr_warn("netfs: Unexpected zero-length slice R=%08x\n",
wreq->debug_id);
__set_bit(NETFS_SREQ_FAILED, &subreq->flags);
netfs_write_subrequest_terminated(subreq, -EIO);
@@ -135,12 +150,6 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
break;
}
- subreq->len = netfs_limit_iter(&subreq->io_iter, 0,
- stream->sreq_max_len,
- stream->sreq_max_segs);
- iov_iter_truncate(&subreq->io_iter, subreq->len);
- stream->submit_extendable_to = subreq->len;
-
trace_netfs_sreq(subreq, netfs_sreq_trace_submit);
stream->issue_write(subreq);
@@ -175,9 +184,13 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
*/
subreq->error = -EAGAIN;
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
+
+ bvecq_pos_unset(&wreq->dispatch_cursor);
+ bvecq_pos_transfer(&wreq->dispatch_cursor, &subreq->io_buffer);
+
if (subreq->transferred > 0) {
- iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
wreq->transferred += subreq->transferred;
+ bvecq_pos_advance(&wreq->dispatch_cursor, subreq->transferred);
}
if (stream->source == NETFS_UPLOAD_TO_SERVER &&
@@ -188,7 +201,6 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
__clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags);
__clear_bit(NETFS_SREQ_FAILED, &subreq->flags);
- subreq->io_iter = wreq->buffer.iter;
subreq->start = wreq->start + wreq->transferred;
subreq->len = wreq->len - wreq->transferred;
subreq->transferred = 0;
@@ -204,6 +216,7 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
netfs_stat(&netfs_n_wh_retry_write_subreq);
}
+ bvecq_pos_unset(&wreq->dispatch_cursor);
netfs_unbuffered_write_done(wreq);
_leave(" = %d", ret);
return ret;
@@ -262,7 +275,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *
* request.
*/
ssize_t n = netfs_extract_iter(iter, len, INT_MAX,
- &wreq->direct_bq, 0, wreq->gfp);
+ &wreq->dispatch_cursor.bvecq, 0, wreq->gfp);
if (n < 0) {
ret = n;
@@ -270,11 +283,8 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *
}
wreq->len = n;
_debug("dio-write %zx/%zx %u/%u",
- n, len, wreq->direct_bq->nr_slots,
- wreq->direct_bq->max_slots);
-
- iov_iter_bvec_queue(&wreq->buffer.iter, ITER_SOURCE,
- wreq->direct_bq, 0, 0, wreq->len);
+ n, len, wreq->dispatch_cursor.bvecq->nr_slots,
+ wreq->dispatch_cursor.bvecq->max_slots);
}
__set_bit(NETFS_RREQ_USE_IO_ITER, &wreq->flags);
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index 827b8898d309..3adf75dcc632 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -70,7 +70,6 @@ static inline void netfs_proc_del_rreq(struct netfs_io_request *rreq) {}
/*
* misc.c
*/
-void netfs_reset_iter(struct netfs_io_subrequest *subreq);
void netfs_wake_collector(struct netfs_io_request *rreq);
void netfs_subreq_clear_in_progress(struct netfs_io_subrequest *subreq);
void netfs_wait_for_in_progress_stream(struct netfs_io_request *rreq,
@@ -237,8 +236,7 @@ void netfs_prepare_write(struct netfs_io_request *wreq,
struct netfs_io_stream *stream,
uoff_t start);
void netfs_reissue_write(struct netfs_io_stream *stream,
- struct netfs_io_subrequest *subreq,
- struct iov_iter *source);
+ struct netfs_io_subrequest *subreq);
void netfs_issue_write(struct netfs_io_request *wreq,
struct netfs_io_stream *stream);
size_t netfs_advance_write(struct netfs_io_request *wreq,
diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c
index 55fd2d3b3b29..99b419066bd0 100644
--- a/fs/netfs/iterator.c
+++ b/fs/netfs/iterator.c
@@ -156,6 +156,7 @@ ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t max_pag
}
EXPORT_SYMBOL_GPL(netfs_extract_iter);
+#if 0 // TODO: Remove in subsequent patch
/**
* netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
* @orig: The original iterator
@@ -450,3 +451,4 @@ size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset,
BUG();
}
EXPORT_SYMBOL(netfs_limit_iter);
+#endif
diff --git a/fs/netfs/misc.c b/fs/netfs/misc.c
index 40e0ff649132..4fa09560fb4b 100644
--- a/fs/netfs/misc.c
+++ b/fs/netfs/misc.c
@@ -8,24 +8,6 @@
#include <linux/swap.h>
#include "internal.h"
-/*
- * Reset the subrequest iterator to refer just to the region remaining to be
- * read. The iterator may or may not have been advanced by socket ops or
- * extraction ops to an extent that may or may not match the amount actually
- * read.
- */
-void netfs_reset_iter(struct netfs_io_subrequest *subreq)
-{
- struct iov_iter *io_iter = &subreq->io_iter;
- size_t remain = subreq->len - subreq->transferred;
-
- if (io_iter->count > remain)
- iov_iter_advance(io_iter, io_iter->count - remain);
- else if (io_iter->count < remain)
- iov_iter_revert(io_iter, remain - io_iter->count);
- iov_iter_truncate(&subreq->io_iter, remain);
-}
-
/**
* netfs_dirty_folio - Mark folio dirty and pin a cache object for writeback
* @mapping: The mapping the folio belongs to.
diff --git a/fs/netfs/objects.c b/fs/netfs/objects.c
index f8a5b6a7a2a9..bf17dc31fd9d 100644
--- a/fs/netfs/objects.c
+++ b/fs/netfs/objects.c
@@ -147,8 +147,10 @@ static void netfs_deinit_request(struct netfs_io_request *rreq)
rreq->netfs_ops->free_request(rreq);
if (rreq->cache_resources.ops)
rreq->cache_resources.ops->end_operation(&rreq->cache_resources);
- bvecq_put(rreq->direct_bq);
- rolling_buffer_clear(&rreq->buffer);
+ bvecq_pos_unset(&rreq->load_cursor);
+ bvecq_pos_unset(&rreq->dispatch_cursor);
+ bvecq_pos_unset(&rreq->collect_cursor);
+ bvecq_put(rreq->spare);
if (atomic_dec_and_test(&ictx->io_count))
wake_up_var(&ictx->io_count);
@@ -242,6 +244,7 @@ static void netfs_free_subrequest(struct netfs_io_subrequest *subreq)
trace_netfs_sreq(subreq, netfs_sreq_trace_free);
if (rreq->netfs_ops->free_subrequest)
rreq->netfs_ops->free_subrequest(subreq);
+ bvecq_pos_unset(&subreq->io_buffer);
mempool_free(subreq, rreq->netfs_ops->subrequest_pool ?: &netfs_subrequest_pool);
netfs_stat_d(&netfs_n_rh_sreq);
netfs_put_request(rreq, netfs_rreq_trace_put_subreq);
diff --git a/fs/netfs/read_collect.c b/fs/netfs/read_collect.c
index 7736a8d670aa..ff3a30b79e06 100644
--- a/fs/netfs/read_collect.c
+++ b/fs/netfs/read_collect.c
@@ -26,9 +26,13 @@
*/
static void netfs_clear_unread(struct netfs_io_subrequest *subreq)
{
- netfs_reset_iter(subreq);
- WARN_ON_ONCE(subreq->len - subreq->transferred != iov_iter_count(&subreq->io_iter));
- iov_iter_zero(iov_iter_count(&subreq->io_iter), &subreq->io_iter);
+ struct iov_iter iter;
+
+ iov_iter_bvec_queue(&iter, ITER_DEST, subreq->io_buffer.bvecq,
+ subreq->io_buffer.slot, subreq->io_buffer.offset, subreq->len);
+ iov_iter_advance(&iter, subreq->transferred);
+ iov_iter_zero(subreq->len, &iter);
+
if (subreq->start + subreq->transferred >= subreq->rreq->i_size)
__set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags);
}
@@ -122,8 +126,8 @@ static void netfs_unlock_read_folio(struct netfs_io_request *rreq,
*/
void netfs_read_set_unlock_at(struct netfs_io_request *rreq)
{
- const struct bvecq *bq = rreq->buffer.tail;
- unsigned int slot = rreq->buffer.first_tail_slot;
+ const struct bvecq *bq = rreq->collect_cursor.bvecq;
+ unsigned int slot = rreq->collect_cursor.slot;
size_t cleaned_to = rreq->cleaned_to - rreq->start;
size_t progress_at = cleaned_to;
size_t minimum = 256 * 1024;
@@ -153,8 +157,8 @@ void netfs_read_set_unlock_at(struct netfs_io_request *rreq)
static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
unsigned int *notes)
{
- struct bvecq *bq = rreq->buffer.tail;
- unsigned int slot = rreq->buffer.first_tail_slot;
+ struct bvecq *bq = rreq->collect_cursor.bvecq;
+ unsigned int slot = rreq->collect_cursor.slot;
uoff_t collected_to = rreq->collected_to;
if (rreq->cleaned_to >= rreq->collected_to)
@@ -162,15 +166,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
// TODO: Begin decryption
- while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&rreq->buffer);
- if (!bq) {
- WRITE_ONCE(rreq->progress_at, rreq->len);
- return;
- }
- slot = 0;
- }
-
/* We have to wait for readahead refs to have been released before we
* can unlock any folios as the ref-dropper walks i_pages and the only
* thing preventing these folios from being removed is the folio lock.
@@ -180,9 +175,24 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
for (;;) {
struct folio *folio;
- uoff_t fpos, fend;
+ uoff_t fpos = rreq->cleaned_to, fend;
size_t fsize;
+ /* Clean up the head bvecq segment. If we clear an entire
+ * segment, then we can get rid of it provided it's not also
+ * the tail segment being filled by the issuer.
+ */
+ if (!bvecq_acquire_slot(bq, slot)) {
+ rreq->collect_cursor.slot = slot;
+ if (!bvecq_delete_spent(&rreq->collect_cursor)) {
+ WRITE_ONCE(rreq->progress_at, rreq->len);
+ return;
+ }
+ bq = rreq->collect_cursor.bvecq;
+ slot = rreq->collect_cursor.slot;
+ continue;
+ }
+
folio = bvec_folio(&bq->bv[slot]);
if (WARN_ONCE(!folio_test_locked(folio),
"R=%08x: folio %lx is not locked\n",
@@ -190,7 +200,6 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
trace_netfs_folio(folio, netfs_folio_trace_not_locked);
fsize = bq->bv[slot].bv_len;
- fpos = folio_pos(folio);
fend = fpos + fsize;
trace_netfs_collect_folio(rreq, folio);
@@ -200,30 +209,16 @@ static void netfs_read_unlock_folios(struct netfs_io_request *rreq,
break;
netfs_unlock_read_folio(rreq, bq, slot);
- WRITE_ONCE(rreq->cleaned_to, fpos + fsize);
- *notes |= MADE_PROGRESS;
-
- /* Clean up the head bq. If we clear an entire bq, then
- * we can get rid of it provided it's not also the tail bq
- * being filled by the issuer.
- */
- bq->bv[slot].bv_page = NULL;
slot++;
- while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&rreq->buffer);
- if (!bq)
- goto done;
- slot = 0;
- }
+ WRITE_ONCE(rreq->cleaned_to, fend);
+ *notes |= MADE_PROGRESS;
if (fpos + fsize >= collected_to)
break;
}
- rreq->buffer.tail = bq;
-done:
- rreq->buffer.first_tail_slot = slot;
-
+ bvecq_pos_move(&rreq->collect_cursor, bq);
+ rreq->collect_cursor.slot = slot;
netfs_read_set_unlock_at(rreq);
}
@@ -398,7 +393,7 @@ static void netfs_rreq_assess_dio(struct netfs_io_request *rreq)
if (rreq->origin == NETFS_UNBUFFERED_READ ||
rreq->origin == NETFS_DIO_READ) {
- for (struct bvecq *bq = rreq->direct_bq; bq; bq = bvecq_next(bq)) {
+ for (struct bvecq *bq = rreq->collect_cursor.bvecq; bq; bq = bvecq_next(bq)) {
unsigned int nr_slots = bvecq_nr_slots_acquire(bq);
/* Read the slot count before the slots. */
@@ -500,7 +495,15 @@ bool netfs_read_collection(struct netfs_io_request *rreq)
trace_netfs_rreq(rreq, netfs_rreq_trace_done);
netfs_clear_subrequests(rreq);
- netfs_unlock_abandoned_read_pages(rreq);
+ switch (rreq->origin) {
+ case NETFS_READAHEAD:
+ case NETFS_READPAGE:
+ case NETFS_READ_FOR_WRITE:
+ netfs_unlock_abandoned_read_pages(rreq);
+ break;
+ default:
+ break;
+ }
if (unlikely(rreq->copy_to_cache))
netfs_pgpriv2_end_copy_to_cache(rreq);
return true;
diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c
index f8e5667e278e..f23c4cbfed58 100644
--- a/fs/netfs/read_pgpriv2.c
+++ b/fs/netfs/read_pgpriv2.c
@@ -19,6 +19,9 @@
static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio *folio)
{
struct netfs_io_stream *cache = &creq->io_streams[1];
+ struct bvecq *queue;
+ unsigned int slot;
+ size_t dio_size = PAGE_SIZE;
size_t fsize = folio_size(folio), flen = fsize;
uoff_t fpos = folio_pos(folio), i_size;
bool to_eof = false;
@@ -48,18 +51,37 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio
to_eof = true;
}
+ flen = round_up(flen, dio_size);
+
_debug("folio %zx %zx", flen, fsize);
trace_netfs_folio(folio, netfs_folio_trace_store_copy);
- /* Attach the folio to the rolling buffer. */
- if (rolling_buffer_append(&creq->buffer, folio, creq->gfp) < 0) {
- set_bit(NETFS_RREQ_CANCEL_CACHING, &creq->flags);
- folio_end_private_2(folio);
- return;
+ /* Institute a new bvec queue segment if the current one is full or if
+ * we encounter a discontiguity. The discontiguity break is important
+ * when it comes to bulk unlocking folios by file range.
+ */
+ queue = creq->load_cursor.bvecq;
+ if (bvecq_is_full(queue) ||
+ (fpos != creq->last_end && creq->last_end > 0 && queue->nr_slots > 0)) {
+ bvecq_buffer_append(&creq->load_cursor, creq->spare);
+ creq->spare = NULL;
+
+ queue = creq->load_cursor.bvecq;
}
- cache->submit_extendable_to = fsize;
+ /* Attach the folio to the rolling buffer. */
+ slot = queue->nr_slots;
+ bvec_set_folio(&queue->bv[slot], folio, fsize, 0);
+ trace_netfs_bv_slot(queue, slot);
+ slot++;
+ bvecq_filled_to(queue, slot);
+ creq->load_cursor.slot = slot;
+ creq->load_cursor.offset = 0;
+ creq->last_end = fpos + flen;
+
+ bvecq_pos_nudge(&creq->dispatch_cursor);
+
cache->submit_off = 0;
cache->submit_len = flen;
@@ -71,10 +93,9 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio
do {
ssize_t part;
- creq->buffer.iter.iov_offset = cache->submit_off;
+ creq->dispatch_cursor.offset = cache->submit_off;
atomic64_set(&creq->issued_to, fpos + cache->submit_off);
- cache->submit_extendable_to = fsize - cache->submit_off;
part = netfs_advance_write(creq, cache, fpos + cache->submit_off,
cache->submit_len, to_eof);
cache->submit_off += part;
@@ -84,8 +105,7 @@ static void netfs_pgpriv2_copy_folio(struct netfs_io_request *creq, struct folio
cache->submit_len -= part;
} while (cache->submit_len > 0);
- creq->buffer.iter.iov_offset = 0;
- rolling_buffer_advance(&creq->buffer, fsize);
+ bvecq_pos_step(&creq->dispatch_cursor);
atomic64_set(&creq->issued_to, fpos + fsize);
if (flen < fsize)
@@ -111,6 +131,11 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache(
if (!creq->io_streams[1].avail)
goto cancel_put;
+ if (bvecq_buffer_init(&creq->load_cursor, creq->gfp, false) < 0)
+ goto cancel_put;
+ bvecq_pos_set(&creq->dispatch_cursor, &creq->load_cursor);
+ bvecq_pos_set(&creq->collect_cursor, &creq->dispatch_cursor);
+
__set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &creq->flags);
trace_netfs_copy2cache(rreq, creq);
trace_netfs_write(creq, netfs_write_trace_copy_to_cache);
@@ -143,6 +168,14 @@ void netfs_pgpriv2_copy_to_cache(struct netfs_io_request *rreq, struct folio *fo
return;
}
+ if (!creq->spare) {
+ creq->spare = bvecq_alloc_one(BVECQ_POOL_SLOTS, creq->gfp, false);
+ if (!creq->spare) {
+ set_bit(NETFS_RREQ_CANCEL_CACHING, &creq->flags);
+ return;
+ }
+ }
+
trace_netfs_folio(folio, netfs_folio_trace_pgpriv2_copy);
netfs_pgpriv2_copy_folio(creq, folio);
}
@@ -173,16 +206,18 @@ void netfs_pgpriv2_end_copy_to_cache(struct netfs_io_request *rreq)
*/
bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq)
{
- struct bvecq *bq = creq->buffer.tail;
- unsigned int slot = creq->buffer.first_tail_slot;
+ struct bvecq *bq = creq->collect_cursor.bvecq;
+ unsigned int slot;
uoff_t collected_to = creq->collected_to;
bool made_progress = false;
+ slot = creq->collect_cursor.slot;
while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&creq->buffer);
- if (!bq)
- return false;
- slot = 0;
+ creq->collect_cursor.slot = slot;
+ if (!bvecq_delete_spent(&creq->collect_cursor))
+ goto out;
+ bq = creq->collect_cursor.bvecq;
+ slot = creq->collect_cursor.slot;
}
for (;;) {
@@ -213,25 +248,25 @@ bool netfs_pgpriv2_unlock_copied_folios(struct netfs_io_request *creq)
creq->cleaned_to = fpos + fsize;
made_progress = true;
- /* Clean up the head bq. If we clear an entire bq, then
- * we can get rid of it provided it's not also the tail bq
- * being filled by the issuer.
+ /* Clean up the head segment. If we clear an entire segment,
+ * then we can get rid of it provided it's not also the tail
+ * segment being filled by the issuer.
*/
bq->bv[slot].bv_page = NULL;
slot++;
while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&creq->buffer);
- if (!bq)
- goto done;
- slot = 0;
+ creq->collect_cursor.slot = slot;
+ if (!bvecq_delete_spent(&creq->collect_cursor))
+ goto out;
+ bq = creq->collect_cursor.bvecq;
+ slot = creq->collect_cursor.slot;
}
if (fpos + fsize >= collected_to)
break;
}
- creq->buffer.tail = bq;
-done:
- creq->buffer.first_tail_slot = slot;
+ creq->collect_cursor.slot = slot;
+out:
return made_progress;
}
diff --git a/fs/netfs/read_retry.c b/fs/netfs/read_retry.c
index 142c3fb8dab1..7490b9ee1bf7 100644
--- a/fs/netfs/read_retry.c
+++ b/fs/netfs/read_retry.c
@@ -12,6 +12,10 @@
static void netfs_reissue_read(struct netfs_io_request *rreq,
struct netfs_io_subrequest *subreq)
{
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->io_buffer.bvecq,
+ subreq->io_buffer.slot, subreq->io_buffer.offset, subreq->len);
+ iov_iter_advance(&subreq->io_iter, subreq->transferred);
+
subreq->error = 0;
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
@@ -27,6 +31,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
{
struct netfs_io_subrequest *subreq;
struct netfs_io_stream *stream = &rreq->io_streams[0];
+ struct bvecq_pos dispatch_cursor = {};
struct list_head *next;
_enter("R=%x", rreq->debug_id);
@@ -46,9 +51,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
if (test_bit(NETFS_SREQ_FAILED, &subreq->flags))
break;
if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) {
- __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
subreq->retry_count++;
- netfs_reset_iter(subreq);
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
netfs_reissue_read(rreq, subreq);
}
@@ -74,11 +77,12 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
do {
struct netfs_io_subrequest *from, *to, *tmp;
- struct iov_iter source;
uoff_t start, len;
size_t part;
bool boundary = false, subreq_superfluous = false;
+ bvecq_pos_unset(&dispatch_cursor);
+
/* Go through the subreqs and find the next span of contiguous
* buffer that we then rejig (cifs, for example, needs the
* rsize renegotiating) and reissue.
@@ -105,7 +109,8 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
break;
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
- if (subreq->start + subreq->transferred != start + len ||
+ if (subreq->start != start + len ||
+ subreq->transferred > 0 ||
test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) ||
!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags))
break;
@@ -118,11 +123,14 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
/* Determine the set of buffers we're going to use. Each
* subreq gets a subset of a single overall contiguous buffer.
*/
- netfs_reset_iter(from);
- source = from->io_iter;
- source.count = len;
+ bvecq_pos_transfer(&dispatch_cursor, &from->io_buffer);
+ bvecq_pos_advance(&dispatch_cursor, from->transferred);
+ from->transferred = 0;
- /* Work through the sublist. */
+ /* Work through the sublist. The chain of buffers we're going
+ * to fill is attached to dispatch_cursor and we need to read
+ * 'len' amount of data from 'start'.
+ */
subreq = from;
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
if (!len) {
@@ -130,16 +138,21 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
break;
}
subreq->source = NETFS_DOWNLOAD_FROM_SERVER;
- subreq->start = start - subreq->transferred;
- subreq->len = len + subreq->transferred;
+ subreq->start = start;
+ subreq->len = len;
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
subreq->retry_count++;
+ subreq->transferred = 0;
+
+ bvecq_pos_unset(&subreq->io_buffer);
+ bvecq_pos_set(&subreq->io_buffer, &dispatch_cursor);
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
/* Renegotiate max_len (rsize) */
- stream->sreq_max_len = subreq->len;
+ stream->sreq_max_len = len;
+ stream->sreq_max_segs = INT_MAX;
if (rreq->netfs_ops->prepare_read &&
rreq->netfs_ops->prepare_read(subreq) < 0) {
trace_netfs_sreq(subreq, netfs_sreq_trace_reprep_failed);
@@ -147,13 +160,12 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
goto abandon;
}
- part = umin(len, stream->sreq_max_len);
- if (unlikely(stream->sreq_max_segs))
- part = netfs_limit_iter(&source, 0, part, stream->sreq_max_segs);
- subreq->len = subreq->transferred + part;
- subreq->io_iter = source;
- iov_iter_truncate(&subreq->io_iter, part);
- iov_iter_advance(&source, part);
+ part = bvecq_slice(&dispatch_cursor,
+ umin(len, stream->sreq_max_len),
+ stream->sreq_max_segs,
+ &subreq->nr_segs);
+ subreq->len = part;
+
len -= part;
start += part;
if (!len) {
@@ -216,9 +228,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
stream->sreq_max_len = umin(len, rreq->rsize);
- stream->sreq_max_segs = 0;
- if (unlikely(stream->sreq_max_segs))
- part = netfs_limit_iter(&source, 0, part, stream->sreq_max_segs);
+ stream->sreq_max_segs = INT_MAX;
netfs_stat(&netfs_n_rh_download);
if (rreq->netfs_ops->prepare_read(subreq) < 0) {
@@ -227,11 +237,12 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
goto abandon;
}
- part = umin(len, stream->sreq_max_len);
- subreq->len = subreq->transferred + part;
- subreq->io_iter = source;
- iov_iter_truncate(&subreq->io_iter, part);
- iov_iter_advance(&source, part);
+ bvecq_pos_set(&subreq->io_buffer, &dispatch_cursor);
+ part = bvecq_slice(&dispatch_cursor,
+ umin(len, stream->sreq_max_len),
+ stream->sreq_max_segs,
+ &subreq->nr_segs);
+ subreq->len = part;
len -= part;
start += part;
@@ -245,12 +256,14 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
} while (!list_is_head(next, &stream->subrequests));
+out:
+ bvecq_pos_unset(&dispatch_cursor);
return;
/* If we hit an error, fail all remaining incomplete subrequests */
abandon_after:
if (list_is_last(&subreq->rreq_link, &stream->subrequests))
- return;
+ goto out;
subreq = list_next_entry(subreq, rreq_link);
abandon:
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
@@ -261,6 +274,7 @@ static void netfs_retry_read_subrequests(struct netfs_io_request *rreq)
__set_bit(NETFS_SREQ_FAILED, &subreq->flags);
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
}
+ goto out;
}
/*
@@ -300,26 +314,24 @@ void netfs_unlock_abandoned_read_pages(struct netfs_io_request *rreq)
if (test_bit(NETFS_RREQ_NEED_PUT_RA_REFS, &rreq->flags))
netfs_wait_for_put_ra_refs(rreq);
- for (p = rreq->buffer.tail; p; p = p->next) {
- for (int slot = rreq->buffer.first_tail_slot;
- bvecq_acquire_slot(p, slot);
- slot++) {
- struct folio *folio;
+ for (p = rreq->collect_cursor.bvecq; p; p = bvecq_next(p)) {
+ unsigned int nr_slots = bvecq_nr_slots_acquire(p);
+ for (int slot = 0; slot < nr_slots; slot++) {
if (!p->bv[slot].bv_page)
continue;
- folio = bvec_folio(&p->bv[slot]);
+ struct folio *folio = bvec_folio(&p->bv[slot]);
+
netfs_cancel_copy_to_cache(rreq, folio);
if (folio == rreq->no_unlock_folio &&
test_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, &rreq->flags)) {
_debug("no unlock");
- } else {
- trace_netfs_folio(folio, netfs_folio_trace_abandon);
- folio_unlock(folio);
+ continue;
}
+ trace_netfs_folio(folio, netfs_folio_trace_abandon);
+ folio_unlock(folio);
}
- rreq->buffer.first_tail_slot = 0;
}
}
diff --git a/fs/netfs/read_single.c b/fs/netfs/read_single.c
index b248e34bd0c8..c70941121de0 100644
--- a/fs/netfs/read_single.c
+++ b/fs/netfs/read_single.c
@@ -101,7 +101,11 @@ static int netfs_single_dispatch_read(struct netfs_io_request *rreq)
subreq->start = 0;
subreq->len = rreq->len;
- subreq->io_iter = rreq->buffer.iter;
+
+ bvecq_pos_set(&subreq->io_buffer, &rreq->dispatch_cursor);
+
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_DEST, subreq->io_buffer.bvecq,
+ subreq->io_buffer.slot, subreq->io_buffer.offset, subreq->len);
netfs_queue_read(rreq, subreq);
@@ -174,6 +178,15 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite
if (IS_ERR(rreq))
return PTR_ERR(rreq);
+ ret = netfs_extract_iter(iter, rreq->len, INT_MAX, &rreq->dispatch_cursor.bvecq,
+ 0, rreq->gfp);
+ if (ret < 0)
+ goto cleanup_free;
+ if (ret < rreq->len) {
+ ret = -EIO;
+ goto cleanup_free;
+ }
+
rreq->progress_at = rreq->len;
ret = netfs_single_begin_cache_read(rreq, ictx);
@@ -183,7 +196,6 @@ ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_ite
netfs_stat(&netfs_n_rh_read_single);
trace_netfs_read(rreq, 0, rreq->len, netfs_read_trace_read_single);
- rreq->buffer.iter = *iter;
netfs_single_dispatch_read(rreq);
ret = netfs_wait_for_read(rreq);
diff --git a/fs/netfs/write_collect.c b/fs/netfs/write_collect.c
index 91b42820c892..dcacbab254b9 100644
--- a/fs/netfs/write_collect.c
+++ b/fs/netfs/write_collect.c
@@ -114,12 +114,12 @@ int netfs_folio_written_back(struct folio *folio)
static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq,
unsigned int *notes)
{
- struct bvecq *bq = wreq->buffer.tail;
- unsigned int slot = wreq->buffer.first_tail_slot;
+ struct bvecq *bq = wreq->collect_cursor.bvecq;
+ unsigned int slot = wreq->collect_cursor.slot;
uoff_t collected_to = wreq->collected_to;
if (WARN_ON_ONCE(!bq)) {
- pr_err("[!] Writeback unlock found empty rolling buffer!\n");
+ pr_err("[!] Writeback unlock found empty buffer!\n");
netfs_dump_request(wreq);
return;
}
@@ -130,19 +130,28 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq,
return;
}
- while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&wreq->buffer);
- if (!bq)
- return;
- slot = 0;
- }
-
for (;;) {
struct folio *folio;
struct netfs_folio *finfo;
uoff_t fpos, fend;
size_t fsize, flen;
+ /* Try to clean up the head of the queue if it appears to be
+ * used up, but we need to be very careful - the cleanup can
+ * catch the dispatcher, which could lead to us having nothing
+ * left in the queue, causing the front and back pointers to
+ * end up on different tracks. To avoid this, we must always
+ * keep at least one segment in the queue.
+ */
+ if (!bvecq_acquire_slot(bq, slot)) {
+ wreq->collect_cursor.slot = slot;
+ if (!bvecq_delete_spent(&wreq->collect_cursor))
+ return;
+ bq = wreq->collect_cursor.bvecq;
+ slot = wreq->collect_cursor.slot;
+ continue;
+ }
+
folio = bvec_folio(&bq->bv[slot]);
if (WARN_ONCE(!folio_test_writeback(folio),
"R=%08x: folio %lx is not under writeback\n",
@@ -166,26 +175,13 @@ static void netfs_writeback_unlock_folios(struct netfs_io_request *wreq,
wreq->cleaned_to = fpos + fsize;
*notes |= MADE_PROGRESS;
- /* Clean up the head bq. If we clear an entire bq, then
- * we can get rid of it provided it's not also the tail bq
- * being filled by the issuer.
- */
bq->bv[slot].bv_page = NULL;
slot++;
- while (!bvecq_acquire_slot(bq, slot)) {
- bq = rolling_buffer_delete_spent(&wreq->buffer);
- if (!bq)
- goto done;
- slot = 0;
- }
-
if (fpos + fsize >= collected_to)
break;
}
- wreq->buffer.tail = bq;
-done:
- wreq->buffer.first_tail_slot = slot;
+ wreq->collect_cursor.slot = slot;
}
/*
@@ -230,7 +226,8 @@ static void netfs_collect_write_results(struct netfs_io_request *wreq)
trace_netfs_rreq(wreq, netfs_rreq_trace_collect);
reassess_streams:
- issued_to = atomic64_read(&wreq->issued_to);
+ /* Order reading the issued_to point before reading the queue it refers to. */
+ issued_to = atomic64_read_acquire(&wreq->issued_to);
smp_rmb();
collected_to = ULLONG_MAX;
if (wreq->origin == NETFS_WRITEBACK ||
@@ -560,8 +557,12 @@ void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error)
* data is tracked.
*/
netfs_stat(&netfs_n_wh_write_failed);
- if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags))
- break;
+ if (test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) {
+ /* We don't retry failed cache writes. */
+ __clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
+ if (!subreq->error)
+ subreq->error = -ENOBUFS;
+ }
trace_netfs_failure(wreq, subreq, transferred_or_error, netfs_fail_write);
__set_bit(NETFS_SREQ_CANCELLED, &subreq->flags);
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index f0f478666651..8fab5cf00d7b 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -107,10 +107,6 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
ictx = netfs_inode(wreq->inode);
if (is_cacheable)
fscache_begin_write_operation(&wreq->cache_resources, netfs_i_cookie(ictx));
- if (rolling_buffer_init(&wreq->buffer, ITER_SOURCE, wreq->gfp,
- (origin == NETFS_WRITEBACK ||
- origin == NETFS_WRITEBACK_SINGLE)) < 0)
- goto nomem;
wreq->cleaned_to = wreq->start;
if (wreq->cache_resources.dio_size > 1)
@@ -135,9 +131,6 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
}
return wreq;
-nomem:
- netfs_put_failed_request(wreq);
- return ERR_PTR(-ENOMEM);
}
/**
@@ -163,22 +156,14 @@ void netfs_prepare_write(struct netfs_io_request *wreq,
uoff_t start)
{
struct netfs_io_subrequest *subreq;
- struct iov_iter *wreq_iter = &wreq->buffer.iter;
-
- /* Make sure we don't point the iterator at a used-up bvecq struct
- * being used as a placeholder to prevent the queue from collapsing.
- * In such a case, extend the queue.
- */
- if (iov_iter_is_bvecq(wreq_iter) &&
- !bvecq_acquire_slot(wreq_iter->bvecq, wreq_iter->bvecq_slot))
- rolling_buffer_make_space(&wreq->buffer, wreq->gfp);
subreq = netfs_alloc_subrequest(wreq, stream->source);
if (!subreq)
return;
subreq->start = start;
subreq->stream_nr = stream->stream_nr;
- subreq->io_iter = *wreq_iter;
+
+ bvecq_pos_set(&subreq->io_buffer, &wreq->dispatch_cursor);
_enter("R=%x[%x]", wreq->debug_id, subreq->debug_index);
@@ -259,15 +244,14 @@ static void netfs_do_issue_write(struct netfs_io_stream *stream,
}
void netfs_reissue_write(struct netfs_io_stream *stream,
- struct netfs_io_subrequest *subreq,
- struct iov_iter *source)
+ struct netfs_io_subrequest *subreq)
{
- size_t size = subreq->len - subreq->transferred;
-
// TODO: Use encrypted buffer
- subreq->io_iter = *source;
- iov_iter_advance(source, size);
- iov_iter_truncate(&subreq->io_iter, size);
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE,
+ subreq->io_buffer.bvecq, subreq->io_buffer.slot,
+ subreq->io_buffer.offset,
+ subreq->len);
+ iov_iter_advance(&subreq->io_iter, subreq->transferred);
subreq->retry_count++;
subreq->error = 0;
@@ -285,8 +269,12 @@ void netfs_issue_write(struct netfs_io_request *wreq,
if (!subreq)
return;
+ iov_iter_bvec_queue(&subreq->io_iter, ITER_SOURCE,
+ subreq->io_buffer.bvecq, subreq->io_buffer.slot,
+ subreq->io_buffer.offset,
+ subreq->len);
+
stream->construct = NULL;
- subreq->io_iter.count = subreq->len;
netfs_do_issue_write(stream, subreq);
}
@@ -323,7 +311,6 @@ size_t netfs_advance_write(struct netfs_io_request *wreq,
_debug("part %zx/%zx %zx/%zx", subreq->len, stream->sreq_max_len, part, len);
subreq->len += part;
subreq->nr_segs++;
- stream->submit_extendable_to -= part;
if (subreq->len >= stream->sreq_max_len ||
subreq->nr_segs >= stream->sreq_max_segs ||
@@ -347,7 +334,8 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
struct netfs_io_stream *stream;
struct netfs_group *fgroup; /* TODO: Use this with ceph */
struct netfs_folio *finfo;
- size_t iter_off = 0;
+ struct bvecq *queue = wreq->load_cursor.bvecq;
+ unsigned int slot;
size_t fsize = folio_size(folio), flen = fsize, foff = 0;
uoff_t fpos = folio_pos(folio), i_size;
bool to_eof = false, streamw = false;
@@ -355,12 +343,20 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
_enter("");
- if (rolling_buffer_make_space(&wreq->buffer, wreq->gfp) < 0)
- return -ENOMEM;
+ if (!wreq->spare) {
+ wreq->spare = bvecq_alloc_one(BVECQ_POOL_SLOTS, wreq->gfp, true);
+ if (!wreq->spare)
+ return -ENOMEM;
+ }
- /* netfs_perform_write() may shift i_size around the page or from out
- * of the page to beyond it, but cannot move i_size into or through the
- * page since we have it locked.
+ /* netfs_perform_write() may shift i_size around the folio or from out
+ * of the folio to beyond it, but cannot move i_size into or through
+ * the folio since we have it locked.
+ *
+ * Truncate could in theory move i_size into or before the folio, but
+ * it should take steps to prevent writeback from happening
+ * concurrently and should wait for any in-progress writebacks before
+ * proceeding.
*/
i_size = i_size_read(wreq->inode);
@@ -452,8 +448,29 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
trace_netfs_folio(folio, netfs_folio_trace_store_plus);
}
+ /* Institute a new bvec queue segment if the current one is full or if
+ * we encounter a discontiguity. The discontiguity break is important
+ * when it comes to bulk unlocking folios by file range.
+ */
+ if (bvecq_is_full(queue) ||
+ (fpos != wreq->last_end && wreq->last_end > 0)) {
+ bvecq_buffer_append(&wreq->load_cursor, wreq->spare);
+ wreq->spare = NULL;
+
+ queue = wreq->load_cursor.bvecq;
+ bvecq_pos_move(&wreq->dispatch_cursor, queue);
+ wreq->dispatch_cursor.slot = 0;
+ }
+
/* Attach the folio to the rolling buffer. */
- rolling_buffer_append(&wreq->buffer, folio, wreq->gfp);
+ slot = queue->nr_slots;
+ bvec_set_folio(&queue->bv[slot], folio, fsize, 0);
+ trace_netfs_bv_slot(queue, slot);
+ slot++;
+ bvecq_filled_to(queue, slot);
+ wreq->load_cursor.slot = slot;
+ wreq->load_cursor.offset = 0;
+ wreq->last_end = fpos + fsize;
/* Move the submission point forward to allow for write-streaming data
* not starting at the front of the page. We don't do write-streaming
@@ -462,10 +479,19 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
* Also skip uploading for data that's been read and just needs copying
* to the cache.
*/
+ bvecq_pos_nudge(&wreq->dispatch_cursor);
+
for (int s = 0; s < NR_IO_STREAMS; s++) {
+ size_t soff = foff, slen = flen, alignment = 1;
+
stream = &wreq->io_streams[s];
- stream->submit_off = foff;
- stream->submit_len = flen;
+ if (stream->source == NETFS_WRITE_TO_CACHE)
+ alignment = wreq->cache_resources.dio_size;
+ stream = &wreq->io_streams[s];
+ stream->submit_off = round_down(soff, alignment);
+ slen += foff - stream->submit_off;
+ stream->submit_len = round_up(slen, alignment);
+
if (!stream->avail ||
(stream->source == NETFS_WRITE_TO_CACHE && streamw) ||
(stream->source == NETFS_UPLOAD_TO_SERVER &&
@@ -499,14 +525,10 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
break;
stream = &wreq->io_streams[choose_s];
- /* Advance the iterator(s). */
- if (stream->submit_off > iter_off) {
- rolling_buffer_advance(&wreq->buffer, stream->submit_off - iter_off);
- iter_off = stream->submit_off;
- }
+ /* Advance the cursor. */
+ wreq->dispatch_cursor.offset = stream->submit_off;
atomic64_set(&wreq->issued_to, fpos + stream->submit_off);
- stream->submit_extendable_to = fsize - stream->submit_off;
part = netfs_advance_write(wreq, stream, fpos + stream->submit_off,
stream->submit_len, to_eof);
stream->submit_off += part;
@@ -518,9 +540,9 @@ static int netfs_write_folio(struct netfs_io_request *wreq,
debug = true;
}
- if (fsize > iter_off)
- rolling_buffer_advance(&wreq->buffer, fsize - iter_off);
- atomic64_set(&wreq->issued_to, fpos + fsize);
+ bvecq_pos_step(&wreq->dispatch_cursor);
+ /* Order loading the queue before updating the issue_to point */
+ atomic64_set_release(&wreq->issued_to, fpos + fsize);
if (!debug)
kdebug("R=%x: No submit", wreq->debug_id);
@@ -581,6 +603,11 @@ int netfs_writepages(struct address_space *mapping,
goto couldnt_start;
}
+ if (bvecq_buffer_init(&wreq->load_cursor, wreq->gfp, true) < 0)
+ goto nomem;
+ bvecq_pos_set(&wreq->dispatch_cursor, &wreq->load_cursor);
+ bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor);
+
__set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags);
trace_netfs_write(wreq, netfs_write_trace_writeback);
netfs_stat(&netfs_n_wh_writepages);
@@ -605,12 +632,17 @@ int netfs_writepages(struct address_space *mapping,
} while ((folio = writeback_iter(mapping, wbc, folio, &error)));
netfs_end_issue_write(wreq);
+ bvecq_pos_unset(&wreq->load_cursor);
+ bvecq_pos_unset(&wreq->dispatch_cursor);
netfs_wake_collector(wreq);
netfs_put_request(wreq, netfs_rreq_trace_put_return);
_leave(" = %d", error);
return error;
+nomem:
+ error = -ENOMEM;
+ netfs_put_failed_request(wreq);
couldnt_start:
if (error == -ENOMEM) {
folio_redirty_for_writepage(wbc, folio);
@@ -631,23 +663,28 @@ EXPORT_SYMBOL(netfs_writepages);
* netfs_writeback_single - Write back a monolithic payload
* @mapping: The mapping to write from
* @wbc: Hints from the VM
- * @iter: Data to write.
+ * @iter: Buffer to write from
+ * @len: Amount to write from buffer
*
* Write a monolithic, non-pagecache object back to the server and/or the
- * cache. The caller must explicitly set NETFS_RREQ_UPLOAD_TO_SERVER when
- * initialising the request if it wants the data to be written to the server
- * (for AFS directories and symlinks, this is not possible; things like mkdir,
- * symlink, rmdir and unlink must be used instead).
+ * cache. There's a maximum of one subrequest per stream. The buffer should be
+ * rounded out sufficiently that it can accommodate cache DIO rounding.
+ *
+ * The caller must explicitly set NETFS_RREQ_UPLOAD_TO_SERVER when initialising
+ * the request if it wants the data to be written to the server (for AFS
+ * directories and symlinks, this is not possible; things like mkdir, symlink,
+ * rmdir and unlink must be used instead).
*
* Return: 0 if successful; 1 if skipped due to lock conflict and WB_SYNC_NONE;
* or a negative error code.
*/
int netfs_writeback_single(struct address_space *mapping,
struct writeback_control *wbc,
- struct iov_iter *iter)
+ struct iov_iter *iter, size_t len)
{
struct netfs_io_request *wreq;
struct netfs_inode *ictx = netfs_inode(mapping->host);
+ size_t clen;
int ret;
if (!netfs_wb_begin(ictx, wbc->sync_mode == WB_SYNC_NONE)) {
@@ -661,10 +698,27 @@ int netfs_writeback_single(struct address_space *mapping,
ret = PTR_ERR(wreq);
goto couldnt_start;
}
+ wreq->len = len;
+ clen = len;
+
+ if (wreq->cache_resources.dio_size > 1) {
+ clen = round_up(len, wreq->cache_resources.dio_size);
+ if (clen > iov_iter_count(iter)) {
+ ret = -EIO;
+ goto cleanup_free;
+ }
+ }
- wreq->buffer.iter = *iter;
- wreq->len = iov_iter_count(iter);
- wreq->submitted = wreq->len;
+ ret = netfs_extract_iter(iter, clen, INT_MAX, &wreq->dispatch_cursor.bvecq,
+ 0, wreq->gfp);
+ if (ret < 0)
+ goto cleanup_free;
+ if (ret < clen) {
+ ret = -EIO;
+ goto cleanup_free;
+ }
+
+ bvecq_pos_set(&wreq->collect_cursor, &wreq->dispatch_cursor);
__set_bit(NETFS_RREQ_OFFLOAD_COLLECTION, &wreq->flags);
trace_netfs_write(wreq, netfs_write_trace_writeback_single);
@@ -685,12 +739,14 @@ int netfs_writeback_single(struct address_space *mapping,
subreq = stream->construct;
subreq->len = wreq->len;
+ if (stream->source == NETFS_WRITE_TO_CACHE)
+ subreq->len = clen;
stream->submit_len = subreq->len;
- stream->submit_extendable_to = round_up(wreq->len, PAGE_SIZE);
netfs_issue_write(wreq, stream);
}
+ wreq->submitted = wreq->len;
netfs_all_subreqs_queued(wreq);
netfs_wake_collector(wreq);
@@ -705,6 +761,8 @@ int netfs_writeback_single(struct address_space *mapping,
_leave(" = %d", ret);
return ret;
+cleanup_free:
+ netfs_put_failed_request(wreq);
couldnt_start:
netfs_wb_end(ictx);
_leave(" = %d", ret);
diff --git a/fs/netfs/write_retry.c b/fs/netfs/write_retry.c
index 2f20577563e1..235e75eb1091 100644
--- a/fs/netfs/write_retry.c
+++ b/fs/netfs/write_retry.c
@@ -17,15 +17,17 @@
static void netfs_retry_write_stream(struct netfs_io_request *wreq,
struct netfs_io_stream *stream)
{
+ struct bvecq_pos dispatch_cursor = {};
struct list_head *next;
_enter("R=%x[%x:]", wreq->debug_id, stream->stream_nr);
if (list_empty(&stream->subrequests))
return;
+ if (WARN_ON_ONCE(stream->source != NETFS_UPLOAD_TO_SERVER))
+ return; /* Shouldn't be retrying cache writes. */
- if (stream->source == NETFS_UPLOAD_TO_SERVER &&
- wreq->netfs_ops->retry_request)
+ if (wreq->netfs_ops->retry_request)
wreq->netfs_ops->retry_request(wreq, stream);
if (unlikely(stream->failed))
@@ -39,12 +41,8 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
if (test_bit(NETFS_SREQ_FAILED, &subreq->flags))
break;
if (__test_and_clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags)) {
- struct iov_iter source;
-
- netfs_reset_iter(subreq);
- source = subreq->io_iter;
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
- netfs_reissue_write(stream, subreq, &source);
+ netfs_reissue_write(stream, subreq);
}
}
return;
@@ -54,11 +52,12 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
do {
struct netfs_io_subrequest *subreq = NULL, *from, *to, *tmp;
- struct iov_iter source;
uoff_t start, len;
size_t part;
bool boundary = false;
+ bvecq_pos_unset(&dispatch_cursor);
+
/* Go through the stream and find the next span of contiguous
* data that we then rejig (cifs, for example, needs the wsize
* renegotiating) and reissue.
@@ -70,7 +69,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
if (test_bit(NETFS_SREQ_FAILED, &from->flags) ||
!test_bit(NETFS_SREQ_NEED_RETRY, &from->flags))
- return;
+ goto out;
for (;;) {
/* Read pointer to subreq before reading subreq state. */
@@ -79,7 +78,8 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
break;
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
- if (subreq->start + subreq->transferred != start + len ||
+ if (subreq->start != start + len ||
+ subreq->transferred > 0 ||
test_bit(NETFS_SREQ_BOUNDARY, &subreq->flags) ||
!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags))
break;
@@ -90,11 +90,13 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
/* Determine the set of buffers we're going to use. Each
* subreq gets a subset of a single overall contiguous buffer.
*/
- netfs_reset_iter(from);
- source = from->io_iter;
- source.count = len;
+ bvecq_pos_transfer(&dispatch_cursor, &from->io_buffer);
+ bvecq_pos_advance(&dispatch_cursor, from->transferred);
- /* Work through the sublist. */
+ /* Work through the sublist. The chain of buffers we're going
+ * to fill is attached to dispatch_cursor and we need to read
+ * 'len' amount of data from 'start'.
+ */
subreq = from;
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
if (!len)
@@ -104,16 +106,22 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
subreq->len = len;
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
+ subreq->transferred = 0;
+
+ bvecq_pos_unset(&subreq->io_buffer);
/* Renegotiate max_len (wsize) */
stream->sreq_max_len = len;
+ stream->sreq_max_segs = INT_MAX;
stream->prepare_write(subreq);
- part = umin(len, stream->sreq_max_len);
- if (unlikely(stream->sreq_max_segs))
- part = netfs_limit_iter(&source, 0, part, stream->sreq_max_segs);
+ bvecq_pos_set(&subreq->io_buffer, &dispatch_cursor);
+ part = bvecq_slice(&dispatch_cursor,
+ umin(len, stream->sreq_max_len),
+ stream->sreq_max_segs,
+ &subreq->nr_segs);
subreq->len = part;
- subreq->transferred = 0;
+
len -= part;
start += part;
if (len && subreq == to &&
@@ -121,7 +129,7 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
boundary = true;
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
- netfs_reissue_write(stream, subreq, &source);
+ netfs_reissue_write(stream, subreq);
if (subreq == to)
break;
}
@@ -172,17 +180,19 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
netfs_stat(&netfs_n_wh_upload);
stream->sreq_max_len = umin(len, wreq->wsize);
break;
- case NETFS_WRITE_TO_CACHE:
- netfs_stat(&netfs_n_wh_write);
- break;
default:
WARN_ON_ONCE(1);
}
stream->prepare_write(subreq);
- part = umin(len, stream->sreq_max_len);
+ bvecq_pos_set(&subreq->io_buffer, &dispatch_cursor);
+ part = bvecq_slice(&dispatch_cursor,
+ umin(len, stream->sreq_max_len),
+ stream->sreq_max_segs,
+ &subreq->nr_segs);
subreq->len = subreq->transferred + part;
+
len -= part;
start += part;
if (!len && boundary) {
@@ -190,13 +200,16 @@ static void netfs_retry_write_stream(struct netfs_io_request *wreq,
boundary = false;
}
- netfs_reissue_write(stream, subreq, &source);
+ netfs_reissue_write(stream, subreq);
if (!len)
break;
} while (len);
} while (!list_is_head(next, &stream->subrequests));
+
+out:
+ bvecq_pos_unset(&dispatch_cursor);
}
/*
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 94f155517f5a..c25493ef9016 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -19,10 +19,12 @@
#include <linux/pagemap.h>
#include <linux/bvecq.h>
#include <linux/uio.h>
-#include <linux/rolling_buffer.h>
enum netfs_sreq_ref_trace;
typedef struct mempool mempool_t;
+struct readahead_control;
+struct netfs_io_request;
+struct netfs_io_subrequest;
struct fscache_occupancy;
/**
@@ -144,7 +146,6 @@ struct netfs_io_stream {
unsigned int sreq_max_segs; /* 0 or max number of segments in an iterator */
unsigned int submit_off; /* Folio offset we're submitting from */
unsigned int submit_len; /* Amount of data left to submit */
- unsigned int submit_extendable_to; /* Amount I/O can be rounded up to */
void (*prepare_write)(struct netfs_io_subrequest *subreq);
void (*issue_write)(struct netfs_io_subrequest *subreq);
/* Collection tracking */
@@ -187,6 +188,7 @@ struct netfs_io_subrequest {
struct netfs_io_request *rreq; /* Supervising I/O request */
struct work_struct work;
struct list_head rreq_link; /* Link in rreq->subrequests */
+ struct bvecq_pos io_buffer; /* Bookmark in the combined queue of the start */
struct iov_iter io_iter; /* Iterator for this subrequest */
uoff_t start; /* Where to start the I/O */
size_t len; /* Size of the I/O */
@@ -247,11 +249,14 @@ struct netfs_io_request {
struct netfs_io_stream io_streams[2]; /* Streams of parallel I/O operations */
#define NR_IO_STREAMS 2 //wreq->nr_io_streams
struct netfs_group *group; /* Writeback group being written back */
- struct rolling_buffer buffer; /* Unencrypted buffer */
+ struct bvecq *spare; /* Advance allocation of bvecq */
+ struct bvecq_pos load_cursor; /* Point at which new folios are loaded in */
+ struct bvecq_pos dispatch_cursor; /* Point from which buffers are dispatched */
+ struct bvecq_pos collect_cursor; /* Clear-up point of I/O buffer */
wait_queue_head_t waitq; /* Processor waiter */
void *netfs_priv; /* Private data for the netfs */
void *netfs_priv2; /* Private data for the netfs */
- struct bvecq *direct_bq; /* DIO buffer list (when handling iovec-iter) */
+ uoff_t last_end; /* End pos of last folio submitted */
uoff_t submitted; /* Amount submitted for I/O so far */
uoff_t len; /* Length of the request */
size_t transferred; /* Amount to be indicated as transferred */
@@ -423,7 +428,7 @@ void netfs_single_mark_inode_dirty(struct inode *inode);
ssize_t netfs_read_single(struct inode *inode, struct file *file, struct iov_iter *iter);
int netfs_writeback_single(struct address_space *mapping,
struct writeback_control *wbc,
- struct iov_iter *iter);
+ struct iov_iter *iter, size_t len);
/* Address operations API */
struct readahead_control;
diff --git a/include/trace/events/netfs.h b/include/trace/events/netfs.h
index e80c27c49ce2..81a3b7aaa187 100644
--- a/include/trace/events/netfs.h
+++ b/include/trace/events/netfs.h
@@ -229,7 +229,9 @@
EM(netfs_folio_trace_sched_copy, "sched-copy") \
EM(netfs_folio_trace_store, "store") \
EM(netfs_folio_trace_store_copy, "store-copy") \
- E_(netfs_folio_trace_store_plus, "store+")
+ EM(netfs_folio_trace_store_plus, "store+") \
+ EM(netfs_folio_trace_zero, "zero") \
+ E_(netfs_folio_trace_zero_ra, "zero-ra")
#define netfs_collect_contig_traces \
EM(netfs_contig_trace_collect, "Collect") \
@@ -386,10 +388,10 @@ TRACE_EVENT(netfs_sreq,
__entry->len = sreq->len;
__entry->transferred = sreq->transferred;
__entry->start = sreq->start;
- __entry->slot = sreq->io_iter.bvecq_slot;
+ __entry->slot = sreq->io_buffer.slot;
),
- TP_printk("R=%08x[%x] %s %s f=%03x s=%llx %zx/%zx s=%u e=%d",
+ TP_printk("R=%08x[%x] %s %s f=%03x s=%llx %zx/%zx bv=%u e=%d",
__entry->rreq, __entry->index,
__print_symbolic(__entry->source, netfs_sreq_sources),
__print_symbolic(__entry->what, netfs_sreq_traces),
@@ -782,6 +784,30 @@ TRACE_EVENT(netfs_read_progress_at,
__entry->rreq, __entry->cleaned_to, __entry->progress_at)
);
+TRACE_EVENT(netfs_bv_slot,
+ TP_PROTO(const struct bvecq *bq, int slot),
+
+ TP_ARGS(bq, slot),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, pfn)
+ __field(unsigned int, offset)
+ __field(unsigned int, len)
+ __field(unsigned int, slot)
+ ),
+
+ TP_fast_assign(
+ __entry->slot = slot;
+ __entry->pfn = page_to_pfn(bq->bv[slot].bv_page);
+ __entry->offset = bq->bv[slot].bv_offset;
+ __entry->len = bq->bv[slot].bv_len;
+ ),
+
+ TP_printk("bq[%x] p=%lx %x-%x",
+ __entry->slot,
+ __entry->pfn, __entry->offset, __entry->offset + __entry->len)
+ );
+
#undef EM
#undef E_
#endif /* _TRACE_NETFS_H */
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 7/8] netfs: Remove the rolling_buffer implementation
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
` (5 preceding siblings ...)
2026-10-05 7:12 ` [PATCH v12 6/8] netfs: Use bvecq_pos to hold the buffer positions David Howells
@ 2026-10-05 7:12 ` David Howells
2026-10-05 7:12 ` [PATCH v12 8/8] netfs: Remove netfs_extract_user_iter() David Howells
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Remove the rolling_buffer implementation as it has been replaced with the
use of bvecq_pos.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/rolling_buffer.c | 182 ---------------------------------
include/linux/rolling_buffer.h | 47 ---------
2 files changed, 229 deletions(-)
delete mode 100644 fs/netfs/rolling_buffer.c
delete mode 100644 include/linux/rolling_buffer.h
diff --git a/fs/netfs/rolling_buffer.c b/fs/netfs/rolling_buffer.c
deleted file mode 100644
index 66ce9add4012..000000000000
--- a/fs/netfs/rolling_buffer.c
+++ /dev/null
@@ -1,182 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/* Rolling buffer helpers
- *
- * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
- * Written by David Howells (dhowells@redhat.com)
- */
-
-#include <linux/bitops.h>
-#include <linux/mempool.h>
-#include <linux/pagemap.h>
-#include <linux/rolling_buffer.h>
-#include <linux/slab.h>
-#include "internal.h"
-
-/*
- * Initialise a rolling buffer. We allocate an empty folio queue struct to so
- * that the pointers can be independently driven by the producer and the
- * consumer.
- */
-int rolling_buffer_init(struct rolling_buffer *roll, unsigned int direction,
- gfp_t gfp, bool for_writeback)
-{
- struct bvecq *bq;
-
- roll->for_writeback = for_writeback;
-
- bq = bvecq_alloc_one(BVECQ_POOL_SLOTS, gfp, for_writeback);
- if (!bq)
- return -ENOMEM;
-
- roll->head = bq;
- roll->tail = bq;
- iov_iter_bvec_queue(&roll->iter, direction, bq, 0, 0, 0);
- return 0;
-}
-
-/*
- * Add another bvecq to a rolling buffer if there's no space left.
- */
-int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp)
-{
- struct bvecq *bq, *head = roll->head;
-
- if (!bvecq_is_full(head))
- return 0;
-
- bq = bvecq_alloc_one(BVECQ_POOL_SLOTS, gfp, roll->for_writeback);
- if (!bq)
- return -ENOMEM;
-
- roll->head = bq;
- if (bvecq_is_full(head)) {
- /* Make sure we don't leave the master iterator pointing to a
- * block that might get immediately consumed.
- */
- if (roll->iter.bvecq == head &&
- roll->iter.bvecq_slot == head->nr_slots) {
- roll->iter.bvecq = bq;
- roll->iter.bvecq_slot = 0;
- }
- }
-
- /* Make sure the initialisation is stored before the next pointer.
- *
- * [!] NOTE: After we set head->next, the consumer is at liberty to
- * immediately delete the old head.
- */
- bvecq_append(head, bq);
- return 0;
-}
-
-/*
- * Decant the entire list of folios to read into a rolling buffer.
- */
-ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll,
- struct readahead_control *ractl,
- gfp_t gfp)
-{
- struct bvecq *bq;
- size_t loaded = 0;
-
- while (ractl->_nr_pages - ractl->_batch_count > 0) {
- struct page **pages;
- unsigned int nr;
-
- /* Allocate a bvecq to put some folios into and attach it to
- * the rolling buffer.
- */
- bq = bvecq_alloc_one(BVECQ_POOL_SLOTS, gfp, false);
- if (!bq)
- goto nomem_unlock;
- bq->mem_type = BVECQ_MEM_EXTERNAL; /* Folio cleanup handled separately. */
-
- if (!roll->tail)
- roll->tail = bq;
- else
- bvecq_append(roll->head, bq);
- roll->head = bq;
-
- /* Get a bunch of folios and note their sizes. */
- pages = (struct page **)(bq->bv + bq->max_slots);
- pages -= bq->max_slots;
- nr = __readahead_batch(ractl, pages, bq->max_slots);
- if (WARN_ON_ONCE(!nr))
- break;
-
- for (int slot = 0; slot < nr; slot++) {
- struct folio *folio = page_folio(pages[slot]);
- size_t len = folio_size(folio);
-
- bvec_set_folio(&bq->bv[slot], folio, len, 0);
- loaded += len;
- trace_netfs_folio(folio, netfs_folio_trace_read);
- }
-
- bvecq_filled_to(bq, nr);
- }
-
- WRITE_ONCE(roll->iter.count, loaded);
- iov_iter_bvec_queue(&roll->iter, ITER_DEST, roll->tail, 0, 0, loaded);
- return loaded;
-
-nomem_unlock:
- for (bq = roll->tail; bq; bq = bq->next) {
- for (int slot = 0; slot < bq->nr_slots; slot++) {
- struct folio *folio = bvec_folio(&bq->bv[slot]);
-
- folio_unlock(folio);
- folio_put(folio);
- }
- }
- rolling_buffer_clear(roll);
- roll->head = NULL;
- roll->tail = NULL;
- return -ENOMEM;
-}
-
-/*
- * Append a folio to the rolling buffer.
- */
-ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio,
- gfp_t gfp)
-{
- ssize_t size = folio_size(folio);
- int slot;
-
- if (rolling_buffer_make_space(roll, gfp) < 0)
- return -ENOMEM;
-
- slot = roll->head->nr_slots;
- bvec_set_folio(&roll->head->bv[slot], folio, size, 0);
- bvecq_filled_to(roll->head, slot + 1);
-
- WRITE_ONCE(roll->iter.count, roll->iter.count + size);
- return size;
-}
-
-/*
- * Delete a spent buffer from a rolling queue and return the next in line. We
- * don't return the last buffer to keep the pointers independent, but return
- * NULL instead.
- */
-struct bvecq *rolling_buffer_delete_spent(struct rolling_buffer *roll)
-{
- struct bvecq *spent = roll->tail, *next = bvecq_next(spent);
-
- if (!next)
- return NULL;
- next->prev = NULL;
- roll->tail = next;
- spent->next = NULL;
- bvecq_put(spent);
- return next;
-}
-
-/*
- * Clear out a rolling queue.
- */
-void rolling_buffer_clear(struct rolling_buffer *roll)
-{
- bvecq_put(roll->tail);
-}
diff --git a/include/linux/rolling_buffer.h b/include/linux/rolling_buffer.h
deleted file mode 100644
index 5c0bc4221f01..000000000000
--- a/include/linux/rolling_buffer.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Rolling buffer of folios
- *
- * Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
- * Written by David Howells (dhowells@redhat.com)
- */
-
-#ifndef _ROLLING_BUFFER_H
-#define _ROLLING_BUFFER_H
-
-#include <linux/bvecq.h>
-#include <linux/uio.h>
-
-/*
- * Rolling buffer. Whilst the buffer is live and in use, folios and bvecq
- * segments can be added to one end by one thread and removed from the other
- * end by another thread. The buffer isn't allowed to be empty; it must always
- * have at least one bvecq in it so that neither side has to modify both queue
- * pointers.
- *
- * The iterator in the buffer is extended as buffers are inserted. It can be
- * snapshotted to use a segment of the buffer.
- */
-struct rolling_buffer {
- struct bvecq *head; /* Producer's insertion point */
- struct bvecq *tail; /* Consumer's removal point */
- struct iov_iter iter; /* Iterator tracking what's left in the buffer */
- u8 first_tail_slot; /* First slot in ->tail */
- bool for_writeback; /* T if being used for writeback */
-};
-
-int rolling_buffer_init(struct rolling_buffer *roll, unsigned int direction,
- gfp_t gfp, bool for_writeback);
-int rolling_buffer_make_space(struct rolling_buffer *roll, gfp_t gfp);
-ssize_t rolling_buffer_bulk_load_from_ra(struct rolling_buffer *roll,
- struct readahead_control *ractl,
- gfp_t gfp);
-ssize_t rolling_buffer_append(struct rolling_buffer *roll, struct folio *folio, gfp_t gfp);
-struct bvecq *rolling_buffer_delete_spent(struct rolling_buffer *roll);
-void rolling_buffer_clear(struct rolling_buffer *roll);
-
-static inline void rolling_buffer_advance(struct rolling_buffer *roll, size_t amount)
-{
- iov_iter_advance(&roll->iter, amount);
-}
-
-#endif /* _ROLLING_BUFFER_H */
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v12 8/8] netfs: Remove netfs_extract_user_iter()
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
` (6 preceding siblings ...)
2026-10-05 7:12 ` [PATCH v12 7/8] netfs: Remove the rolling_buffer implementation David Howells
@ 2026-10-05 7:12 ` David Howells
7 siblings, 0 replies; 9+ messages in thread
From: David Howells @ 2026-10-05 7:12 UTC (permalink / raw)
To: Christian Brauner
Cc: David Howells, Paulo Alcantara, Matthew Wilcox, Namjae Jeon,
Marc Dionne, Stefan Metzmacher, Eric Van Hensbergen,
Dominique Martinet, Ilya Dryomov, netfs, linux-afs, linux-cifs,
linux-nfs, ceph-devel, v9fs, linux-fsdevel, linux-kernel
Remove netfs_extract_user_iter() as it has been replaced with
netfs_extract_iter().
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/iterator.c | 297 ------------------------------------------
include/linux/netfs.h | 5 -
2 files changed, 302 deletions(-)
diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c
index 99b419066bd0..dc97e5b0d449 100644
--- a/fs/netfs/iterator.c
+++ b/fs/netfs/iterator.c
@@ -155,300 +155,3 @@ ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t max_pag
}
EXPORT_SYMBOL_GPL(netfs_extract_iter);
-
-#if 0 // TODO: Remove in subsequent patch
-/**
- * netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
- * @orig: The original iterator
- * @orig_len: The amount of iterator to copy
- * @new: The iterator to be set up
- * @extraction_flags: Flags to qualify the request
- *
- * Extract the page fragments from the given amount of the source iterator and
- * build up a second iterator that refers to all of those bits. This allows
- * the original iterator to be disposed of.
- *
- * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-peer DMA be
- * allowed on the pages extracted.
- *
- * On success, the number of elements in the bvec is returned, the original
- * iterator will have been advanced by the amount extracted.
- *
- * The iov_iter_extract_mode() function should be used to query how cleanup
- * should be performed.
- */
-ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
- struct iov_iter *new,
- iov_iter_extraction_t extraction_flags)
-{
- struct bio_vec *bv = NULL;
- struct page **pages;
- unsigned int cur_npages;
- unsigned int max_pages;
- unsigned int npages = 0;
- unsigned int i;
- ssize_t ret = 0;
- size_t count = orig_len, offset, len;
- size_t bv_size, pg_size;
-
- if (WARN_ON_ONCE(!iter_is_ubuf(orig) && !iter_is_iovec(orig)))
- return -EIO;
-
- max_pages = iov_iter_npages(orig, INT_MAX);
- bv_size = array_size(max_pages, sizeof(*bv));
- bv = kvmalloc(bv_size, GFP_KERNEL);
- if (!bv)
- return -ENOMEM;
-
- /* Put the page list at the end of the bvec list storage. bvec
- * elements are larger than page pointers, so as long as we work
- * 0->last, we should be fine.
- */
- pg_size = array_size(max_pages, sizeof(*pages));
- pages = (void *)bv + bv_size - pg_size;
-
- while (count && npages < max_pages) {
- ret = iov_iter_extract_pages(orig, &pages, count,
- max_pages - npages, extraction_flags,
- &offset);
- if (unlikely(ret <= 0)) {
- ret = ret ?: -EIO;
- break;
- }
-
- if (WARN(ret > count,
- "%s: extract_pages overrun %zd > %zu bytes\n",
- __func__, ret, count)) {
- ret = -EIO;
- break;
- }
-
- cur_npages = DIV_ROUND_UP(offset + ret, PAGE_SIZE);
- if (WARN(cur_npages > max_pages - npages,
- "%s: extract_pages overrun %u > %u pages\n",
- __func__, npages + cur_npages, max_pages)) {
- ret = -EIO;
- break;
- }
-
- count -= ret;
- ret += offset;
-
- for (i = 0; i < cur_npages; i++) {
- len = ret > PAGE_SIZE ? PAGE_SIZE : ret;
- bvec_set_page(bv + npages + i, *pages++, len - offset, offset);
- ret -= len;
- offset = 0;
- }
-
- npages += cur_npages;
- }
-
- /* Note: Don't try to clean up after EIO. Either we got no pages, so
- * nothing to clean up, or we got a buffer overrun, memory corruption
- * and can't trust the stuff in the buffer (a WARN was emitted).
- */
-
- if (ret < 0 && (ret == -ENOMEM || npages == 0)) {
- for (i = 0; i < npages; i++)
- unpin_user_page(bv[i].bv_page);
- kvfree(bv);
- return ret;
- }
-
- iov_iter_bvec(new, orig->data_source, bv, npages, orig_len - count);
- return npages;
-}
-EXPORT_SYMBOL_GPL(netfs_extract_user_iter);
-
-/*
- * Select the span of a bvec iterator we're going to use. Limit it by both maximum
- * size and maximum number of segments. Returns the size of the span in bytes.
- */
-static size_t netfs_limit_bvec(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs)
-{
- const struct bio_vec *bvecs = iter->bvec;
- unsigned int nbv = iter->nr_segs, ix = 0, nsegs = 0;
- size_t len, span = 0, n = iter->count;
- size_t skip = iter->iov_offset + start_offset;
-
- if (WARN_ON(!iov_iter_is_bvec(iter)) ||
- WARN_ON(start_offset > n) ||
- n == 0)
- return 0;
-
- while (n && ix < nbv && skip) {
- len = bvecs[ix].bv_len;
- if (skip < len)
- break;
- skip -= len;
- n -= len;
- ix++;
- }
-
- while (n && ix < nbv) {
- len = min3(n, bvecs[ix].bv_len - skip, max_size);
- span += len;
- nsegs++;
- ix++;
- if (span >= max_size || nsegs >= max_segs)
- break;
- skip = 0;
- n -= len;
- }
-
- return min(span, max_size);
-}
-
-/*
- * Select the span of a kvec iterator we're going to use. Limit it by both
- * maximum size and maximum number of segments. Returns the size of the span
- * in bytes.
- */
-static size_t netfs_limit_kvec(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs)
-{
- const struct kvec *kvecs = iter->kvec;
- unsigned int nkv = iter->nr_segs, ix = 0, nsegs = 0;
- size_t len, span = 0, n = iter->count;
- size_t skip = iter->iov_offset + start_offset;
-
- if (WARN_ON(!iov_iter_is_kvec(iter)) ||
- WARN_ON(start_offset > n) ||
- n == 0)
- return 0;
-
- while (n && ix < nkv && skip) {
- len = kvecs[ix].iov_len;
- if (skip < len)
- break;
- skip -= len;
- n -= len;
- ix++;
- }
-
- while (n && ix < nkv) {
- len = min3(n, kvecs[ix].iov_len - skip, max_size);
- span += len;
- nsegs++;
- ix++;
- if (span >= max_size || nsegs >= max_segs)
- break;
- skip = 0;
- n -= len;
- }
-
- return min(span, max_size);
-}
-
-/*
- * Select the span of an xarray iterator we're going to use. Limit it by both
- * maximum size and maximum number of segments. It is assumed that segments
- * can be larger than a page in size, provided they're physically contiguous.
- * Returns the size of the span in bytes.
- */
-static size_t netfs_limit_xarray(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs)
-{
- struct folio *folio;
- unsigned int nsegs = 0;
- uoff_t pos = iter->xarray_start + iter->iov_offset;
- pgoff_t index = pos / PAGE_SIZE;
- size_t span = 0, n = iter->count;
-
- XA_STATE(xas, iter->xarray, index);
-
- if (WARN_ON(!iov_iter_is_xarray(iter)) ||
- WARN_ON(start_offset > n) ||
- n == 0)
- return 0;
- max_size = min(max_size, n - start_offset);
-
- rcu_read_lock();
- xas_for_each(&xas, folio, ULONG_MAX) {
- size_t offset, flen, len;
- if (xas_retry(&xas, folio))
- continue;
- if (WARN_ON(xa_is_value(folio)))
- break;
- if (WARN_ON(folio_test_hugetlb(folio)))
- break;
-
- flen = folio_size(folio);
- offset = offset_in_folio(folio, pos);
- len = min(max_size, flen - offset);
- span += len;
- nsegs++;
- if (span >= max_size || nsegs >= max_segs)
- break;
- }
-
- rcu_read_unlock();
- return min(span, max_size);
-}
-
-/*
- * Select the span of a bvecq iterator we're going to use. Limit it by both
- * maximum size and maximum number of segments. Returns the size of the span
- * in bytes.
- */
-static size_t netfs_limit_bvecq(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs)
-{
- const struct bvecq *bq = iter->bvecq;
- unsigned int nsegs = 0;
- unsigned int slot = iter->bvecq_slot;
- size_t span = 0, n = iter->count;
-
- if (WARN_ON(!iov_iter_is_bvecq(iter)) ||
- WARN_ON(start_offset > n) ||
- n == 0)
- return 0;
- max_size = umin(max_size, n - start_offset);
-
- if (!bvecq_acquire_slot(bq, slot)) {
- bq = bvecq_next(bq);
- slot = 0;
- }
-
- start_offset += iter->iov_offset;
- do {
- size_t flen;
-
- flen = bq->bv[slot].bv_len;
- if (start_offset < flen) {
- span += flen - start_offset;
- nsegs++;
- start_offset = 0;
- } else {
- start_offset -= flen;
- }
- if (span >= max_size || nsegs >= max_segs)
- break;
-
- slot++;
- if (!bvecq_acquire_slot(bq, slot)) {
- bq = bvecq_next(bq);
- slot = 0;
- }
- } while (bq);
-
- return umin(span, max_size);
-}
-
-size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs)
-{
- if (iov_iter_is_bvecq(iter))
- return netfs_limit_bvecq(iter, start_offset, max_size, max_segs);
- if (iov_iter_is_bvec(iter))
- return netfs_limit_bvec(iter, start_offset, max_size, max_segs);
- if (iov_iter_is_xarray(iter))
- return netfs_limit_xarray(iter, start_offset, max_size, max_segs);
- if (iov_iter_is_kvec(iter))
- return netfs_limit_kvec(iter, start_offset, max_size, max_segs);
- BUG();
-}
-EXPORT_SYMBOL(netfs_limit_iter);
-#endif
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index c25493ef9016..b3a58ce8115c 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -458,11 +458,6 @@ void netfs_put_subrequest(struct netfs_io_subrequest *subreq,
ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t max_pages,
struct bvecq **_bvecq_head,
iov_iter_extraction_t extraction_flags, gfp_t gfp);
-ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
- struct iov_iter *new,
- iov_iter_extraction_t extraction_flags);
-size_t netfs_limit_iter(const struct iov_iter *iter, size_t start_offset,
- size_t max_size, size_t max_segs);
void netfs_prepare_write_failed(struct netfs_io_subrequest *subreq);
void netfs_write_subrequest_terminated(void *_op, ssize_t transferred_or_error);
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-05 7:13 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-05 7:12 [PATCH v12 0/8] netfs: Use a bvecq chain position abstraction David Howells
2026-10-05 7:12 ` [PATCH v12 1/8] mm: Make readahead store folio count in readahead_control David Howells
2026-10-05 7:12 ` [PATCH v12 2/8] netfs: Add a function to extract from an iter into a bvecq David Howells
2026-10-05 7:12 ` [PATCH v12 3/8] netfs: Make unbuffered/DIO read and write use bvecq David Howells
2026-10-05 7:12 ` [PATCH v12 4/8] netfs: Add some tools for managing a position in a bvecq chain David Howells
2026-10-05 7:12 ` [PATCH v12 5/8] netfs: Provide a func to load the readahead buffers into " David Howells
2026-10-05 7:12 ` [PATCH v12 6/8] netfs: Use bvecq_pos to hold the buffer positions David Howells
2026-10-05 7:12 ` [PATCH v12 7/8] netfs: Remove the rolling_buffer implementation David Howells
2026-10-05 7:12 ` [PATCH v12 8/8] netfs: Remove netfs_extract_user_iter() David Howells
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®