* [PATCH net-next 1/7] bnxt_en: let the stack segment GSO skbs with too many frags
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 19:14 ` [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize Stanislav Fomichev
` (6 subsequent siblings)
7 siblings, 0 replies; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
With MAX_SKB_FRAGS > TX_MAX_FRAGS, the xmit path linearizes skbs with
more frags than a packet can have BDs. For GSO skbs, linearizing needs a
large allocation, can produce a head which doesn't fit in a BD with BIG
TCP, and always fails for unreadable skbs. Clear the GSO features of
such skbs in ndo_features_check instead, so that the stack segments
them. Non-GSO skbs are still linearized.
Suggested-by: Eric Dumazet <edumazet@google.com>
Link: https://lore.kernel.org/all/CANn89i+ps_CozfOyRnKav0_LUmSekJ9ExB5JkDbTAVf_ss_98g@mail.gmail.com/
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 757c7266ce15..e7a4190a8c78 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -14300,6 +14300,11 @@ static netdev_features_t bnxt_features_check(struct sk_buff *skb,
skb_shinfo(skb)->gso_size))
features &= ~NETIF_F_GSO_MASK;
+#if (MAX_SKB_FRAGS > TX_MAX_FRAGS)
+ if (skb_shinfo(skb)->nr_frags > TX_MAX_FRAGS)
+ features &= ~NETIF_F_GSO_MASK;
+#endif
+
switch (vlan_get_protocol(skb)) {
case htons(ETH_P_IP):
if (!skb->encapsulation)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
2026-11-02 19:14 ` [PATCH net-next 1/7] bnxt_en: let the stack segment GSO skbs with too many frags Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 20:22 ` Mina Almasry
2026-11-02 19:14 ` [PATCH net-next 3/7] net: devmem: use niov_shift in net_devmem_get_niov_at() Stanislav Fomichev
` (5 subsequent siblings)
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
truesize isn't well defined for devmem TX frags: they point into a
dmabuf which stays mapped for the lifetime of the binding, and freeing
the skb releases no memory. The charge only feeds sk_sndbuf and TSQ,
which limit queued bytes.
Charge the frag length instead of PAGE_SIZE:
- TCP uncharges in bytes (tcp_trim_head(), tcp_fragment(),
tso_fragment()), so the charge must not be smaller than the frag.
PAGE_SIZE only guarantees that while net_iovs are PAGE_SIZE.
- Frags shorter than a page now count their length, not a page.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/core/datagram.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/core/datagram.c b/net/core/datagram.c
index ecc30a6f8302..489d92fe6dae 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -737,7 +737,7 @@ zerocopy_fill_skb_from_devmem(struct sk_buff *skb, struct iov_iter *from,
netmem_inc_ref(net_iov_to_netmem(niov));
skb_add_rx_frag_netmem(skb, i, net_iov_to_netmem(niov), off,
- size, PAGE_SIZE);
+ size, size);
iov_iter_advance(from, size);
length -= size;
i++;
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize
2026-11-02 19:14 ` [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize Stanislav Fomichev
@ 2026-11-02 20:22 ` Mina Almasry
2026-11-02 23:23 ` Stanislav Fomichev
0 siblings, 1 reply; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:22 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> truesize isn't well defined for devmem TX frags: they point into a
> dmabuf which stays mapped for the lifetime of the binding, and freeing
> the skb releases no memory. The charge only feeds sk_sndbuf and TSQ,
> which limit queued bytes.
>
> Charge the frag length instead of PAGE_SIZE:
> - TCP uncharges in bytes (tcp_trim_head(), tcp_fragment(),
> tso_fragment()), so the charge must not be smaller than the frag.
> PAGE_SIZE only guarantees that while net_iovs are PAGE_SIZE.
> - Frags shorter than a page now count their length, not a page.
>
I don't know about this patch. The justification reads a bit like.
"net-iov charging is weird and broken anyway, so we can do whatever we
want. Charge size instead of PAGE_SIZE for no reason."
I agree that charging for net-iov is extremely weird. Neither of
today's net-iov types actually allocates any memory. Of course when I
was developing the original infra, I took every shortcut I could and
just charged net-iov as regular memory, but that doesn't mean we can
do whatever we want.
I think either:
1. say net-iovs are not really occupied memory, so do a global cleanup
that charges 0 for all net-iovs (optionally making it conditional on
the net-iov type, so in the future someone can easily add a net-iov
that actually can be charged)", or
2. change the net-iov size to be actually size instead of PAGE_SIZE,
and keep the current 'we charge net-iovs even though they're not
really memory', and then this patch makes sense.
But I welcome disagreements.
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize
2026-11-02 20:22 ` Mina Almasry
@ 2026-11-02 23:23 ` Stanislav Fomichev
0 siblings, 0 replies; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 23:23 UTC (permalink / raw)
To: Mina Almasry
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On 11/02, Mina Almasry wrote:
> On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
> >
> > truesize isn't well defined for devmem TX frags: they point into a
> > dmabuf which stays mapped for the lifetime of the binding, and freeing
> > the skb releases no memory. The charge only feeds sk_sndbuf and TSQ,
> > which limit queued bytes.
> >
> > Charge the frag length instead of PAGE_SIZE:
> > - TCP uncharges in bytes (tcp_trim_head(), tcp_fragment(),
> > tso_fragment()), so the charge must not be smaller than the frag.
> > PAGE_SIZE only guarantees that while net_iovs are PAGE_SIZE.
> > - Frags shorter than a page now count their length, not a page.
> >
>
> I don't know about this patch. The justification reads a bit like.
> "net-iov charging is weird and broken anyway, so we can do whatever we
> want. Charge size instead of PAGE_SIZE for no reason."
>
> I agree that charging for net-iov is extremely weird. Neither of
> today's net-iov types actually allocates any memory. Of course when I
> was developing the original infra, I took every shortcut I could and
> just charged net-iov as regular memory, but that doesn't mean we can
> do whatever we want.
>
> I think either:
>
> 1. say net-iovs are not really occupied memory, so do a global cleanup
> that charges 0 for all net-iovs (optionally making it conditional on
> the net-iov type, so in the future someone can easily add a net-iov
> that actually can be charged)", or
> 2. change the net-iov size to be actually size instead of PAGE_SIZE,
> and keep the current 'we charge net-iovs even though they're not
> really memory', and then this patch makes sense.
SG, let me try to explain it better in the commit msg. I don't think
we can charge 0 because the uncharge path is non-zero. It currently
works ok because skb frag uncharges the small error that can accumulate.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 3/7] net: devmem: use niov_shift in net_devmem_get_niov_at()
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
2026-11-02 19:14 ` [PATCH net-next 1/7] bnxt_en: let the stack segment GSO skbs with too many frags Stanislav Fomichev
2026-11-02 19:14 ` [PATCH net-next 2/7] net: devmem: charge the frag length to TX skb truesize Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 20:24 ` Mina Almasry
2026-11-02 19:14 ` [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit Stanislav Fomichev
` (4 subsequent siblings)
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
TX bindings use PAGE_SIZE net_iovs, and the TX lookup hard-codes it.
Derive the index and offset from binding->niov_shift instead, so TX
bindings can use larger net_iovs. No functional change, TX bindings are
still created with PAGE_SHIFT.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/core/devmem.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 32fafbfa1359..6339f221bc82 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -455,13 +455,15 @@ struct net_iov *
net_devmem_get_niov_at(struct net_devmem_dmabuf_binding *binding,
size_t virt_addr, size_t *off, size_t *size)
{
+ size_t niov_size = 1UL << binding->niov_shift;
+
if (virt_addr >= binding->dmabuf->size)
return NULL;
- *off = virt_addr % PAGE_SIZE;
- *size = PAGE_SIZE - *off;
+ *off = virt_addr & (niov_size - 1);
+ *size = niov_size - *off;
- return binding->tx_vec[virt_addr / PAGE_SIZE];
+ return binding->tx_vec[virt_addr >> binding->niov_shift];
}
/*** "Dmabuf devmem memory provider" ***/
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 3/7] net: devmem: use niov_shift in net_devmem_get_niov_at()
2026-11-02 19:14 ` [PATCH net-next 3/7] net: devmem: use niov_shift in net_devmem_get_niov_at() Stanislav Fomichev
@ 2026-11-02 20:24 ` Mina Almasry
0 siblings, 0 replies; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:24 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> TX bindings use PAGE_SIZE net_iovs, and the TX lookup hard-codes it.
> Derive the index and offset from binding->niov_shift instead, so TX
> bindings can use larger net_iovs. No functional change, TX bindings are
> still created with PAGE_SHIFT.
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
> ---
> net/core/devmem.c | 8 +++++---
> 1 file changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index 32fafbfa1359..6339f221bc82 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c
> @@ -455,13 +455,15 @@ struct net_iov *
> net_devmem_get_niov_at(struct net_devmem_dmabuf_binding *binding,
> size_t virt_addr, size_t *off, size_t *size)
> {
> + size_t niov_size = 1UL << binding->niov_shift;
I always dreamed of net-iovs can be non-power of 2 and also can vary
in size inside the same binding (no reason why not), but this is
providing excellent value anyway.
Reviewed-by: Mina Almasry <almasrymina@google.com>
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
` (2 preceding siblings ...)
2026-11-02 19:14 ` [PATCH net-next 3/7] net: devmem: use niov_shift in net_devmem_get_niov_at() Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 20:31 ` Mina Almasry
2026-11-02 19:14 ` [PATCH net-next 5/7] bnxt_en: limit netmem TX frags to 32KB Stanislav Fomichev
` (3 subsequent siblings)
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
Devmem TX frags are bounded only by the net_iov size, which is about
to grow up to 2MB, more than some devices can describe in a single TX
descriptor.
Add netmem_tx_max_frag_size, U32_MAX by default, and cap devmem TX
frags to the limit of the device the binding is attached to.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
Documentation/networking/net_cachelines/net_device.rst | 1 +
Documentation/networking/netmem.rst | 6 ++++++
include/linux/netdevice.h | 2 ++
net/core/datagram.c | 1 +
net/core/dev.c | 1 +
net/core/devmem.c | 1 +
net/core/devmem.h | 1 +
7 files changed, 13 insertions(+)
diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst
index 8eceaa80b686..4f5fd5b99154 100644
--- a/Documentation/networking/net_cachelines/net_device.rst
+++ b/Documentation/networking/net_cachelines/net_device.rst
@@ -76,6 +76,7 @@ unsigned_short dev_id
unsigned_short dev_port
spinlock_t addr_list_lock
int irq
+u32 netmem_tx_max_frag_size
struct netdev_hw_addr_list uc
struct netdev_hw_addr_list mc
struct netdev_hw_addr_list dev_addrs
diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst
index 6871dcdd700c..04861c60cfa1 100644
--- a/Documentation/networking/netmem.rst
+++ b/Documentation/networking/netmem.rst
@@ -155,3 +155,9 @@ Driver TX Requirements
- `NETMEM_TX_NO_DMA`: for virtual or passthrough devices that do
not DMA, but still support handling of netmem-backed skbs.
+
+3. Drivers which limit the size of a single TX buffer should set
+ `netdev->netmem_tx_max_frag_size` to that limit. Devmem TX frags are
+ capped at it, otherwise a frag can span a whole net_iov, which may be
+ much larger than a page. The default is U32_MAX, the value is read when
+ the dmabuf binding is created.
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index c0ae442c4344..4453b80bf7ea 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1845,6 +1845,7 @@ enum netdev_reg_state {
* drivers. Mainly used by logical interfaces, such as
* bonding and tunnels
* @netmem_tx: device netmem TX mode
+ * @netmem_tx_max_frag_size: max size of a netmem TX frag
* @pacing_offload: enable EDT pacing offload.
*
* @name: This is the first field of the "visible" part of this structure
@@ -2342,6 +2343,7 @@ struct net_device {
unsigned short dev_port;
int irq;
u32 priv_len;
+ u32 netmem_tx_max_frag_size;
spinlock_t addr_list_lock;
diff --git a/net/core/datagram.c b/net/core/datagram.c
index 489d92fe6dae..a8d3f87d38c5 100644
--- a/net/core/datagram.c
+++ b/net/core/datagram.c
@@ -734,6 +734,7 @@ zerocopy_fill_skb_from_devmem(struct sk_buff *skb, struct iov_iter *from,
size = min_t(size_t, size, length);
size = min_t(size_t, size, iter_iov_len(from));
+ size = min_t(size_t, size, binding->tx_max_frag_size);
netmem_inc_ref(net_iov_to_netmem(niov));
skb_add_rx_frag_netmem(skb, i, net_iov_to_netmem(niov), off,
diff --git a/net/core/dev.c b/net/core/dev.c
index 3ac700d37e59..ba2f887dcd39 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -12350,6 +12350,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
dev->gro_ipv4_max_size = GRO_LEGACY_MAX_SIZE;
dev->tso_max_size = TSO_LEGACY_MAX_SIZE;
dev->tso_max_segs = TSO_MAX_SEGS;
+ dev->netmem_tx_max_frag_size = U32_MAX;
dev->upper_level = 1;
dev->lower_level = 1;
#ifdef CONFIG_LOCKDEP
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 6339f221bc82..19c039ed2cb2 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -218,6 +218,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
binding->dev = dev;
binding->vdev = vdev;
+ binding->tx_max_frag_size = dev->netmem_tx_max_frag_size;
write_pnet(&binding->net, sock_net(nl_sk));
binding->uid = sk_uid(nl_sk);
binding->niov_shift = niov_shift;
diff --git a/net/core/devmem.h b/net/core/devmem.h
index 8fa089776610..97ff1f13fb84 100644
--- a/net/core/devmem.h
+++ b/net/core/devmem.h
@@ -79,6 +79,7 @@ struct net_devmem_dmabuf_binding {
struct net_iov **tx_vec;
unsigned int niov_shift;
+ u32 tx_max_frag_size;
struct work_struct unbind_w;
};
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit
2026-11-02 19:14 ` [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit Stanislav Fomichev
@ 2026-11-02 20:31 ` Mina Almasry
2026-11-02 23:27 ` Stanislav Fomichev
0 siblings, 1 reply; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:31 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> Devmem TX frags are bounded only by the net_iov size, which is about
> to grow up to 2MB, more than some devices can describe in a single TX
> descriptor.
>
> Add netmem_tx_max_frag_size, U32_MAX by default, and cap devmem TX
> frags to the limit of the device the binding is attached to.
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
Abstractly LGTM but I'm not sure why this is TX-specific. Didn't we
add non-PAGE_SIZE rx net-iovs? This should have been a problem then.
Either you know what you're doing (maybe document in the commit
message) or we need some more alignment on RX and TX.
TBH I was dissapointed when LLMs started reporting that
binding->vdev/dev are treated very differently for RX and TX and I
have to write a cleanup that consolidates this:
https://github.com/mina/linux/compare/devmem-teardown-v4...devmem-lease-bind-map-dev
I think maybe my reviews and yours maybe should be more mindful about
keeping these memory things consolidated and as much as possible not
RX/TX specific.
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit
2026-11-02 20:31 ` Mina Almasry
@ 2026-11-02 23:27 ` Stanislav Fomichev
0 siblings, 0 replies; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 23:27 UTC (permalink / raw)
To: Mina Almasry
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On 11/02, Mina Almasry wrote:
> On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
> >
> > Devmem TX frags are bounded only by the net_iov size, which is about
> > to grow up to 2MB, more than some devices can describe in a single TX
> > descriptor.
> >
> > Add netmem_tx_max_frag_size, U32_MAX by default, and cap devmem TX
> > frags to the limit of the device the binding is attached to.
> >
> > Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
>
> Abstractly LGTM but I'm not sure why this is TX-specific. Didn't we
> add non-PAGE_SIZE rx net-iovs? This should have been a problem then.
> Either you know what you're doing (maybe document in the commit
> message) or we need some more alignment on RX and TX.
>
> TBH I was dissapointed when LLMs started reporting that
> binding->vdev/dev are treated very differently for RX and TX and I
> have to write a cleanup that consolidates this:
>
> https://github.com/mina/linux/compare/devmem-teardown-v4...devmem-lease-bind-map-dev
>
> I think maybe my reviews and yours maybe should be more mindful about
> keeping these memory things consolidated and as much as possible not
> RX/TX specific.
This patch is mostly a bandaid for bnxt which can't express sizes
longer than 2^16-1 in its tx descriptors. And rx is even more restricted
I think? Up to 32K HW GRO aggregated?
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 5/7] bnxt_en: limit netmem TX frags to 32KB
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
` (3 preceding siblings ...)
2026-11-02 19:14 ` [PATCH net-next 4/7] net: devmem: add a per-device netmem TX frag size limit Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 20:31 ` Mina Almasry
2026-11-02 19:14 ` [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table Stanislav Fomichev
` (2 subsequent siblings)
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
The TX BD length field is 16 bits wide. Limit netmem TX frags to the
largest power of 2 which fits, which keeps 4K aligned frags 4K aligned.
With 64K pages this already applies to PAGE_SIZE net_iovs, whose 64KB
frags would otherwise wrap the BD length to 0 with BIG TCP.
Tested on a 50Gb/s NIC: devmem TX from a 2MB hugepage-backed udmabuf
runs at line rate with 2-3 frags per 64KB skb, none above 32KB.
kperf output (sender side, 1 flow):
client: == Source <sender>
client: Tx 46.358 Gbps (174483046400 bytes in 30110590 usec)
client: Rx 0.000 Gbps (0 bytes in 30110590 usec)
client: TCP retrans reord rtt rttvar d_ce snd_wnd cwnd
client: 729 153 599 48 0 13893632 3147
client: net CPU 71: usr: 0.03% sys: 0.06% idle:67.62% iow: 0.00% irq: 1.12% sirq:31.14%
client: app CPU 72: usr: 0.16% sys: 6.01% idle:90.66% iow: 0.00% irq: 0.83% sirq: 2.32%
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index e7a4190a8c78..c8a887facc20 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -17439,6 +17439,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (BNXT_SUPPORTS_QUEUE_API(bp))
dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops;
dev->netmem_tx = NETMEM_TX_DMA;
+ dev->netmem_tx_max_frag_size = SZ_32K;
rc = register_netdev(dev);
if (rc)
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 5/7] bnxt_en: limit netmem TX frags to 32KB
2026-11-02 19:14 ` [PATCH net-next 5/7] bnxt_en: limit netmem TX frags to 32KB Stanislav Fomichev
@ 2026-11-02 20:31 ` Mina Almasry
0 siblings, 0 replies; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:31 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> The TX BD length field is 16 bits wide. Limit netmem TX frags to the
> largest power of 2 which fits, which keeps 4K aligned frags 4K aligned.
>
> With 64K pages this already applies to PAGE_SIZE net_iovs, whose 64KB
> frags would otherwise wrap the BD length to 0 with BIG TCP.
>
> Tested on a 50Gb/s NIC: devmem TX from a 2MB hugepage-backed udmabuf
> runs at line rate with 2-3 frags per 64KB skb, none above 32KB.
>
> kperf output (sender side, 1 flow):
>
> client: == Source <sender>
> client: Tx 46.358 Gbps (174483046400 bytes in 30110590 usec)
> client: Rx 0.000 Gbps (0 bytes in 30110590 usec)
> client: TCP retrans reord rtt rttvar d_ce snd_wnd cwnd
> client: 729 153 599 48 0 13893632 3147
> client: net CPU 71: usr: 0.03% sys: 0.06% idle:67.62% iow: 0.00% irq: 1.12% sirq:31.14%
> client: app CPU 72: usr: 0.16% sys: 6.01% idle:90.66% iow: 0.00% irq: 0.83% sirq: 2.32%
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
Acked-by: Mina Almasry <almasrymina@google.com>
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
` (4 preceding siblings ...)
2026-11-02 19:14 ` [PATCH net-next 5/7] bnxt_en: limit netmem TX frags to 32KB Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-02 20:40 ` Mina Almasry
2026-11-02 19:14 ` [PATCH net-next 7/7] selftests: drv-net: devmem: TX from a hugepage-backed dmabuf Stanislav Fomichev
2026-11-02 20:07 ` [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Mina Almasry
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
PAGE_SIZE TX net_iovs cap devmem skbs at MAX_SKB_FRAGS pages (~68KB),
which makes BIG TCP ineffective.
TX net_iovs only need to be DMA contiguous. Use the largest power of 2,
up to 2MB, which aligns every DMA segment of the dmabuf:
- 2MB hugepage-backed udmabufs get 2MB net_iovs
- no UAPI change, TX addresses are dmabuf offsets
- skbs are still bounded by TCP's size_goal, only frags get larger
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
net/core/devmem.c | 28 ++++++++++++++++++++++++++--
net/core/netdev-genl.c | 2 +-
2 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/net/core/devmem.c b/net/core/devmem.c
index 19c039ed2cb2..98c9594dbf99 100644
--- a/net/core/devmem.c
+++ b/net/core/devmem.c
@@ -181,6 +181,20 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
return err;
}
+static bool net_devmem_sgt_aligned(struct sg_table *sgt, size_t size)
+{
+ struct scatterlist *sg;
+ unsigned int i;
+
+ for_each_sgtable_dma_sg(sgt, sg, i) {
+ if (!IS_ALIGNED(sg_dma_address(sg), size) ||
+ !IS_ALIGNED(sg_dma_len(sg), size))
+ return false;
+ }
+ return true;
+}
+
+/* niov_shift == 0 picks the net_iov size from the dmabuf, TX only */
struct net_devmem_dmabuf_binding *
net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
struct device *dma_dev,
@@ -191,11 +205,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
struct netlink_ext_ack *extack)
{
struct net_devmem_dmabuf_binding *binding;
- size_t niov_size = 1UL << niov_shift;
static u32 id_alloc_next;
struct scatterlist *sg;
struct dma_buf *dmabuf;
unsigned int sg_idx;
+ size_t niov_size;
size_t niov_idx;
size_t i;
long err;
@@ -221,7 +235,6 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
binding->tx_max_frag_size = dev->netmem_tx_max_frag_size;
write_pnet(&binding->net, sock_net(nl_sk));
binding->uid = sk_uid(nl_sk);
- binding->niov_shift = niov_shift;
xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
err = percpu_ref_init(&binding->ref,
@@ -266,6 +279,17 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
goto err_unmap;
}
+ if (!niov_shift) {
+ niov_size = SZ_2M;
+ while (niov_size > PAGE_SIZE &&
+ (!IS_ALIGNED(dmabuf->size, niov_size) ||
+ !net_devmem_sgt_aligned(binding->sgt, niov_size)))
+ niov_size /= 2;
+ niov_shift = ilog2(niov_size);
+ }
+ binding->niov_shift = niov_shift;
+ niov_size = 1UL << niov_shift;
+
if (!IS_ALIGNED(dmabuf->size, niov_size)) {
err = -EINVAL;
NL_SET_ERR_MSG_FMT(extack,
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 384fdc5ae514..bfd229bd31ca 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -1261,7 +1261,7 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info)
binding = net_devmem_bind_dmabuf(bind_dev,
bind_dev != netdev ? netdev : NULL,
dma_dev, DMA_TO_DEVICE, dmabuf_fd,
- PAGE_SHIFT, priv, NETLINK_CB(skb).sk,
+ 0, priv, NETLINK_CB(skb).sk,
info->extack);
if (IS_ERR(binding)) {
err = PTR_ERR(binding);
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table
2026-11-02 19:14 ` [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table Stanislav Fomichev
@ 2026-11-02 20:40 ` Mina Almasry
2026-11-02 23:37 ` Stanislav Fomichev
0 siblings, 1 reply; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:40 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> PAGE_SIZE TX net_iovs cap devmem skbs at MAX_SKB_FRAGS pages (~68KB),
> which makes BIG TCP ineffective.
>
> TX net_iovs only need to be DMA contiguous. Use the largest power of 2,
> up to 2MB, which aligns every DMA segment of the dmabuf:
> - 2MB hugepage-backed udmabufs get 2MB net_iovs
> - no UAPI change, TX addresses are dmabuf offsets
> - skbs are still bounded by TCP's size_goal, only frags get larger
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
> ---
> net/core/devmem.c | 28 ++++++++++++++++++++++++++--
> net/core/netdev-genl.c | 2 +-
> 2 files changed, 27 insertions(+), 3 deletions(-)
>
> diff --git a/net/core/devmem.c b/net/core/devmem.c
> index 19c039ed2cb2..98c9594dbf99 100644
> --- a/net/core/devmem.c
> +++ b/net/core/devmem.c
> @@ -181,6 +181,20 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
> return err;
> }
>
> +static bool net_devmem_sgt_aligned(struct sg_table *sgt, size_t size)
> +{
> + struct scatterlist *sg;
> + unsigned int i;
> +
> + for_each_sgtable_dma_sg(sgt, sg, i) {
> + if (!IS_ALIGNED(sg_dma_address(sg), size) ||
> + !IS_ALIGNED(sg_dma_len(sg), size))
> + return false;
> + }
> + return true;
> +}
> +
> +/* niov_shift == 0 picks the net_iov size from the dmabuf, TX only */
> struct net_devmem_dmabuf_binding *
> net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> struct device *dma_dev,
> @@ -191,11 +205,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> struct netlink_ext_ack *extack)
> {
> struct net_devmem_dmabuf_binding *binding;
> - size_t niov_size = 1UL << niov_shift;
> static u32 id_alloc_next;
> struct scatterlist *sg;
> struct dma_buf *dmabuf;
> unsigned int sg_idx;
> + size_t niov_size;
> size_t niov_idx;
> size_t i;
> long err;
> @@ -221,7 +235,6 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> binding->tx_max_frag_size = dev->netmem_tx_max_frag_size;
> write_pnet(&binding->net, sock_net(nl_sk));
> binding->uid = sk_uid(nl_sk);
> - binding->niov_shift = niov_shift;
> xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
>
> err = percpu_ref_init(&binding->ref,
> @@ -266,6 +279,17 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> goto err_unmap;
> }
>
> + if (!niov_shift) {
> + niov_size = SZ_2M;
> + while (niov_size > PAGE_SIZE &&
> + (!IS_ALIGNED(dmabuf->size, niov_size) ||
> + !net_devmem_sgt_aligned(binding->sgt, niov_size)))
> + niov_size /= 2;
> + niov_shift = ilog2(niov_size);
> + }
> + binding->niov_shift = niov_shift;
> + niov_size = 1UL << niov_shift;
> +
How come we're not checknig the largest supported size of the netdev here?
Also, maybe should clarify in the cl with the largest supported size,
is it actually only powers of 2 up to that power of 2? Or all sizes up
to that (not-necassirly) power of 2 size?
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table
2026-11-02 20:40 ` Mina Almasry
@ 2026-11-02 23:37 ` Stanislav Fomichev
2026-11-03 2:33 ` Mina Almasry
0 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 23:37 UTC (permalink / raw)
To: Mina Almasry
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On 11/02, Mina Almasry wrote:
> On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
> >
> > PAGE_SIZE TX net_iovs cap devmem skbs at MAX_SKB_FRAGS pages (~68KB),
> > which makes BIG TCP ineffective.
> >
> > TX net_iovs only need to be DMA contiguous. Use the largest power of 2,
> > up to 2MB, which aligns every DMA segment of the dmabuf:
> > - 2MB hugepage-backed udmabufs get 2MB net_iovs
> > - no UAPI change, TX addresses are dmabuf offsets
> > - skbs are still bounded by TCP's size_goal, only frags get larger
> >
> > Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
> > ---
> > net/core/devmem.c | 28 ++++++++++++++++++++++++++--
> > net/core/netdev-genl.c | 2 +-
> > 2 files changed, 27 insertions(+), 3 deletions(-)
> >
> > diff --git a/net/core/devmem.c b/net/core/devmem.c
> > index 19c039ed2cb2..98c9594dbf99 100644
> > --- a/net/core/devmem.c
> > +++ b/net/core/devmem.c
> > @@ -181,6 +181,20 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
> > return err;
> > }
> >
> > +static bool net_devmem_sgt_aligned(struct sg_table *sgt, size_t size)
> > +{
> > + struct scatterlist *sg;
> > + unsigned int i;
> > +
> > + for_each_sgtable_dma_sg(sgt, sg, i) {
> > + if (!IS_ALIGNED(sg_dma_address(sg), size) ||
> > + !IS_ALIGNED(sg_dma_len(sg), size))
> > + return false;
> > + }
> > + return true;
> > +}
> > +
> > +/* niov_shift == 0 picks the net_iov size from the dmabuf, TX only */
> > struct net_devmem_dmabuf_binding *
> > net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > struct device *dma_dev,
> > @@ -191,11 +205,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > struct netlink_ext_ack *extack)
> > {
> > struct net_devmem_dmabuf_binding *binding;
> > - size_t niov_size = 1UL << niov_shift;
> > static u32 id_alloc_next;
> > struct scatterlist *sg;
> > struct dma_buf *dmabuf;
> > unsigned int sg_idx;
> > + size_t niov_size;
> > size_t niov_idx;
> > size_t i;
> > long err;
> > @@ -221,7 +235,6 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > binding->tx_max_frag_size = dev->netmem_tx_max_frag_size;
> > write_pnet(&binding->net, sock_net(nl_sk));
> > binding->uid = sk_uid(nl_sk);
> > - binding->niov_shift = niov_shift;
> > xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
> >
> > err = percpu_ref_init(&binding->ref,
> > @@ -266,6 +279,17 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > goto err_unmap;
> > }
> >
> > + if (!niov_shift) {
> > + niov_size = SZ_2M;
> > + while (niov_size > PAGE_SIZE &&
> > + (!IS_ALIGNED(dmabuf->size, niov_size) ||
> > + !net_devmem_sgt_aligned(binding->sgt, niov_size)))
> > + niov_size /= 2;
> > + niov_shift = ilog2(niov_size);
> > + }
> > + binding->niov_shift = niov_shift;
> > + niov_size = 1UL << niov_shift;
> > +
>
> How come we're not checknig the largest supported size of the netdev here?
>
> Also, maybe should clarify in the cl with the largest supported size,
> is it actually only powers of 2 up to that power of 2? Or all sizes up
> to that (not-necassirly) power of 2 size?
I think these are a bit orthogonal, no? We can have niovs larger than
the HW can tx. If that's the case, we'll just sub-divide when we build
the skb. If we limit to the same chunk the device can handle here,
we might use 2 frags to send single chunk worth of data more often (offset
doesn't have to be aligned to the chunk size).
Assuming device can only send 16K, current patch:
[ 2MB ] 2BM niov
[ 16K ] sending 16K from that 2MB will use one chunk
Limiting chunks to device's chunk size:
[ 16K ][ 16K ] 16K niov
[ 16K ] sending 16K from offset 8K will have to use 2 frags
(obviously, the same thing can happen with 2MB chunks, but less often)
LMK if it does not sound convincing.
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table
2026-11-02 23:37 ` Stanislav Fomichev
@ 2026-11-03 2:33 ` Mina Almasry
0 siblings, 0 replies; 19+ messages in thread
From: Mina Almasry @ 2026-11-03 2:33 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 3:38 PM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> On 11/02, Mina Almasry wrote:
> > On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
> > >
> > > PAGE_SIZE TX net_iovs cap devmem skbs at MAX_SKB_FRAGS pages (~68KB),
> > > which makes BIG TCP ineffective.
> > >
> > > TX net_iovs only need to be DMA contiguous. Use the largest power of 2,
> > > up to 2MB, which aligns every DMA segment of the dmabuf:
> > > - 2MB hugepage-backed udmabufs get 2MB net_iovs
> > > - no UAPI change, TX addresses are dmabuf offsets
> > > - skbs are still bounded by TCP's size_goal, only frags get larger
> > >
> > > Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
> > > ---
> > > net/core/devmem.c | 28 ++++++++++++++++++++++++++--
> > > net/core/netdev-genl.c | 2 +-
> > > 2 files changed, 27 insertions(+), 3 deletions(-)
> > >
> > > diff --git a/net/core/devmem.c b/net/core/devmem.c
> > > index 19c039ed2cb2..98c9594dbf99 100644
> > > --- a/net/core/devmem.c
> > > +++ b/net/core/devmem.c
> > > @@ -181,6 +181,20 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx,
> > > return err;
> > > }
> > >
> > > +static bool net_devmem_sgt_aligned(struct sg_table *sgt, size_t size)
> > > +{
> > > + struct scatterlist *sg;
> > > + unsigned int i;
> > > +
> > > + for_each_sgtable_dma_sg(sgt, sg, i) {
> > > + if (!IS_ALIGNED(sg_dma_address(sg), size) ||
> > > + !IS_ALIGNED(sg_dma_len(sg), size))
> > > + return false;
> > > + }
> > > + return true;
> > > +}
> > > +
> > > +/* niov_shift == 0 picks the net_iov size from the dmabuf, TX only */
> > > struct net_devmem_dmabuf_binding *
> > > net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > > struct device *dma_dev,
> > > @@ -191,11 +205,11 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > > struct netlink_ext_ack *extack)
> > > {
> > > struct net_devmem_dmabuf_binding *binding;
> > > - size_t niov_size = 1UL << niov_shift;
> > > static u32 id_alloc_next;
> > > struct scatterlist *sg;
> > > struct dma_buf *dmabuf;
> > > unsigned int sg_idx;
> > > + size_t niov_size;
> > > size_t niov_idx;
> > > size_t i;
> > > long err;
> > > @@ -221,7 +235,6 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > > binding->tx_max_frag_size = dev->netmem_tx_max_frag_size;
> > > write_pnet(&binding->net, sock_net(nl_sk));
> > > binding->uid = sk_uid(nl_sk);
> > > - binding->niov_shift = niov_shift;
> > > xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC);
> > >
> > > err = percpu_ref_init(&binding->ref,
> > > @@ -266,6 +279,17 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev,
> > > goto err_unmap;
> > > }
> > >
> > > + if (!niov_shift) {
> > > + niov_size = SZ_2M;
> > > + while (niov_size > PAGE_SIZE &&
> > > + (!IS_ALIGNED(dmabuf->size, niov_size) ||
> > > + !net_devmem_sgt_aligned(binding->sgt, niov_size)))
> > > + niov_size /= 2;
> > > + niov_shift = ilog2(niov_size);
> > > + }
> > > + binding->niov_shift = niov_shift;
> > > + niov_size = 1UL << niov_shift;
> > > +
> >
> > How come we're not checknig the largest supported size of the netdev here?
> >
> > Also, maybe should clarify in the cl with the largest supported size,
> > is it actually only powers of 2 up to that power of 2? Or all sizes up
> > to that (not-necassirly) power of 2 size?
>
> I think these are a bit orthogonal, no? We can have niovs larger than
> the HW can tx. If that's the case, we'll just sub-divide when we build
> the skb. If we limit to the same chunk the device can handle here,
> we might use 2 frags to send single chunk worth of data more often (offset
> doesn't have to be aligned to the chunk size).
>
>
> Assuming device can only send 16K, current patch:
>
> [ 2MB ] 2BM niov
> [ 16K ] sending 16K from that 2MB will use one chunk
>
>
> Limiting chunks to device's chunk size:
>
> [ 16K ][ 16K ] 16K niov
> [ 16K ] sending 16K from offset 8K will have to use 2 frags
>
> (obviously, the same thing can happen with 2MB chunks, but less often)
>
> LMK if it does not sound convincing.
Ah, I see, the skb gets segmented somewhere else of course. Derp
Reviewed-by: Mina Almasry <almasrymina@google.com>
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net-next 7/7] selftests: drv-net: devmem: TX from a hugepage-backed dmabuf
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
` (5 preceding siblings ...)
2026-11-02 19:14 ` [PATCH net-next 6/7] net: devmem: size TX net_iovs from the dmabuf sg table Stanislav Fomichev
@ 2026-11-02 19:14 ` Stanislav Fomichev
2026-11-03 2:58 ` Mina Almasry
2026-11-02 20:07 ` [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Mina Almasry
7 siblings, 1 reply; 19+ messages in thread
From: Stanislav Fomichev @ 2026-11-02 19:14 UTC (permalink / raw)
To: netdev
Cc: davem, edumazet, kuba, pabeni, andrew+netdev, edumazet, horms,
corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
almasrymina, bobbyeshleman, asml.silence, daniel, razor, leitao,
linux-doc, linux-kernel, linux-kselftest
TX net_iovs are now sized from the dmabuf layout, but the TX tests
use a regular memfd, which usually keeps them small. Add -H to ncdevmem
to back the udmabuf with 2MB hugepages and a TX test which sends a 3MB
buffer from it, so that frags end at and continue past the 2MB net_iov
boundary. The payload has a 7 byte period to catch offset errors.
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
---
.../selftests/drivers/net/hw/devmem.py | 10 ++++-
.../selftests/drivers/net/hw/devmem_lib.py | 43 +++++++++++++++++--
.../selftests/drivers/net/hw/ncdevmem.c | 8 +++-
.../selftests/drivers/net/hw/nk_devmem.py | 11 ++++-
4 files changed, 63 insertions(+), 9 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/hw/devmem.py b/tools/testing/selftests/drivers/net/hw/devmem.py
index 82c11ffc4add..aa7141fb36f7 100755
--- a/tools/testing/selftests/drivers/net/hw/devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem.py
@@ -3,7 +3,7 @@
from os import path
from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
- run_rx_large_niov)
+ run_rx_large_niov, run_tx_large_niov)
from lib.py import ksft_run, ksft_exit, ksft_disruptive
from lib.py import NetDrvEpEnv
@@ -36,12 +36,18 @@ from lib.py import NetDrvEpEnv
run_rx_large_niov(cfg)
+@ksft_disruptive
+def check_tx_large_niov(cfg) -> None:
+ """Run the devmem TX test from a hugepage-backed dmabuf."""
+ run_tx_large_niov(cfg)
+
+
def main() -> None:
"""Run the devmem test cases."""
with NetDrvEpEnv(__file__) as cfg:
setup_test(cfg, path.abspath(path.dirname(__file__) + "/ncdevmem"))
ksft_run([check_rx, check_tx, check_tx_chunks, check_rx_hds,
- check_rx_large_niov],
+ check_rx_large_niov, check_tx_large_niov],
args=(cfg,))
ksft_exit()
diff --git a/tools/testing/selftests/drivers/net/hw/devmem_lib.py b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
index 71b916c16196..8058fd1ddd50 100644
--- a/tools/testing/selftests/drivers/net/hw/devmem_lib.py
+++ b/tools/testing/selftests/drivers/net/hw/devmem_lib.py
@@ -2,6 +2,7 @@
# pylint: disable=invalid-name,too-many-arguments
"""Shared helpers for devmem TCP selftests."""
+import hashlib
import os
import re
@@ -42,15 +43,17 @@ NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages"
defer(_restore_nr_hugepages, nr_hugepages)
-def _probe_devmem(cfg, rx_page_size):
+def _probe_devmem(cfg, rx_page_size, hugepages=False):
"""Return True if ncdevmem can bind cfg.ifname at @rx_page_size."""
probe_command = f"{cfg.bin_local} -f {cfg.ifname}"
if rx_page_size != RX_PAGE_SIZE_DEFAULT:
probe_command += f" -b {rx_page_size}"
+ if hugepages:
+ probe_command += " -H"
return cmd(probe_command, fail=False, shell=True).ret == 0
-def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT):
+def require_devmem(cfg, rx_page_size=RX_PAGE_SIZE_DEFAULT, hugepages=False):
"""Probe ncdevmem on cfg.ifname and SKIP the test if devmem isn't supported."""
if rx_page_size not in PROBE_RX_PAGE_SIZES:
raise RuntimeError(
@@ -63,10 +66,15 @@ NR_HUGEPAGES_FILE = "/proc/sys/vm/nr_hugepages"
# ncdevmem's queue range and cause the probe to fail.
cfg.devmem_supported = {size: _probe_devmem(cfg, size)
for size in PROBE_RX_PAGE_SIZES}
+ cfg.devmem_hugepages = _probe_devmem(cfg, RX_PAGE_SIZE_DEFAULT,
+ hugepages=True)
if not cfg.devmem_supported[RX_PAGE_SIZE_DEFAULT]:
raise KsftSkipEx("Test requires devmem support")
+ if hugepages and not cfg.devmem_hugepages:
+ raise KsftSkipEx("Test requires a 2MB hugepage-backed dmabuf")
+
if rx_page_size != RX_PAGE_SIZE_DEFAULT:
page_size = os.sysconf("SC_PAGE_SIZE")
if not _is_aligned(rx_page_size, page_size):
@@ -169,7 +177,7 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
return " ".join(parts)
-def ncdevmem_tx(cfg, port, chunk_size=0):
+def ncdevmem_tx(cfg, port, chunk_size=0, hugepages=False):
"""Build the ncdevmem TX send command."""
if hasattr(cfg, 'netns'):
ifname = cfg.nk_guest_ifname
@@ -182,6 +190,8 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
if chunk_size:
extras.append(f"-z {chunk_size}")
+ if hugepages:
+ extras.append("-H")
parts = [cfg.bin_local, f"-f {ifname}", f"-s {addr}",
f"-p {port}", *extras]
@@ -269,6 +279,33 @@ def ncdevmem_rx(cfg, port, verify=True, fail_on_linear=False, flow_steer=False,
ksft_eq(socat.stdout.strip(), "hello\nworld")
+def run_tx_large_niov(cfg):
+ """Run the devmem TX test from a 2MB hugepage-backed dmabuf.
+
+ The kernel sizes TX niovs from the dmabuf layout. Send a single buffer
+ larger than a hugepage so that frags end at and continue past the niov
+ boundary.
+ """
+ require_devmem(cfg, hugepages=True)
+ _reserve_hugepages()
+ configure_nic(cfg)
+ netns = getattr(cfg, "netns", None)
+ port = rand_port()
+ size = 3 * 1024 * 1024
+ pattern = "abcdefg"
+ expected = (pattern * (size // len(pattern) + 1))[:size]
+ tx_cmd = ncdevmem_tx(cfg, port, hugepages=True)
+ # Hash on the remote, bkg() only drains stdout once the command exits.
+ listen_cmd = f"{socat_listen(cfg, port)} | sha256sum"
+
+ with bkg(listen_cmd, host=cfg.remote, exit_wait=True) as socat:
+ wait_port_listen(port, host=cfg.remote)
+ cmd(f"bash -c 'yes {pattern} | tr -d \"\\n\" | head -c {size} | {tx_cmd}'",
+ ns=netns, shell=True)
+ ksft_eq(socat.stdout.split()[0],
+ hashlib.sha256(expected.encode()).hexdigest())
+
+
def run_rx_large_niov(cfg):
"""Run the devmem RX test with a large niov (rx-page-size > PAGE_SIZE).
diff --git a/tools/testing/selftests/drivers/net/hw/ncdevmem.c b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
index 918e3b51f3b8..e29ff27087e7 100644
--- a/tools/testing/selftests/drivers/net/hw/ncdevmem.c
+++ b/tools/testing/selftests/drivers/net/hw/ncdevmem.c
@@ -104,6 +104,7 @@ static uint32_t tx_dmabuf_id;
static int waittime_ms = 500;
static bool fail_on_linear;
static uint32_t rx_page_size;
+static bool use_hugepages;
/* System state loaded by current_config_load() */
#define MAX_FLOWS 8
@@ -162,7 +163,7 @@ static struct memory_buffer *udmabuf_alloc(size_t size)
}
memfd_flags = MFD_ALLOW_SEALING;
- if (rx_page_size > getpagesize())
+ if (use_hugepages || rx_page_size > getpagesize())
memfd_flags |= MFD_HUGETLB | MFD_HUGE_2MB;
ctx->memfd = memfd_create("udmabuf-test", memfd_flags);
@@ -1428,11 +1429,14 @@ int main(int argc, char *argv[])
int is_server = 0, opt;
int ret, err = 1;
- while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:")) != -1) {
+ while ((opt = getopt(argc, argv, "Lls:c:p:v:q:t:f:z:nb:H")) != -1) {
switch (opt) {
case 'L':
fail_on_linear = true;
break;
+ case 'H':
+ use_hugepages = true;
+ break;
case 'l':
is_server = 1;
break;
diff --git a/tools/testing/selftests/drivers/net/hw/nk_devmem.py b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
index 61c6f31f01e5..799030db7c12 100755
--- a/tools/testing/selftests/drivers/net/hw/nk_devmem.py
+++ b/tools/testing/selftests/drivers/net/hw/nk_devmem.py
@@ -4,7 +4,7 @@
import os
from devmem_lib import (setup_test, run_rx, run_tx, run_tx_chunks, run_rx_hds,
- run_rx_large_niov)
+ run_rx_large_niov, run_tx_large_niov)
from lib.py import ksft_run, ksft_exit, ksft_disruptive
from lib.py import NetDrvContEnv
@@ -37,6 +37,12 @@ from lib.py import NetDrvContEnv
run_rx_large_niov(cfg)
+@ksft_disruptive
+def check_nk_tx_large_niov(cfg) -> None:
+ """Run the devmem TX large-niov test through netkit."""
+ run_tx_large_niov(cfg)
+
+
def main() -> None:
"""Run the netkit devmem test cases."""
with NetDrvContEnv(__file__, rxqueues=2, primary_rx_redirect=True) as cfg:
@@ -44,7 +50,8 @@ from lib.py import NetDrvContEnv
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"ncdevmem"))
ksft_run([check_nk_rx, check_nk_tx, check_nk_tx_chunks,
- check_nk_rx_hds, check_nk_rx_large_niov], args=(cfg,))
+ check_nk_rx_hds, check_nk_rx_large_niov,
+ check_nk_tx_large_niov], args=(cfg,))
ksft_exit()
--
2.53.0-Meta
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH net-next 7/7] selftests: drv-net: devmem: TX from a hugepage-backed dmabuf
2026-11-02 19:14 ` [PATCH net-next 7/7] selftests: drv-net: devmem: TX from a hugepage-backed dmabuf Stanislav Fomichev
@ 2026-11-03 2:58 ` Mina Almasry
0 siblings, 0 replies; 19+ messages in thread
From: Mina Almasry @ 2026-11-03 2:58 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> TX net_iovs are now sized from the dmabuf layout, but the TX tests
> use a regular memfd, which usually keeps them small. Add -H to ncdevmem
> to back the udmabuf with 2MB hugepages and a TX test which sends a 3MB
> buffer from it, so that frags end at and continue past the 2MB net_iov
> boundary. The payload has a 7 byte period to catch offset errors.
>
> Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
Acked-by: Mina Almasry <almasrymina@google.com>
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout
2026-11-02 19:14 [PATCH net-next 0/7] net: devmem: size TX net_iovs from the dmabuf layout Stanislav Fomichev
` (6 preceding siblings ...)
2026-11-02 19:14 ` [PATCH net-next 7/7] selftests: drv-net: devmem: TX from a hugepage-backed dmabuf Stanislav Fomichev
@ 2026-11-02 20:07 ` Mina Almasry
7 siblings, 0 replies; 19+ messages in thread
From: Mina Almasry @ 2026-11-02 20:07 UTC (permalink / raw)
To: Stanislav Fomichev
Cc: netdev, davem, edumazet, kuba, pabeni, andrew+netdev, edumazet,
horms, corbet, skhan, rdunlap, michael.chan, pavan.chebbi, sdf,
bobbyeshleman, asml.silence, daniel, razor, leitao, linux-doc,
linux-kernel, linux-kselftest
On Mon, Nov 2, 2026 at 11:14 AM Stanislav Fomichev <sdf.kernel@gmail.com> wrote:
>
> TX bindings use PAGE_SIZE net_iovs, so a frag covers at most a page.
> A 64KB skb needs 16 frags, and MAX_SKB_FRAGS caps devmem skbs at about
> 68KB, which makes BIG TCP ineffective for devmem TX.
>
> This series picks the TX net_iov size from the dmabuf sg table instead:
> the largest power of 2, up to 2MB, which aligns every DMA segment. With
> a 2MB hugepage-backed udmabuf a BIG TCP skb takes a few frags. There's
> no UAPI change since TX addresses are dmabuf offsets.
>
> TCP still bounds skbs by size_goal, so frags get larger, skbs don't.
> A single frag can now be as large as the skb, which matters for
> NETMEM_TX drivers with tso_max_size above 64KB:
> - mlx5: 32-bit length per data segment
> - gve: splits frags into 16KB descriptors
> - fbnic: keeps the default 64KB tso_max_size
> - bnxt: 16-bit BD length, devmem TX frags are capped at 32KB by a new
> per-device limit
>
> Tested on two mlx5 400Gb/s hosts with kperf (udmabuf, 32KB RX
> net_iovs, 2MB hugepage-backed TX buffer), series backported to a 7.1
> kernel on the sender only. 4 flows, MTU 4200, 6 x 60s runs, medians
> (CPU over the 3 runs where every flow got its own CPUs):
>
> before after
> frags per skb (sender) 16-17 1-2
> throughput (Gb/s) 389.6 389.6
> sender sendmsg CPU (sys) 12.0% 6.7% (-44%)
>
> The link is the bottleneck, so the gain is CPU, not throughput.
>
I mean, test single flow please so we see the throughput gain. But
CPU% reduction is good enough reason to merge I guess too.
--
Thanks,
Mina
^ permalink raw reply [flat|nested] 19+ messages in thread