[PATCH net-next v2 3/4] net: add skb_frags_can_coalesce() helper

From: Mina Almasry

Date: Mon Nov 02 2026 - 18:24:17 EST


Add skb_has_net_iovs(skb), assuming skb->unreadable and net_iov-backed
frags are equivalent for now (with a comment noting the two can be
decoupled in the future if readable net_iovs are introduced).

Also add skb_mem_provider_compat() and skb_frags_can_coalesce() so all
multi-skb coalescing, shifting, and GRO paths can share a single check
across pp_recycle, skb_zcopy(), and fragment memory providers:
- empty 0-length skbs are compatible without mixing readable payload
- page vs net_iov is rejected via skb_has_net_iovs() (0 pointer chases)
- page vs page is allowed via !skb_has_net_iovs() (0 pointer chases)
- net_iov vs net_iov compares mp_priv (walking frag_list if nr_frags is 0)

Signed-off-by: Mina Almasry <almasrymina@xxxxxxxxxx>
---
include/linux/skbuff.h | 58 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)

diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 6787fcf6ac3e4..1e6e919c656d5 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -1882,6 +1882,16 @@ static inline bool skb_frags_readable(const struct sk_buff *skb)
return !skb->unreadable;
}

+/* Return true if frags in this skb are backed by net_iovs.
+ * Assumes skb->unreadable and net_iov-backed frags are equivalent for now;
+ * the two can be decoupled in the future if readable net_iovs in skbs are
+ * introduced.
+ */
+static inline bool skb_has_net_iovs(const struct sk_buff *skb)
+{
+ return skb->unreadable;
+}
+
static inline void skb_mark_not_on_list(struct sk_buff *skb)
{
skb->next = NULL;
@@ -3763,6 +3773,54 @@ static inline void *skb_frag_address(const skb_frag_t *frag)
return page_address(skb_frag_page(frag)) + skb_frag_off(frag);
}

+static inline void *skb_mem_provider_priv(const struct sk_buff *skb)
+{
+ netmem_ref netmem;
+
+ if (!skb_has_net_iovs(skb) || !skb_shinfo(skb)->nr_frags)
+ return NULL;
+
+ netmem = skb_frag_netmem(&skb_shinfo(skb)->frags[0]);
+ return netmem_to_net_iov(netmem)->mp_priv;
+}
+
+/* Return true if the memory providers of @skb1 and @skb2 are compatible
+ * for coalescing. For any struct page-backed provider (the buddy allocator
+ * or a future provider handing out struct pages), coalescing is assumed
+ * feasible. For net_iov-backed providers, the exact mp_priv pointer must
+ * match.
+ */
+static inline bool skb_mem_provider_compat(const struct sk_buff *skb1,
+ const struct sk_buff *skb2)
+{
+ void *priv1, *priv2;
+
+ if (!skb1->len || !skb2->len)
+ return true;
+
+ if (skb_has_net_iovs(skb1) != skb_has_net_iovs(skb2))
+ return false;
+
+ if (!skb_has_net_iovs(skb1))
+ return true;
+
+ priv1 = skb_mem_provider_priv(skb1);
+ priv2 = skb_mem_provider_priv(skb2);
+ return priv1 && priv1 == priv2;
+}
+
+static inline bool skb_frags_can_coalesce(struct sk_buff *to,
+ struct sk_buff *from)
+{
+ if (to->pp_recycle != from->pp_recycle)
+ return false;
+
+ if (skb_zcopy(to) || skb_zcopy(from))
+ return false;
+
+ return skb_mem_provider_compat(to, from);
+}
+
/**
* skb_frag_address_safe - gets the address of the data contained in a paged fragment
* @frag: the paged fragment buffer
--
2.56.0.385.gac671c819a-goog