* [PATCH v17 01/17] iov_iter: Fix kunit test to remove __init and __refdata marks
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
@ 2026-11-11 20:30 ` David Howells
2026-11-11 20:30 ` [PATCH v17 02/17] Add a function to kmap one page of a multipage bio_vec David Howells
` (4 subsequent siblings)
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Jens Axboe, Christoph Hellwig, linux-block
Remove the __init marks from the functions in the iov_iter kunit test so
that they don't get removed after the module is loaded or if the test is
built in as the tests can be triggered through debugfs too.
Also remove the __refdata mark which is unnecessary if the table so marked
doesn't point at any __init funcs.
This was found by claude-opus-5-5 run locally.
Fixes: 2d71340ff1d4 ("iov_iter: Kunit tests for copying to/from an iterator")
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
lib/tests/kunit_iov_iter.c | 96 +++++++++++++++++++-------------------
1 file changed, 47 insertions(+), 49 deletions(-)
diff --git a/lib/tests/kunit_iov_iter.c b/lib/tests/kunit_iov_iter.c
index 32e42d8c7ca1..b4ad38d1a4bd 100644
--- a/lib/tests/kunit_iov_iter.c
+++ b/lib/tests/kunit_iov_iter.c
@@ -48,9 +48,9 @@ static void iov_kunit_unmap(void *data)
vfree(data);
}
-static void *__init iov_kunit_create_buffer(struct kunit *test,
- struct page ***ppages,
- size_t npages)
+static void *iov_kunit_create_buffer(struct kunit *test,
+ struct page ***ppages,
+ size_t npages)
{
struct page **pages;
unsigned long got, last;
@@ -90,11 +90,11 @@ static void *__init iov_kunit_create_buffer(struct kunit *test,
return buffer;
}
-static void __init iov_kunit_load_kvec(struct kunit *test,
- struct iov_iter *iter, int dir,
- struct kvec *kvec, unsigned int kvmax,
- void *buffer, size_t bufsize,
- const struct kvec_test_range *pr)
+static void iov_kunit_load_kvec(struct kunit *test,
+ struct iov_iter *iter, int dir,
+ struct kvec *kvec, unsigned int kvmax,
+ void *buffer, size_t bufsize,
+ const struct kvec_test_range *pr)
{
size_t size = 0;
int i;
@@ -116,7 +116,7 @@ static void __init iov_kunit_load_kvec(struct kunit *test,
/*
* Test copying to a ITER_KVEC-type iterator.
*/
-static void __init iov_kunit_copy_to_kvec(struct kunit *test)
+static void iov_kunit_copy_to_kvec(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -166,7 +166,7 @@ static void __init iov_kunit_copy_to_kvec(struct kunit *test)
/*
* Test copying from a ITER_KVEC-type iterator.
*/
-static void __init iov_kunit_copy_from_kvec(struct kunit *test)
+static void iov_kunit_copy_from_kvec(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -234,12 +234,12 @@ static const struct bvec_test_range bvec_test_ranges[] = {
{ -1, -1, -1 }
};
-static void __init iov_kunit_load_bvec(struct kunit *test,
- struct iov_iter *iter, int dir,
- struct bio_vec *bvec, unsigned int bvmax,
- struct page **pages, size_t npages,
- size_t bufsize,
- const struct bvec_test_range *pr)
+static void iov_kunit_load_bvec(struct kunit *test,
+ struct iov_iter *iter, int dir,
+ struct bio_vec *bvec, unsigned int bvmax,
+ struct page **pages, size_t npages,
+ size_t bufsize,
+ const struct bvec_test_range *pr)
{
struct page *can_merge = NULL, *page;
size_t size = 0;
@@ -275,7 +275,7 @@ static void __init iov_kunit_load_bvec(struct kunit *test,
/*
* Test copying to a ITER_BVEC-type iterator.
*/
-static void __init iov_kunit_copy_to_bvec(struct kunit *test)
+static void iov_kunit_copy_to_bvec(struct kunit *test)
{
const struct bvec_test_range *pr;
struct iov_iter iter;
@@ -328,7 +328,7 @@ static void __init iov_kunit_copy_to_bvec(struct kunit *test)
/*
* Test copying from a ITER_BVEC-type iterator.
*/
-static void __init iov_kunit_copy_from_bvec(struct kunit *test)
+static void iov_kunit_copy_from_bvec(struct kunit *test)
{
const struct bvec_test_range *pr;
struct iov_iter iter;
@@ -392,10 +392,10 @@ static void iov_kunit_destroy_folioq(void *data)
}
}
-static void __init iov_kunit_load_folioq(struct kunit *test,
- struct iov_iter *iter, int dir,
- struct folio_queue *folioq,
- struct page **pages, size_t npages)
+static void iov_kunit_load_folioq(struct kunit *test,
+ struct iov_iter *iter, int dir,
+ struct folio_queue *folioq,
+ struct page **pages, size_t npages)
{
struct folio_queue *p = folioq;
size_t size = 0;
@@ -429,7 +429,7 @@ static struct folio_queue *iov_kunit_create_folioq(struct kunit *test)
/*
* Test copying to a ITER_FOLIOQ-type iterator.
*/
-static void __init iov_kunit_copy_to_folioq(struct kunit *test)
+static void iov_kunit_copy_to_folioq(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -491,7 +491,7 @@ static void __init iov_kunit_copy_to_folioq(struct kunit *test)
/*
* Test copying from a ITER_FOLIOQ-type iterator.
*/
-static void __init iov_kunit_copy_from_folioq(struct kunit *test)
+static void iov_kunit_copy_from_folioq(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -560,10 +560,10 @@ static void iov_kunit_destroy_xarray(void *data)
kfree(xarray);
}
-static void __init iov_kunit_load_xarray(struct kunit *test,
- struct iov_iter *iter, int dir,
- struct xarray *xarray,
- struct page **pages, size_t npages)
+static void iov_kunit_load_xarray(struct kunit *test,
+ struct iov_iter *iter, int dir,
+ struct xarray *xarray,
+ struct page **pages, size_t npages)
{
size_t size = 0;
int i;
@@ -591,7 +591,7 @@ static struct xarray *iov_kunit_create_xarray(struct kunit *test)
/*
* Test copying to a ITER_XARRAY-type iterator.
*/
-static void __init iov_kunit_copy_to_xarray(struct kunit *test)
+static void iov_kunit_copy_to_xarray(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -649,7 +649,7 @@ static void __init iov_kunit_copy_to_xarray(struct kunit *test)
/*
* Test copying from a ITER_XARRAY-type iterator.
*/
-static void __init iov_kunit_copy_from_xarray(struct kunit *test)
+static void iov_kunit_copy_from_xarray(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -712,7 +712,7 @@ static void __init iov_kunit_copy_from_xarray(struct kunit *test)
/*
* Test the extraction of ITER_KVEC-type iterators.
*/
-static void __init iov_kunit_extract_pages_kvec(struct kunit *test)
+static void iov_kunit_extract_pages_kvec(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -792,7 +792,7 @@ static void __init iov_kunit_extract_pages_kvec(struct kunit *test)
/*
* Test the extraction of ITER_BVEC-type iterators.
*/
-static void __init iov_kunit_extract_pages_bvec(struct kunit *test)
+static void iov_kunit_extract_pages_bvec(struct kunit *test)
{
const struct bvec_test_range *pr;
struct iov_iter iter;
@@ -870,7 +870,7 @@ static void __init iov_kunit_extract_pages_bvec(struct kunit *test)
/*
* Test the extraction of ITER_FOLIOQ-type iterators.
*/
-static void __init iov_kunit_extract_pages_folioq(struct kunit *test)
+static void iov_kunit_extract_pages_folioq(struct kunit *test)
{
const struct kvec_test_range *pr;
struct folio_queue *folioq;
@@ -949,7 +949,7 @@ static void __init iov_kunit_extract_pages_folioq(struct kunit *test)
/*
* Test the extraction of ITER_XARRAY-type iterators.
*/
-static void __init iov_kunit_extract_pages_xarray(struct kunit *test)
+static void iov_kunit_extract_pages_xarray(struct kunit *test)
{
const struct kvec_test_range *pr;
struct iov_iter iter;
@@ -1033,8 +1033,7 @@ struct iov_kunit_iter_to_sg_data {
size_t npages;
};
-static void __init
-iov_kunit_iter_unpin_sgt(void *data)
+static void iov_kunit_iter_unpin_sgt(void *data)
{
struct sg_table *sgt = data;
@@ -1042,9 +1041,9 @@ iov_kunit_iter_unpin_sgt(void *data)
unpin_user_page(sg_page(&sgt->sgl[i]));
}
-static void __init
-iov_kunit_iter_to_sg_init(struct kunit *test, size_t bufsize, bool user,
- struct iov_kunit_iter_to_sg_data *data)
+static void iov_kunit_iter_to_sg_init(struct kunit *test, size_t bufsize,
+ bool user,
+ struct iov_kunit_iter_to_sg_data *data)
{
struct page **spages;
struct scatterlist *sg;
@@ -1080,10 +1079,9 @@ iov_kunit_iter_to_sg_init(struct kunit *test, size_t bufsize, bool user,
memset(data->scratch, 0, bufsize);
}
-static void __init
-iov_kunit_iter_to_sg_check(struct kunit *test, struct iov_iter *iter,
- size_t bufsize,
- struct iov_kunit_iter_to_sg_data *data)
+static void iov_kunit_iter_to_sg_check(struct kunit *test, struct iov_iter *iter,
+ size_t bufsize,
+ struct iov_kunit_iter_to_sg_data *data)
{
static const size_t tail = 16 * PAGE_SIZE;
size_t i;
@@ -1128,7 +1126,7 @@ iov_kunit_iter_to_sg_check(struct kunit *test, struct iov_iter *iter,
KUNIT_EXPECT_EQ(test, i, bufsize);
}
-static void __init iov_kunit_iter_to_sg_kvec(struct kunit *test)
+static void iov_kunit_iter_to_sg_kvec(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
struct iov_iter iter;
@@ -1145,7 +1143,7 @@ static void __init iov_kunit_iter_to_sg_kvec(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
-static void __init iov_kunit_iter_to_sg_bvec(struct kunit *test)
+static void iov_kunit_iter_to_sg_bvec(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
struct page *p, *can_merge = NULL;
@@ -1173,7 +1171,7 @@ static void __init iov_kunit_iter_to_sg_bvec(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
-static void __init iov_kunit_iter_to_sg_folioq(struct kunit *test)
+static void iov_kunit_iter_to_sg_folioq(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
struct folio_queue *folioq;
@@ -1190,7 +1188,7 @@ static void __init iov_kunit_iter_to_sg_folioq(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
-static void __init iov_kunit_iter_to_sg_xarray(struct kunit *test)
+static void iov_kunit_iter_to_sg_xarray(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
struct xarray *xarray;
@@ -1207,7 +1205,7 @@ static void __init iov_kunit_iter_to_sg_xarray(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
-static void __init iov_kunit_iter_to_sg_ubuf(struct kunit *test)
+static void iov_kunit_iter_to_sg_ubuf(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
struct iov_iter iter;
@@ -1221,7 +1219,7 @@ static void __init iov_kunit_iter_to_sg_ubuf(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
-static struct kunit_case __refdata iov_kunit_cases[] = {
+static struct kunit_case iov_kunit_cases[] = {
KUNIT_CASE(iov_kunit_copy_to_kvec),
KUNIT_CASE(iov_kunit_copy_from_kvec),
KUNIT_CASE(iov_kunit_copy_to_bvec),
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v17 02/17] Add a function to kmap one page of a multipage bio_vec
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
2026-11-11 20:30 ` [PATCH v17 01/17] iov_iter: Fix kunit test to remove __init and __refdata marks David Howells
@ 2026-11-11 20:30 ` David Howells
2026-11-11 20:30 ` [PATCH v17 03/17] iov_iter: Add a segmented queue of bio_vec[] David Howells
` (3 subsequent siblings)
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Jens Axboe, Christoph Hellwig, linux-block
Add a function to kmap one page of a multipage bio_vec by offset (which is
added to the offset in the bio_vec internally). The caller is responsible
for calculating how much of the page is then available.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
include/linux/bvec.h | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/include/linux/bvec.h b/include/linux/bvec.h
index b63914ff56e3..7ea62643cfb7 100644
--- a/include/linux/bvec.h
+++ b/include/linux/bvec.h
@@ -344,4 +344,22 @@ static inline phys_addr_t bvec_phys(const struct bio_vec *bvec)
return page_to_phys(bvec->bv_page) + bvec->bv_offset;
}
+/**
+ * bvec_kmap_partial - Map part of a bvec into the kernel virtual address space
+ * @bvec: bvec to map
+ * @offset: Offset into bvec
+ *
+ * Map the page containing the byte at @offset into the kernel virtual address
+ * space. The caller is responsible for making sure this doesn't overrun.
+ *
+ * Call kunmap_local on the returned address to unmap.
+ */
+static inline void *bvec_kmap_partial(const struct bio_vec *bvec, size_t offset)
+{
+ offset += bvec->bv_offset;
+
+ return kmap_local_page(bvec->bv_page + (offset >> PAGE_SHIFT)) +
+ (offset & ~PAGE_MASK);
+}
+
#endif /* __LINUX_BVEC_H */
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v17 03/17] iov_iter: Add a segmented queue of bio_vec[]
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
2026-11-11 20:30 ` [PATCH v17 01/17] iov_iter: Fix kunit test to remove __init and __refdata marks David Howells
2026-11-11 20:30 ` [PATCH v17 02/17] Add a function to kmap one page of a multipage bio_vec David Howells
@ 2026-11-11 20:30 ` David Howells
2026-11-11 20:30 ` [PATCH v17 05/17] iov_iter: Add an API document for the bvecq structure David Howells
` (2 subsequent siblings)
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Alexander Viro, Jens Axboe, Christoph Hellwig, linux-block
Add the concept of a segmented queue of bio_vec[] arrays. This allows an
indefinite quantity of elements to be handled and allows things like
network filesystems and crypto drivers to glue bits on the ends without
having to reallocate the array.
The bvecq struct that defines each segment also carries capacity/usage
information along with flags indicating whether the constituent memory
regions need freeing or unpinning. The bvecq structs are refcounted to
allow a queue to be extracted in batches and split between a number of
subrequests.
The bvecq can have the bio_vec[] it manages allocated in with it, but this
is not required. A flag is provided for if this is the case as comparing
->bv to ->__bv is not sufficient to detect this case.
Add an iterator type ITER_BVECQ for it. This is intended to replace
ITER_FOLIOQ (and ITER_XARRAY).
Note that the prev pointer is only really needed for iov_iter_revert() and
could be dispensed with if struct iov_iter contained the head information
as well as the current point.
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Alexander Viro <viro@zeniv.linux.org.uk>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
MAINTAINERS | 1 +
include/linux/bvecq.h | 50 +++++
include/linux/iov_iter.h | 74 ++++++-
include/linux/uio.h | 11 +
lib/iov_iter.c | 435 ++++++++++++++++++++++++++++++++++++-
lib/scatterlist.c | 67 +++++-
lib/tests/kunit_iov_iter.c | 277 +++++++++++++++++++++++
7 files changed, 909 insertions(+), 6 deletions(-)
create mode 100644 include/linux/bvecq.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 93abbb315065..6fa777744163 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10385,6 +10385,7 @@ S: Supported
F: Documentation/filesystems/caching/
F: Documentation/filesystems/netfs_library.rst
F: fs/netfs/
+F: include/linux/bvecq.h
F: include/linux/fscache*.h
F: include/linux/netfs.h
F: include/trace/events/fscache.h
diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h
new file mode 100644
index 000000000000..77fd07852c33
--- /dev/null
+++ b/include/linux/bvecq.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Implementation of a segmented queue of bio_vec[].
+ *
+ * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#ifndef _LINUX_BVECQ_H
+#define _LINUX_BVECQ_H
+
+#include <linux/bvec.h>
+
+/*
+ * The type of memory retention used by the elements in bvecq->bv[] and how to
+ * clean it up.
+ */
+enum bvecq_mem {
+ BVECQ_MEM_EXTERNAL, /* Externally retained memory - no freeing */
+ BVECQ_MEM_PAGECACHE, /* Ref'd pagecache pages - must put */
+ BVECQ_MEM_GUP, /* Pinned memory from get_user_pages() - unpin */
+ BVECQ_MEM_ALLOCED, /* Memory alloc'd by bvecq - can be freed/pooled */
+} __mode(byte);
+
+/*
+ * Segmented bio_vec queue.
+ *
+ * These can be linked together to form messages of indefinite length and
+ * iterated over with an ITER_BVECQ iterator. The list is non-circular; next
+ * and prev are NULL at the ends.
+ *
+ * The bv pointer points to the bio_vec array; this may be __bv if allocated
+ * together. The caller is responsible for determining whether or not this is
+ * the case as the array pointed to by bv may be follow on directly from the
+ * bvecq by accident of allocation (ie. ->bv == ->__bv is *not* sufficient to
+ * determine this).
+ */
+struct bvecq {
+ struct bvecq *next; /* Next bvec in the list or NULL */
+ struct bvecq *prev; /* Prev bvec in the list or NULL */
+ refcount_t ref;
+ u32 priv; /* Private data */
+ u16 nr_slots; /* Number of elements in bv[] used */
+ u16 max_slots; /* Number of elements allocated in bv[] */
+ enum bvecq_mem mem_type:3; /* What sort of memory and how to free it */
+ bool inline_bv:1; /* T if __bv[] is being used */
+ struct bio_vec *bv; /* Pointer to array of page fragments */
+ struct bio_vec __bv[]; /* Default array (if ->inline_bv) */
+};
+
+#endif /* _LINUX_BVECQ_H */
diff --git a/include/linux/iov_iter.h b/include/linux/iov_iter.h
index 2f91884d733b..79825cf84e58 100644
--- a/include/linux/iov_iter.h
+++ b/include/linux/iov_iter.h
@@ -10,6 +10,7 @@
#include <linux/uio.h>
#include <linux/bvec.h>
+#include <linux/bvecq.h>
#include <linux/folio_queue.h>
typedef size_t (*iov_step_f)(void *iter_base, size_t progress, size_t len,
@@ -141,6 +142,71 @@ size_t iterate_bvec(struct iov_iter *iter, size_t len, void *priv, void *priv2,
return progress;
}
+/*
+ * Handle ITER_BVECQ.
+ */
+static __always_inline
+size_t iterate_bvecq(struct iov_iter *iter, size_t len, void *priv, void *priv2,
+ iov_step_f step)
+{
+ const struct bvecq *bq = iter->bvecq;
+ unsigned int slot = iter->bvecq_slot;
+ size_t progress = 0, skip = iter->iov_offset;
+
+ do {
+ const struct bio_vec *bvec;
+ struct page *page;
+ size_t poff, plen;
+ void *base;
+
+ if (slot >= bq->nr_slots) {
+ if (!bq->next)
+ break;
+ bq = bq->next;
+ slot = 0;
+ continue;
+ }
+
+ bvec = &bq->bv[slot];
+ /*
+ * The caller must ensure that a slot with bv_len>0 has a valid
+ * bv_page.
+ */
+ page = bvec->bv_page + (bvec->bv_offset + skip) / PAGE_SIZE;
+ poff = (bvec->bv_offset + skip) % PAGE_SIZE;
+ plen = min(bvec->bv_len - skip, len);
+
+ while (plen > 0) {
+ size_t part, remain, consumed;
+
+ part = min(plen, PAGE_SIZE - poff);
+ base = kmap_local_page(page) + poff;
+ remain = step(base, progress, part, priv, priv2);
+ kunmap_local(base);
+
+ consumed = part - remain;
+ progress += consumed;
+ skip += consumed;
+ len -= consumed;
+ if (!len || remain)
+ goto stop;
+ page++;
+ poff = 0;
+ plen -= consumed;
+ }
+
+ skip = 0;
+ slot++;
+ } while (len);
+
+stop:
+ iter->bvecq_slot = slot;
+ iter->bvecq = bq;
+ iter->iov_offset = skip;
+ iter->count -= progress;
+ return progress;
+}
+
/*
* Handle ITER_FOLIOQ.
*/
@@ -306,6 +372,8 @@ size_t iterate_and_advance2(struct iov_iter *iter, size_t len, void *priv,
return iterate_bvec(iter, len, priv, priv2, step);
if (iov_iter_is_kvec(iter))
return iterate_kvec(iter, len, priv, priv2, step);
+ if (iov_iter_is_bvecq(iter))
+ return iterate_bvecq(iter, len, priv, priv2, step);
if (iov_iter_is_folioq(iter))
return iterate_folioq(iter, len, priv, priv2, step);
if (iov_iter_is_xarray(iter))
@@ -344,8 +412,8 @@ size_t iterate_and_advance(struct iov_iter *iter, size_t len, void *priv,
* buffer is presented in segments, which for kernel iteration are broken up by
* physical pages and mapped, with the mapped address being presented.
*
- * [!] Note This will only handle BVEC, KVEC, FOLIOQ, XARRAY and DISCARD-type
- * iterators; it will not handle UBUF or IOVEC-type iterators.
+ * [!] Note This will only handle BVEC, KVEC, BVECQ, FOLIOQ, XARRAY and
+ * DISCARD-type iterators; it will not handle UBUF or IOVEC-type iterators.
*
* A step functions, @step, must be provided, one for handling mapped kernel
* addresses and the other is given user addresses which have the potential to
@@ -372,6 +440,8 @@ size_t iterate_and_advance_kernel(struct iov_iter *iter, size_t len, void *priv,
return iterate_bvec(iter, len, priv, priv2, step);
if (iov_iter_is_kvec(iter))
return iterate_kvec(iter, len, priv, priv2, step);
+ if (iov_iter_is_bvecq(iter))
+ return iterate_bvecq(iter, len, priv, priv2, step);
if (iov_iter_is_folioq(iter))
return iterate_folioq(iter, len, priv, priv2, step);
if (iov_iter_is_xarray(iter))
diff --git a/include/linux/uio.h b/include/linux/uio.h
index 638c1116e912..f87e05033eac 100644
--- a/include/linux/uio.h
+++ b/include/linux/uio.h
@@ -27,6 +27,7 @@ enum iter_type {
ITER_IOVEC,
ITER_BVEC,
ITER_KVEC,
+ ITER_BVECQ,
ITER_FOLIOQ,
ITER_XARRAY,
ITER_DISCARD,
@@ -70,6 +71,7 @@ struct iov_iter {
const struct iovec *__iov;
const struct kvec *kvec;
const struct bio_vec *bvec;
+ const struct bvecq *bvecq;
const struct folio_queue *folioq;
struct xarray *xarray;
void __user *ubuf;
@@ -80,6 +82,7 @@ struct iov_iter {
};
union {
unsigned long nr_segs;
+ u16 bvecq_slot;
u8 folioq_slot;
loff_t xarray_start;
};
@@ -148,6 +151,11 @@ static inline bool iov_iter_is_discard(const struct iov_iter *i)
return iov_iter_type(i) == ITER_DISCARD;
}
+static inline bool iov_iter_is_bvecq(const struct iov_iter *i)
+{
+ return iov_iter_type(i) == ITER_BVECQ;
+}
+
static inline bool iov_iter_is_folioq(const struct iov_iter *i)
{
return iov_iter_type(i) == ITER_FOLIOQ;
@@ -303,6 +311,9 @@ void iov_iter_kvec(struct iov_iter *i, unsigned int direction, const struct kvec
void iov_iter_bvec(struct iov_iter *i, unsigned int direction, const struct bio_vec *bvec,
unsigned long nr_segs, size_t count);
void iov_iter_discard(struct iov_iter *i, unsigned int direction, size_t count);
+void iov_iter_bvec_queue(struct iov_iter *i, unsigned int direction,
+ const struct bvecq *bvecq,
+ unsigned int first_slot, unsigned int offset, size_t count);
void iov_iter_folio_queue(struct iov_iter *i, unsigned int direction,
const struct folio_queue *folioq,
unsigned int first_slot, unsigned int offset, size_t count);
diff --git a/lib/iov_iter.c b/lib/iov_iter.c
index fa4fe2e92383..e2e01a2e47fe 100644
--- a/lib/iov_iter.c
+++ b/lib/iov_iter.c
@@ -538,6 +538,40 @@ static void iov_iter_iovec_advance(struct iov_iter *i, size_t size)
i->__iov = iov;
}
+static void iov_iter_bvecq_advance(struct iov_iter *i, size_t by)
+{
+ const struct bvecq *bq = i->bvecq;
+ unsigned int slot = i->bvecq_slot;
+
+ if (!i->count)
+ return;
+ i->count -= by;
+
+ by += i->iov_offset; /* From beginning of current segment. */
+ do {
+ size_t len;
+
+ if (slot >= bq->nr_slots) {
+ if (!bq->next)
+ break;
+ bq = bq->next;
+ slot = 0;
+ continue;
+ }
+
+ len = bq->bv[slot].bv_len;
+
+ if (likely(by < len))
+ break;
+ by -= len;
+ slot++;
+ } while (by);
+
+ i->iov_offset = by;
+ i->bvecq_slot = slot;
+ i->bvecq = bq;
+}
+
static void iov_iter_folioq_advance(struct iov_iter *i, size_t size)
{
const struct folio_queue *folioq = i->folioq;
@@ -584,6 +618,8 @@ void iov_iter_advance(struct iov_iter *i, size_t size)
iov_iter_iovec_advance(i, size);
} else if (iov_iter_is_bvec(i)) {
iov_iter_bvec_advance(i, size);
+ } else if (iov_iter_is_bvecq(i)) {
+ iov_iter_bvecq_advance(i, size);
} else if (iov_iter_is_folioq(i)) {
iov_iter_folioq_advance(i, size);
} else if (iov_iter_is_discard(i)) {
@@ -592,6 +628,33 @@ void iov_iter_advance(struct iov_iter *i, size_t size)
}
EXPORT_SYMBOL(iov_iter_advance);
+static void iov_iter_bvecq_revert(struct iov_iter *i, size_t unroll)
+{
+ const struct bvecq *bq = i->bvecq;
+ unsigned int slot = i->bvecq_slot;
+
+ for (;;) {
+ size_t len;
+
+ if (slot == 0) {
+ bq = bq->prev;
+ slot = bq->nr_slots;
+ continue;
+ }
+ slot--;
+
+ len = bq->bv[slot].bv_len;
+ if (unroll <= len) {
+ i->iov_offset = len - unroll;
+ break;
+ }
+ unroll -= len;
+ }
+
+ i->bvecq_slot = slot;
+ i->bvecq = bq;
+}
+
static void iov_iter_folioq_revert(struct iov_iter *i, size_t unroll)
{
const struct folio_queue *folioq = i->folioq;
@@ -650,6 +713,9 @@ void iov_iter_revert(struct iov_iter *i, size_t unroll)
}
unroll -= n;
}
+ } else if (iov_iter_is_bvecq(i)) {
+ i->iov_offset = 0;
+ iov_iter_bvecq_revert(i, unroll);
} else if (iov_iter_is_folioq(i)) {
i->iov_offset = 0;
iov_iter_folioq_revert(i, unroll);
@@ -680,9 +746,30 @@ size_t iov_iter_single_seg_count(const struct iov_iter *i)
if (iov_iter_is_bvec(i))
return min(i->count, i->bvec->bv_len - i->iov_offset);
}
+ if (!i->count)
+ return 0;
+ if (unlikely(iov_iter_is_bvecq(i))) {
+ const struct bvecq *bq = i->bvecq;
+ unsigned int slot = i->bvecq_slot;
+ size_t offset = i->iov_offset;
+
+ for (;;) {
+ while (slot >= bq->nr_slots) {
+ bq = bq->next;
+ if (!bq)
+ return 0;
+ slot = 0;
+ offset = 0;
+ }
+ if (bq->bv[slot].bv_len > offset)
+ break;
+ slot++;
+ offset = 0;
+ }
+ return min(i->count, bq->bv[slot].bv_len - offset);
+ }
if (unlikely(iov_iter_is_folioq(i)))
- return !i->count ? 0 :
- umin(folioq_folio_size(i->folioq, i->folioq_slot), i->count);
+ return umin(folioq_folio_size(i->folioq, i->folioq_slot), i->count);
return i->count;
}
EXPORT_SYMBOL(iov_iter_single_seg_count);
@@ -719,6 +806,36 @@ void iov_iter_bvec(struct iov_iter *i, unsigned int direction,
}
EXPORT_SYMBOL(iov_iter_bvec);
+/**
+ * iov_iter_bvec_queue - Initialise an I/O iterator to use a segmented bvec queue
+ * @i: The iterator to initialise.
+ * @direction: The direction of the transfer.
+ * @bvecq: The starting point in the bvec queue.
+ * @first_slot: The first slot in the bvec queue to use
+ * @offset: The offset into the bvec in the first slot to start at
+ * @count: The size of the I/O buffer in bytes.
+ *
+ * Set up an I/O iterator to either draw data out of the buffers attached to an
+ * inode or to inject data into those buffers. The pages *must* be prevented
+ * from evaporation by the caller, by taking a ref or a lock on them or by
+ * cleaning them up after the I/O is complete.
+ */
+void iov_iter_bvec_queue(struct iov_iter *i, unsigned int direction,
+ const struct bvecq *bvecq, unsigned int first_slot,
+ unsigned int offset, size_t count)
+{
+ WARN_ON(direction & ~(READ | WRITE));
+ *i = (struct iov_iter) {
+ .iter_type = ITER_BVECQ,
+ .data_source = direction,
+ .bvecq = bvecq,
+ .bvecq_slot = first_slot,
+ .count = count,
+ .iov_offset = offset,
+ };
+}
+EXPORT_SYMBOL(iov_iter_bvec_queue);
+
/**
* iov_iter_folio_queue - Initialise an I/O iterator to use the folios in a folio queue
* @i: The iterator to initialise.
@@ -855,6 +972,39 @@ static unsigned long iov_iter_alignment_bvec(const struct iov_iter *i)
return res;
}
+static unsigned long iov_iter_alignment_bvecq(const struct iov_iter *iter)
+{
+ const struct bvecq *bq;
+ unsigned long res = 0;
+ unsigned int slot = iter->bvecq_slot;
+ size_t skip = iter->iov_offset;
+ size_t size = iter->count;
+
+ if (!size)
+ return res;
+
+ for (bq = iter->bvecq; bq; bq = bq->next) {
+ for (; slot < bq->nr_slots; slot++) {
+ const struct bio_vec *bvec = &bq->bv[slot];
+ size_t part = min(bvec->bv_len - skip, size);
+
+ if (part) {
+ res |= bvec->bv_offset + skip;
+ res |= part;
+ }
+
+ size -= part;
+ if (size == 0)
+ return res;
+ skip = 0;
+ }
+
+ slot = 0;
+ }
+
+ return res;
+}
+
unsigned long iov_iter_alignment(const struct iov_iter *i)
{
if (likely(iter_is_ubuf(i))) {
@@ -874,6 +1024,8 @@ unsigned long iov_iter_alignment(const struct iov_iter *i)
if (iov_iter_is_bvec(i))
return iov_iter_alignment_bvec(i);
+ if (iov_iter_is_bvecq(i))
+ return iov_iter_alignment_bvecq(i);
/* With both xarray and folioq types, we're dealing with whole folios. */
if (iov_iter_is_folioq(i) || iov_iter_is_dmabuf_map(i))
@@ -930,6 +1082,161 @@ static int want_pages_array(struct page ***res, size_t size,
return count;
}
+/*
+ * Count the number of virtually contiguous pages coming up next in an
+ * ITER_BVECQ iterator, up to the specified maxima.
+ */
+static unsigned int iter_count_bvecq_pages(const struct iov_iter *iter,
+ size_t maxsize,
+ unsigned int maxpages)
+{
+ const struct bvecq *bvecq = iter->bvecq;
+ unsigned int slot = iter->bvecq_slot;
+ ssize_t remain = umin(maxsize, iter->count);
+ size_t count = 0, offset = iter->iov_offset;
+
+ do {
+ const struct bio_vec *bv;
+ size_t boff, blen;
+
+ if (slot >= bvecq->nr_slots) {
+ if (!bvecq->next) {
+ WARN_ON_ONCE(remain > 0);
+ break;
+ }
+ bvecq = bvecq->next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ bv = &bvecq->bv[slot++];
+ boff = bv->bv_offset;
+ blen = bv->bv_len;
+
+ /* bv_page is not allowed to be NULL unless bv_len == 0. */
+ if (WARN_ON_ONCE(!bv->bv_page && blen > 0))
+ break;
+ if (!PAGE_ALIGNED(boff) && count > 0)
+ break;
+
+ boff += offset;
+ blen -= offset;
+ offset = 0;
+ if (!blen)
+ continue;
+
+ blen = umin(blen, remain);
+ remain -= blen;
+ blen += offset_in_page(boff);
+ count += DIV_ROUND_UP(blen, PAGE_SIZE);
+
+ if (!PAGE_ALIGNED(blen))
+ break;
+ } while (remain > 0 && count < maxpages);
+
+ return min(count, maxpages);
+}
+
+/*
+ * Get a list of virtually contiguous pages from an ITER_BVECQ iterator and pin
+ * them.
+ */
+static ssize_t iter_bvecq_get_pages(struct iov_iter *iter,
+ struct page ***ppages, size_t maxsize,
+ unsigned int maxpages, size_t *_start_offset)
+{
+ const struct bvecq *bvecq = iter->bvecq;
+ struct page **pages;
+ unsigned int slot = iter->bvecq_slot, nr = 0;
+ size_t extracted = 0, offset = iter->iov_offset;
+
+ for (;;) {
+ if (slot >= bvecq->nr_slots) {
+ if (!bvecq->next)
+ return 0;
+ bvecq = bvecq->next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+ if (offset >= bvecq->bv[slot].bv_len) {
+ slot++;
+ offset = 0;
+ continue;
+ }
+ break;
+ }
+
+ /* Count the next run of virtually contiguous pages. */
+ maxpages = iter_count_bvecq_pages(iter, maxsize, maxpages);
+ if (!maxpages)
+ return 0;
+
+ maxpages = want_pages_array(ppages, maxsize,
+ (bvecq->bv[slot].bv_offset + offset) & ~PAGE_MASK,
+ maxpages);
+ if (!maxpages)
+ return -ENOMEM;
+ pages = *ppages;
+
+ /* Now transcribe the page pointers. */
+ do {
+ const struct bio_vec *bv;
+ size_t boff, blen;
+
+ if (slot >= bvecq->nr_slots) {
+ if (!bvecq->next) {
+ WARN_ON_ONCE(extracted < iter->count);
+ break;
+ }
+ bvecq = bvecq->next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ bv = &bvecq->bv[slot];
+ boff = bv->bv_offset;
+ blen = bv->bv_len;
+
+ /* bv_page is not allowed to be NULL unless bv_len == 0. */
+
+ if (offset < blen) {
+ struct folio *folio;
+ size_t poff = (boff + offset) % PAGE_SIZE;
+ size_t part = min(maxsize - extracted, blen - offset);
+ size_t pix = (boff + offset) / PAGE_SIZE;
+
+ if (poff + part > PAGE_SIZE)
+ part = PAGE_SIZE - poff;
+
+ if (!extracted)
+ *_start_offset = poff;
+
+ /* Get a ref on the page if it's not a slab page. */
+ folio = page_folio(bv->bv_page + pix);
+ if (!folio_test_slab(folio))
+ folio_get(folio);
+
+ pages[nr++] = bv->bv_page + pix;
+ offset += part;
+ extracted += part;
+ }
+
+ if (offset >= blen) {
+ offset = 0;
+ slot++;
+ }
+ } while (nr < maxpages && extracted < maxsize);
+
+ iter->bvecq = bvecq;
+ iter->bvecq_slot = slot;
+ iter->iov_offset = offset;
+ iter->count -= extracted;
+ return extracted;
+}
+
static ssize_t iter_folioq_get_pages(struct iov_iter *iter,
struct page ***ppages, size_t maxsize,
unsigned maxpages, size_t *_start_offset)
@@ -1140,6 +1447,8 @@ static ssize_t __iov_iter_get_pages_alloc(struct iov_iter *i,
}
return maxsize;
}
+ if (iov_iter_is_bvecq(i))
+ return iter_bvecq_get_pages(i, pages, maxsize, maxpages, start);
if (iov_iter_is_folioq(i))
return iter_folioq_get_pages(i, pages, maxsize, maxpages, start);
if (iov_iter_is_xarray(i))
@@ -1212,6 +1521,38 @@ static int bvec_npages(const struct iov_iter *i, int maxpages)
return npages;
}
+static size_t iov_npages_bvecq(const struct iov_iter *iter, size_t maxpages)
+{
+ const struct bvecq *bq;
+ unsigned int slot = iter->bvecq_slot;
+ size_t npages = 0;
+ size_t skip = iter->iov_offset;
+ size_t size = iter->count;
+
+ for (bq = iter->bvecq; bq; bq = bq->next) {
+ for (; slot < bq->nr_slots; slot++) {
+ const struct bio_vec *bvec = &bq->bv[slot];
+ size_t offs = (bvec->bv_offset + skip) % PAGE_SIZE;
+ size_t part = min(bvec->bv_len - skip, size);
+
+ if (part) {
+ npages += DIV_ROUND_UP(offs + part, PAGE_SIZE);
+ if (npages >= maxpages)
+ goto out;
+ }
+
+ size -= part;
+ if (!size)
+ goto out;
+ skip = 0;
+ }
+
+ slot = 0;
+ }
+out:
+ return umin(npages, maxpages);
+}
+
int iov_iter_npages(const struct iov_iter *i, int maxpages)
{
if (unlikely(!i->count))
@@ -1226,6 +1567,8 @@ int iov_iter_npages(const struct iov_iter *i, int maxpages)
return iov_npages(i, maxpages);
if (iov_iter_is_bvec(i))
return bvec_npages(i, maxpages);
+ if (iov_iter_is_bvecq(i))
+ return iov_npages_bvecq(i, maxpages);
if (iov_iter_is_folioq(i)) {
unsigned offset = i->iov_offset % PAGE_SIZE;
int npages = DIV_ROUND_UP(offset + i->count, PAGE_SIZE);
@@ -1514,6 +1857,90 @@ void iov_iter_restore(struct iov_iter *i, struct iov_iter_state *state)
}
EXPORT_SYMBOL_FOR_MODULES(iov_iter_restore, "vmw_vsock_virtio_transport_common");
+/*
+ * Extract a list of virtually contiguous pages from an ITER_BVECQ iterator.
+ * This does not get references on the pages, nor does it get a pin on them.
+ */
+static ssize_t iov_iter_extract_bvecq_pages(struct iov_iter *iter,
+ struct page ***pages, size_t maxsize,
+ unsigned int maxpages,
+ iov_iter_extraction_t extraction_flags,
+ size_t *offset0)
+{
+ const struct bvecq *bvecq;
+ struct page **p;
+ unsigned int slot, nr = 0;
+ size_t extracted = 0, offset;
+
+ /* Count the next run of virtually contiguous pages. */
+ maxpages = iter_count_bvecq_pages(iter, maxsize, maxpages);
+ if (!maxpages)
+ return 0;
+
+ if (!*pages) {
+ *pages = kvmalloc_array(maxpages, sizeof(struct page *), GFP_KERNEL);
+ if (!*pages)
+ return -ENOMEM;
+ }
+
+ p = *pages;
+
+ /* Now transcribe the page pointers. */
+ extracted = 0;
+ bvecq = iter->bvecq;
+ offset = iter->iov_offset;
+ slot = iter->bvecq_slot;
+
+ do {
+ const struct bio_vec *bv;
+ size_t boff, blen;
+
+ if (slot >= bvecq->nr_slots) {
+ if (!bvecq->next) {
+ WARN_ON_ONCE(extracted < iter->count);
+ break;
+ }
+ bvecq = bvecq->next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ bv = &bvecq->bv[slot];
+ boff = bv->bv_offset;
+ blen = bv->bv_len;
+
+ /* bv_page is not allowed to be NULL unless bv_len == 0. */
+
+ if (offset < blen) {
+ size_t part = umin(maxsize - extracted, blen - offset);
+ size_t poff = (boff + offset) % PAGE_SIZE;
+ size_t pix = (boff + offset) / PAGE_SIZE;
+
+ if (poff + part > PAGE_SIZE)
+ part = PAGE_SIZE - poff;
+
+ if (!extracted)
+ *offset0 = poff;
+
+ p[nr++] = bv->bv_page + pix;
+ offset += part;
+ extracted += part;
+ }
+
+ if (offset >= blen) {
+ offset = 0;
+ slot++;
+ }
+ } while (nr < maxpages && extracted < maxsize);
+
+ iter->bvecq = bvecq;
+ iter->bvecq_slot = slot;
+ iter->iov_offset = offset;
+ iter->count -= extracted;
+ return extracted;
+}
+
/*
* Extract a list of contiguous pages from an ITER_FOLIOQ iterator. This does
* not get references on the pages, nor does it get a pin on them.
@@ -1874,6 +2301,10 @@ ssize_t iov_iter_extract_pages(struct iov_iter *i,
return iov_iter_extract_bvec_pages(i, pages, maxsize,
maxpages, extraction_flags,
offset0);
+ if (iov_iter_is_bvecq(i))
+ return iov_iter_extract_bvecq_pages(i, pages, maxsize,
+ maxpages, extraction_flags,
+ offset0);
if (iov_iter_is_folioq(i))
return iov_iter_extract_folioq_pages(i, pages, maxsize,
maxpages, extraction_flags,
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index 6ea40d2e6247..23e5a180103b 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -10,6 +10,7 @@
#include <linux/highmem.h>
#include <linux/kmemleak.h>
#include <linux/bvec.h>
+#include <linux/bvecq.h>
#include <linux/uio.h>
#include <linux/folio_queue.h>
@@ -1267,6 +1268,65 @@ static ssize_t extract_kvec_to_sg(struct iov_iter *iter,
return ret;
}
+/*
+ * Extract up to sg_max folios from an BVECQ-type iterator and add them to
+ * the scatterlist. The pages are not pinned.
+ */
+static ssize_t extract_bvecq_to_sg(struct iov_iter *iter,
+ ssize_t maxsize,
+ struct sg_table *sgtable,
+ unsigned int sg_max,
+ iov_iter_extraction_t extraction_flags)
+{
+ const struct bvecq *bvecq = iter->bvecq;
+ struct scatterlist *sg = sgtable->sgl + sgtable->nents;
+ unsigned int slot = iter->bvecq_slot;
+ ssize_t ret = 0;
+ size_t offset = iter->iov_offset;
+
+ maxsize = umin(maxsize, iov_iter_count(iter));
+
+ while (sg_max > 0 && ret < maxsize) {
+ const struct bio_vec *bv;
+ size_t blen, part;
+
+ if (slot >= bvecq->nr_slots) {
+ if (!bvecq->next) {
+ WARN_ON_ONCE(ret < iter->count);
+ break;
+ }
+ bvecq = bvecq->next;
+ slot = 0;
+ offset = 0;
+ continue;
+ }
+
+ bv = &bvecq->bv[slot];
+ blen = bv->bv_len;
+
+ if (offset >= blen) {
+ offset = 0;
+ slot++;
+ continue;
+ }
+
+ part = umin(maxsize - ret, blen - offset);
+
+ sg_set_page(sg, bv->bv_page, part, bv->bv_offset + offset);
+ sgtable->nents++;
+ sg++;
+ sg_max--;
+ offset += part;
+ ret += part;
+ }
+
+ iter->bvecq = bvecq;
+ iter->bvecq_slot = slot;
+ iter->iov_offset = offset;
+ iter->count -= ret;
+ return ret;
+}
+
/*
* Extract up to sg_max folios from an FOLIOQ-type iterator and add them to
* the scatterlist. The pages are not pinned.
@@ -1391,8 +1451,8 @@ static ssize_t extract_xarray_to_sg(struct iov_iter *iter,
* addition of @sg_max elements.
*
* The pages referred to by UBUF- and IOVEC-type iterators are extracted and
- * pinned; BVEC-, KVEC-, FOLIOQ- and XARRAY-type are extracted but aren't
- * pinned; DISCARD-type is not supported.
+ * pinned; BVEC-, BVECQ-, KVEC-, FOLIOQ- and XARRAY-type are extracted but
+ * aren't pinned; DISCARD-type is not supported.
*
* No end mark is placed on the scatterlist; that's left to the caller.
*
@@ -1424,6 +1484,9 @@ ssize_t extract_iter_to_sg(struct iov_iter *iter, size_t maxsize,
case ITER_KVEC:
return extract_kvec_to_sg(iter, maxsize, sgtable, sg_max,
extraction_flags);
+ case ITER_BVECQ:
+ return extract_bvecq_to_sg(iter, maxsize, sgtable, sg_max,
+ extraction_flags);
case ITER_FOLIOQ:
return extract_folioq_to_sg(iter, maxsize, sgtable, sg_max,
extraction_flags);
diff --git a/lib/tests/kunit_iov_iter.c b/lib/tests/kunit_iov_iter.c
index b4ad38d1a4bd..1f5c72b4c4d6 100644
--- a/lib/tests/kunit_iov_iter.c
+++ b/lib/tests/kunit_iov_iter.c
@@ -12,6 +12,7 @@
#include <linux/mm.h>
#include <linux/uio.h>
#include <linux/bvec.h>
+#include <linux/bvecq.h>
#include <linux/folio_queue.h>
#include <linux/scatterlist.h>
#include <linux/minmax.h>
@@ -552,6 +553,183 @@ static void iov_kunit_copy_from_folioq(struct kunit *test)
KUNIT_SUCCEED(test);
}
+static void iov_kunit_destroy_bvecq(void *data)
+{
+ struct bvecq *bq, *next;
+
+ for (bq = data; bq; bq = next) {
+ next = bq->next;
+ /* The pages are freed by vmap with VM_MAP_PUT_PAGES. */
+ kfree(bq);
+ }
+}
+
+static struct bvecq *iov_kunit_alloc_bvecq(struct kunit *test, unsigned int max_slots)
+{
+ struct bvecq *bq;
+
+ bq = kzalloc(struct_size(bq, __bv, max_slots), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, bq);
+ bq->max_slots = max_slots;
+ bq->bv = bq->__bv;
+ bq->inline_bv = true;
+ return bq;
+}
+
+static struct bvecq *iov_kunit_create_bvecq(struct kunit *test, unsigned int max_slots)
+{
+ struct bvecq *bq;
+
+ bq = iov_kunit_alloc_bvecq(test, max_slots);
+ kunit_add_action_or_reset(test, iov_kunit_destroy_bvecq, bq);
+ return bq;
+}
+
+static void iov_kunit_load_bvecq(struct kunit *test,
+ struct iov_iter *iter, int dir,
+ struct bvecq *bq_head,
+ struct page **pages, size_t npages)
+{
+ struct bvecq *bq = bq_head;
+ size_t size = 0;
+
+ for (int i = 0; i < npages; i++) {
+ if (bq->nr_slots >= bq->max_slots) {
+ bq->next = iov_kunit_alloc_bvecq(test, 13);
+ bq->next->prev = bq;
+ bq = bq->next;
+ }
+ bvec_set_page(&bq->bv[bq->nr_slots], pages[i], PAGE_SIZE, 0);
+ bq->nr_slots++;
+ size += PAGE_SIZE;
+ }
+ iov_iter_bvec_queue(iter, dir, bq_head, 0, 0, size);
+}
+
+/*
+ * Test copying to a ITER_BVECQ-type iterator.
+ */
+static void iov_kunit_copy_to_bvecq(struct kunit *test)
+{
+ const struct kvec_test_range *pr;
+ struct iov_iter iter;
+ struct bvecq *bq;
+ struct page **spages, **bpages;
+ u8 *scratch, *buffer;
+ size_t bufsize, npages, size, copied;
+ int i, patt;
+
+ bufsize = 0x100000;
+ npages = bufsize / PAGE_SIZE;
+
+ bq = iov_kunit_create_bvecq(test, 13);
+
+ scratch = iov_kunit_create_buffer(test, &spages, npages);
+ for (i = 0; i < bufsize; i++)
+ scratch[i] = pattern(i);
+
+ buffer = iov_kunit_create_buffer(test, &bpages, npages);
+ memset(buffer, 0, bufsize);
+
+ iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages);
+
+ i = 0;
+ for (pr = kvec_test_ranges; pr->from >= 0; pr++) {
+ size = pr->to - pr->from;
+ KUNIT_ASSERT_LE(test, pr->to, bufsize);
+
+ iov_iter_bvec_queue(&iter, READ, bq, 0, 0, pr->to);
+ iov_iter_advance(&iter, pr->from);
+ copied = copy_to_iter(scratch + i, size, &iter);
+
+ KUNIT_EXPECT_EQ(test, copied, size);
+ KUNIT_EXPECT_EQ(test, iter.count, 0);
+ i += size;
+ if (test->status == KUNIT_FAILURE)
+ goto stop;
+ }
+
+ /* Build the expected image in the scratch buffer. */
+ patt = 0;
+ memset(scratch, 0, bufsize);
+ for (pr = kvec_test_ranges; pr->from >= 0; pr++)
+ for (i = pr->from; i < pr->to; i++)
+ scratch[i] = pattern(patt++);
+
+ /* Compare the images */
+ for (i = 0; i < bufsize; i++) {
+ KUNIT_EXPECT_EQ_MSG(test, buffer[i], scratch[i], "at i=%x", i);
+ if (buffer[i] != scratch[i])
+ return;
+ }
+
+stop:
+ KUNIT_SUCCEED(test);
+}
+
+/*
+ * Test copying from a ITER_BVECQ-type iterator.
+ */
+static void iov_kunit_copy_from_bvecq(struct kunit *test)
+{
+ const struct kvec_test_range *pr;
+ struct iov_iter iter;
+ struct bvecq *bq;
+ struct page **spages, **bpages;
+ u8 *scratch, *buffer;
+ size_t bufsize, npages, size, copied;
+ int i, j;
+
+ bufsize = 0x100000;
+ npages = bufsize / PAGE_SIZE;
+
+ bq = iov_kunit_create_bvecq(test, 13);
+
+ buffer = iov_kunit_create_buffer(test, &bpages, npages);
+ for (i = 0; i < bufsize; i++)
+ buffer[i] = pattern(i);
+
+ scratch = iov_kunit_create_buffer(test, &spages, npages);
+ memset(scratch, 0, bufsize);
+
+ iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages);
+
+ i = 0;
+ for (pr = kvec_test_ranges; pr->from >= 0; pr++) {
+ size = pr->to - pr->from;
+ KUNIT_ASSERT_LE(test, pr->to, bufsize);
+
+ iov_iter_bvec_queue(&iter, WRITE, bq, 0, 0, pr->to);
+ iov_iter_advance(&iter, pr->from);
+ copied = copy_from_iter(scratch + i, size, &iter);
+
+ KUNIT_EXPECT_EQ(test, copied, size);
+ KUNIT_EXPECT_EQ(test, iter.count, 0);
+ i += size;
+ }
+
+ /* Build the expected image in the main buffer. */
+ i = 0;
+ memset(buffer, 0, bufsize);
+ for (pr = kvec_test_ranges; pr->from >= 0; pr++) {
+ for (j = pr->from; j < pr->to; j++) {
+ buffer[i++] = pattern(j);
+ if (i >= bufsize)
+ goto stop;
+ }
+ }
+stop:
+
+ /* Compare the images */
+ for (i = 0; i < bufsize; i++) {
+ KUNIT_EXPECT_EQ_MSG(test, scratch[i], buffer[i], "at i=%x", i);
+ if (scratch[i] != buffer[i])
+ return;
+ }
+
+ KUNIT_SUCCEED(test);
+}
+
static void iov_kunit_destroy_xarray(void *data)
{
struct xarray *xarray = data;
@@ -867,6 +1045,85 @@ static void iov_kunit_extract_pages_bvec(struct kunit *test)
KUNIT_SUCCEED(test);
}
+/*
+ * Test the extraction of ITER_BVECQ-type iterators.
+ */
+static void iov_kunit_extract_pages_bvecq(struct kunit *test)
+{
+ const struct kvec_test_range *pr;
+ struct iov_iter iter;
+ struct bvecq *bq;
+ struct page **bpages, *pagelist[8], **pages = pagelist;
+ ssize_t len;
+ size_t bufsize, size = 0, npages;
+ int i, from;
+
+ bufsize = 0x100000;
+ npages = bufsize / PAGE_SIZE;
+
+ bq = iov_kunit_create_bvecq(test, 13);
+
+ iov_kunit_create_buffer(test, &bpages, npages);
+ iov_kunit_load_bvecq(test, &iter, READ, bq, bpages, npages);
+
+ for (pr = kvec_test_ranges; pr->from >= 0; pr++) {
+ from = pr->from;
+ size = pr->to - from;
+ KUNIT_ASSERT_LE(test, pr->to, bufsize);
+
+ iov_iter_bvec_queue(&iter, WRITE, bq, 0, 0, pr->to);
+ iov_iter_advance(&iter, from);
+
+ do {
+ size_t offset0 = LONG_MAX;
+
+ for (i = 0; i < ARRAY_SIZE(pagelist); i++)
+ pagelist[i] = (void *)(unsigned long)0xaa55aa55aa55aa55ULL;
+
+ len = iov_iter_extract_pages(&iter, &pages, 100 * 1024,
+ ARRAY_SIZE(pagelist), 0, &offset0);
+ KUNIT_EXPECT_GE(test, len, 0);
+ if (len < 0)
+ break;
+ KUNIT_EXPECT_LE(test, len, size);
+ KUNIT_EXPECT_EQ(test, iter.count, size - len);
+ if (len == 0)
+ break;
+ size -= len;
+ KUNIT_EXPECT_GE(test, (ssize_t)offset0, 0);
+ KUNIT_EXPECT_LT(test, offset0, PAGE_SIZE);
+
+ for (i = 0; i < ARRAY_SIZE(pagelist); i++) {
+ struct page *p;
+ ssize_t part = min_t(ssize_t, len, PAGE_SIZE - offset0);
+ int ix;
+
+ KUNIT_ASSERT_GE(test, part, 0);
+ ix = from / PAGE_SIZE;
+ KUNIT_ASSERT_LT(test, ix, npages);
+ p = bpages[ix];
+ KUNIT_EXPECT_PTR_EQ(test, pagelist[i], p);
+ KUNIT_EXPECT_EQ(test, offset0, from % PAGE_SIZE);
+ from += part;
+ len -= part;
+ KUNIT_ASSERT_GE(test, len, 0);
+ if (len == 0)
+ break;
+ offset0 = 0;
+ }
+
+ if (test->status == KUNIT_FAILURE)
+ goto stop;
+ } while (iov_iter_count(&iter) > 0);
+
+ KUNIT_EXPECT_EQ(test, size, 0);
+ KUNIT_EXPECT_EQ(test, iter.count, 0);
+ }
+
+stop:
+ KUNIT_SUCCEED(test);
+}
+
/*
* Test the extraction of ITER_FOLIOQ-type iterators.
*/
@@ -1171,6 +1428,22 @@ static void iov_kunit_iter_to_sg_bvec(struct kunit *test)
iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
}
+static void iov_kunit_iter_to_sg_bvecq(struct kunit *test)
+{
+ struct iov_kunit_iter_to_sg_data data;
+ struct iov_iter iter;
+ struct bvecq *bq;
+ size_t bufsize;
+
+ bufsize = 0x200000;
+ iov_kunit_iter_to_sg_init(test, bufsize, false, &data);
+
+ bq = iov_kunit_create_bvecq(test, 13);
+ iov_kunit_load_bvecq(test, &iter, READ, bq, data.pages, data.npages);
+
+ iov_kunit_iter_to_sg_check(test, &iter, bufsize, &data);
+}
+
static void iov_kunit_iter_to_sg_folioq(struct kunit *test)
{
struct iov_kunit_iter_to_sg_data data;
@@ -1224,16 +1497,20 @@ static struct kunit_case iov_kunit_cases[] = {
KUNIT_CASE(iov_kunit_copy_from_kvec),
KUNIT_CASE(iov_kunit_copy_to_bvec),
KUNIT_CASE(iov_kunit_copy_from_bvec),
+ KUNIT_CASE(iov_kunit_copy_to_bvecq),
+ KUNIT_CASE(iov_kunit_copy_from_bvecq),
KUNIT_CASE(iov_kunit_copy_to_folioq),
KUNIT_CASE(iov_kunit_copy_from_folioq),
KUNIT_CASE(iov_kunit_copy_to_xarray),
KUNIT_CASE(iov_kunit_copy_from_xarray),
KUNIT_CASE(iov_kunit_extract_pages_kvec),
KUNIT_CASE(iov_kunit_extract_pages_bvec),
+ KUNIT_CASE(iov_kunit_extract_pages_bvecq),
KUNIT_CASE(iov_kunit_extract_pages_folioq),
KUNIT_CASE(iov_kunit_extract_pages_xarray),
KUNIT_CASE(iov_kunit_iter_to_sg_kvec),
KUNIT_CASE(iov_kunit_iter_to_sg_bvec),
+ KUNIT_CASE(iov_kunit_iter_to_sg_bvecq),
KUNIT_CASE(iov_kunit_iter_to_sg_folioq),
KUNIT_CASE(iov_kunit_iter_to_sg_xarray),
KUNIT_CASE(iov_kunit_iter_to_sg_ubuf),
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v17 05/17] iov_iter: Add an API document for the bvecq structure
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
` (2 preceding siblings ...)
2026-11-11 20:30 ` [PATCH v17 03/17] iov_iter: Add a segmented queue of bio_vec[] David Howells
@ 2026-11-11 20:30 ` David Howells
2026-11-11 20:30 ` [PATCH v17 06/17] iov_iter: Add a bvecq kunit test David Howells
2026-11-11 20:30 ` [PATCH v17 10/17] netfs: Pass the write length to request creation where appropriate David Howells
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Jens Axboe, Christoph Hellwig, linux-block
Add an API description document for the bvecq structure.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
Documentation/core-api/bvecq.rst | 229 +++++++++++++++++++++++++++++++
Documentation/core-api/index.rst | 1 +
MAINTAINERS | 1 +
fs/netfs/bvecq.c | 2 +
include/linux/bvecq.h | 2 +
5 files changed, 235 insertions(+)
create mode 100644 Documentation/core-api/bvecq.rst
diff --git a/Documentation/core-api/bvecq.rst b/Documentation/core-api/bvecq.rst
new file mode 100644
index 000000000000..4a2b3e288982
--- /dev/null
+++ b/Documentation/core-api/bvecq.rst
@@ -0,0 +1,229 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================================
+bvecq - Chainable bio_vec queue structure
+=========================================
+
+Author: David Howells <dhowells@redhat.com>
+
+.. Contents:
+
+ * Overview
+ * Allocating bvecqs
+ * Refcounting
+ * Expandable buffer
+ * Producer functions
+ * Consumer functions
+ * Utility functions
+ * I/O iterators
+ * API Function Reference
+
+
+Overview
+========
+
+The ``bvecq`` struct can be used to form chains of arrays of ``bio_vec``
+structures - kind of a single layer of indirection. Each bvecq segment points
+to the previous and next bvecq segments in the chain, but is not intended to
+form a circular queue as there are applications where the producer is adding to
+one end whilst the consumer is taking off the other.
+
+To use this, the caller should do::
+
+ #include <linux/bvecq.h>
+
+The publicly accessible members of the structure are::
+
+ struct bvecq {
+ struct bvecq *next;
+ struct bvecq *prev;
+ u32 priv;
+ u16 nr_slots;
+ u16 max_slots;
+ enum bvecq_mem mem_type:3;
+ struct bio_vec *bv;
+ ...
+ };
+
+A pair of pointers are provided, ``next`` and ``prev``, that point to the
+segments on either side of the segment being accessed. Whilst this is a
+doubly-linked list, it is intentionally not a circular list; the outward
+sibling pointers in terminal segments should be NULL.
+
+The ``priv`` member is for the owner of the bvecq to use as they will.
+
+The ``bv`` pointer points at an array of bio_vecs that list a number of regions
+of memory. ``max_slots`` indicates how large the array is and ``nr_slots``
+says how much of the array is being used. Note that there are access functions
+for nr_slots that insert appropriate barriering in the case that there are
+separate producer and consumer threads.
+
+The ``mem_type`` member informs bvecq_put() of how to clean up the bio_vecs
+attached to this segment. It can be one of:
+
+ * ``BVECQ_MEM_EXTERNAL`` - Externally managed; no cleanup.
+ * ``BVECQ_MEM_PAGECACHE`` - Part of pagecache; put_page() used.
+ * ``BVECQ_MEM_GUP`` - Pinned userspace memory; unpin_user_page() used.
+ * ``BVECQ_MEM_ALLOCED`` - Allocated page; __free_pages() used, may be pooled.
+
+
+Allocating bvecqs
+=================
+
+A number of functions are provided to allocate bvecqs::
+
+ 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);
+ struct bvecq *bvecq_alloc_buffer(size_t size,
+ gfp_t gfp,
+ bool for_writeback);
+ struct bvecq *bvecq_alloc_buffer2(size_t size,
+ unsigned int pre_slots,
+ gfp_t gfp,
+ bool for_writeback);
+
+The first of these, ``bvecq_alloc_one()`` allocates a single bvecq with at
+least the requested number of inline slots. The size of the object is rounded
+up to the size of the slab granule that it will be allocated in and the number
+of slots increased appropriately to match. If ``for_writeback`` is set, then
+the netfslib writeback bvecq pool will be used to backstop the allocation if
+the initial slab request fails; in such a case, the number of slots will get
+reduced to ``BVECQ_POOL_SLOTS``.
+
+Two convenience slot count constants are provided:
+
+ * ``BVECQ_POOL_SLOTS`` - The right number of slots to alloc from the mempool
+ * ``BVECQ_4KB_SLOTS`` - The right number of slots to get a 4K allocation
+
+``bvecq_alloc_chain()`` will allocate a sufficiently large chain of bvecqs to
+hold the requested number of slots, capping each allocation at 4K.
+
+``bvecq_alloc_buffer()`` will allocate a chain and populate it, allocating
+sufficient pages to provide a buffer of the specified size attached to the
+chain. ``bvecq_alloc_buffer2()`` is similar, but it reserves some unused
+slots at the front of the chain. Note that these functions may add multipage
+pages in the future.
+
+
+Refcounting
+===========
+
+Each bvecq has a refcount and two functions are provided to handle that::
+
+ struct bvecq *bvecq_get(struct bvecq *bq);
+ void bvecq_put(struct bvecq *bq);
+
+The first of these gets a new reference and the second drops a reference. When
+the refcount hits zero, the second iteratively drops the refs along the chain
+until it hits a segment that has a refcount that doesn't reduce to zero.
+
+
+Expandable buffer
+=================
+
+This facility provides a function to manage an expandable buffer::
+
+ int bvecq_expand_buffer(struct bvecq **_buffer,
+ size_t *_cur_size,
+ size_t size,
+ gfp_t gfp);
+
+Pages are added to the bvecq chain and the chain expanded such that, on
+returning, it holds ``size`` amount of space (without losing any content). The
+caller needs to retain ``*_cur_size`` between calls as that tracks the amount
+of space currently allocated. Note that pages in the chain may be allocated as
+compound pages larger than PAGE_SIZE.
+
+
+Producer functions
+==================
+
+When building and filling a bvecq, there are a number of utility functions that
+can be used to provide the correct barriering and to query the fields in the
+bvecq currently being filled.
+
+Firstly, there's a function to check to see if a bvecq has any spare slots
+left::
+
+ bool bvecq_is_full(const struct bvecq *bvecq);
+
+If not, ``bvecq_alloc_one()`` can be called to allocate a new segment which can
+be initialised and then added to the chain with::
+
+ void bvecq_append(struct bvecq *to, struct bvecq *add);
+
+bio_vec slots in the segment can then be filled and the number of occupied
+slots updated by calling::
+
+ void bvecq_filled_to(struct bvecq *bvecq,
+ unsigned int to);
+
+to insert the appropriate barrier.
+
+
+Consumer functions
+==================
+
+When consuming and destroying a bvecq, there are some utility functions for
+that too, providing the needed barriering. Firstly, there's a function to find
+out how many slots have been used in the current bvecq::
+
+ unsigned int bvecq_nr_slots_acquire(const struct bvecq *bvecq);
+
+Or, more conveniently, if the current slot position is valid within the bvecq::
+
+ bool bvecq_acquire_slot(const struct bvecq *bvecq,
+ unsigned int slot);
+
+If this returns true, the slot is valid and can be looked at; if it returns
+false, the bvecq may have been consumed. If it has, the next bvecq can be
+obtained by calling::
+
+ struct bvecq *bvecq_next(const struct bvecq *bq);
+
+which inserts the appropriate barrier - though bvecq_acquire_slot should be
+rechecked after calling this in case the producer added more slots and then
+extended the list.
+
+
+Utility functions
+=================
+
+A utility function is provided to list the bio_vecs pointed to by a bvecq
+chain::
+
+ void bvecq_dump(const struct bvecq *bq);
+
+
+I/O iterators
+=============
+
+bvecq chains can be used with the ``iov_iter`` facility too. An iterator can
+be initialised to point at a chain by calling::
+
+ void iov_iter_bvec_queue(struct iov_iter *i,
+ unsigned int direction,
+ const struct bvecq *bvecq,
+ unsigned int first_slot,
+ unsigned int offset,
+ size_t count);
+
+In addition to setting the iterator, direction and count, the first bvecq in
+the chain to be iterated over is set, along with the starting slot in that
+iterator and the offset into the slot. Once the iterator is set up, it can be
+treated like any other I/O iterator.
+
+In a producer/consumer environment, it is expected that when an iterator is set
+up, all the bvecqs and all the slots within the count of the iterator must
+exist and must be populated before this function is called.
+
+
+API Function Reference
+======================
+
+.. kernel-doc:: include/linux/bvecq.h
+.. kernel-doc:: fs/netfs/bvecq.c
diff --git a/Documentation/core-api/index.rst b/Documentation/core-api/index.rst
index 92f91c6a0d79..e39edef58ab6 100644
--- a/Documentation/core-api/index.rst
+++ b/Documentation/core-api/index.rst
@@ -39,6 +39,7 @@ Library functionality that is used throughout the kernel.
kref
cleanup
assoc_array
+ bvecq
folio_queue
xarray
maple_tree
diff --git a/MAINTAINERS b/MAINTAINERS
index 6fa777744163..9cb3785a5bd2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10382,6 +10382,7 @@ M: Paulo Alcantara <pc@manguebit.org>
L: netfs@lists.linux.dev
L: linux-fsdevel@vger.kernel.org
S: Supported
+F: Documentation/core-api/bvecq.rst
F: Documentation/filesystems/caching/
F: Documentation/filesystems/netfs_library.rst
F: fs/netfs/
diff --git a/fs/netfs/bvecq.c b/fs/netfs/bvecq.c
index 1d4da3de9b9d..f1264ae52033 100644
--- a/fs/netfs/bvecq.c
+++ b/fs/netfs/bvecq.c
@@ -3,6 +3,8 @@
*
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
+ *
+ * See also Documentation/core-api/bvecq.rst
*/
#include <linux/bvecq.h>
diff --git a/include/linux/bvecq.h b/include/linux/bvecq.h
index d9d3dd487a27..90e6eb9588bf 100644
--- a/include/linux/bvecq.h
+++ b/include/linux/bvecq.h
@@ -3,6 +3,8 @@
*
* Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
+ *
+ * See also Documentation/core-api/bvecq.rst
*/
#ifndef _LINUX_BVECQ_H
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v17 06/17] iov_iter: Add a bvecq kunit test
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
` (3 preceding siblings ...)
2026-11-11 20:30 ` [PATCH v17 05/17] iov_iter: Add an API document for the bvecq structure David Howells
@ 2026-11-11 20:30 ` David Howells
2026-11-11 20:30 ` [PATCH v17 10/17] netfs: Pass the write length to request creation where appropriate David Howells
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Jens Axboe, Christoph Hellwig, linux-block
Add a bvecq kunit test to exercise the bvecq iterator in a bunch of
different ways. Each kunit test sets up a series of folios of different
sizes (1, 2, 4 or 8 pages) and fills each subpage thereof with a distinct
predictable pattern that it can then sample to check that the iterator
appears to be in the right place.
The folios are then assembled into a bvecq chain depending on a particular
recipe and partialness list and then advance, revert and copy-out is
tested, along with checking iov_iter_npages() at each point.
Each test is a combination of a bvecq chain recipe:
- A single bvecq with 16 folios with no gaps (zero-len bio_vec).
- A chain of two bvecqs, each with 8 folios and no gaps.
- As the previous, but with a gap at the end of the first chain.
- A chain of four bvecqs, each with 4 folios and scattered gaps,
including at the front and back.
- A chain of five bvecqs, 3 with folios and two with no folios, one empty
and one with a couple of gaps.
- A chain of seven bvecqs, 3 with folios, 3 completely empty (including
first and last bvecqs in the chain) and 1 with just a couple of gaps.
and a selection of folio parts:
- Whole folios only.
- Scattered parts of folios, with some contiguities.
- Every folio with the first and last bytes excluded.
and a test algorithm:
- For each possible starting position in the bvecq, do a small advance,
check fill, a couple of small reverts, each checked and then advance to
the next starting position.
- Starting at the beginning, advance in jumps by a largish step forward,
checking after each jump; then when the end is hit revert in jumps by a
slightly larger step back, checking each time; then double the step and
repeat until the step exceeds the size of the iterator.
iov_iter_alignment() is compared at the beginning of one of the test sets
to a compound of alignments from an ITER_BVEC. Note that the ITER_BVEC
alignment check malfunctions if given a zero-length bio_vec array.
Exercising extraction, copy-from and copy-to is done in kunit_iov_iter.c,
with those bits added in a different patch.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
MAINTAINERS | 1 +
lib/Kconfig.debug | 13 +
lib/tests/Makefile | 1 +
lib/tests/kunit_bvecq.c | 599 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 614 insertions(+)
create mode 100644 lib/tests/kunit_bvecq.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9cb3785a5bd2..bdda4ad58ab4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10391,6 +10391,7 @@ F: include/linux/fscache*.h
F: include/linux/netfs.h
F: include/trace/events/fscache.h
F: include/trace/events/netfs.h
+F: lib/tests/kunit_bvecq.c
FILESYSTEMS [STACKABLE]
M: Miklos Szeredi <miklos@szeredi.hu>
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 2d22638ec13a..06b4546421a1 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2385,6 +2385,19 @@ config TEST_IOV_ITER
If unsure, say N.
+config TEST_BVECQ
+ tristate "Test bvecq operation" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ depends on MMU
+ depends on NETFS_SUPPORT
+ default KUNIT_ALL_TESTS
+ help
+ Enable this to turn on testing of the operation of the bvecq
+ structure. This test is executed only once during system
+ boot (so affects only boot time), or at module load time.
+
+ If unsure, say N.
+
config KPROBES_SANITY_TEST
tristate "Kprobes sanity tests" if !KUNIT_ALL_TESTS
depends on DEBUG_KERNEL
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index df0537e1b91f..85e3c5e1f7d2 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_GLOB_KUNIT_TEST) += glob_kunit.o
obj-$(CONFIG_HASHTABLE_KUNIT_TEST) += hashtable_test.o
obj-$(CONFIG_HASH_KUNIT_TEST) += test_hash.o
obj-$(CONFIG_TEST_IOV_ITER) += kunit_iov_iter.o
+obj-$(CONFIG_TEST_BVECQ) += kunit_bvecq.o
obj-$(CONFIG_IS_SIGNED_TYPE_KUNIT_TEST) += is_signed_type_kunit.o
obj-$(CONFIG_KPROBES_SANITY_TEST) += test_kprobes.o
obj-$(CONFIG_LIST_KUNIT_TEST) += list-test.o
diff --git a/lib/tests/kunit_bvecq.c b/lib/tests/kunit_bvecq.c
new file mode 100644
index 000000000000..19f3dc41eda7
--- /dev/null
+++ b/lib/tests/kunit_bvecq.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* bvecq iterator tests. Exercises:
+ * - advance
+ * - revert
+ * - npages
+ * - alignment
+ *
+ * See also kunit_iov_iter.c for bvecq copy and extract tests.
+ *
+ * Copyright (C) 2026 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/uio.h>
+#include <linux/bvecq.h>
+#include <kunit/test.h>
+
+MODULE_DESCRIPTION("bvecq testing");
+MODULE_AUTHOR("David Howells <dhowells@redhat.com>");
+MODULE_LICENSE("GPL");
+
+/*
+ * Info for the fill pattern generator.
+ */
+static const u8 bvecq_kunit_orders[16] = {
+ 0, 1, 2, 3,
+ 0, 1, 3, 2,
+ 0, 2, 1, 3,
+ 2, 0, 3, 1
+};
+
+static const s8 bvecq_kunit_fills[16] = {
+ 1, 2, 3, 5, 7, 11, 13, 17,
+ -1, -2, -3, -5, -7, -11, -13, -17
+};
+static struct folio *bvecq_kunit_folios[16];
+static size_t bvecq_kunit_folios_nr;
+
+/*
+ * Info for the fill pattern predictor.
+ */
+struct bvecq_test_pagemap {
+ u32 pos;
+ u32 offset;
+ u8 folio;
+ u8 subpage;
+};
+
+static struct bvecq_test_pagemap bvecq_kunit_pagemap[64];
+static int bvecq_kunit_pagemap_nr;
+
+/*
+ * bvecq assembly recipes.
+ */
+enum bvecq_test_cmd {
+ BVECQ_KUNIT_STOP, /* Stop loading */
+ BVECQ_KUNIT_ALLOC, /* Append new bvecq of size count and move to it */
+ BVECQ_KUNIT_LOAD, /* Load count segments into current bvecq */
+ BVECQ_KUNIT_EMPTY, /* Add count empty segments into current bvecq */
+} __mode(byte);
+
+struct bvecq_test_recipe {
+ enum bvecq_test_cmd cmd;
+ u8 count;
+};
+
+#define ALLOC(C) { .cmd = BVECQ_KUNIT_ALLOC, .count = C }
+#define LOAD(C) { .cmd = BVECQ_KUNIT_LOAD, .count = C }
+#define EMPTY(C) { .cmd = BVECQ_KUNIT_EMPTY, .count = C }
+#define END { .cmd = BVECQ_KUNIT_STOP, }
+
+static const struct bvecq_test_recipe bvecq_test_recipe_0[] = {
+ ALLOC(16), LOAD(16),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_1[] = {
+ ALLOC(8), LOAD(8),
+ ALLOC(8), LOAD(8),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_2[] = {
+ ALLOC(9), LOAD(8), EMPTY(1),
+ ALLOC(8), LOAD(8),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_3[] = {
+ ALLOC(9), EMPTY(1), LOAD(2), EMPTY(2), LOAD(2), EMPTY(1),
+ ALLOC(8), LOAD(4), EMPTY(2),
+ ALLOC(8), EMPTY(1), LOAD(4),
+ ALLOC(8), EMPTY(2), LOAD(4), EMPTY(1),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_4[] = {
+ ALLOC(9), LOAD(2), EMPTY(1),
+ ALLOC(2),
+ ALLOC(2), LOAD(2),
+ ALLOC(2), EMPTY(2),
+ ALLOC(8), LOAD(8),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_5[] = {
+ ALLOC(2),
+ ALLOC(9), LOAD(2), EMPTY(1),
+ ALLOC(2),
+ ALLOC(2), LOAD(2),
+ ALLOC(2), EMPTY(2),
+ ALLOC(8), LOAD(8),
+ ALLOC(2),
+ END
+};
+
+static const struct bvecq_test_recipe bvecq_test_recipe_6[] = {
+ ALLOC(2), EMPTY(1),
+ ALLOC(2),
+ ALLOC(9), LOAD(2), EMPTY(1),
+ ALLOC(2),
+ ALLOC(2), LOAD(2),
+ ALLOC(2), EMPTY(2),
+ ALLOC(8), LOAD(8),
+ ALLOC(2),
+ ALLOC(2), EMPTY(1),
+ END
+};
+
+/*
+ * Sets of variations of how much of each folio is added to the bvecq chain,
+ * allowing a part of the folio to be excluded from each end.
+ */
+struct bvecq_test_partials {
+ u16 add_start; /* Amount to exclude from the low end */
+ u16 sub_end; /* Amount to exclude from the high end */
+};
+
+/* All whole folios. */
+static const struct bvecq_test_partials bvecq_test_partials_full[16] = {
+ [0 ... 15] = { 0, 0 }
+};
+
+/* Scattered partial folios, with some folios contiguous. */
+static const struct bvecq_test_partials bvecq_test_partials_scattered[16] = {
+ //0, 1, 2, 3,
+ { 0x277, 0x136 },
+ { 0x999, 0x1011 },
+ { 0x2020, 0x0 },
+ { 0x0, 0x1123 },
+ //0, 1, 3, 2,
+ { 0x0, 0x136 },
+ { 0x27, 0x777 },
+ { 0x771, 0x0 },
+ { 0x555, 0x0 },
+ //0, 2, 1, 3,
+ { 0x277, 0x136 },
+ { 0x1123, 0x0 },
+ { 0x0, 0x0 },
+ { 0, 0x5011 },
+ //2, 0, 3, 1
+ { 0x2020, 0x0 },
+ { 0x277, 0x136 },
+ { 0x1999, 0x1011 },
+ { 0x0, 0x1010 },
+};
+
+/* Partial folios, all with a byte excluded from each end. */
+static const struct bvecq_test_partials bvecq_test_partials_edges[16] = {
+ [0 ... 15] = { 1, 1 }
+};
+
+/*
+ * Clean up routines.
+ */
+static void bvecq_kunit_release_folios(void *data)
+{
+ release_pages(bvecq_kunit_folios, bvecq_kunit_folios_nr);
+ memset(bvecq_kunit_folios, 0, sizeof(bvecq_kunit_folios));
+ bvecq_kunit_folios_nr = 0;
+}
+
+static void bvecq_kunit_bvecq_put(void *data)
+{
+ bvecq_put(data);
+}
+
+/*
+ * Calculate the fill pattern, for a particular byte of a particular subpage of
+ * a particular folio.
+ */
+static inline u8 bvecq_kunit_pattern(unsigned int folio, int subpage, int offset)
+{
+ int fill_step = bvecq_kunit_fills[folio];
+
+ return (subpage + offset * fill_step) & 0xff;
+}
+
+/*
+ * Calculate the fill pattern, given the 'byte position' in the bvecq chain.
+ * This looks up the folio, subpage and offset from a table generated whilst
+ * the bvecq is being assembled.
+ */
+static inline u8 bvecq_kunit_predict(struct kunit *test, size_t pos)
+{
+ size_t last_pos = 0;
+
+ for (int i = 0; i < bvecq_kunit_pagemap_nr; i++) {
+ if (pos < bvecq_kunit_pagemap[i].pos)
+ return bvecq_kunit_pattern(
+ bvecq_kunit_pagemap[i].folio,
+ bvecq_kunit_pagemap[i].subpage,
+ bvecq_kunit_pagemap[i].offset + (pos - last_pos));
+ last_pos = bvecq_kunit_pagemap[i].pos;
+ }
+
+ KUNIT_FAIL(test, "Overran pagemap pos=%zx\n", pos);
+ return 0;
+}
+
+/*
+ * Determine which page we're looking at, taking all the folios to be split
+ * into pages. This can be used to determine what iov_iter_npages() will say.
+ */
+static inline int bvecq_kunit_pos2page(struct kunit *test, size_t pos)
+{
+ for (int i = 0; i < bvecq_kunit_pagemap_nr; i++)
+ if (pos < bvecq_kunit_pagemap[i].pos)
+ return i;
+
+ if (pos > bvecq_kunit_pagemap[bvecq_kunit_pagemap_nr - 1].pos)
+ KUNIT_FAIL(test, "Overran pagemap pos=%zx\n", pos);
+ return bvecq_kunit_pagemap_nr;
+}
+
+/*
+ * Allocate a variety of folios of different orders and fill them with the
+ * pattern set for each folio.
+ */
+static void iov_bvecq_alloc_folios(struct kunit *test)
+{
+ struct folio *folio, **folios = bvecq_kunit_folios;
+ const int nr = ARRAY_SIZE(bvecq_kunit_folios);
+
+ bvecq_kunit_pagemap_nr = 0;
+ memset(bvecq_kunit_pagemap, 0, sizeof(bvecq_kunit_pagemap));
+ memset(bvecq_kunit_folios, 0, sizeof(bvecq_kunit_folios));
+
+ kunit_add_action_or_reset(test, bvecq_kunit_release_folios, NULL);
+
+ for (int i = 0; i < nr; i++) {
+ folio = folio_alloc(GFP_KERNEL, bvecq_kunit_orders[i]);
+ KUNIT_ASSERT_NOT_NULL(test, folio);
+ folios[i] = folio;
+ bvecq_kunit_folios_nr = i + 1;
+
+ /* Fill each subpage with a different but predictable pattern. */
+ for (int j = 0; j < folio_nr_pages(folio); j++) {
+ u8 *p = kmap_local_folio(folio, PAGE_SIZE * j);
+
+ for (int k = 0; k < PAGE_SIZE; k++)
+ p[k] = bvecq_kunit_pattern(i, j, k);
+ kunmap_local(p);
+ }
+ }
+}
+
+/*
+ * Follow the specified recipe to allocate a bvecq chain and stick the folios
+ * and empty gaps into it. Further, the offset into and length of each folio
+ * is adjusted from the partials list. Each ALLOC command will append a new
+ * bvecq to the chain and move to filling that.
+ */
+static struct bvecq *bvecq_kunit_load_chain(struct kunit *test,
+ const struct bvecq_test_recipe *rec,
+ const struct bvecq_test_partials *parts,
+ size_t *size)
+{
+ struct bvecq *head = NULL, *bq = NULL, *p;
+ size_t pos = 0;
+ int folio_index = 0;
+
+ *size = 0;
+
+ for (; rec->cmd != BVECQ_KUNIT_STOP; rec++) {
+ if (!head)
+ KUNIT_ASSERT_EQ(test, rec->cmd, BVECQ_KUNIT_ALLOC);
+
+ switch (rec->cmd) {
+ case BVECQ_KUNIT_STOP:
+ break;
+
+ case BVECQ_KUNIT_ALLOC:
+ p = bvecq_alloc_one(rec->count, GFP_KERNEL, false);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+
+ if (!head) {
+ head = p;
+ kunit_add_action_or_reset(test, bvecq_kunit_bvecq_put, head);
+ bq = p;
+ } else {
+ bvecq_append(bq, p);
+ bq = p;
+ }
+ break;
+
+ case BVECQ_KUNIT_LOAD:
+ KUNIT_ASSERT_LE(test, folio_index + rec->count,
+ ARRAY_SIZE(bvecq_kunit_orders));
+ KUNIT_ASSERT_LE(test, bq->nr_slots + rec->count, bq->max_slots);
+ for (int i = 0; i < rec->count; i++) {
+ struct folio *folio = bvecq_kunit_folios[folio_index];
+ size_t flen = PAGE_SIZE << bvecq_kunit_orders[folio_index];
+ size_t foff = 0;
+
+ KUNIT_ASSERT_LE(test,
+ parts[folio_index].add_start +
+ parts[folio_index].sub_end,
+ flen);
+
+ foff += parts[folio_index].add_start;
+ flen -= parts[folio_index].add_start;
+ flen -= parts[folio_index].sub_end;
+
+ bvec_set_page(&bq->bv[bq->nr_slots + i],
+ folio_page(folio, 0), flen, foff);
+ *size += flen;
+
+ for (int j = 0; j < folio_nr_pages(folio); j++) {
+ struct bvecq_test_pagemap *pm;
+ size_t part;
+
+ if (foff >= PAGE_SIZE) {
+ foff -= PAGE_SIZE;
+ continue;
+ }
+ part = min(flen, PAGE_SIZE - foff);
+ pos += part;
+ pm = &bvecq_kunit_pagemap[bvecq_kunit_pagemap_nr];
+ pm->pos = pos;
+ pm->folio = folio_index;
+ pm->subpage = j;
+ pm->offset = foff;
+ bvecq_kunit_pagemap_nr++;
+ flen -= part;
+ if (!flen)
+ break;
+ foff = 0;
+ }
+
+ folio_index++;
+ }
+ bvecq_filled_to(bq, bq->nr_slots + rec->count);
+ break;
+
+ case BVECQ_KUNIT_EMPTY:
+ KUNIT_ASSERT_LE(test, bq->nr_slots + rec->count, bq->max_slots);
+ for (int i = 0; i < rec->count; i++) {
+ bq->bv[bq->nr_slots + i].bv_page = ERR_PTR(-EIO);
+ bq->bv[bq->nr_slots + i].bv_offset = 0;
+ bq->bv[bq->nr_slots + i].bv_len = 0;
+ }
+ bvecq_filled_to(bq, bq->nr_slots + rec->count);
+ break;
+ }
+ }
+
+ return head;
+}
+
+/*
+ * Check the next few bytes of pattern from an iterator. Note that it is
+ * permitted for the iterator to have less data remaining than the number to
+ * check - in which case the copy is required to come up short.
+ */
+static void bvecq_kunit_check_next(struct kunit *test, struct iov_iter *iter,
+ size_t *pos, size_t *left, size_t nr)
+{
+ size_t part, n;
+ u8 buf[3], pred[3];
+
+ KUNIT_ASSERT_EQ(test, iov_iter_count(iter), *left);
+
+ part = min(*left, nr);
+ n = copy_from_iter(buf, nr, iter);
+ KUNIT_ASSERT_EQ(test, n, part);
+ *left -= n;
+
+ for (size_t i = 0; i < n; i++)
+ pred[i] = bvecq_kunit_predict(test, *pos + i);
+
+ for (size_t i = 0; i < n; i++) {
+ if (buf[i] != pred[i]) {
+ pr_notice(">>> pos=%05zx+%zx c=%zx buf=%*phN pred=%*phN\n",
+ *pos, i, iov_iter_count(iter) + n,
+ (int)n, buf, (int)n, pred);
+ }
+ KUNIT_ASSERT_EQ(test, buf[i], pred[i]);
+ }
+
+ *pos += n;
+}
+
+/*
+ * Test advancing and reverting over a bvecq chain.
+ *
+ * The loop starts at each 'byte position' in the chain, advancing a small
+ * amount, copying a few bytes of data, checking the fill pattern, reverting a
+ * bit, checking, reverting, checking and then reverting to a byte position one
+ * further on than the previous loop. Use small prime numbers for the advance
+ * and first revert so that we should test crossing the edges of the folios.
+ */
+static void bvecq_kunit_advance(struct kunit *test,
+ const struct bvecq_test_recipe *recipe,
+ const struct bvecq_test_partials *parts)
+{
+ struct iov_iter iter;
+ struct bvecq *bq, *p;
+ unsigned long bvec_align = 0;
+ size_t size, pos = 0, from;
+ int npages;
+
+ iov_bvecq_alloc_folios(test);
+ bq = bvecq_kunit_load_chain(test, recipe, parts, &size);
+
+ /* Compound up the alignments for ITER_BVEC on each individual bvecq
+ * span and compare that to the alignment for ITER_BVECQ. There is an
+ * issue in ITER_BVEC, however, in that it includes bv_offset from
+ * bio_vecs for which bv_len is 0, which ITER_BVECQ does not.
+ */
+ for (p = bq; p; p = p->next) {
+ size_t psize = 0;
+
+ for (int i = 0; i < p->nr_slots; i++)
+ psize += p->bv[i].bv_len;
+
+ if (!psize)
+ continue;
+ iov_iter_bvec(&iter, ITER_SOURCE, p->bv, p->nr_slots, psize);
+ bvec_align |= iov_iter_alignment(&iter);
+ }
+
+ iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, size);
+
+ KUNIT_ASSERT_EQ(test, iov_iter_alignment(&iter), bvec_align);
+
+ npages = iov_iter_npages(&iter, INT_MAX);
+ from = iov_iter_count(&iter);
+ do {
+ size_t left = from, part;
+
+ KUNIT_ASSERT_EQ(test, iov_iter_npages(&iter, INT_MAX),
+ npages - bvecq_kunit_pos2page(test, pos));
+
+ /* 3 steps forward. */
+ part = min(left, 3);
+ iov_iter_advance(&iter, 3); /* May be short. */
+ pos += part;
+ left -= part;
+ bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+ /* 2 steps back */
+ part = umin(2, left);
+ iov_iter_revert(&iter, part);
+ pos -= part;
+ left += part;
+ bvecq_kunit_check_next(test, &iter, &pos, &left, 2);
+
+ /* 1 more step back */
+ part = umin(1, left);
+ iov_iter_revert(&iter, part);
+ pos -= part;
+ left += part;
+ bvecq_kunit_check_next(test, &iter, &pos, &left, 1);
+
+ KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+ /* Move to one place on from the current loop. */
+ from--;
+ if (iov_iter_count(&iter) < from) {
+ part = from - iov_iter_count(&iter);
+ iov_iter_revert(&iter, part);
+ pos -= part;
+ }
+ KUNIT_ASSERT_EQ(test, from, iov_iter_count(&iter));
+ } while (from > 0);
+}
+
+/*
+ * Test jumping around in a bvecq chain using advance and revert.
+ *
+ * The loop starts at the start of the chain, then jumps forwards through the
+ * chain and then back again by large amounts, sampling the data each time.
+ */
+static void bvecq_kunit_jump(struct kunit *test,
+ const struct bvecq_test_recipe *recipe,
+ const struct bvecq_test_partials *parts)
+{
+ struct iov_iter iter;
+ struct bvecq *bq;
+ size_t size, pos = 0, step = 0x100;
+ bool dir = false;
+ int npages;
+
+ iov_bvecq_alloc_folios(test);
+ bq = bvecq_kunit_load_chain(test, recipe, parts, &size);
+
+ iov_iter_bvec_queue(&iter, ITER_SOURCE, bq, 0, 0, size);
+
+ npages = iov_iter_npages(&iter, INT_MAX);
+ while (step < size) {
+ size_t left = iov_iter_count(&iter), part;
+
+ KUNIT_ASSERT_EQ(test, iov_iter_npages(&iter, INT_MAX),
+ npages - bvecq_kunit_pos2page(test, pos));
+
+ if (!dir) {
+ /* Advance by step and then 3 more in check. */
+ part = min(left, step);
+ iov_iter_advance(&iter, step); /* May be short. */
+ pos += part;
+ left -= part;
+ bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+ KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+ if (left == 0) {
+ /* Switch to reverting. */
+ dir = true;
+ step += 3;
+ }
+ } else {
+ /* Revert by step and then advance by 3 in check. */
+ part = min(size - left, step);
+ iov_iter_revert(&iter, part);
+ pos -= part;
+ left += part;
+ bvecq_kunit_check_next(test, &iter, &pos, &left, 3);
+
+ KUNIT_ASSERT_EQ(test, left, iov_iter_count(&iter));
+
+ if (pos <= 3) {
+ /* Switch to advancing. */
+ dir = false;
+ step *= 2;
+ }
+ }
+ }
+}
+
+/*
+ * Run the individual test combinations
+ */
+#define ADVANCE_TESTS_SET(CHECK, PARTIALS) \
+ TEST(CHECK, PARTIALS, 0) \
+ TEST(CHECK, PARTIALS, 1) \
+ TEST(CHECK, PARTIALS, 2) \
+ TEST(CHECK, PARTIALS, 3) \
+ TEST(CHECK, PARTIALS, 4) \
+ TEST(CHECK, PARTIALS, 5) \
+ TEST(CHECK, PARTIALS, 6)
+
+#define ADVANCE_TESTS(CHECK) \
+ ADVANCE_TESTS_SET(CHECK, full) \
+ ADVANCE_TESTS_SET(CHECK, scattered) \
+ ADVANCE_TESTS_SET(CHECK, edges)
+
+#define TEST(CHECK, PARTIALS, RECIPE) \
+static void bvecq_kunit_##CHECK##_##PARTIALS##_##RECIPE(struct kunit *test) \
+{ \
+ bvecq_kunit_##CHECK(test, \
+ bvecq_test_recipe_##RECIPE, \
+ bvecq_test_partials_##PARTIALS); \
+}
+
+/* Emit test stub functions. */
+ADVANCE_TESTS(advance);
+ADVANCE_TESTS(jump);
+#undef TEST
+
+#define TEST(CHECK, PARTIALS, RECIPE) \
+ KUNIT_CASE(bvecq_kunit_##CHECK##_##PARTIALS##_##RECIPE),
+
+static struct kunit_case bvecq_kunit_cases[] = {
+ ADVANCE_TESTS(advance)
+ ADVANCE_TESTS(jump)
+ {}
+};
+
+static struct kunit_suite bvecq_kunit_suite = {
+ .name = "bvecq",
+ .test_cases = bvecq_kunit_cases,
+};
+
+kunit_test_suites(&bvecq_kunit_suite);
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v17 10/17] netfs: Pass the write length to request creation where appropriate
[not found] <20261111203034.2126314-1-dhowells@redhat.com>
` (4 preceding siblings ...)
2026-11-11 20:30 ` [PATCH v17 06/17] iov_iter: Add a bvecq kunit test David Howells
@ 2026-11-11 20:30 ` David Howells
5 siblings, 0 replies; 6+ messages in thread
From: David Howells @ 2026-11-11 20:30 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,
Jens Axboe, Christoph Hellwig, linux-block
For unbuffered/DIO writes and writes to single-type objects, pass the
length down into the request creation function so that wreq->len gets set
correctly. For buffered writeback and pgpriv2 copying, this remains 0 as
we don't know in advance how big the request will be.
A subsequent patch will use this to set as the i_size on single-type
objects (e.g. afs directories) as we don't care what the old i_size was and
we don't want to see inode->i_size in the netfs file changing due to
third-party invalidation before we update fscache to say how big the object
is going to be.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Matthew Wilcox <willy@infradead.org>
cc: Jens Axboe <axboe@kernel.dk>
cc: Christoph Hellwig <hch@infradead.org>
cc: linux-block@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
---
fs/netfs/direct_write.c | 4 ++--
fs/netfs/internal.h | 2 +-
fs/netfs/read_pgpriv2.c | 2 +-
fs/netfs/write_issue.c | 9 +++++----
4 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index cc46b7d9321f..ff1795e1f0cb 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -222,7 +222,7 @@ 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;
@@ -241,7 +241,7 @@ ssize_t netfs_unbuffered_write_iter_locked(struct kiocb *iocb, struct iov_iter *
_debug("uw %llx-%llx", start, end);
- wreq = netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp, start,
+ wreq = netfs_create_write_req(iocb->ki_filp->f_mapping, iocb->ki_filp, start, len,
iocb->ki_flags & IOCB_DIRECT ?
NETFS_DIO_WRITE : NETFS_UNBUFFERED_WRITE);
if (IS_ERR(wreq))
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index d554468bf694..6e30112c9112 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -238,7 +238,7 @@ void netfs_write_collection_worker(struct work_struct *work);
*/
struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
struct file *file,
- uoff_t start,
+ uoff_t start, size_t len,
enum netfs_io_origin origin);
void netfs_prepare_write(struct netfs_io_request *wreq,
struct netfs_io_stream *stream,
diff --git a/fs/netfs/read_pgpriv2.c b/fs/netfs/read_pgpriv2.c
index 5280b606fda4..85084aad9e31 100644
--- a/fs/netfs/read_pgpriv2.c
+++ b/fs/netfs/read_pgpriv2.c
@@ -103,7 +103,7 @@ static struct netfs_io_request *netfs_pgpriv2_begin_copy_to_cache(
if (!fscache_resources_valid(&rreq->cache_resources))
goto cancel;
- creq = netfs_create_write_req(rreq->mapping, NULL, folio_pos(folio),
+ creq = netfs_create_write_req(rreq->mapping, NULL, folio_pos(folio), 0,
NETFS_PGPRIV2_COPY_TO_CACHE);
if (IS_ERR(creq))
goto cancel;
diff --git a/fs/netfs/write_issue.c b/fs/netfs/write_issue.c
index f084a6796667..4c1e597a6d03 100644
--- a/fs/netfs/write_issue.c
+++ b/fs/netfs/write_issue.c
@@ -89,7 +89,7 @@ static void netfs_kill_dirty_pages(struct address_space *mapping,
*/
struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
struct file *file,
- uoff_t start,
+ uoff_t start, size_t len,
enum netfs_io_origin origin)
{
struct netfs_io_request *wreq;
@@ -98,7 +98,7 @@ struct netfs_io_request *netfs_create_write_req(struct address_space *mapping,
origin == NETFS_WRITEBACK_SINGLE ||
origin == NETFS_PGPRIV2_COPY_TO_CACHE);
- wreq = netfs_alloc_request(mapping, file, start, 0, origin);
+ wreq = netfs_alloc_request(mapping, file, start, len, origin);
if (IS_ERR(wreq))
return wreq;
@@ -573,7 +573,8 @@ int netfs_writepages(struct address_space *mapping,
if (!folio)
goto out;
- wreq = netfs_create_write_req(mapping, NULL, folio_pos(folio), NETFS_WRITEBACK);
+ wreq = netfs_create_write_req(mapping, NULL, folio_pos(folio), 0,
+ NETFS_WRITEBACK);
if (IS_ERR(wreq)) {
error = PTR_ERR(wreq);
goto couldnt_start;
@@ -659,7 +660,7 @@ int netfs_writeback_single(struct address_space *mapping,
return 1;
}
- wreq = netfs_create_write_req(mapping, NULL, 0, NETFS_WRITEBACK_SINGLE);
+ wreq = netfs_create_write_req(mapping, NULL, 0, len, NETFS_WRITEBACK_SINGLE);
if (IS_ERR(wreq)) {
ret = PTR_ERR(wreq);
goto couldnt_start;
^ permalink raw reply related [flat|nested] 6+ messages in thread