* [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter()
@ 2026-11-10 11:23 Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 1/4] net: preserve splice iterator progress on rejection Alex Markuze
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Alex Markuze @ 2026-11-10 11:23 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, linux-kernel
skb_splice_from_iter() does per-page work even when the input describes a
contiguous range of a larger folio. Adjacent tail pages have different page
pointers, so appending them separately also prevents fragment coalescing.
For ITER_BVEC, the helper first builds a page array, then walks it to append
the bytes that the original bvecs already described.
This series removes that redundant work from the MSG_SPLICE_PAGES path
used by sendfile(), splice() and other in-kernel socket senders. Contiguous
folio ranges become fewer skb fragments, with fewer page-reference
operations; BVEC input is consumed directly, without the temporary array.
The patches separate a correctness fix from the optimizations:
1. Make a zero-byte request a no-op and restore the unappended iterator
remainder after rejection, preserving any successfully appended prefix.
2. Group contiguous bytes of a direct-mapped folio, including across
extraction batches and repeated calls. Representation-safe anchors
and wide endpoint arithmetic keep offsets and lengths well-defined.
3. Walk ITER_BVEC segments directly and advance only by appended bytes.
Bound the request against the unsigned iterator count before assigning
the result to the signed return type.
4. Add two KUnit cases for append/resume and zero-length/rejection
progress, on both the extraction path and the direct BVEC walk.
Grouping stays within a folio and preserves the append helper's ownership
checks. CONFIG_HIGHMEM retains actual-page, in-page-offset, page-bounded
fragments. Software checksum traversal remains page-aware. The caller's
byte-count and partial-progress interface is unchanged.
Performance
-----------
An alternating A/B of the folio-grouping and direct-BVEC implementation
showed lower sender CPU cost for sendfile() from large folios:
sendfile() workload change in sender CPU/GB
64 KiB folios, 64 KiB transfers, full speed -11%
64 KiB folios, 64 KiB transfers, paced at 1 GB/s -5%
4 KiB folios, tested transfer/rate combinations -3% to +3%
At 64 KiB, large-folio sends used about 2 fragments per skb instead of
about 16. Single-page folios retained the per-page layout, providing a
control for the optimization. The large-folio 256 KiB CPU result was within
run-to-run variation, rather than a resolved improvement.
The measurements used a Linux 7.3-rc6-based evaluation kernel (532788178)
and virtio-net VMs, with interrupts pinned to NUMA node 0. Each
cell had four runs per kernel in alternating rounds. CPU/GB is whole-host
sender CPU time, total minus idle, per GB sent; received data was verified.
These measurements cover the earlier implementation of the optimizations.
The present revision adds iterator and representation corner-case fixes
and the regression coverage described below.
Testing
-------
The mailed tests are two KUnit cases, not a broad ownership or coordinate
matrix. Each runs through both the extraction path and the direct BVEC
walk. One appends part of a multi-page folio and resumes: payload,
checksum, fragment count, iterator residual and ordinary references.
The other checks a zero-length no-op and rejected-page rollback with and
without an accepted prefix.
Those cases passed on UML, with no skips. The focused build used W=1 and
WERROR=y. The mails apply to the stated base and match the recorded
intermediate trees. This is not a KASAN run, a physical-NIC test, or a
rerun of the measurement above.
Together, the measured reduction in fragments and sender CPU cost, the
single-page-folio control, and the progress regressions support making
this a generic improvement to skb_splice_from_iter().
Thanks,
Alex Markuze
Alex Markuze (4):
net: preserve splice iterator progress on rejection
net: skb_splice_from_iter(): group consecutive pages of a folio
net: skb_splice_from_iter(): walk ITER_BVEC segments directly
net: add basic splice KUnit tests
include/linux/skbuff.h | 3 +-
net/core/net_test.c | 170 +++++++++++++++++++++++++++++++++++++++++++++++
net/core/skbuff.c | 141 ++++++++++++++++++++++++++++++++++++---
net/core/skbuff_splice.h | 34 ++++++++++
4 files changed, 338 insertions(+), 10 deletions(-)
create mode 100644 net/core/skbuff_splice.h
base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH net-next 1/4] net: preserve splice iterator progress on rejection
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
@ 2026-11-10 11:23 ` Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 2/4] net: skb_splice_from_iter(): group consecutive pages of a folio Alex Markuze
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Alex Markuze @ 2026-11-10 11:23 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, linux-kernel
When the iterator is nonempty and the maximum length is zero, the generic
extraction path reports -EIO instead of performing a no-op. Return zero
before attempting extraction.
The extractor advances the iterator before checking whether the pages can
be sent. If a page is rejected, revert the unappended extraction
remainder, as the fragment-append failure path already does. This keeps
an accepted prefix and leaves the rejected suffix available to the caller.
Fixes: 2e910b95329c ("net: Add a function to splice pages into an skbuff for MSG_SPLICE_PAGES")
Signed-off-by: Alex Markuze <amarkuze@redhat.com>
---
net/core/skbuff.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 48f676411724..ee7a1bc72cc2 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -7554,6 +7554,9 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
ssize_t spliced = 0, ret = 0;
unsigned int i;
+ if (!maxsize)
+ return 0;
+
while (iter->count > 0) {
ssize_t space, nr, len;
size_t off;
@@ -7578,8 +7581,10 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
size_t part = min_t(size_t, PAGE_SIZE - off, len);
ret = -EIO;
- if (WARN_ON_ONCE(!sendpage_ok(page)))
+ if (WARN_ON_ONCE(!sendpage_ok(page))) {
+ iov_iter_revert(iter, len);
goto out;
+ }
ret = skb_append_pagefrags(skb, page, off, part,
frag_limit);
base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH net-next 2/4] net: skb_splice_from_iter(): group consecutive pages of a folio
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 1/4] net: preserve splice iterator progress on rejection Alex Markuze
@ 2026-11-10 11:23 ` Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 3/4] net: skb_splice_from_iter(): walk ITER_BVEC segments directly Alex Markuze
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Alex Markuze @ 2026-11-10 11:23 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, linux-kernel
skb_splice_from_iter() appends extracted pages separately, preventing
adjacent tail pages of one folio from coalescing.
Group contiguous ranges of a folio on direct-mapped builds. Normalize
large folio positions into page-aligned coordinate windows so fragment
offsets and append lengths remain representable, and widen the coalescing
endpoint calculation to prevent wraparound matches. The boundaries follow
fragment representation limits, not a device-sized fragment cap.
Keep skb_append_pagefrags() ownership checks. HIGHMEM retains actual-page,
in-page-offset, page-bounded fragments; software checksums walk base pages.
Signed-off-by: Alex Markuze <amarkuze@redhat.com>
---
include/linux/skbuff.h | 3 +-
net/core/skbuff.c | 63 ++++++++++++++++++++++++++++++++++------
net/core/skbuff_splice.h | 34 ++++++++++++++++++++++
3 files changed, 90 insertions(+), 10 deletions(-)
create mode 100644 net/core/skbuff_splice.h
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 6787fcf6ac3e..b19ba550ae11 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -4009,7 +4009,8 @@ static inline bool skb_can_coalesce_netmem(struct sk_buff *skb, int i,
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i - 1];
return netmem == skb_frag_netmem(frag) &&
- off == skb_frag_off(frag) + skb_frag_size(frag);
+ (u64)(unsigned int)off ==
+ (u64)skb_frag_off(frag) + skb_frag_size(frag);
}
return false;
}
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index ee7a1bc72cc2..3a66a50513a3 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -98,6 +98,7 @@
#include "devmem.h"
#include "net-sysfs.h"
#include "netmem_priv.h"
+#include "skbuff_splice.h"
#ifdef CONFIG_SKB_EXTENSIONS
static struct kmem_cache *skbuff_ext_cache __ro_after_init;
@@ -7532,6 +7533,23 @@ static void skb_splice_csum_page(struct sk_buff *skb, struct page *page,
skb->csum = csum_block_add(skb->csum, csum, csum_offset);
}
+/* Add the checksum of @len bytes at @offset in @page to skb->csum; the range
+ * may run on into the pages that follow @page.
+ */
+static void skb_splice_csum_range(struct sk_buff *skb, struct page *page,
+ size_t offset, size_t len,
+ unsigned int csum_offset)
+{
+ while (len) {
+ size_t part = min_t(size_t, PAGE_SIZE - offset, len);
+
+ skb_splice_csum_page(skb, page++, offset, part, csum_offset);
+ csum_offset += part;
+ len -= part;
+ offset = 0;
+ }
+}
+
/**
* skb_splice_from_iter - Splice (or copy) pages to skbuff
* @skb: The buffer to add pages to
@@ -7540,8 +7558,12 @@ static void skb_splice_csum_page(struct sk_buff *skb, struct page *page,
*
* This is a common helper function for supporting MSG_SPLICE_PAGES. It
* extracts pages from an iterator and adds them to the socket buffer if
- * possible, copying them to fragments if not possible (such as if they're slab
- * pages).
+ * possible. Pages that cannot be sent (such as slab pages) are rejected
+ * with -EIO, not copied. Consecutive pages of one folio are grouped within
+ * the fragment representation limits. A fragment is anchored at the head
+ * or a tail page of that folio and holds one reference on the folio either
+ * way. On CONFIG_HIGHMEM builds a fragment stays within one actual page
+ * of the folio, because kmap_local_page() maps only one page.
*
* Returns the amount of data spliced/copied or -EMSGSIZE if there's
* insufficient space in the buffer to transfer anything.
@@ -7552,7 +7574,7 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
size_t frag_limit = READ_ONCE(net_hotdata.sysctl_max_skb_frags);
struct page *pages[8], **ppages = pages;
ssize_t spliced = 0, ret = 0;
- unsigned int i;
+ unsigned int i, nr_pages;
if (!maxsize)
return 0;
@@ -7575,29 +7597,52 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
break;
}
+ nr_pages = DIV_ROUND_UP(off + len, PAGE_SIZE);
i = 0;
do {
struct page *page = pages[i++];
+ struct folio *folio = page_folio(page);
+ struct page *fpage = page;
+ size_t foff = off;
size_t part = min_t(size_t, PAGE_SIZE - off, len);
+ unsigned int first = i - 1;
+
+ if (!IS_ENABLED(CONFIG_HIGHMEM)) {
+ struct skb_splice_frag range;
+ u64 pos = ((u64)folio_page_idx(folio, page) << PAGE_SHIFT) + off;
+
+ range = skb_splice_frag_normalize(pos, len);
+ fpage = folio_page(folio, range.anchor >> PAGE_SHIFT);
+ foff = range.offset;
+ part = min_t(size_t, part, range.len);
+ /* A tail anchor also takes/drops a folio reference. */
+ while (i < nr_pages && part < range.len &&
+ pages[i] == pages[i - 1] + 1 &&
+ page_folio(pages[i]) == folio) {
+ part += min_t(size_t, PAGE_SIZE, range.len - part);
+ i++;
+ }
+ }
ret = -EIO;
- if (WARN_ON_ONCE(!sendpage_ok(page))) {
+ if (WARN_ON_ONCE(!sendpage_ok(fpage))) {
iov_iter_revert(iter, len);
goto out;
}
- ret = skb_append_pagefrags(skb, page, off, part,
- frag_limit);
+ ret = skb_append_pagefrags(skb, fpage, foff, part, frag_limit);
if (ret < 0) {
iov_iter_revert(iter, len);
goto out;
}
if (skb->ip_summed == CHECKSUM_NONE)
- skb_splice_csum_page(skb, page, off, part,
- skb->len + spliced);
+ skb_splice_csum_range(skb, pages[first], off, part,
+ skb->len + spliced);
- off = 0;
+ off += part;
+ i = first + (off >> PAGE_SHIFT);
+ off = offset_in_page(off);
spliced += part;
maxsize -= part;
len -= part;
diff --git a/net/core/skbuff_splice.h b/net/core/skbuff_splice.h
new file mode 100644
index 000000000000..dd51daf74903
--- /dev/null
+++ b/net/core/skbuff_splice.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _NET_CORE_SKBUFF_SPLICE_H
+#define _NET_CORE_SKBUFF_SPLICE_H
+
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/types.h>
+
+struct skb_splice_frag {
+ u64 anchor;
+ unsigned int offset;
+ unsigned int len;
+};
+
+/*
+ * Use page-aligned windows of INT_MAX + 1 bytes. The anchor is a byte
+ * position within the folio, not a pointer; callers have the live folio.
+ * Every offset and append length fits the int arguments of the fragment
+ * helpers, and offset + length fits u32 without wrapping. Consecutive
+ * calls use the same anchor until a representational boundary is reached.
+ */
+static inline struct skb_splice_frag skb_splice_frag_normalize(u64 pos, size_t len)
+{
+ struct skb_splice_frag range = {
+ .anchor = pos & ~(u64)INT_MAX,
+ .offset = pos & INT_MAX,
+ };
+ u64 space = (u64)INT_MAX + 1 - range.offset;
+
+ range.len = min_t(u64, len, min_t(u64, space, INT_MAX));
+ return range;
+}
+
+#endif /* _NET_CORE_SKBUFF_SPLICE_H */
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH net-next 3/4] net: skb_splice_from_iter(): walk ITER_BVEC segments directly
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 1/4] net: preserve splice iterator progress on rejection Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 2/4] net: skb_splice_from_iter(): group consecutive pages of a folio Alex Markuze
@ 2026-11-10 11:23 ` Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 4/4] net: add basic splice KUnit tests Alex Markuze
2026-11-10 11:26 ` [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() netdev-bot+sinfo
4 siblings, 0 replies; 6+ messages in thread
From: Alex Markuze @ 2026-11-10 11:23 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, linux-kernel
ITER_BVEC already describes source pages and byte ranges. Walk its
segments directly instead of extracting a temporary page array and
reconstructing those ranges.
Split at folio and representation boundaries, retaining page-bounded
fragments on HIGHMEM. Reuse the fragment append and checksum helpers.
Advance only by appended bytes, leaving a short request's remainder
available for another skb. Compare a positive limit with the unsigned
iterator count before assigning the bounded result to ssize_t.
Other iterator types keep the page-extraction path.
Signed-off-by: Alex Markuze <amarkuze@redhat.com>
---
net/core/skbuff.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index 3a66a50513a3..189fe25293ac 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -7550,6 +7550,75 @@ static void skb_splice_csum_range(struct sk_buff *skb, struct page *page,
}
}
+/*
+ * Splice from an ITER_BVEC by walking its segments: there is no page array
+ * to fill. Direct-mapped folio runs are split only at representation
+ * boundaries; CONFIG_HIGHMEM builds retain page-bounded fragments.
+ */
+static ssize_t skb_splice_from_bvec(struct sk_buff *skb, struct iov_iter *iter,
+ ssize_t maxsize, size_t frag_limit)
+{
+ const struct bio_vec *bv = iter->bvec;
+ size_t skip = iter->iov_offset;
+ ssize_t spliced = 0, ret = 0;
+ unsigned long seg;
+
+ if (maxsize <= 0)
+ return 0;
+ maxsize = min_t(size_t, maxsize, iter->count);
+ for (seg = 0; seg < iter->nr_segs && maxsize > 0; seg++, skip = 0) {
+ size_t boff = bv[seg].bv_offset + skip;
+ size_t blen = min_t(size_t, bv[seg].bv_len - skip, maxsize);
+
+ while (blen) {
+ struct page *page, *fpage;
+ struct folio *folio;
+ size_t foff, part;
+
+ page = bv[seg].bv_page + (boff >> PAGE_SHIFT);
+ folio = page_folio(page);
+ if (IS_ENABLED(CONFIG_HIGHMEM)) {
+ fpage = page;
+ foff = offset_in_page(boff);
+ part = min_t(size_t, blen, PAGE_SIZE - foff);
+ } else {
+ struct skb_splice_frag range;
+ u64 pos = ((u64)folio_page_idx(folio, page) << PAGE_SHIFT) +
+ offset_in_page(boff);
+
+ part = min_t(u64, blen, folio_size(folio) - pos);
+ range = skb_splice_frag_normalize(pos, part);
+ fpage = folio_page(folio, range.anchor >> PAGE_SHIFT);
+ foff = range.offset;
+ part = range.len;
+ }
+
+ ret = -EIO;
+ if (WARN_ON_ONCE(!sendpage_ok(fpage)))
+ goto out;
+
+ ret = skb_append_pagefrags(skb, fpage, foff, part, frag_limit);
+ if (ret < 0)
+ goto out;
+
+ if (skb->ip_summed == CHECKSUM_NONE)
+ skb_splice_csum_range(skb, page,
+ offset_in_page(boff),
+ part, skb->len + spliced);
+
+ boff += part;
+ blen -= part;
+ spliced += part;
+ maxsize -= part;
+ }
+ }
+
+out:
+ iov_iter_advance(iter, spliced);
+ skb_len_add(skb, spliced);
+ return spliced ?: ret;
+}
+
/**
* skb_splice_from_iter - Splice (or copy) pages to skbuff
* @skb: The buffer to add pages to
@@ -7564,6 +7633,7 @@ static void skb_splice_csum_range(struct sk_buff *skb, struct page *page,
* or a tail page of that folio and holds one reference on the folio either
* way. On CONFIG_HIGHMEM builds a fragment stays within one actual page
* of the folio, because kmap_local_page() maps only one page.
+ * A bvec iterator is walked directly, without extracting a page array.
*
* Returns the amount of data spliced/copied or -EMSGSIZE if there's
* insufficient space in the buffer to transfer anything.
@@ -7579,6 +7649,9 @@ ssize_t skb_splice_from_iter(struct sk_buff *skb, struct iov_iter *iter,
if (!maxsize)
return 0;
+ if (iov_iter_is_bvec(iter))
+ return skb_splice_from_bvec(skb, iter, maxsize, frag_limit);
+
while (iter->count > 0) {
ssize_t space, nr, len;
size_t off;
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* [RFC PATCH net-next 4/4] net: add basic splice KUnit tests
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
` (2 preceding siblings ...)
2026-11-10 11:23 ` [RFC PATCH net-next 3/4] net: skb_splice_from_iter(): walk ITER_BVEC segments directly Alex Markuze
@ 2026-11-10 11:23 ` Alex Markuze
2026-11-10 11:26 ` [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() netdev-bot+sinfo
4 siblings, 0 replies; 6+ messages in thread
From: Alex Markuze @ 2026-11-10 11:23 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, netdev, linux-kernel
Add small KUnit checks for the generic extraction and direct BVEC paths.
Exercise a multi-page folio over short appends and resumption, checking
payload, checksum, fragment count and ordinary references through release.
Check zero-length requests and rejected-page rollback with and without an
accepted prefix, comparing iterator position as well as remaining length.
Use separate valid and rejected segments, without a mapping fixture.
These are basic helper regressions, not an exhaustive ownership,
arithmetic-boundary or device test suite.
Signed-off-by: Alex Markuze <amarkuze@redhat.com>
---
net/core/net_test.c | 170 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 170 insertions(+)
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 2b5080c22663..c295560afbd4 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -1721,7 +1721,177 @@ static void netmem_ref_test_tcp_devmem_recv(struct kunit *test)
#endif
#endif
+/* skb_splice_from_iter() */
+
+#include <linux/bvec.h>
+#include <net/checksum.h>
+#include <net/hotdata.h>
+
+struct splice_basic {
+ struct sk_buff *skb;
+ struct folio *folio;
+};
+
+static void splice_basic_free_skb(struct splice_basic *c)
+{
+ int refs, nr;
+
+ if (!c->skb)
+ return;
+ nr = skb_shinfo(c->skb)->nr_frags;
+ refs = folio_ref_count(c->folio);
+ /* Keep the owner alive during cleanup of a missing-reference failure. */
+ if (refs <= nr)
+ folio_ref_add(c->folio, nr + 1 - refs);
+ kfree_skb(c->skb);
+ c->skb = NULL;
+}
+
+static void splice_basic_release(void *data)
+{
+ struct splice_basic *c = data;
+
+ splice_basic_free_skb(c);
+ if (c->folio)
+ folio_put(c->folio);
+}
+
+static struct splice_basic *splice_basic_new(struct kunit *test)
+{
+ struct splice_basic *c = kunit_kzalloc(test, sizeof(*c), GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_NULL(test, c);
+ KUNIT_ASSERT_EQ(test,
+ kunit_add_action_or_reset(test, splice_basic_release, c), 0);
+ c->folio = folio_alloc(GFP_KERNEL | __GFP_ZERO, 1);
+ KUNIT_ASSERT_NOT_NULL(test, c->folio);
+ c->skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, c->skb);
+ return c;
+}
+
+static void splice_basic_iter(struct iov_iter *iter, struct bio_vec *bv,
+ struct kvec *kv, unsigned int nr, bool bvec)
+{
+ size_t len = 0;
+ unsigned int i;
+
+ for (i = 0; i < nr; i++) {
+ kv[i].iov_base = page_address(bv[i].bv_page) + bv[i].bv_offset;
+ kv[i].iov_len = bv[i].bv_len;
+ len += bv[i].bv_len;
+ }
+ if (bvec)
+ iov_iter_bvec(iter, ITER_SOURCE, bv, nr, len);
+ else
+ iov_iter_kvec(iter, ITER_SOURCE, kv, nr, len);
+}
+
+static void splice_basic_expect_iter(struct kunit *test, struct iov_iter *iter,
+ const struct iov_iter *want)
+{
+ KUNIT_EXPECT_EQ(test, iov_iter_count(iter), iov_iter_count(want));
+ KUNIT_EXPECT_EQ(test, iter->iov_offset, want->iov_offset);
+ KUNIT_EXPECT_EQ(test, iter->nr_segs, want->nr_segs);
+ KUNIT_EXPECT_PTR_EQ(test,
+ iov_iter_is_bvec(iter) ? (const void *)iter->bvec : iter->kvec,
+ iov_iter_is_bvec(want) ? (const void *)want->bvec : want->kvec);
+}
+
+static void skb_splice_basic_append(struct kunit *test)
+{
+ unsigned int bvec;
+
+ if (IS_ENABLED(CONFIG_HIGHMEM) &&
+ READ_ONCE(net_hotdata.sysctl_max_skb_frags) < 2)
+ kunit_skip(test, "two page-bounded fragments need two slots");
+ for (bvec = 0; bvec < 2; bvec++) {
+ struct splice_basic *c = splice_basic_new(test);
+ size_t len = PAGE_SIZE + 64, first = PAGE_SIZE - 27;
+ unsigned int nr = IS_ENABLED(CONFIG_HIGHMEM) ? 2 : 1;
+ u8 *data = folio_address(c->folio) + 17;
+ u8 *copy = kunit_kmalloc(test, len, GFP_KERNEL);
+ struct bio_vec bv[2];
+ struct iov_iter iter;
+ struct kvec kv[2];
+ size_t i;
+
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ for (i = 0; i < len; i++)
+ data[i] = (u8)(i * 13 + 7);
+ bvec_set_page(&bv[0], &c->folio->page, PAGE_SIZE - 17, 17);
+ bvec_set_page(&bv[1], folio_page(c->folio, 1), 81, 0);
+ splice_basic_iter(&iter, bv, kv, 2, bvec);
+ KUNIT_ASSERT_EQ(test, skb_splice_from_iter(c->skb, &iter, first),
+ (ssize_t)first);
+ KUNIT_EXPECT_EQ(test, iov_iter_count(&iter), len - first);
+ KUNIT_ASSERT_EQ(test, skb_splice_from_iter(c->skb, &iter, len),
+ (ssize_t)(len - first));
+ KUNIT_EXPECT_EQ(test, iov_iter_count(&iter), 0UL);
+ KUNIT_EXPECT_EQ(test, c->skb->len, (unsigned int)len);
+ KUNIT_EXPECT_EQ(test, skb_shinfo(c->skb)->nr_frags, nr);
+ KUNIT_EXPECT_EQ(test, folio_ref_count(c->folio), (int)nr + 1);
+ KUNIT_ASSERT_EQ(test, skb_copy_bits(c->skb, 0, copy, len), 0);
+ KUNIT_EXPECT_MEMEQ(test, copy, data, len);
+ KUNIT_EXPECT_EQ(test, (__force u16)csum_fold(c->skb->csum),
+ (__force u16)csum_fold(csum_partial(data, len, 0)));
+ splice_basic_free_skb(c);
+ KUNIT_EXPECT_EQ(test, folio_ref_count(c->folio), 1);
+ }
+}
+
+static void skb_splice_basic_progress(struct kunit *test)
+{
+ unsigned int bvec;
+
+ for (bvec = 0; bvec < 2; bvec++) {
+ struct splice_basic *c = splice_basic_new(test);
+ void *bad = kunit_kmalloc(test, 16, GFP_KERNEL);
+ struct bio_vec bv[2];
+ struct iov_iter iter, expected;
+ struct kvec kv[2];
+ ssize_t ret = 0;
+
+ KUNIT_ASSERT_NOT_NULL(test, bad);
+ KUNIT_ASSERT_TRUE(test, PageSlab(virt_to_page(bad)));
+ bvec_set_page(&bv[0], &c->folio->page, PAGE_SIZE, 0);
+ splice_basic_iter(&iter, bv, kv, 1, bvec);
+ expected = iter;
+ KUNIT_EXPECT_EQ(test, skb_splice_from_iter(c->skb, &iter, 0), 0);
+ splice_basic_expect_iter(test, &iter, &expected);
+ KUNIT_EXPECT_EQ(test, c->skb->len, 0U);
+ KUNIT_EXPECT_EQ(test, folio_ref_count(c->folio), 1);
+
+ bvec_set_page(&bv[0], virt_to_page(bad), 16, offset_in_page(bad));
+ splice_basic_iter(&iter, bv, kv, 1, bvec);
+ expected = iter;
+ kunit_warning_suppress(test) {
+ ret = skb_splice_from_iter(c->skb, &iter, 16);
+ }
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)-EIO);
+ splice_basic_expect_iter(test, &iter, &expected);
+ KUNIT_ASSERT_EQ(test, skb_shinfo(c->skb)->nr_frags, 0);
+
+ bvec_set_page(&bv[0], &c->folio->page, PAGE_SIZE, 0);
+ bvec_set_page(&bv[1], virt_to_page(bad), 16, offset_in_page(bad));
+ splice_basic_iter(&iter, bv, kv, 2, bvec);
+ expected = iter;
+ iov_iter_advance(&expected, PAGE_SIZE);
+ kunit_warning_suppress(test) {
+ ret = skb_splice_from_iter(c->skb, &iter, PAGE_SIZE + 16);
+ }
+ KUNIT_EXPECT_EQ(test, ret, (ssize_t)PAGE_SIZE);
+ splice_basic_expect_iter(test, &iter, &expected);
+ KUNIT_EXPECT_EQ(test, c->skb->len, (unsigned int)PAGE_SIZE);
+ KUNIT_EXPECT_EQ(test, folio_ref_count(c->folio), 2);
+ splice_basic_free_skb(c);
+ KUNIT_EXPECT_EQ(test, folio_ref_count(c->folio), 1);
+ }
+}
+
static struct kunit_case net_test_cases[] = {
+ KUNIT_CASE(skb_splice_basic_append),
+ KUNIT_CASE(skb_splice_basic_progress),
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
ip_tunnel_flags_test_gen_params),
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter()
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
` (3 preceding siblings ...)
2026-11-10 11:23 ` [RFC PATCH net-next 4/4] net: add basic splice KUnit tests Alex Markuze
@ 2026-11-10 11:26 ` netdev-bot+sinfo
4 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sinfo @ 2026-11-10 11:26 UTC (permalink / raw)
To: Alex Markuze
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, netdev, linux-kernel
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-10 11:26 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 11:23 [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 1/4] net: preserve splice iterator progress on rejection Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 2/4] net: skb_splice_from_iter(): group consecutive pages of a folio Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 3/4] net: skb_splice_from_iter(): walk ITER_BVEC segments directly Alex Markuze
2026-11-10 11:23 ` [RFC PATCH net-next 4/4] net: add basic splice KUnit tests Alex Markuze
2026-11-10 11:26 ` [RFC PATCH net-next 0/4] net: streamline skb_splice_from_iter() netdev-bot+sinfo
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®