* [PATCH v11 net-next 0/5] psp: Add support for dev-assoc/disassoc
From: Wei Wang @ 2026-04-08 23:14 UTC (permalink / raw)
To: netdev, Jakub Kicinski, Daniel Zahka, Willem de Bruijn, David Wei,
Andrew Lunn, David S . Miller, Eric Dumazet, Simon Horman
Cc: Wei Wang
From: Wei Wang <weibunny@fb.com>
The main purpose of this feature is to associate virtual devices like
veth or netkit with a real PSP device, so we could provide PSP
functionality to the application running with virtual devices.
A typical deployment that works with this feature is as follows:
Host Namespace:
psp_dev_local ←──physically linked──→ psp_dev_peer
(PSP device)
│
│ BPF on psp_dev_local ingress: bpf_redirect_peer() to nk_guest
│
nk_host / veth_host
│
│ BPF on nk_host ingress: bpf_redirect_neigh() to psp_dev_local
│
Guest Namespace (netns):
│
nk_guest / veth_guest
★ PSP application run here
Remote Namespace (_netns):
psp_dev_peer
★ PSP server application runs here
Note:
The general requirement for this feature to work:
For PSP to work correctly, the egress device at validate_xmit_skb()
time must have psp_dev matching the association's psd. Any device
stacking or traffic redirection that changes the egress device will
cause either:
1. TX validation failure (SKB_DROP_REASON_PSP_OUTPUT) - fail-safe
2. RX policy failure after tx-assoc - packets without PSP extension
are rejected by receiver expecting encrypted traffic
Here are a few examples that this feature would not work:
- Bonding with load balancing in round-robin, XOR, 802.3ad mode across
multiple PSP devices, or mixed PSP and non-PSP devices
- Bonding with active-backup mode might work without PSP migration for
failover case.
- ipvlan/macvlan in bridge mode would not work given packets are
loopbacked locally without going through the PSP device.
Changes since v10:
- Corrected typo on patch 1
- Removed the kdoc style comments, Use goto style in
psp_nl_dev_assoc_doit() clean up code, Resolved "TOCTOU" issue in
psp_assoc_device_get_locked() in patch 2
- Replaced psp_devs_lock with a new mutex in
psp_attach_netdev_notifier(), Fixed kdoc style comments in patch 3
Changes since v9:
- Added comments for psp_device_get_locked(), fixed lint issue, fixed
rcu warning in patch 2
- Return error if register_netdevice_notifier() fails in
psp_device_get_locked_dev_assoc() in patch 3
- Removed psp version and ip version for unnecessary tests cases in
patch 5
Changes since v8:
- Rebase
Changes since v7:
- Refactor in patch 1 to have a common helper for
psp_device_get_locked_admin() and psp_device_get_locked()
- Take psd->lock in psp_assoc_device_get_locked() before
psp_dev_check_access() in patch 2
- Use cmpxchg() for assoc_dev->psp_dev assignment when doing dev-assoc
in patch 2
- Check for err for register_netdevice_notifier() in patch 3
- Call psp_attach_netdev_notifier() in pre_doit handler for dev-assoc to
avoid releasing of psd->lock in patch 3
Changes since v6:
- Remove the unused remote_addr, nk_guest_addr and import cmd in patch 5
Changes since v5:
- Remove module_exit() in patch 3
Changes since v4:
- Address compilation warning in patch 3
- Removed the call to psp_nl_has_listeners_any_ns() and check listeners
when looping through netns in psp_nl_notify_dev() in patch 2. This
makes sure we only send notification to netns that has listeners.
Changes since v3:
- Make nsid optional for dev-assoc/dev-disassoc operation, and use
the ns user is in when it's not specified. Also added a test for this.
- Fix psp_nl_notify_dev() to compute the correct nsid relative to the
listener's netns.
- Only register the new netdev event for psp dev cleanup upon the first
successful dev-assoc operation.
- Change the following in selftest:
- Add CONFIG_NETKIT to driver/net's config
- Fall back to NetDrvEpEnv and run basic test cases if NetDrvContEnv
does not load
- Use ksft_variants instead of psp_ip_ver_test_builder
Changes since v2:
- Change the newly added parameter to psp_device_get_and_lock() to
admin in patch 1. Introduce 2 device check functions:
- psp_device_get_locked_admin() for dev-set and key-rotate
- psp_device_get_locked() for all other operations
Flip the logic for checking the dev_assoc_list accordingly in patch 2.
- Move psp_nl_notify_dev() before removing the dev from assoc_dev_list
in psp_nl_dev_disassoc_doit() and correct the typo in commit msg in
patch 2.
- Remove the threading and subprocess and some comment updates in patch 5.
Changes since v1:
- Update the first 4 patches to reflect the latest changes in
https://lore.kernel.org/netdev/20260302053315.1919859-1-dw@davidwei.uk/
- Update patch 9 to add a param to NetDrvContEnv to control the loading
of the tx forwarding bpf program
Wei Wang (5):
psp: add admin/non-admin version of psp_device_get_locked
psp: add new netlink cmd for dev-assoc and dev-disassoc
psp: add a new netdev event for dev unregister
selftests/net: Add bpf skb forwarding program
selftest/net: psp: Add test for dev-assoc/disassoc
Documentation/netlink/specs/psp.yaml | 71 ++-
include/net/psp/types.h | 15 +
include/uapi/linux/psp.h | 13 +
net/psp/psp-nl-gen.c | 36 +-
net/psp/psp-nl-gen.h | 7 +
net/psp/psp.h | 3 +-
net/psp/psp_main.c | 102 +++-
net/psp/psp_nl.c | 374 +++++++++++++-
tools/testing/selftests/drivers/net/config | 1 +
.../drivers/net/hw/nk_redirect.bpf.c | 60 +++
.../selftests/drivers/net/lib/py/env.py | 54 ++-
tools/testing/selftests/drivers/net/psp.py | 457 ++++++++++++++++--
12 files changed, 1132 insertions(+), 61 deletions(-)
create mode 100644 tools/testing/selftests/drivers/net/hw/nk_redirect.bpf.c
--
2.52.0
^ permalink raw reply
* [net-next v10 10/10] selftests: drv-net: Add USO test
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan
Cc: horms, michael.chan, pavan.chebbi, linux-kernel, leon, Joe Damato,
linux-kselftest
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Add a simple test for USO. Tests both ipv4 and ipv6 with several full
segments and a partial segment.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Joe Damato <joe@dama.to>
---
v10:
- Moved test from drivers/net/ to drivers/net/hw/ since it requires real
hardware. No functional changes.
v9:
- Use UDP-LISTEN instead of UDP-RECV in socat receiver (suggested by AI).
- Fixed stale docstring.
- Removed unused return value.
v7:
- Dropped Pavan's Reviewed-by as there were changes.
- Update to use ksft_variants with a generator and a parameterized test_uso
function.
- Save original USO state and restore it at the end of the test.
- Replace sleep with cfg.wait_hw_stats_settle
- Use a socat receiver and check tx stats locally instead of rx on the
remote.
v5:
- Added Pavan's Reviewed-by. No functional changes.
v4:
- Fix python linter issues (unused imports, docstring, etc).
rfcv2:
- new in rfcv2
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/uso.py | 103 ++++++++++++++++++
2 files changed, 104 insertions(+)
create mode 100755 tools/testing/selftests/drivers/net/hw/uso.py
diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile
index deeca3f8d080..5c348c8d72ae 100644
--- a/tools/testing/selftests/drivers/net/hw/Makefile
+++ b/tools/testing/selftests/drivers/net/hw/Makefile
@@ -43,6 +43,7 @@ TEST_PROGS = \
rss_input_xfrm.py \
toeplitz.py \
tso.py \
+ uso.py \
xdp_metadata.py \
xsk_reconfig.py \
#
diff --git a/tools/testing/selftests/drivers/net/hw/uso.py b/tools/testing/selftests/drivers/net/hw/uso.py
new file mode 100755
index 000000000000..6d61e56cab3c
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/hw/uso.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+"""Test USO
+
+Sends large UDP datagrams with UDP_SEGMENT and verifies that the peer
+receives the expected total payload and that the NIC transmitted at least
+the expected number of segments.
+"""
+import random
+import socket
+import string
+
+from lib.py import ksft_run, ksft_exit, KsftSkipEx
+from lib.py import ksft_eq, ksft_ge, ksft_variants, KsftNamedVariant
+from lib.py import NetDrvEpEnv
+from lib.py import bkg, defer, ethtool, ip, rand_port, wait_port_listen
+
+# python doesn't expose this constant, so we need to hardcode it to enable UDP
+# segmentation for large payloads
+UDP_SEGMENT = 103
+
+
+def _send_uso(cfg, ipver, mss, total_payload, port):
+ if ipver == "4":
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ dst = (cfg.remote_addr_v["4"], port)
+ else:
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ dst = (cfg.remote_addr_v["6"], port)
+
+ sock.setsockopt(socket.IPPROTO_UDP, UDP_SEGMENT, mss)
+ payload = ''.join(random.choice(string.ascii_lowercase)
+ for _ in range(total_payload))
+ sock.sendto(payload.encode(), dst)
+ sock.close()
+
+
+def _get_tx_packets(cfg):
+ stats = ip(f"-s link show dev {cfg.ifname}", json=True)[0]
+ return stats['stats64']['tx']['packets']
+
+
+def _test_uso(cfg, ipver, mss, total_payload):
+ cfg.require_ipver(ipver)
+ cfg.require_cmd("socat", remote=True)
+
+ features = ethtool(f"-k {cfg.ifname}", json=True)
+ uso_was_on = features[0]["tx-udp-segmentation"]["active"]
+
+ try:
+ ethtool(f"-K {cfg.ifname} tx-udp-segmentation on")
+ except Exception as exc:
+ raise KsftSkipEx(
+ "Device does not support tx-udp-segmentation") from exc
+ if not uso_was_on:
+ defer(ethtool, f"-K {cfg.ifname} tx-udp-segmentation off")
+
+ expected_segs = (total_payload + mss - 1) // mss
+
+ port = rand_port(stype=socket.SOCK_DGRAM)
+ rx_cmd = f"socat -{ipver} -T 2 -u UDP-LISTEN:{port},reuseport STDOUT"
+
+ tx_before = _get_tx_packets(cfg)
+
+ with bkg(rx_cmd, host=cfg.remote, exit_wait=True) as rx:
+ wait_port_listen(port, proto="udp", host=cfg.remote)
+ _send_uso(cfg, ipver, mss, total_payload, port)
+
+ ksft_eq(len(rx.stdout), total_payload,
+ comment=f"Received {len(rx.stdout)}B, expected {total_payload}B")
+
+ cfg.wait_hw_stats_settle()
+
+ tx_after = _get_tx_packets(cfg)
+ tx_delta = tx_after - tx_before
+
+ ksft_ge(tx_delta, expected_segs,
+ comment=f"Expected >= {expected_segs} tx packets, got {tx_delta}")
+
+
+def _uso_variants():
+ for ipver in ["4", "6"]:
+ yield KsftNamedVariant(f"v{ipver}_partial", ipver, 1400, 1400 * 10 + 500)
+ yield KsftNamedVariant(f"v{ipver}_exact", ipver, 1400, 1400 * 5)
+
+
+@ksft_variants(_uso_variants())
+def test_uso(cfg, ipver, mss, total_payload):
+ """Send a USO datagram and verify the peer receives the expected segments."""
+ _test_uso(cfg, ipver, mss, total_payload)
+
+
+def main() -> None:
+ """Run USO tests."""
+ with NetDrvEpEnv(__file__) as cfg:
+ ksft_run([test_uso],
+ args=(cfg, ))
+ ksft_exit()
+
+
+if __name__ == "__main__":
+ main()
--
2.52.0
^ permalink raw reply related
* [net-next v10 09/10] net: bnxt: Dispatch to SW USO
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Wire in the SW USO path added in preceding commits when hardware USO is
not possible.
When a GSO skb with SKB_GSO_UDP_L4 arrives and the NIC lacks HW USO
capability, redirect to bnxt_sw_udp_gso_xmit() which handles software
segmentation into individual UDP frames submitted directly to the TX
ring.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v5:
- Added Pavan's Reviewed-by. No functional changes.
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 26aae48a7d0e..2715632115a5 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -508,6 +508,11 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
}
}
#endif
+ if (skb_is_gso(skb) &&
+ (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) &&
+ !(bp->flags & BNXT_FLAG_UDP_GSO_CAP))
+ return bnxt_sw_udp_gso_xmit(bp, txr, txq, skb);
+
free_size = bnxt_tx_avail(bp, txr);
if (unlikely(free_size < skb_shinfo(skb)->nr_frags + 2)) {
/* We must have raced with NAPI cleanup */
--
2.52.0
^ permalink raw reply related
* [net-next v10 08/10] net: bnxt: Add SW GSO completion and teardown support
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Update __bnxt_tx_int and bnxt_free_one_tx_ring_skbs to handle SW GSO
segments:
- MID segments: adjust tx_pkts/tx_bytes accounting and skip skb free
(the skb is shared across all segments and freed only once)
- LAST segments: call tso_dma_map_complete() to tear down the IOVA
mapping if one was used. On the fallback path, payload DMA unmapping
is handled by the existing per-BD dma_unmap_len walk.
Both MID and LAST completions advance tx_inline_cons to release the
segment's inline header slot back to the ring.
is_sw_gso is initialized to zero, so the new code paths are not run.
Add logic for feature advertisement and guardrails for ring sizing.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v10:
- Wrap tx_inline_cons in WRITE_ONCE to pair with READ_ONCE in
bnxt_inline_avail.
v9:
- Always allocate header buffer for non-HW-USO NICs. Avoids a possible
NULL deref if USO is toggled off and the device is brought down, up,
and USO is re-enabled (suggested by AI).
- Adjust bnxt_min_tx_desc_cnt to take a feature parameter. This is needed
to prevent stale features from being examined (suggested by AI).
v7:
- Dropped Pavan's Reviewed-by because some changes were made.
- Added helper bnxt_min_tx_desc_cnt to avoid repeated code computing
descriptor counts.
- Updated to use tso_dma_map_complete helper instead of calling the DMA
IOVA API directly.
v5:
- Added Pavan's Reviewed-by. No functional changes.
v3:
- completion paths updated to use DMA IOVA APIs to teardown mappings.
rfcv2:
- Update the shared header buffer consumer on TX completion.
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 75 ++++++++++++++++---
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 19 ++++-
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 9 +++
3 files changed, 92 insertions(+), 11 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index bd93edb09ee0..26aae48a7d0e 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -74,6 +74,8 @@
#include "bnxt_debugfs.h"
#include "bnxt_coredump.h"
#include "bnxt_hwmon.h"
+#include "bnxt_gso.h"
+#include <net/tso.h>
#define BNXT_TX_TIMEOUT (5 * HZ)
#define BNXT_DEF_MSG_ENABLE (NETIF_MSG_DRV | NETIF_MSG_HW | \
@@ -817,12 +819,13 @@ static bool __bnxt_tx_int(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
bool rc = false;
while (RING_TX(bp, cons) != hw_cons) {
- struct bnxt_sw_tx_bd *tx_buf;
+ struct bnxt_sw_tx_bd *tx_buf, *head_buf;
struct sk_buff *skb;
bool is_ts_pkt;
int j, last;
tx_buf = &txr->tx_buf_ring[RING_TX(bp, cons)];
+ head_buf = tx_buf;
skb = tx_buf->skb;
if (unlikely(!skb)) {
@@ -869,6 +872,22 @@ static bool __bnxt_tx_int(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
DMA_TO_DEVICE, 0);
}
}
+
+ if (unlikely(head_buf->is_sw_gso)) {
+ u16 inline_cons = txr->tx_inline_cons + 1;
+
+ WRITE_ONCE(txr->tx_inline_cons, inline_cons);
+ if (head_buf->is_sw_gso == BNXT_SW_GSO_LAST) {
+ tso_dma_map_complete(&pdev->dev,
+ &head_buf->sw_gso_cstate);
+ } else {
+ tx_pkts--;
+ tx_bytes -= skb->len;
+ skb = NULL;
+ }
+ head_buf->is_sw_gso = 0;
+ }
+
if (unlikely(is_ts_pkt)) {
if (BNXT_CHIP_P5(bp)) {
/* PTP worker takes ownership of the skb */
@@ -3412,6 +3431,7 @@ static void bnxt_free_one_tx_ring_skbs(struct bnxt *bp,
for (i = 0; i < max_idx;) {
struct bnxt_sw_tx_bd *tx_buf = &txr->tx_buf_ring[i];
+ struct bnxt_sw_tx_bd *head_buf = tx_buf;
struct sk_buff *skb;
int j, last;
@@ -3466,7 +3486,20 @@ static void bnxt_free_one_tx_ring_skbs(struct bnxt *bp,
DMA_TO_DEVICE, 0);
}
}
- dev_kfree_skb(skb);
+ if (head_buf->is_sw_gso) {
+ u16 inline_cons = txr->tx_inline_cons + 1;
+
+ WRITE_ONCE(txr->tx_inline_cons, inline_cons);
+ if (head_buf->is_sw_gso == BNXT_SW_GSO_LAST) {
+ tso_dma_map_complete(&pdev->dev,
+ &head_buf->sw_gso_cstate);
+ } else {
+ skb = NULL;
+ }
+ head_buf->is_sw_gso = 0;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
}
netdev_tx_reset_queue(netdev_get_tx_queue(bp->dev, idx));
}
@@ -3992,9 +4025,9 @@ static void bnxt_free_tx_inline_buf(struct bnxt_tx_ring_info *txr,
txr->tx_inline_size = 0;
}
-static int __maybe_unused bnxt_alloc_tx_inline_buf(struct bnxt_tx_ring_info *txr,
- struct pci_dev *pdev,
- unsigned int size)
+static int bnxt_alloc_tx_inline_buf(struct bnxt_tx_ring_info *txr,
+ struct pci_dev *pdev,
+ unsigned int size)
{
txr->tx_inline_buf = kmalloc(size, GFP_KERNEL);
if (!txr->tx_inline_buf)
@@ -4097,6 +4130,13 @@ static int bnxt_alloc_tx_rings(struct bnxt *bp)
sizeof(struct tx_push_bd);
txr->data_mapping = cpu_to_le64(mapping);
}
+ if (!(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) {
+ rc = bnxt_alloc_tx_inline_buf(txr, pdev,
+ BNXT_SW_USO_MAX_SEGS *
+ TSO_HEADER_SIZE);
+ if (rc)
+ return rc;
+ }
qidx = bp->tc_to_qidx[j];
ring->queue_id = bp->q_info[qidx].queue_id;
spin_lock_init(&txr->xdp_tx_lock);
@@ -4635,10 +4675,13 @@ static int bnxt_init_rx_rings(struct bnxt *bp)
static int bnxt_init_tx_rings(struct bnxt *bp)
{
+ netdev_features_t features;
u16 i;
+ features = bp->dev->features;
+
bp->tx_wake_thresh = max_t(int, bp->tx_ring_size / 2,
- BNXT_MIN_TX_DESC_CNT);
+ bnxt_min_tx_desc_cnt(bp, features));
for (i = 0; i < bp->tx_nr_rings; i++) {
struct bnxt_tx_ring_info *txr = &bp->tx_ring[i];
@@ -13837,6 +13880,11 @@ static netdev_features_t bnxt_fix_features(struct net_device *dev,
if ((features & NETIF_F_NTUPLE) && !bnxt_rfs_capable(bp, false))
features &= ~NETIF_F_NTUPLE;
+ if ((features & NETIF_F_GSO_UDP_L4) &&
+ !(bp->flags & BNXT_FLAG_UDP_GSO_CAP) &&
+ bp->tx_ring_size < 2 * BNXT_SW_USO_MAX_DESCS)
+ features &= ~NETIF_F_GSO_UDP_L4;
+
if ((bp->flags & BNXT_FLAG_NO_AGG_RINGS) || bp->xdp_prog)
features &= ~(NETIF_F_LRO | NETIF_F_GRO_HW);
@@ -13882,6 +13930,9 @@ static int bnxt_set_features(struct net_device *dev, netdev_features_t features)
int rc = 0;
bool re_init = false;
+ bp->tx_wake_thresh = max_t(int, bp->tx_ring_size / 2,
+ bnxt_min_tx_desc_cnt(bp, features));
+
flags &= ~BNXT_FLAG_ALL_CONFIG_FEATS;
if (features & NETIF_F_GRO_HW)
flags |= BNXT_FLAG_GRO;
@@ -16907,8 +16958,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
NETIF_F_GSO_UDP_TUNNEL_CSUM | NETIF_F_GSO_GRE_CSUM |
NETIF_F_GSO_PARTIAL | NETIF_F_RXHASH |
NETIF_F_RXCSUM | NETIF_F_GRO;
- if (bp->flags & BNXT_FLAG_UDP_GSO_CAP)
- dev->hw_features |= NETIF_F_GSO_UDP_L4;
+ dev->hw_features |= NETIF_F_GSO_UDP_L4;
if (BNXT_SUPPORTS_TPA(bp))
dev->hw_features |= NETIF_F_LRO;
@@ -16941,8 +16991,15 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
dev->priv_flags |= IFF_UNICAST_FLT;
netif_set_tso_max_size(dev, GSO_MAX_SIZE);
- if (bp->tso_max_segs)
+ if (!(bp->flags & BNXT_FLAG_UDP_GSO_CAP)) {
+ u16 max_segs = BNXT_SW_USO_MAX_SEGS;
+
+ if (bp->tso_max_segs)
+ max_segs = min_t(u16, max_segs, bp->tso_max_segs);
+ netif_set_tso_max_segs(dev, max_segs);
+ } else if (bp->tso_max_segs) {
netif_set_tso_max_segs(dev, bp->tso_max_segs);
+ }
dev->xdp_features = NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT |
NETDEV_XDP_ACT_RX_SG;
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
index 6826bf762d26..9ded88196bb4 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c
@@ -33,6 +33,7 @@
#include "bnxt_xdp.h"
#include "bnxt_ptp.h"
#include "bnxt_ethtool.h"
+#include "bnxt_gso.h"
#include "bnxt_nvm_defs.h" /* NVRAM content constant and structure defs */
#include "bnxt_fw_hdr.h" /* Firmware hdr constant and structure defs */
#include "bnxt_coredump.h"
@@ -852,12 +853,18 @@ static int bnxt_set_ringparam(struct net_device *dev,
u8 tcp_data_split = kernel_ering->tcp_data_split;
struct bnxt *bp = netdev_priv(dev);
u8 hds_config_mod;
+ int rc;
if ((ering->rx_pending > BNXT_MAX_RX_DESC_CNT) ||
(ering->tx_pending > BNXT_MAX_TX_DESC_CNT) ||
(ering->tx_pending < BNXT_MIN_TX_DESC_CNT))
return -EINVAL;
+ if ((dev->features & NETIF_F_GSO_UDP_L4) &&
+ !(bp->flags & BNXT_FLAG_UDP_GSO_CAP) &&
+ ering->tx_pending < 2 * BNXT_SW_USO_MAX_DESCS)
+ return -EINVAL;
+
hds_config_mod = tcp_data_split != dev->cfg->hds_config;
if (tcp_data_split == ETHTOOL_TCP_DATA_SPLIT_DISABLED && hds_config_mod)
return -EINVAL;
@@ -882,9 +889,17 @@ static int bnxt_set_ringparam(struct net_device *dev,
bp->tx_ring_size = ering->tx_pending;
bnxt_set_ring_params(bp);
- if (netif_running(dev))
- return bnxt_open_nic(bp, false, false);
+ if (netif_running(dev)) {
+ rc = bnxt_open_nic(bp, false, false);
+ if (rc)
+ return rc;
+ }
+ /* ring size changes may affect features (SW USO requires a minimum
+ * ring size), so recalculate features to ensure the correct features
+ * are blocked/available.
+ */
+ netdev_update_features(dev);
return 0;
}
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
index 6ba8ccc451de..47528c20f311 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
@@ -29,6 +29,15 @@ static inline u16 bnxt_inline_avail(struct bnxt_tx_ring_info *txr)
(u16)(txr->tx_inline_prod - READ_ONCE(txr->tx_inline_cons));
}
+static inline int bnxt_min_tx_desc_cnt(struct bnxt *bp,
+ netdev_features_t features)
+{
+ if (!(bp->flags & BNXT_FLAG_UDP_GSO_CAP) &&
+ (features & NETIF_F_GSO_UDP_L4))
+ return BNXT_SW_USO_MAX_DESCS;
+ return BNXT_MIN_TX_DESC_CNT;
+}
+
netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp,
struct bnxt_tx_ring_info *txr,
struct netdev_queue *txq,
--
2.52.0
^ permalink raw reply related
* [net-next v10 07/10] net: bnxt: Implement software USO
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Implement bnxt_sw_udp_gso_xmit() using the core tso_dma_map API and
the pre-allocated TX inline buffer for per-segment headers.
The xmit path:
1. Calls tso_start() to initialize TSO state
2. Stack-allocates a tso_dma_map and calls tso_dma_map_init() to
DMA-map the linear payload and all frags upfront.
3. For each segment:
- Copies and patches headers via tso_build_hdr() into the
pre-allocated tx_inline_buf (DMA-synced per segment)
- Counts payload BDs via tso_dma_map_count()
- Emits long BD (header) + ext BD + payload BDs
- Payload BDs use tso_dma_map_next() which yields (dma_addr,
chunk_len, mapping_len) tuples.
Header BDs set dma_unmap_len=0 since the inline buffer is pre-allocated
and unmapped only at ring teardown.
Completion state is updated by calling tso_dma_map_completion_save() for
the last segment.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Joe Damato <joe@dama.to>
---
v10:
- Fixes the inline slot check added in the v9. Uses netif_txq_maybe_stop
and an inline helper.
v9:
- Added inline slot check to prevent possible overwriting of in-flight
headers (suggested by AI).
- Set TX_BD_FLAGS_IP_CKSUM conditionally on !tso.ipv6 (suggested by AI).
v8:
- Zero csum fields on per-segment header copy after tso_build_hdr()
instead of on the original skb, avoiding the need for skb_cow_head, as
suggested by Eric Dumazet.
v7:
- Dropped Pavan's Reviewed-by as some changes were made.
- Updated struct bnxt_sw_tx_bd to embed a tso_dma_map_completion_state
struct for tracking completion state.
- Dropped an unnecessary slot check.
- Eliminated an ugly looking ternary to simplify the code.
- Call tso_dma_map_completion_save to update completion state.
v6:
- Addressed Paolo's feedback where the IOVA API could fail transiently,
leaving stale state in iova_state. Fix this by always copying the state,
noting that dma_iova_try_alloc is called unconditionally in the
tso_dma_map_init function (via tso_dma_iova_try), which zeroes the state
even if the API can't be used.
- Since this was a very minor change, I retained Pavan's Reviewed-by.
v5:
- Added __maybe_unused to last_unmap_len and last_unmap_addr to silence a
build warning when CONFIG_NEED_DMA_MAP_STATE is disabled. No functional
changes.
- Added Pavan's Reviewed-by.
v4:
- Fixed the early return issue Pavan pointed out when num_segs <= 1; use the
drop label instead of returning.
v3:
- Added iova_state and iova_total_len to struct bnxt_sw_tx_bd.
- Stores iova_state on the last segment's tx_buf during xmit.
rfcv2:
- set the unmap len on the last descriptor, so that when completions fire
only the last completion unmaps the region.
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 3 +
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 210 ++++++++++++++++++
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 6 +
3 files changed, 219 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index 6b38b84924e0..fe50576ae525 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -11,6 +11,8 @@
#ifndef BNXT_H
#define BNXT_H
+#include <net/tso.h>
+
#define DRV_MODULE_NAME "bnxt_en"
/* DO NOT CHANGE DRV_VER_* defines
@@ -899,6 +901,7 @@ struct bnxt_sw_tx_bd {
u16 rx_prod;
u16 txts_prod;
};
+ struct tso_dma_map_completion_state sw_gso_cstate;
};
#define BNXT_SW_GSO_MID 1
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
index b296769ee4fe..f317f60414e8 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
@@ -19,11 +19,221 @@
#include "bnxt.h"
#include "bnxt_gso.h"
+static u32 bnxt_sw_gso_lhint(unsigned int len)
+{
+ if (len <= 512)
+ return TX_BD_FLAGS_LHINT_512_AND_SMALLER;
+ else if (len <= 1023)
+ return TX_BD_FLAGS_LHINT_512_TO_1023;
+ else if (len <= 2047)
+ return TX_BD_FLAGS_LHINT_1024_TO_2047;
+ else
+ return TX_BD_FLAGS_LHINT_2048_AND_LARGER;
+}
+
netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp,
struct bnxt_tx_ring_info *txr,
struct netdev_queue *txq,
struct sk_buff *skb)
{
+ unsigned int last_unmap_len __maybe_unused = 0;
+ dma_addr_t last_unmap_addr __maybe_unused = 0;
+ struct bnxt_sw_tx_bd *last_unmap_buf = NULL;
+ unsigned int hdr_len, mss, num_segs;
+ struct pci_dev *pdev = bp->pdev;
+ unsigned int total_payload;
+ struct tso_dma_map map;
+ u32 vlan_tag_flags = 0;
+ int i, bds_needed;
+ struct tso_t tso;
+ u16 cfa_action;
+ __le32 csum;
+ u16 prod;
+
+ hdr_len = tso_start(skb, &tso);
+ mss = skb_shinfo(skb)->gso_size;
+ total_payload = skb->len - hdr_len;
+ num_segs = DIV_ROUND_UP(total_payload, mss);
+
+ if (unlikely(num_segs <= 1))
+ goto drop;
+
+ /* Upper bound on the number of descriptors needed.
+ *
+ * Each segment uses 1 long BD + 1 ext BD + payload BDs, which is
+ * at most num_segs + nr_frags (each frag boundary crossing adds at
+ * most 1 extra BD).
+ */
+ bds_needed = 3 * num_segs + skb_shinfo(skb)->nr_frags + 1;
+
+ if (unlikely(bnxt_tx_avail(bp, txr) < bds_needed)) {
+ netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr),
+ bp->tx_wake_thresh);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* BD backpressure alone cannot prevent overwriting in-flight
+ * headers in the inline buffer. Check slot availability directly.
+ */
+ if (!netif_txq_maybe_stop(txq, bnxt_inline_avail(txr),
+ num_segs, num_segs))
+ return NETDEV_TX_BUSY;
+
+ if (unlikely(tso_dma_map_init(&map, &pdev->dev, skb, hdr_len)))
+ goto drop;
+
+ cfa_action = bnxt_xmit_get_cfa_action(skb);
+ if (skb_vlan_tag_present(skb)) {
+ vlan_tag_flags = TX_BD_CFA_META_KEY_VLAN |
+ skb_vlan_tag_get(skb);
+ if (skb->vlan_proto == htons(ETH_P_8021Q))
+ vlan_tag_flags |= 1 << TX_BD_CFA_META_TPID_SHIFT;
+ }
+
+ csum = cpu_to_le32(TX_BD_FLAGS_TCP_UDP_CHKSUM);
+ if (!tso.ipv6)
+ csum |= cpu_to_le32(TX_BD_FLAGS_IP_CKSUM);
+
+ prod = txr->tx_prod;
+
+ for (i = 0; i < num_segs; i++) {
+ unsigned int seg_payload = min_t(unsigned int, mss,
+ total_payload - i * mss);
+ u16 slot = (txr->tx_inline_prod + i) &
+ (BNXT_SW_USO_MAX_SEGS - 1);
+ struct bnxt_sw_tx_bd *tx_buf;
+ unsigned int mapping_len;
+ dma_addr_t this_hdr_dma;
+ unsigned int chunk_len;
+ unsigned int offset;
+ dma_addr_t dma_addr;
+ struct tx_bd *txbd;
+ struct udphdr *uh;
+ void *this_hdr;
+ int bd_count;
+ bool last;
+ u32 flags;
+
+ last = (i == num_segs - 1);
+ offset = slot * TSO_HEADER_SIZE;
+ this_hdr = txr->tx_inline_buf + offset;
+ this_hdr_dma = txr->tx_inline_dma + offset;
+
+ tso_build_hdr(skb, this_hdr, &tso, seg_payload, last);
+
+ /* Zero stale csum fields copied from the original skb;
+ * HW offload recomputes from scratch.
+ */
+ uh = this_hdr + skb_transport_offset(skb);
+ uh->check = 0;
+ if (!tso.ipv6) {
+ struct iphdr *iph = this_hdr + skb_network_offset(skb);
+
+ iph->check = 0;
+ }
+
+ dma_sync_single_for_device(&pdev->dev, this_hdr_dma,
+ hdr_len, DMA_TO_DEVICE);
+
+ bd_count = tso_dma_map_count(&map, seg_payload);
+
+ tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
+ txbd = &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+
+ tx_buf->skb = skb;
+ tx_buf->nr_frags = bd_count;
+ tx_buf->is_push = 0;
+ tx_buf->is_ts_pkt = 0;
+
+ dma_unmap_addr_set(tx_buf, mapping, this_hdr_dma);
+ dma_unmap_len_set(tx_buf, len, 0);
+
+ if (last) {
+ tx_buf->is_sw_gso = BNXT_SW_GSO_LAST;
+ tso_dma_map_completion_save(&map, &tx_buf->sw_gso_cstate);
+ } else {
+ tx_buf->is_sw_gso = BNXT_SW_GSO_MID;
+ }
+
+ flags = (hdr_len << TX_BD_LEN_SHIFT) |
+ TX_BD_TYPE_LONG_TX_BD |
+ TX_BD_CNT(2 + bd_count);
+
+ flags |= bnxt_sw_gso_lhint(hdr_len + seg_payload);
+
+ txbd->tx_bd_len_flags_type = cpu_to_le32(flags);
+ txbd->tx_bd_haddr = cpu_to_le64(this_hdr_dma);
+ txbd->tx_bd_opaque = SET_TX_OPAQUE(bp, txr, prod,
+ 2 + bd_count);
+
+ prod = NEXT_TX(prod);
+ bnxt_init_ext_bd(bp, txr, prod, csum,
+ vlan_tag_flags, cfa_action);
+
+ /* set dma_unmap_len on the LAST BD touching each
+ * region. Since completions are in-order, the last segment
+ * completes after all earlier ones, so the unmap is safe.
+ */
+ while (tso_dma_map_next(&map, &dma_addr, &chunk_len,
+ &mapping_len, seg_payload)) {
+ prod = NEXT_TX(prod);
+ txbd = &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+ tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
+
+ txbd->tx_bd_haddr = cpu_to_le64(dma_addr);
+ dma_unmap_addr_set(tx_buf, mapping, dma_addr);
+ dma_unmap_len_set(tx_buf, len, 0);
+ tx_buf->skb = NULL;
+ tx_buf->is_sw_gso = 0;
+
+ if (mapping_len) {
+ if (last_unmap_buf) {
+ dma_unmap_addr_set(last_unmap_buf,
+ mapping,
+ last_unmap_addr);
+ dma_unmap_len_set(last_unmap_buf,
+ len,
+ last_unmap_len);
+ }
+ last_unmap_addr = dma_addr;
+ last_unmap_len = mapping_len;
+ }
+ last_unmap_buf = tx_buf;
+
+ flags = chunk_len << TX_BD_LEN_SHIFT;
+ txbd->tx_bd_len_flags_type = cpu_to_le32(flags);
+ txbd->tx_bd_opaque = 0;
+
+ seg_payload -= chunk_len;
+ }
+
+ txbd->tx_bd_len_flags_type |=
+ cpu_to_le32(TX_BD_FLAGS_PACKET_END);
+
+ prod = NEXT_TX(prod);
+ }
+
+ if (last_unmap_buf) {
+ dma_unmap_addr_set(last_unmap_buf, mapping, last_unmap_addr);
+ dma_unmap_len_set(last_unmap_buf, len, last_unmap_len);
+ }
+
+ txr->tx_inline_prod += num_segs;
+
+ netdev_tx_sent_queue(txq, skb->len);
+
+ WRITE_ONCE(txr->tx_prod, prod);
+ /* Sync BDs before doorbell */
+ wmb();
+ bnxt_db_write(bp, &txr->tx_db, prod);
+
+ if (unlikely(bnxt_tx_avail(bp, txr) <= bp->tx_wake_thresh))
+ netif_txq_try_stop(txq, bnxt_tx_avail(bp, txr),
+ bp->tx_wake_thresh);
+
+ return NETDEV_TX_OK;
+
+drop:
dev_kfree_skb_any(skb);
dev_core_stats_tx_dropped_inc(bp->dev);
return NETDEV_TX_OK;
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
index f01e8102dcd7..6ba8ccc451de 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
@@ -23,6 +23,12 @@
*/
#define BNXT_SW_USO_MAX_DESCS (3 * BNXT_SW_USO_MAX_SEGS + MAX_SKB_FRAGS + 1)
+static inline u16 bnxt_inline_avail(struct bnxt_tx_ring_info *txr)
+{
+ return BNXT_SW_USO_MAX_SEGS -
+ (u16)(txr->tx_inline_prod - READ_ONCE(txr->tx_inline_cons));
+}
+
netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp,
struct bnxt_tx_ring_info *txr,
struct netdev_queue *txq,
--
2.52.0
^ permalink raw reply related
* [net-next v10 06/10] net: bnxt: Add boilerplate GSO code
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Richard Cochran,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev
Cc: horms, linux-kernel, leon, Joe Damato, bpf
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Add bnxt_gso.c and bnxt_gso.h with a stub bnxt_sw_udp_gso_xmit()
function, SW USO constants (BNXT_SW_USO_MAX_SEGS,
BNXT_SW_USO_MAX_DESCS), and the is_sw_gso field in bnxt_sw_tx_bd
with BNXT_SW_GSO_MID/LAST markers.
The full SW USO implementation will be added in a future commit.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v7:
- Changed the placement of is_sw_gso in struct bnxt_sw_tx_bd to be near
other is_* fields.
- No functional changes.
v5:
- Added Pavan's Reviewed-by. No functional changes.
drivers/net/ethernet/broadcom/bnxt/Makefile | 2 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 4 +++
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 30 ++++++++++++++++++
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 31 +++++++++++++++++++
4 files changed, 66 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
create mode 100644 drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
diff --git a/drivers/net/ethernet/broadcom/bnxt/Makefile b/drivers/net/ethernet/broadcom/bnxt/Makefile
index ba6c239d52fa..debef78c8b6d 100644
--- a/drivers/net/ethernet/broadcom/bnxt/Makefile
+++ b/drivers/net/ethernet/broadcom/bnxt/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_BNXT) += bnxt_en.o
-bnxt_en-y := bnxt.o bnxt_hwrm.o bnxt_sriov.o bnxt_ethtool.o bnxt_dcb.o bnxt_ulp.o bnxt_xdp.o bnxt_ptp.o bnxt_vfr.o bnxt_devlink.o bnxt_dim.o bnxt_coredump.o
+bnxt_en-y := bnxt.o bnxt_hwrm.o bnxt_sriov.o bnxt_ethtool.o bnxt_dcb.o bnxt_ulp.o bnxt_xdp.o bnxt_ptp.o bnxt_vfr.o bnxt_devlink.o bnxt_dim.o bnxt_coredump.o bnxt_gso.o
bnxt_en-$(CONFIG_BNXT_FLOWER_OFFLOAD) += bnxt_tc.o
bnxt_en-$(CONFIG_DEBUG_FS) += bnxt_debugfs.o
bnxt_en-$(CONFIG_BNXT_HWMON) += bnxt_hwmon.o
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index d98a58aa30f6..6b38b84924e0 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -892,6 +892,7 @@ struct bnxt_sw_tx_bd {
struct page *page;
u8 is_ts_pkt;
u8 is_push;
+ u8 is_sw_gso;
u8 action;
unsigned short nr_frags;
union {
@@ -900,6 +901,9 @@ struct bnxt_sw_tx_bd {
};
};
+#define BNXT_SW_GSO_MID 1
+#define BNXT_SW_GSO_LAST 2
+
struct bnxt_sw_rx_bd {
void *data;
u8 *data_ptr;
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
new file mode 100644
index 000000000000..b296769ee4fe
--- /dev/null
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Broadcom NetXtreme-C/E network driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation.
+ */
+
+#include <linux/pci.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/netdev_queues.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/udp.h>
+#include <net/tso.h>
+#include <linux/bnxt/hsi.h>
+
+#include "bnxt.h"
+#include "bnxt_gso.h"
+
+netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp,
+ struct bnxt_tx_ring_info *txr,
+ struct netdev_queue *txq,
+ struct sk_buff *skb)
+{
+ dev_kfree_skb_any(skb);
+ dev_core_stats_tx_dropped_inc(bp->dev);
+ return NETDEV_TX_OK;
+}
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
new file mode 100644
index 000000000000..f01e8102dcd7
--- /dev/null
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Broadcom NetXtreme-C/E network driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation.
+ */
+
+#ifndef BNXT_GSO_H
+#define BNXT_GSO_H
+
+/* Maximum segments the stack may send in a single SW USO skb.
+ * This caps gso_max_segs for NICs without HW USO support.
+ */
+#define BNXT_SW_USO_MAX_SEGS 64
+
+/* Worst-case TX descriptors consumed by one SW USO packet:
+ * Each segment: 1 long BD + 1 ext BD + payload BDs.
+ * Total payload BDs across all segs <= num_segs + nr_frags (each frag
+ * boundary crossing adds at most 1 extra BD).
+ * So: 3 * max_segs + MAX_SKB_FRAGS + 1 = 3 * 64 + 17 + 1 = 210.
+ */
+#define BNXT_SW_USO_MAX_DESCS (3 * BNXT_SW_USO_MAX_SEGS + MAX_SKB_FRAGS + 1)
+
+netdev_tx_t bnxt_sw_udp_gso_xmit(struct bnxt *bp,
+ struct bnxt_tx_ring_info *txr,
+ struct netdev_queue *txq,
+ struct sk_buff *skb);
+
+#endif
--
2.52.0
^ permalink raw reply related
* [net-next v10 05/10] net: bnxt: Add TX inline buffer infrastructure
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Add per-ring pre-allocated inline buffer fields (tx_inline_buf,
tx_inline_dma, tx_inline_size) to bnxt_tx_ring_info and helpers to
allocate and free them. A producer and consumer (tx_inline_prod,
tx_inline_cons) are added to track which slot(s) of the inline buffer
are in-use.
The inline buffer will be used by the SW USO path for pre-allocated,
pre-DMA-mapped per-segment header copies. In the future, this
could be extended to support TX copybreak.
Allocation helper is marked __maybe_unused in this commit because it
will be wired in later.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v5:
- Added Pavan's Reviewed-by. No functional changes.
rfcv2:
- Added a producer and consumer to correctly track the in use header slots.
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 35 +++++++++++++++++++++++
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 6 ++++
2 files changed, 41 insertions(+)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index bc2dac2f137d..bd93edb09ee0 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -3979,6 +3979,39 @@ static int bnxt_alloc_rx_rings(struct bnxt *bp)
return rc;
}
+static void bnxt_free_tx_inline_buf(struct bnxt_tx_ring_info *txr,
+ struct pci_dev *pdev)
+{
+ if (!txr->tx_inline_buf)
+ return;
+
+ dma_unmap_single(&pdev->dev, txr->tx_inline_dma,
+ txr->tx_inline_size, DMA_TO_DEVICE);
+ kfree(txr->tx_inline_buf);
+ txr->tx_inline_buf = NULL;
+ txr->tx_inline_size = 0;
+}
+
+static int __maybe_unused bnxt_alloc_tx_inline_buf(struct bnxt_tx_ring_info *txr,
+ struct pci_dev *pdev,
+ unsigned int size)
+{
+ txr->tx_inline_buf = kmalloc(size, GFP_KERNEL);
+ if (!txr->tx_inline_buf)
+ return -ENOMEM;
+
+ txr->tx_inline_dma = dma_map_single(&pdev->dev, txr->tx_inline_buf,
+ size, DMA_TO_DEVICE);
+ if (dma_mapping_error(&pdev->dev, txr->tx_inline_dma)) {
+ kfree(txr->tx_inline_buf);
+ txr->tx_inline_buf = NULL;
+ return -ENOMEM;
+ }
+ txr->tx_inline_size = size;
+
+ return 0;
+}
+
static void bnxt_free_tx_rings(struct bnxt *bp)
{
int i;
@@ -3997,6 +4030,8 @@ static void bnxt_free_tx_rings(struct bnxt *bp)
txr->tx_push = NULL;
}
+ bnxt_free_tx_inline_buf(txr, pdev);
+
ring = &txr->tx_ring_struct;
bnxt_free_ring(bp, &ring->ring_mem);
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index 83b4136ccd31..d98a58aa30f6 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -996,6 +996,12 @@ struct bnxt_tx_ring_info {
dma_addr_t tx_push_mapping;
__le64 data_mapping;
+ void *tx_inline_buf;
+ dma_addr_t tx_inline_dma;
+ unsigned int tx_inline_size;
+ u16 tx_inline_prod;
+ u16 tx_inline_cons;
+
#define BNXT_DEV_STATE_CLOSING 0x1
u32 dev_state;
--
2.52.0
^ permalink raw reply related
* [net-next v10 04/10] net: bnxt: Use dma_unmap_len for TX completion unmapping
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Store the DMA mapping length in each TX buffer descriptor via
dma_unmap_len_set at submit time, and use dma_unmap_len at completion
time.
This is a no-op for normal packets but prepares for software USO,
where header BDs set dma_unmap_len to 0 because the header buffer
is unmapped collectively rather than per-segment.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v10:
- Wrapped some long lines. No functional changes.
v4:
- Added Pavan's Reviewed-by tag. No functional changes.
rfcv2:
- Use some local variables to shorten long lines. No functional change from
rfcv1.
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 63 +++++++++++++++--------
1 file changed, 41 insertions(+), 22 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index d1f0969b781c..bc2dac2f137d 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -656,6 +656,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
goto tx_free;
dma_unmap_addr_set(tx_buf, mapping, mapping);
+ dma_unmap_len_set(tx_buf, len, len);
flags = (len << TX_BD_LEN_SHIFT) | TX_BD_TYPE_LONG_TX_BD |
TX_BD_CNT(last_frag + 2);
@@ -720,6 +721,7 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
tx_buf = &txr->tx_buf_ring[RING_TX(bp, prod)];
netmem_dma_unmap_addr_set(skb_frag_netmem(frag), tx_buf,
mapping, mapping);
+ dma_unmap_len_set(tx_buf, len, len);
txbd->tx_bd_haddr = cpu_to_le64(mapping);
@@ -809,7 +811,8 @@ static bool __bnxt_tx_int(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
u16 hw_cons = txr->tx_hw_cons;
unsigned int tx_bytes = 0;
u16 cons = txr->tx_cons;
- skb_frag_t *frag;
+ unsigned int dma_len;
+ dma_addr_t dma_addr;
int tx_pkts = 0;
bool rc = false;
@@ -844,19 +847,27 @@ static bool __bnxt_tx_int(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
goto next_tx_int;
}
- dma_unmap_single(&pdev->dev, dma_unmap_addr(tx_buf, mapping),
- skb_headlen(skb), DMA_TO_DEVICE);
+ if (dma_unmap_len(tx_buf, len)) {
+ dma_addr = dma_unmap_addr(tx_buf, mapping);
+ dma_len = dma_unmap_len(tx_buf, len);
+
+ dma_unmap_single(&pdev->dev, dma_addr, dma_len,
+ DMA_TO_DEVICE);
+ }
+
last = tx_buf->nr_frags;
for (j = 0; j < last; j++) {
- frag = &skb_shinfo(skb)->frags[j];
cons = NEXT_TX(cons);
tx_buf = &txr->tx_buf_ring[RING_TX(bp, cons)];
- netmem_dma_unmap_page_attrs(&pdev->dev,
- dma_unmap_addr(tx_buf,
- mapping),
- skb_frag_size(frag),
- DMA_TO_DEVICE, 0);
+ if (dma_unmap_len(tx_buf, len)) {
+ dma_addr = dma_unmap_addr(tx_buf, mapping);
+ dma_len = dma_unmap_len(tx_buf, len);
+
+ netmem_dma_unmap_page_attrs(&pdev->dev,
+ dma_addr, dma_len,
+ DMA_TO_DEVICE, 0);
+ }
}
if (unlikely(is_ts_pkt)) {
if (BNXT_CHIP_P5(bp)) {
@@ -3394,6 +3405,8 @@ static void bnxt_free_one_tx_ring_skbs(struct bnxt *bp,
{
int i, max_idx;
struct pci_dev *pdev = bp->pdev;
+ unsigned int dma_len;
+ dma_addr_t dma_addr;
max_idx = bp->tx_nr_pages * TX_DESC_CNT;
@@ -3404,9 +3417,10 @@ static void bnxt_free_one_tx_ring_skbs(struct bnxt *bp,
if (idx < bp->tx_nr_rings_xdp &&
tx_buf->action == XDP_REDIRECT) {
- dma_unmap_single(&pdev->dev,
- dma_unmap_addr(tx_buf, mapping),
- dma_unmap_len(tx_buf, len),
+ dma_addr = dma_unmap_addr(tx_buf, mapping);
+ dma_len = dma_unmap_len(tx_buf, len);
+
+ dma_unmap_single(&pdev->dev, dma_addr, dma_len,
DMA_TO_DEVICE);
xdp_return_frame(tx_buf->xdpf);
tx_buf->action = 0;
@@ -3429,23 +3443,28 @@ static void bnxt_free_one_tx_ring_skbs(struct bnxt *bp,
continue;
}
- dma_unmap_single(&pdev->dev,
- dma_unmap_addr(tx_buf, mapping),
- skb_headlen(skb),
- DMA_TO_DEVICE);
+ if (dma_unmap_len(tx_buf, len)) {
+ dma_addr = dma_unmap_addr(tx_buf, mapping);
+ dma_len = dma_unmap_len(tx_buf, len);
+
+ dma_unmap_single(&pdev->dev, dma_addr, dma_len,
+ DMA_TO_DEVICE);
+ }
last = tx_buf->nr_frags;
i += 2;
for (j = 0; j < last; j++, i++) {
int ring_idx = i & bp->tx_ring_mask;
- skb_frag_t *frag = &skb_shinfo(skb)->frags[j];
tx_buf = &txr->tx_buf_ring[ring_idx];
- netmem_dma_unmap_page_attrs(&pdev->dev,
- dma_unmap_addr(tx_buf,
- mapping),
- skb_frag_size(frag),
- DMA_TO_DEVICE, 0);
+ if (dma_unmap_len(tx_buf, len)) {
+ dma_addr = dma_unmap_addr(tx_buf, mapping);
+ dma_len = dma_unmap_len(tx_buf, len);
+
+ netmem_dma_unmap_page_attrs(&pdev->dev,
+ dma_addr, dma_len,
+ DMA_TO_DEVICE, 0);
+ }
}
dev_kfree_skb(skb);
}
--
2.52.0
^ permalink raw reply related
* [net-next v10 03/10] net: bnxt: Add a helper for tx_bd_ext
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Factor out some code to setup tx_bd_exts into a helper function. This
helper will be used by SW USO implementation in the following commits.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v4:
- Added Pavan's Reviewed-by tag. No functional changes.
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 9 ++-------
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 18 ++++++++++++++++++
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index d4288c458576..d1f0969b781c 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -663,10 +663,9 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
txbd->tx_bd_opaque = SET_TX_OPAQUE(bp, txr, prod, 2 + last_frag);
prod = NEXT_TX(prod);
- txbd1 = (struct tx_bd_ext *)
- &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+ txbd1 = bnxt_init_ext_bd(bp, txr, prod, lflags, vlan_tag_flags,
+ cfa_action);
- txbd1->tx_bd_hsize_lflags = lflags;
if (skb_is_gso(skb)) {
bool udp_gso = !!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4);
u32 hdr_len;
@@ -693,7 +692,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
txbd1->tx_bd_hsize_lflags |=
cpu_to_le32(TX_BD_FLAGS_TCP_UDP_CHKSUM);
- txbd1->tx_bd_mss = 0;
}
length >>= 9;
@@ -706,9 +704,6 @@ static netdev_tx_t bnxt_start_xmit(struct sk_buff *skb, struct net_device *dev)
flags |= bnxt_lhint_arr[length];
txbd->tx_bd_len_flags_type = cpu_to_le32(flags);
- txbd1->tx_bd_cfa_meta = cpu_to_le32(vlan_tag_flags);
- txbd1->tx_bd_cfa_action =
- cpu_to_le32(cfa_action << TX_BD_CFA_ACTION_SHIFT);
txbd0 = txbd;
for (i = 0; i < last_frag; i++) {
frag = &skb_shinfo(skb)->frags[i];
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index 2b40a5bd57af..83b4136ccd31 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -2836,6 +2836,24 @@ static inline u32 bnxt_tx_avail(struct bnxt *bp,
return bp->tx_ring_size - (used & bp->tx_ring_mask);
}
+static inline struct tx_bd_ext *
+bnxt_init_ext_bd(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
+ u16 prod, __le32 lflags, u32 vlan_tag_flags,
+ u32 cfa_action)
+{
+ struct tx_bd_ext *txbd1;
+
+ txbd1 = (struct tx_bd_ext *)
+ &txr->tx_desc_ring[TX_RING(bp, prod)][TX_IDX(prod)];
+ txbd1->tx_bd_hsize_lflags = lflags;
+ txbd1->tx_bd_mss = 0;
+ txbd1->tx_bd_cfa_meta = cpu_to_le32(vlan_tag_flags);
+ txbd1->tx_bd_cfa_action =
+ cpu_to_le32(cfa_action << TX_BD_CFA_ACTION_SHIFT);
+
+ return txbd1;
+}
+
static inline void bnxt_writeq(struct bnxt *bp, u64 val,
volatile void __iomem *addr)
{
--
2.52.0
^ permalink raw reply related
* [net-next v10 02/10] net: bnxt: Export bnxt_xmit_get_cfa_action
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, Michael Chan, Pavan Chebbi, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni
Cc: horms, linux-kernel, leon, Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Export bnxt_xmit_get_cfa_action so that it can be used in future commits
which add software USO support to bnxt.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Pavan Chebbi <pavan.chebbi@broadcom.com>
Signed-off-by: Joe Damato <joe@dama.to>
---
v4:
- Added Pavan's Reviewed-by tag. No functional changes.
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index fe8b886ff82e..d4288c458576 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -447,7 +447,7 @@ const u16 bnxt_lhint_arr[] = {
TX_BD_FLAGS_LHINT_2048_AND_LARGER,
};
-static u16 bnxt_xmit_get_cfa_action(struct sk_buff *skb)
+u16 bnxt_xmit_get_cfa_action(struct sk_buff *skb)
{
struct metadata_dst *md_dst = skb_metadata_dst(skb);
diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.h b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
index 3558a36ece12..2b40a5bd57af 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.h
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.h
@@ -2969,6 +2969,7 @@ unsigned int bnxt_get_avail_cp_rings_for_en(struct bnxt *bp);
int bnxt_reserve_rings(struct bnxt *bp, bool irq_re_init);
void bnxt_tx_disable(struct bnxt *bp);
void bnxt_tx_enable(struct bnxt *bp);
+u16 bnxt_xmit_get_cfa_action(struct sk_buff *skb);
void bnxt_sched_reset_txr(struct bnxt *bp, struct bnxt_tx_ring_info *txr,
u16 curr);
void bnxt_report_link(struct bnxt *bp);
--
2.52.0
^ permalink raw reply related
* [net-next v10 01/10] net: tso: Introduce tso_dma_map and helpers
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman
Cc: andrew+netdev, michael.chan, pavan.chebbi, linux-kernel, leon,
Joe Damato
In-Reply-To: <20260408230607.2019402-1-joe@dama.to>
Add struct tso_dma_map to tso.h for tracking DMA addresses of mapped
GSO payload data and tso_dma_map_completion_state.
The tso_dma_map combines DMA mapping storage with iterator state, allowing
drivers to walk pre-mapped DMA regions linearly. Includes fields for
the DMA IOVA path (iova_state, iova_offset, total_len) and a fallback
per-region path (linear_dma, frags[], frag_idx, offset).
The tso_dma_map_completion_state makes the IOVA completion state opaque
for drivers. Drivers are expected to allocate this and use the added
helpers to update the completion state.
Adds skb_frag_phys() to skbuff.h, returning the physical address
of a paged fragment's data, which is used by the tso_dma_map helpers
introduced in this commit described below.
The added TSO DMA map helpers are:
tso_dma_map_init(): DMA-maps the linear payload region and all frags
upfront. Prefers the DMA IOVA API for a single contiguous mapping with
one IOTLB sync; falls back to per-region dma_map_phys() otherwise.
Returns 0 on success, cleans up partial mappings on failure.
tso_dma_map_cleanup(): Handles both IOVA and fallback teardown paths.
tso_dma_map_count(): counts how many descriptors the next N bytes of
payload will need. Returns 1 if IOVA is used since the mapping is
contiguous.
tso_dma_map_next(): yields the next (dma_addr, chunk_len) pair.
On the IOVA path, each segment is a single contiguous chunk. On the
fallback path, indicates when a chunk starts a new DMA mapping so the
driver can set dma_unmap_len on that descriptor for completion-time
unmapping.
tso_dma_map_completion_save(): updates the completion state. Drivers
will call this at xmit time.
tso_dma_map_complete(): tears down the mapping at completion time and
returns true if the IOVA path was used. If it was not used, this is a
no-op and returns false.
Suggested-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Joe Damato <joe@dama.to>
---
v10:
- Wrapped some long lines. No functional changes.
v9:
- Fix typo in commit message.
- Fix kdoc.
- Initialize tso_dma_map before early return in tso_dma_map_init
(suggested by AI).
v7:
- Squashed the struct and helpers (patch 1 and 2 from v6) into this one
patch.
- Added tso_dma_map_completion_state and helpers
tso_dma_map_completion_save and tso_dma_map_complete to operate on the
struct and keep the DMA IOVA completely opaque from drivers.
- Removed unnecessary duplicated code in tso_dma_map_next and
tso_dma_map_cleanup.
v4:
- Fix the kdoc for the TSO helpers. No functional changes.
v3:
- struct tso_dma_map extended to track IOVA state and
a fallback per-region path.
- Added skb_frag_phys helper include/linux/skbuff.h.
- Added tso_dma_map_use_iova() inline helper in tso.h.
- Updated the helpers to use the DMA IOVA API and falls back to per-region
mapping instead.
include/linux/skbuff.h | 11 ++
include/net/tso.h | 100 +++++++++++++++
net/core/tso.c | 269 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 380 insertions(+)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 26fe18bcfad8..2bcf78a4de7b 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3763,6 +3763,17 @@ static inline void *skb_frag_address_safe(const skb_frag_t *frag)
return ptr + skb_frag_off(frag);
}
+/**
+ * skb_frag_phys - gets the physical address of the data in a paged fragment
+ * @frag: the paged fragment buffer
+ *
+ * Returns: the physical address of the data within @frag.
+ */
+static inline phys_addr_t skb_frag_phys(const skb_frag_t *frag)
+{
+ return page_to_phys(skb_frag_page(frag)) + skb_frag_off(frag);
+}
+
/**
* skb_frag_page_copy() - sets the page in a fragment from another fragment
* @fragto: skb fragment where page is set
diff --git a/include/net/tso.h b/include/net/tso.h
index e7e157ae0526..da82aabd1d48 100644
--- a/include/net/tso.h
+++ b/include/net/tso.h
@@ -3,6 +3,7 @@
#define _TSO_H
#include <linux/skbuff.h>
+#include <linux/dma-mapping.h>
#include <net/ip.h>
#define TSO_HEADER_SIZE 256
@@ -28,4 +29,103 @@ void tso_build_hdr(const struct sk_buff *skb, char *hdr, struct tso_t *tso,
void tso_build_data(const struct sk_buff *skb, struct tso_t *tso, int size);
int tso_start(struct sk_buff *skb, struct tso_t *tso);
+/**
+ * struct tso_dma_map - DMA mapping state for GSO payload
+ * @dev: device used for DMA mapping
+ * @skb: the GSO skb being mapped
+ * @hdr_len: per-segment header length
+ * @iova_state: DMA IOVA state (when IOMMU available)
+ * @iova_offset: global byte offset into IOVA range (IOVA path only)
+ * @total_len: total payload length
+ * @frag_idx: current region (-1 = linear, 0..nr_frags-1 = frag)
+ * @offset: byte offset within current region
+ * @linear_dma: DMA address of the linear payload
+ * @linear_len: length of the linear payload
+ * @nr_frags: number of frags successfully DMA-mapped
+ * @frags: per-frag DMA address and length
+ *
+ * DMA-maps the payload regions of a GSO skb (linear data + frags).
+ * Prefers the DMA IOVA API for a single contiguous mapping with one
+ * IOTLB sync; falls back to per-region dma_map_phys() otherwise.
+ */
+struct tso_dma_map {
+ struct device *dev;
+ const struct sk_buff *skb;
+ unsigned int hdr_len;
+ /* IOVA path */
+ struct dma_iova_state iova_state;
+ size_t iova_offset;
+ size_t total_len;
+ /* Fallback path if IOVA path fails */
+ int frag_idx;
+ unsigned int offset;
+ dma_addr_t linear_dma;
+ unsigned int linear_len;
+ unsigned int nr_frags;
+ struct {
+ dma_addr_t dma;
+ unsigned int len;
+ } frags[MAX_SKB_FRAGS];
+};
+
+/**
+ * struct tso_dma_map_completion_state - Completion-time cleanup state
+ * @iova_state: DMA IOVA state (when IOMMU available)
+ * @total_len: total payload length of the IOVA mapping
+ *
+ * Drivers store this on their SW ring at xmit time via
+ * tso_dma_map_completion_save(), then call tso_dma_map_complete() at
+ * completion time.
+ */
+struct tso_dma_map_completion_state {
+ struct dma_iova_state iova_state;
+ size_t total_len;
+};
+
+int tso_dma_map_init(struct tso_dma_map *map, struct device *dev,
+ const struct sk_buff *skb, unsigned int hdr_len);
+void tso_dma_map_cleanup(struct tso_dma_map *map);
+unsigned int tso_dma_map_count(struct tso_dma_map *map, unsigned int len);
+bool tso_dma_map_next(struct tso_dma_map *map, dma_addr_t *addr,
+ unsigned int *chunk_len, unsigned int *mapping_len,
+ unsigned int seg_remaining);
+
+/**
+ * tso_dma_map_completion_save - save state needed for completion-time cleanup
+ * @map: the xmit-time DMA map
+ * @cstate: driver-owned storage that persists until completion
+ *
+ * Should be called at xmit time to update the completion state and later passed
+ * to tso_dma_map_complete().
+ */
+static inline void
+tso_dma_map_completion_save(const struct tso_dma_map *map,
+ struct tso_dma_map_completion_state *cstate)
+{
+ cstate->iova_state = map->iova_state;
+ cstate->total_len = map->total_len;
+}
+
+/**
+ * tso_dma_map_complete - tear down mapping at completion time
+ * @dev: the device that owns the mapping
+ * @cstate: state saved by tso_dma_map_completion_save()
+ *
+ * Return: true if the IOVA path was used and the mapping has been
+ * destroyed; false if the fallback per-region path was used and the
+ * driver must unmap via its normal completion path.
+ */
+static inline bool
+tso_dma_map_complete(struct device *dev,
+ struct tso_dma_map_completion_state *cstate)
+{
+ if (dma_use_iova(&cstate->iova_state)) {
+ dma_iova_destroy(dev, &cstate->iova_state, cstate->total_len,
+ DMA_TO_DEVICE, 0);
+ return true;
+ }
+
+ return false;
+}
+
#endif /* _TSO_H */
diff --git a/net/core/tso.c b/net/core/tso.c
index 6df997b9076e..347b3856ddb9 100644
--- a/net/core/tso.c
+++ b/net/core/tso.c
@@ -3,6 +3,7 @@
#include <linux/if_vlan.h>
#include <net/ip.h>
#include <net/tso.h>
+#include <linux/dma-mapping.h>
#include <linux/unaligned.h>
void tso_build_hdr(const struct sk_buff *skb, char *hdr, struct tso_t *tso,
@@ -87,3 +88,271 @@ int tso_start(struct sk_buff *skb, struct tso_t *tso)
return hdr_len;
}
EXPORT_SYMBOL(tso_start);
+
+static int tso_dma_iova_try(struct device *dev, struct tso_dma_map *map,
+ phys_addr_t phys, size_t linear_len,
+ size_t total_len, size_t *offset)
+{
+ const struct sk_buff *skb;
+ unsigned int nr_frags;
+ int i;
+
+ if (!dma_iova_try_alloc(dev, &map->iova_state, phys, total_len))
+ return 1;
+
+ skb = map->skb;
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
+ if (linear_len) {
+ if (dma_iova_link(dev, &map->iova_state,
+ phys, *offset, linear_len,
+ DMA_TO_DEVICE, 0))
+ goto iova_fail;
+ map->linear_len = linear_len;
+ *offset += linear_len;
+ }
+
+ for (i = 0; i < nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ unsigned int frag_len = skb_frag_size(frag);
+
+ if (dma_iova_link(dev, &map->iova_state,
+ skb_frag_phys(frag), *offset,
+ frag_len, DMA_TO_DEVICE, 0)) {
+ map->nr_frags = i;
+ goto iova_fail;
+ }
+ map->frags[i].len = frag_len;
+ *offset += frag_len;
+ map->nr_frags = i + 1;
+ }
+
+ if (dma_iova_sync(dev, &map->iova_state, 0, total_len))
+ goto iova_fail;
+
+ return 0;
+
+iova_fail:
+ dma_iova_destroy(dev, &map->iova_state, *offset,
+ DMA_TO_DEVICE, 0);
+ memset(&map->iova_state, 0, sizeof(map->iova_state));
+
+ /* reset map state */
+ map->frag_idx = -1;
+ map->offset = 0;
+ map->linear_len = 0;
+ map->nr_frags = 0;
+
+ return 1;
+}
+
+/**
+ * tso_dma_map_init - DMA-map GSO payload regions
+ * @map: map struct to initialize
+ * @dev: device for DMA mapping
+ * @skb: the GSO skb
+ * @hdr_len: per-segment header length in bytes
+ *
+ * DMA-maps the linear payload (after headers) and all frags.
+ * Prefers the DMA IOVA API (one contiguous mapping, one IOTLB sync);
+ * falls back to per-region dma_map_phys() when IOVA is not available.
+ * Positions the iterator at byte 0 of the payload.
+ *
+ * Return: 0 on success, -ENOMEM on DMA mapping failure (partial mappings
+ * are cleaned up internally).
+ */
+int tso_dma_map_init(struct tso_dma_map *map, struct device *dev,
+ const struct sk_buff *skb, unsigned int hdr_len)
+{
+ unsigned int linear_len = skb_headlen(skb) - hdr_len;
+ unsigned int nr_frags = skb_shinfo(skb)->nr_frags;
+ size_t total_len = skb->len - hdr_len;
+ size_t offset = 0;
+ phys_addr_t phys;
+ int i;
+
+ map->dev = dev;
+ map->skb = skb;
+ map->hdr_len = hdr_len;
+ map->frag_idx = -1;
+ map->offset = 0;
+ map->iova_offset = 0;
+ map->total_len = total_len;
+ map->linear_len = 0;
+ map->nr_frags = 0;
+ memset(&map->iova_state, 0, sizeof(map->iova_state));
+
+ if (!total_len)
+ return 0;
+
+ if (linear_len)
+ phys = virt_to_phys(skb->data + hdr_len);
+ else
+ phys = skb_frag_phys(&skb_shinfo(skb)->frags[0]);
+
+ if (tso_dma_iova_try(dev, map, phys, linear_len, total_len, &offset)) {
+ /* IOVA path failed, map state was reset. Fallback to
+ * per-region dma_map_phys()
+ */
+ if (linear_len) {
+ map->linear_dma = dma_map_phys(dev, phys, linear_len,
+ DMA_TO_DEVICE, 0);
+ if (dma_mapping_error(dev, map->linear_dma))
+ return -ENOMEM;
+ map->linear_len = linear_len;
+ }
+
+ for (i = 0; i < nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ unsigned int frag_len = skb_frag_size(frag);
+
+ map->frags[i].len = frag_len;
+ map->frags[i].dma = dma_map_phys(dev, skb_frag_phys(frag),
+ frag_len, DMA_TO_DEVICE, 0);
+ if (dma_mapping_error(dev, map->frags[i].dma)) {
+ tso_dma_map_cleanup(map);
+ return -ENOMEM;
+ }
+ map->nr_frags = i + 1;
+ }
+ }
+
+ if (linear_len == 0 && nr_frags > 0)
+ map->frag_idx = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(tso_dma_map_init);
+
+/**
+ * tso_dma_map_cleanup - unmap all DMA regions in a tso_dma_map
+ * @map: the map to clean up
+ *
+ * Handles both IOVA and fallback paths. For IOVA, calls
+ * dma_iova_destroy(). For fallback, unmaps each region individually.
+ */
+void tso_dma_map_cleanup(struct tso_dma_map *map)
+{
+ int i;
+
+ if (dma_use_iova(&map->iova_state)) {
+ dma_iova_destroy(map->dev, &map->iova_state, map->total_len,
+ DMA_TO_DEVICE, 0);
+ memset(&map->iova_state, 0, sizeof(map->iova_state));
+ } else {
+ if (map->linear_len)
+ dma_unmap_phys(map->dev, map->linear_dma,
+ map->linear_len, DMA_TO_DEVICE, 0);
+
+ for (i = 0; i < map->nr_frags; i++)
+ dma_unmap_phys(map->dev, map->frags[i].dma,
+ map->frags[i].len, DMA_TO_DEVICE, 0);
+ }
+
+ map->linear_len = 0;
+ map->nr_frags = 0;
+}
+EXPORT_SYMBOL(tso_dma_map_cleanup);
+
+/**
+ * tso_dma_map_count - count descriptors for a payload range
+ * @map: the payload map
+ * @len: number of payload bytes in this segment
+ *
+ * Counts how many contiguous DMA region chunks the next @len bytes
+ * will span, without advancing the iterator. On the IOVA path this
+ * is always 1 (contiguous). On the fallback path, uses region sizes
+ * from the current position.
+ *
+ * Return: the number of descriptors needed for @len bytes of payload.
+ */
+unsigned int tso_dma_map_count(struct tso_dma_map *map, unsigned int len)
+{
+ unsigned int offset = map->offset;
+ int idx = map->frag_idx;
+ unsigned int count = 0;
+
+ if (!len)
+ return 0;
+
+ if (dma_use_iova(&map->iova_state))
+ return 1;
+
+ while (len > 0) {
+ unsigned int region_len, chunk;
+
+ if (idx == -1)
+ region_len = map->linear_len;
+ else
+ region_len = map->frags[idx].len;
+
+ chunk = min(len, region_len - offset);
+ len -= chunk;
+ count++;
+ offset = 0;
+ idx++;
+ }
+
+ return count;
+}
+EXPORT_SYMBOL(tso_dma_map_count);
+
+/**
+ * tso_dma_map_next - yield the next DMA address range
+ * @map: the payload map
+ * @addr: output DMA address
+ * @chunk_len: output chunk length
+ * @mapping_len: full DMA mapping length when this chunk starts a new
+ * mapping region, or 0 when continuing a previous one.
+ * On the IOVA path this is always 0 (driver must not
+ * do per-region unmaps; use tso_dma_map_cleanup instead).
+ * @seg_remaining: bytes left in current segment
+ *
+ * Yields the next (dma_addr, chunk_len) pair and advances the iterator.
+ * On the IOVA path, the entire payload is contiguous so each segment
+ * is always a single chunk.
+ *
+ * Return: true if a chunk was yielded, false when @seg_remaining is 0.
+ */
+bool tso_dma_map_next(struct tso_dma_map *map, dma_addr_t *addr,
+ unsigned int *chunk_len, unsigned int *mapping_len,
+ unsigned int seg_remaining)
+{
+ unsigned int region_len, chunk;
+
+ if (!seg_remaining)
+ return false;
+
+ /* IOVA path: contiguous DMA range, no region boundaries */
+ if (dma_use_iova(&map->iova_state)) {
+ *addr = map->iova_state.addr + map->iova_offset;
+ *chunk_len = seg_remaining;
+ *mapping_len = 0;
+ map->iova_offset += seg_remaining;
+ return true;
+ }
+
+ /* Fallback path: per-region iteration */
+
+ if (map->frag_idx == -1) {
+ region_len = map->linear_len;
+ chunk = min(seg_remaining, region_len - map->offset);
+ *addr = map->linear_dma + map->offset;
+ } else {
+ region_len = map->frags[map->frag_idx].len;
+ chunk = min(seg_remaining, region_len - map->offset);
+ *addr = map->frags[map->frag_idx].dma + map->offset;
+ }
+
+ *mapping_len = (map->offset == 0) ? region_len : 0;
+ *chunk_len = chunk;
+ map->offset += chunk;
+
+ if (map->offset >= region_len) {
+ map->frag_idx++;
+ map->offset = 0;
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(tso_dma_map_next);
--
2.52.0
^ permalink raw reply related
* Re: [PATCH net-next v2 3/4] bpf-timestamp: keep track of the skb when wait_for_space occurs
From: Jason Xing @ 2026-04-08 23:05 UTC (permalink / raw)
To: Willem de Bruijn
Cc: davem, edumazet, kuba, pabeni, horms, willemb, martin.lau, netdev,
bpf, Jason Xing, Yushan Zhou
In-Reply-To: <willemdebruijn.kernel.257654f9a3f23@gmail.com>
On Wed, Apr 8, 2026 at 11:15 PM Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
>
> > > > > Since we're modifying the kernel, how about adding a new member to
> > > > > record sendmsg time which bpf script is able to read. The whole
> > > > > scenario looks like this:
> > > > > 1) in tcp_sendmsg_locked(), record the sendmsg time for each skb
> > > > > 2) in either tso_fragment() or tcp_gso_tstamp(), each new skb will get
> > > > > a copy of its original skb
> > > > > 3) in each stage, bpf script reads the skb's sendmsg time and the
> > > > > current time, and then effortlessly do the math.
> > > > >
> > > > > At this point, what I had in mind is we have two options:
> > > > > 1) only handle the skb from the view of the send syscall layer, which
> > > > > is, for sure, very simple but not thorough.
> > > > > 2) stick to a pure authentic packet basis, then adding a new member
> > > > > seems inevitable. so the question would be where to add? The space of
> > > > > the skb structure is very precious :(
> > > >
> > > > Finding a suitable place to put this timestamp is really hard. IIRC,
> > > > we can't expand the size of struct skb_shared_info so easily since
> > > > it's a global effect.
> > > >
> > > > I'm wondering if we can turn the per-packet mode into a non-compatible
> > > > feature by reusing 'u32 tskey' to store a microsecond timestamp of
> > > > sendmsg.
> > >
> > > Agreed that an extra field is hard. We should avoid that.
> >
> > Avoiding adding a new one makes the whole work extremely hard. I'm
> > wondering since we have hwtstamp in shared info, why not add a
> > software one for timestamping use? Then, we would support more
> > different protocols in more different stages in a finer grain, which
> > is a big coarse picture in my mind.
>
> I don't understand the need to store more data in the skb for BPF.
I see your point.
>
> With BPF hooks, the bpf program can record the relevant data directly
> in a BPF map.
It works for sure, but, as I said previously, it's not an effective
approach that can be used in production to run a 7x24 monitor.
Either adding a new field or repurposing the tskey makes the whole
logic/arch very simple. Being simple is being efficient and easy to
use. Without changes like that, the performance of flows can get
hugely affected just because of the monitor.
>
> > Adding a software bit will completely reduce the whole complexity and
> > be very easy to use. Would you expect to see a draft by adding such a
> > bit first?
> >
> > Or just like I mentioned, repurposing tskey seems an alternative,
> > which, however, makes the new feature incompatible.
> >
> > >
> > > If the purpose is to group skbs by sendmsg call (e.g., to filter out
> > > all but the last one), it is probably also unnecessary.
> > >
> > > From a process PoV, since the process knows the sendmsg len and each
> > > skb has a tskey in byte offset, it can correlate the skb with a given
> > > sendmsg buffer.
> > >
> > > The BPF program is under control of a third-party admin. So that does
> > > not follow directly. But it can be passed additional metadata.
> > >
> > > I thought about passing the offset of the skb from the start of the
> > > sendmsg buffer to identify all consecutive skbs for a sendmsg call,
> > > as each new buffer will start with an skb with offset 0 ..
> > >
> > > .. but that won't work as there is no guarantee that a sendmsg call
> > > will not append to an existing outstanding skb.
> >
> > Right. TCP is way too complex and we indeed see some tough issues when
> > trying to deploy the feature. So my humble take is to make the design
> > as simple as possible.
> >
> > >
> > > Anyway, the general idea is to pass to the BPF program through
> > > bpf_skops_tx_timestamping some relevant signal , without having to
> > > expand either skb or sk itself.
> > >
> > > I hear you on that measuring every skb is too frequent. But is calling
> > > the BPF program and letting it decide whether to measure too? BPF
> > > program invocation itself should be cheap.
> >
> > Oh, I was clear enough. Sorry. I meant tracing per skb is definitely
> > an awesome way to go. My ultimate goal is to do so. Instead of letting
> > people implement various fine grained bpf progs, we can provide a very
> > easy/understandable/efficient approach with more samples. It should be
> > very beneficial.
> >
> > >
> > > If per-push is preferable, with a filter ability like the above, it
> > > seems more useful to me already.
> >
> > Push-level is a compromise plan. Packet-level is what I always pursue :)
>
> Then why not directly implement per-packet.
>
> If the BPF call is cheap and the BPF program can choose to selectively
> track packets.
>
> Reminder that you do not want to break (BPF) users by changing
> behavior. Let alone more than once. If per-push is going to be
> obsoleted, skip ip entirely.
Understood. My initial version was just to try to solve the missing
tag issues with the minimum change. You're right about the
compatibility across kernels. Let's work on the ultimate plan then.
Thanks,
Jason
^ permalink raw reply
* [net-next v10 00/10] Add TSO map-once DMA helpers and bnxt SW USO support
From: Joe Damato @ 2026-04-08 23:05 UTC (permalink / raw)
To: netdev
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, michael.chan,
pavan.chebbi, linux-kernel, leon, Joe Damato
Greetings:
This series extends net/tso to add a data structure and some helpers allowing
drivers to DMA map headers and packet payloads a single time. The helpers can
then be used to reference slices of shared mapping for each segment. This
helps to avoid the cost of repeated DMA mappings, especially on systems which
use an IOMMU. N per-packet DMA maps are replaced with a single map for the
entire GSO skb. As of v3, the series uses the DMA IOVA API (as suggested by
Leon [1]) and provides a fallback path when an IOMMU is not in use. The DMA
IOVA API provides even better efficiency than the v2; see below.
The added helpers are then used in bnxt to add support for software UDP
Segmentation Offloading (SW USO) for older bnxt devices which do not have
support for USO in hardware. Since the helpers are generic, other drivers
can be extended similarly.
The v2 showed a ~4x reduction in DMA mapping calls at the same wire packet
rate on production traffic with a bnxt device. The v3, however, shows a larger
reduction of about ~6x at the same wire packet rate. This is thanks to Leon's
suggestion of using the DMA IOVA API [1].
Special care is taken to make bnxt ethtool operations work correctly: the ring
size cannot be reduced below a minimum threshold while USO is enabled and
growing the ring automatically re-enables USO if it was previously blocked.
This v10 contains some cosmetic changes (wrapping long lines), moves the test
to the correct directory, and attempts to fix the slot availability check
added in the v9.
I re-ran the python test and the test passed on my bnxt system. I also ran
this on a production system.
Thanks,
Joe
[1]: https://lore.kernel.org/netdev/20260316194419.GH61385@unreal/
[2]: https://lore.kernel.org/netdev/ab1f764b-de03-48f5-a781-356495257d25@redhat.com/
v10:
- Patch 1: Wrapped a few long lines. No functional changes.
- Patch 4: Wrapper a few long lines. No functional changes.
- Patch 7: Fix the slot check added in v9 to use netif_txq_maybe_stop and an inline
helper function.
- Patch 8: Wrap tx_inline_cons in WRITE_ONCE to pair with READ_ONCE in
bnxt_inline_avail.
- Patch 10: Moved test from drivers/net/ to drivers/net/hw/ since it
requires real hardware.
v9: https://lore.kernel.org/netdev/20260407220313.3990909-1-joe@dama.to/
- Patch 1:
- Fix typo in commit message.
- Fix kdoc.
- Initialize tso_dma_map before early return in tso_dma_map_init
(suggested by AI).
- Patch 7 (both suggested by AI):
- Added inline slot check to prevent possible overwriting of in-flight
headers in the buffer.
- Made TX_BD_FLAGS_IP_CKSUM conditional on !tso.ipv6
- Patch 8 (suggested by AI):
- Always allocate header buffer for non-HW-USO NICs. Avoids a possible
NULL deref if USO is toggled off, the device is brought down, brought
up, and USO is re-enabled.
- Adjust bnxt_min_tx_desc_cnt to take a feature parameter, which is needed to
prevent stale features from being examined.
- Patch 10:
- Use UDP-LISTEN instead of UDP-RECV in socat receiver (suggested by AI).
- Fixed docstring.
- Removed unused return value.
v8: https://lore.kernel.org/netdev/20260403003524.2564973-1-joe@dama.to/
- Zero csum fields on per-segment header copy after tso_build_hdr()
instead of on the original skb, avoiding the need for skb_cow_head, as
suggested by Eric Dumazet.
v7: https://lore.kernel.org/netdev/20260401233745.2333858-1-joe@dama.to/
- Squashed patches 1 and 2 of the v6 into patch 1 of this series, as
requested by Jakub.
- Added tso_dma_map_completion_state and helpers so that drivers don't call
any of the DMA IOVA API directly. See the changelog in patch 1 for
details.
- Changed the placement of the is_sw_gso field in struct bnxt_sw_tx_bd in
patch 6, as request by Jakub.
- Updated struct bnxt_sw_tx_bd to embed a tso_dma_map_completion_state for
tracking completion state and dropped an unnecessary slot check from patch
7.
- Added bnxt_min_tx_desc_cnt helper to factor out descriptor counting and
use the newly added tso_dma_map_complete from bnxt instead of calling the
DMA IOVA API directly in patch 8.
- Various fixes to the python test in patch 10: use ksft_variants, socat on
the receiving side, and cfg.wait_hw_stats_settle instead of sleep.
v6: https://lore.kernel.org/netdev/20260326235238.2940471-1-joe@dama.to/
- Addressed Paolo's request [2] to avoid possible stale iova_state if the
IOVA API starts to fail transiently. See patch 8.
v5: https://lore.kernel.org/netdev/20260323183844.3146982-1-joe@dama.to/
- Adjusted patch 8 to address the kernel test robot. See patch changelog, no
functional change.
- Added Pavan's Reviewed-by to patches 6-12.
v4: https://lore.kernel.org/all/20260320144141.260246-1-joe@dama.to/
- Fixed kdoc issues in patch 2. No functional change.
- Added Pavan's Reviewed-by to patches 3, 4, and 5.
- Fixed the issue Pavan (and the AI review) pointed out in patch 8. See
patch changelog.
- Added parentheses around gso_type check in patch 11 for clarity. No
functional change.
- Fixed python linter issues in patch 12. No functional change.
v3: https://lore.kernel.org/netdev/20260318191325.1819881-1-joe@dama.to/
- Converted from RFC to an actual submission.
- Updated based on Leon's feedback to use the DMA IOVA API. See individual
patches for update information.
RFCv2: https://lore.kernel.org/netdev/20260312223457.1999489-1-joe@dama.to/
- Some bugs were discovered shortly after sending: incorrect handling of the
shared header space and a bug in the unmap path in the TX completion.
Sorry about that; I was more careful this time.
- On that note: this rfc includes a test.
RFCv1: https://lore.kernel.org/netdev/20260310212209.2263939-1-joe@dama.to/
Joe Damato (10):
net: tso: Introduce tso_dma_map and helpers
net: bnxt: Export bnxt_xmit_get_cfa_action
net: bnxt: Add a helper for tx_bd_ext
net: bnxt: Use dma_unmap_len for TX completion unmapping
net: bnxt: Add TX inline buffer infrastructure
net: bnxt: Add boilerplate GSO code
net: bnxt: Implement software USO
net: bnxt: Add SW GSO completion and teardown support
net: bnxt: Dispatch to SW USO
selftests: drv-net: Add USO test
drivers/net/ethernet/broadcom/bnxt/Makefile | 2 +-
drivers/net/ethernet/broadcom/bnxt/bnxt.c | 183 +++++++++---
drivers/net/ethernet/broadcom/bnxt/bnxt.h | 32 +++
.../net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 19 +-
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c | 240 ++++++++++++++++
drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h | 46 +++
include/linux/skbuff.h | 11 +
include/net/tso.h | 100 +++++++
net/core/tso.c | 269 ++++++++++++++++++
.../testing/selftests/drivers/net/hw/Makefile | 1 +
tools/testing/selftests/drivers/net/hw/uso.py | 103 +++++++
11 files changed, 967 insertions(+), 39 deletions(-)
create mode 100644 drivers/net/ethernet/broadcom/bnxt/bnxt_gso.c
create mode 100644 drivers/net/ethernet/broadcom/bnxt/bnxt_gso.h
create mode 100755 tools/testing/selftests/drivers/net/hw/uso.py
base-commit: 2ce8a41113eda1adddc1e6dc43cf89383ec6dc22
--
2.52.0
^ permalink raw reply
* Re: [PATCH net-next v3 3/3] gve: implement PTP gettimex64
From: Jacob Keller @ 2026-04-08 22:43 UTC (permalink / raw)
To: Jordan Rhee, Jakub Kicinski
Cc: Harshitha Ramamurthy, netdev, joshwash, andrew+netdev, davem,
edumazet, kuba, pabeni, richardcochran, willemb, nktgrg, jfraker,
ziweixiao, maolson, thostet, jefrogers, alok.a.tiwari, yyd,
linux-kernel, Naman Gulati
In-Reply-To: <CA+mzVtscZ9Dkcx8vq6MpCjNHqep3PoeuZff=o_V9usRo6eLBrw@mail.gmail.com>
On 4/6/2026 1:41 PM, Jordan Rhee wrote:
> On Fri, Apr 3, 2026 at 2:18 PM Jacob Keller <jacob.e.keller@intel.com> wrote:
>>
>> On 4/3/2026 12:44 PM, Harshitha Ramamurthy wrote:
>>> From: Jordan Rhee <jordanrhee@google.com>
>>>
>>> Enable chrony and phc2sys to synchronize system clock to NIC clock.
>>>
>>> The system cycle counters are sampled by the device to minimize the
>>> uncertainty window. If the system times are sampled in the host, the
>>> delta between pre and post readings is 100us or more due to AQ command
>>> latency. The system times returned by the device have a delta of ~1us,
>>> which enables significantly more accurate clock synchronization.
>>>
>>> Reviewed-by: Willem de Bruijn <willemb@google.com>
>>> Reviewed-by: Kevin Yang <yyd@google.com>
>>> Reviewed-by: Naman Gulati <namangulati@google.com>
>>> Signed-off-by: Jordan Rhee <jordanrhee@google.com>
>>> Signed-off-by: Harshitha Ramamurthy <hramamurthy@google.com>
>>> ---
>>
>>> +/*
>>> + * Convert a raw cycle count (e.g. from get_cycles()) to the system clock
>>> + * type specified by clockid. The system_time_snapshot must be taken before
>>> + * the cycle counter is sampled.
>>> + */
>>> +static int gve_cycles_to_timespec64(struct gve_priv *priv, clockid_t clockid,
>>> + struct system_time_snapshot *snap,
>>> + u64 cycles, struct timespec64 *ts)
>>> +{
>>> + struct gve_cycles_to_clock_callback_ctx ctx = {0};
>>> + struct system_device_crosststamp xtstamp;
>>> + int err;
>>> +
>>> + ctx.cycles = cycles;
>>> + err = get_device_system_crosststamp(gve_cycles_to_clock_fn, &ctx, snap,
>>> + &xtstamp);
>>> + if (err) {
>>> + dev_err_ratelimited(&priv->pdev->dev,
>>> + "get_device_system_crosststamp() failed to convert %lld cycles to system time: %d\n",
>>> + cycles,
>>> + err);
>>> + return err;
>>> + }
>>> +
>>
>> This looks a lot like a cross timestamp (i.e. something like PCIe PTM)
>> Why not just implement the .crosstimestamp and PTP_SYS_OFF_PRECISE? Does
>> that not work properly? Or is this not really a cross timestamp despite
>> use of the get_device_system_crosststamp handler? :D
>
> .crosstimestamp is for devices that support simultaneous NIC and
> system timestamps. Devices that don't support simultaneous timestamps
> have to take a system time sandwich by calling
> ptp_read_system_prets()/ptp_read_system_postts() on either side of the
> NIC timestamp. Upper layers (e.g. chrony) use the sandwich delta in
> nontrivial ways when estimating the system clock / NIC clock offset.
> This is information that must be preserved, and it would be incorrect
> to implement .crosstimestamp by returning the midpoint of the
> sandwich, as tempting as that implementation might be.
>
True.
> Gvnic does not support simultaneous NIC and system timestamps, so it
> must use the sandwich technique. Since the NIC timestamp is obtained
> using a firmware (hypervisor) call, the uncertainty window would be
> too large if it were taken inside the VM. Gvnic takes the sandwich in
> the hypervisor and returns the raw TSC values to the VM.
> get_device_system_crosststamp() is used to convert the TSCs to system
> times, which I believe is the only correct way to do this conversion.
> Jordan
>
Hmm. The function says:
"Synchronously capture system/device timestamp". That is what confuses
me. Your implementation uses gve_cycles_to_clock_fn() which just sets
some values in the system_counterval struct and exits. It doesn't
"capture a system/device timestamp" tuple.
This does feel a bit weird. No other caller appears to exist outside of
the cross timestamp implementations.
It sounds like what you want is a function that takes a cycles count
value and does the conversion from TSC to the appropriate clock, along
with all of the interopolation etc. What you've done is sort of a cludge
around get_device_system_crosststamp() to force it to do that for you
without actually using it as intended.
I'd argue it would be better to have a cycles_to_ktime() or something
which takes the TSC cycles value and the appropriate clock and does the
exact same flow as get_device_system_crosststamp() for converting the
cycles into proper ktime values without the mess of the callback
function etc.
I guess in principle what you've implemented is "correct" and
functional, but it definitely feels a bit weird to use the API in this
way. It smells like a neat hack instead of a proper interface for this
purpose.
That said, I won't object strongly if the maintainers are fine with
using it for this purpose.
Thanks,
Jake
^ permalink raw reply
* [PATCH] nfc: hci: fix OOB heap read on short HCP frames.
From: Ashutosh Desai @ 2026-04-08 22:31 UTC (permalink / raw)
To: netdev; +Cc: davem, edumazet, kuba, pabeni, horms, linux-kernel,
Ashutosh Desai
Both nfc_hci_recv_from_llc() and nfc_hci_msg_rx_work() read byte 1 of
an sk_buff (the HCP message header field) without first verifying the
buffer contains at least NFC_HCI_HCP_HEADER_LEN (2) bytes.
The SHDLC LLC layer only filters zero-length frames; a single-byte
I-frame from a malicious NFC peer therefore reaches the HCI reassembly
path where packet->message.header is read one byte past the valid data.
The same issue is present in the NCI HCI implementation (nci/hci.c)
via nci_hci_data_received_cb() and nci_hci_msg_rx_work().
Add an explicit length check before accessing the message header at
all four locations, freeing the skb on malformed input.
Signed-off-by: Ashutosh Desai <ashutoshdesai993@gmail.com>
---
net/nfc/hci/core.c | 9 +++++++++
net/nfc/nci/hci.c | 9 +++++++++
2 files changed, 18 insertions(+)
diff --git a/net/nfc/hci/core.c b/net/nfc/hci/core.c
index 0d33c81a1..13d10b841 100644
--- a/net/nfc/hci/core.c
+++ b/net/nfc/hci/core.c
@@ -134,6 +134,10 @@ static void nfc_hci_msg_rx_work(struct work_struct *work)
u8 instruction;
while ((skb = skb_dequeue(&hdev->msg_rx_queue)) != NULL) {
+ if (skb->len < NFC_HCI_HCP_HEADER_LEN) {
+ kfree_skb(skb);
+ continue;
+ }
pipe = skb->data[0];
skb_pull(skb, NFC_HCI_HCP_PACKET_HEADER_LEN);
message = (struct hcp_message *)skb->data;
@@ -904,6 +908,11 @@ static void nfc_hci_recv_from_llc(struct nfc_hci_dev *hdev, struct sk_buff *skb)
* unblock waiting cmd context. Otherwise, enqueue to dispatch
* in separate context where handler can also execute command.
*/
+ if (hcp_skb->len < NFC_HCI_HCP_HEADER_LEN) {
+ kfree_skb(hcp_skb);
+ return;
+ }
+
packet = (struct hcp_packet *)hcp_skb->data;
type = HCP_MSG_GET_TYPE(packet->message.header);
if (type == NFC_HCI_HCP_RESPONSE) {
diff --git a/net/nfc/nci/hci.c b/net/nfc/nci/hci.c
index 40ae8e5a7..2a6432878 100644
--- a/net/nfc/nci/hci.c
+++ b/net/nfc/nci/hci.c
@@ -412,6 +412,10 @@ static void nci_hci_msg_rx_work(struct work_struct *work)
for (; (skb = skb_dequeue(&hdev->msg_rx_queue)); kcov_remote_stop()) {
kcov_remote_start_common(skb_get_kcov_handle(skb));
+ if (skb->len < NCI_HCI_HCP_HEADER_LEN) {
+ kfree_skb(skb);
+ continue;
+ }
pipe = NCI_HCP_MSG_GET_PIPE(skb->data[0]);
skb_pull(skb, NCI_HCI_HCP_PACKET_HEADER_LEN);
message = (struct nci_hcp_message *)skb->data;
@@ -482,6 +486,11 @@ void nci_hci_data_received_cb(void *context,
* unblock waiting cmd context. Otherwise, enqueue to dispatch
* in separate context where handler can also execute command.
*/
+ if (hcp_skb->len < NCI_HCI_HCP_HEADER_LEN) {
+ kfree_skb(hcp_skb);
+ return;
+ }
+
packet = (struct nci_hcp_packet *)hcp_skb->data;
type = NCI_HCP_MSG_GET_TYPE(packet->message.header);
if (type == NCI_HCI_HCP_RESPONSE) {
--
2.34.1
^ permalink raw reply related
* [TEST] nft_tproxy_udp.sh flaky after Fedora 44 upgrade
From: Jakub Kicinski @ 2026-04-08 22:24 UTC (permalink / raw)
To: Florian Westphal; +Cc: netdev@vger.kernel.org
Hi Florian!
When you have a sec -- we upgraded the NIPA systems to Fedora 44
over the weekend, and nft_tproxy_udp.sh has gotten quite flaky:
https://netdev.bots.linux.dev/contest.html?test=nft-tproxy-udp-sh
One thing that we hit immediately is this, in case tproxy test
uses socat:
commit e65d8b6f3092398efd7c74e722cb7a516d9a0d6d
Date: Sat Apr 4 16:01:03 2026 -0700
selftests: drv-net: adjust to socat changes
socat v1.8.1.0 now defaults to shut-null, it sends an extra
0-length UDP packet when sender disconnects. This breaks
our tests which expect the exact packet sequence.
Add shut-none which was the old default where necessary.
^ permalink raw reply
* [PATCH net-next] selftests: net: py: explicitly forbid multiple ksft_run() calls
From: Jakub Kicinski @ 2026-04-08 22:19 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, Jakub Kicinski,
shuah, petrm, willemb, linux-kselftest
People (do people still write code or is it all AI?) seem to not
get that ksft_run() can only be called once. If we call it
multiple times KTAP parsers will likely cut off after the first
batch has finished.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
CC: shuah@kernel.org
CC: petrm@nvidia.com
CC: willemb@google.com
CC: linux-kselftest@vger.kernel.org
---
tools/testing/selftests/net/lib/py/ksft.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/lib/py/ksft.py b/tools/testing/selftests/net/lib/py/ksft.py
index 7b8af463e35d..7083c99c9444 100644
--- a/tools/testing/selftests/net/lib/py/ksft.py
+++ b/tools/testing/selftests/net/lib/py/ksft.py
@@ -341,10 +341,13 @@ KsftCaseFunction = namedtuple("KsftCaseFunction",
totals = {"pass": 0, "fail": 0, "skip": 0, "xfail": 0}
+ global KSFT_RESULT
+ if KSFT_RESULT is not None:
+ raise RuntimeError("ksft_run() can't be called multiple times.")
+
print("TAP version 13", flush=True)
print("1.." + str(len(test_cases)), flush=True)
- global KSFT_RESULT
cnt = 0
stop = False
for func, args, name in test_cases:
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net v8 4/4] macsec: Support VLAN-filtering lower devices
From: Sabrina Dubroca @ 2026-04-08 22:16 UTC (permalink / raw)
To: Cosmin Ratiu
Cc: netdev, Andrew Lunn, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Stanislav Fomichev,
David Wei, Shuah Khan, linux-kselftest, Dragos Tatulea
In-Reply-To: <20260408115240.1636047-5-cratiu@nvidia.com>
2026-04-08, 14:52:40 +0300, Cosmin Ratiu wrote:
> VLAN-filtering is done through two netdev features
> (NETIF_F_HW_VLAN_CTAG_FILTER and NETIF_F_HW_VLAN_STAG_FILTER) and two
> netdev ops (ndo_vlan_rx_add_vid and ndo_vlan_rx_kill_vid).
>
> Implement these and advertise the features if the lower device supports
> them. This allows proper VLAN filtering to work on top of MACsec
> devices, when the lower device is capable of VLAN filtering.
> As a concrete example, having this chain of interfaces now works:
> vlan_filtering_capable_dev(1) -> macsec_dev(2) -> macsec_vlan_dev(3)
>
> Before the mentioned commit this used to accidentally work because the
> MACsec device (and thus the lower device) was put in promiscuous mode
> and the VLAN filter was not used. But after commit [1] correctly made
> the macsec driver expose the IFF_UNICAST_FLT flag, promiscuous mode was
> no longer used and VLAN filters on dev 1 kicked in. Without support in
> dev 2 for propagating VLAN filters down, the register_vlan_dev ->
> vlan_vid_add -> __vlan_vid_add -> vlan_add_rx_filter_info call from dev
> 3 is silently eaten (because vlan_hw_filter_capable returns false and
> vlan_add_rx_filter_info silently succeeds).
>
> For MACsec, VLAN filters are only relevant for offload, otherwise
> the VLANs are encrypted and the lower devices don't care about them. So
> VLAN filters are only passed on to lower devices in offload mode.
> Flipping between offload modes now needs to offload/unoffload the
> filters with vlan_{get,drop}_rx_*_filter_info().
>
> To avoid the back-and-forth filter updating during rollback, the setting
> of macsec->offload is moved after the add/del secy ops. This is safe
> since none of the code called from those requires macsec->offload.
>
> In case adding the filters fails, the added ones are rolled back and an
> error is returned to the operation toggling the offload state.
>
> Fixes: 0349659fd72f ("macsec: set IFF_UNICAST_FLT priv flag")
> Signed-off-by: Cosmin Ratiu <cratiu@nvidia.com>
> ---
> drivers/net/macsec.c | 71 +++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 63 insertions(+), 8 deletions(-)
Reviewed-by: Sabrina Dubroca <sd@queasysnail.net>
Thanks Cosmin.
--
Sabrina
^ permalink raw reply
* Re: [PATCH net-next v11 03/14] net: Add lease info to queue-get response
From: Jakub Kicinski @ 2026-04-08 22:12 UTC (permalink / raw)
To: Daniel Borkmann
Cc: netdev, bpf, davem, razor, pabeni, willemb, sdf, john.fastabend,
martin.lau, jordan, maciej.fijalkowski, magnus.karlsson, dw, toke,
yangzhenze, wangdongdong.6
In-Reply-To: <b9a046f8-cb02-4d54-9a7e-e8213339d720@iogearbox.net>
On Wed, 8 Apr 2026 11:09:34 +0200 Daniel Borkmann wrote:
> >> +void netif_put_rx_queue_lease_locked(struct net_device *orig_dev,
> >> + struct net_device *dev)
> >> +{
> >> + if (orig_dev != dev)
> >> + netdev_unlock(dev);
> >> +}
> >
> > Pretty sure I already complained about these ugly helpers.
> > I'll try to find the time tomorrow to come up with something better.
>
> Ok, sounds good. Happy to adapt if you find something better and then I'll
> work this into the series, and also integrate the things mentioned in my
> cover letter reply (netkit nl dump + additional tests).
Hi! How would you feel about something like the following on top?
--->8----------
net: remove the netif_get_rx_queue_lease_locked() helpers
The netif_get_rx_queue_lease_locked() API hides the locking
and the descend onto the leased queue. Making the code
harder to follow (at least to me). Remove the API and open
code the descend a bit. Most of the code now looks like:
if (!leased)
return __helper(x);
hw_rxq = ..
netdev_lock(hw_rxq->dev);
ret = __helper(x);
netdev_unlock(hw_rxq->dev);
return ret;
Of course if we have more code paths that need the wrapping
we may need to revisit. For now, IMHO, having to know what
netif_get_rx_queue_lease_locked() does is not worth the 20LoC
it saves.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/netdev_rx_queue.h | 5 ---
net/core/dev.h | 1 +
net/core/netdev-genl.c | 59 +++++++++++++++++----------
net/core/netdev_queues.c | 14 ++++---
net/core/netdev_rx_queue.c | 48 +++++++---------------
net/xdp/xsk.c | 77 ++++++++++++++++++++++-------------
6 files changed, 111 insertions(+), 93 deletions(-)
diff --git a/include/net/netdev_rx_queue.h b/include/net/netdev_rx_queue.h
index 7e98c679ea84..9415a94d333d 100644
--- a/include/net/netdev_rx_queue.h
+++ b/include/net/netdev_rx_queue.h
@@ -76,11 +76,6 @@ struct netdev_rx_queue *
__netif_get_rx_queue_lease(struct net_device **dev, unsigned int *rxq,
enum netif_lease_dir dir);
-struct netdev_rx_queue *
-netif_get_rx_queue_lease_locked(struct net_device **dev, unsigned int *rxq);
-void netif_put_rx_queue_lease_locked(struct net_device *orig_dev,
- struct net_device *dev);
-
int netdev_rx_queue_restart(struct net_device *dev, unsigned int rxq);
void netdev_rx_queue_lease(struct netdev_rx_queue *rxq_dst,
struct netdev_rx_queue *rxq_src);
diff --git a/net/core/dev.h b/net/core/dev.h
index 95edb2d4eff8..376bac4a82da 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -101,6 +101,7 @@ int netdev_queue_config_validate(struct net_device *dev, int rxq_idx,
bool netif_rxq_has_mp(struct net_device *dev, unsigned int rxq_idx);
bool netif_rxq_is_leased(struct net_device *dev, unsigned int rxq_idx);
+bool netif_is_queue_leasee(const struct net_device *dev);
void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
const struct pp_memory_provider_params *p);
diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c
index 056460d01940..b8f6076d8007 100644
--- a/net/core/netdev-genl.c
+++ b/net/core/netdev-genl.c
@@ -395,8 +395,7 @@ netdev_nl_queue_fill_lease(struct sk_buff *rsp, struct net_device *netdev,
struct netdev_rx_queue *rxq;
struct net *net, *peer_net;
- rxq = __netif_get_rx_queue_lease(&netdev, &q_idx,
- NETIF_PHYS_TO_VIRT);
+ rxq = __netif_get_rx_queue_lease(&netdev, &q_idx, NETIF_PHYS_TO_VIRT);
if (!rxq || orig_netdev == netdev)
return 0;
@@ -436,13 +435,45 @@ netdev_nl_queue_fill_lease(struct sk_buff *rsp, struct net_device *netdev,
return -ENOMEM;
}
+static int
+__netdev_nl_queue_fill_mp(struct sk_buff *rsp, struct netdev_rx_queue *rxq)
+{
+ struct pp_memory_provider_params *params = &rxq->mp_params;
+
+ if (params->mp_ops &&
+ params->mp_ops->nl_fill(params->mp_priv, rsp, rxq))
+ return -EMSGSIZE;
+
+#ifdef CONFIG_XDP_SOCKETS
+ if (rxq->pool)
+ if (nla_put_empty_nest(rsp, NETDEV_A_QUEUE_XSK))
+ return -EMSGSIZE;
+#endif
+ return 0;
+}
+
+static int
+netdev_nl_queue_fill_mp(struct sk_buff *rsp, struct net_device *netdev,
+ struct netdev_rx_queue *rxq)
+{
+ struct netdev_rx_queue *hw_rxq;
+ int ret;
+
+ hw_rxq = rxq->lease;
+ if (!hw_rxq || !netif_is_queue_leasee(netdev))
+ return __netdev_nl_queue_fill_mp(rsp, rxq);
+
+ netdev_lock(hw_rxq->dev);
+ ret = __netdev_nl_queue_fill_mp(rsp, hw_rxq);
+ netdev_unlock(hw_rxq->dev);
+ return ret;
+}
+
static int
netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev,
u32 q_idx, u32 q_type, const struct genl_info *info)
{
- struct pp_memory_provider_params *params;
- struct net_device *orig_netdev = netdev;
- struct netdev_rx_queue *rxq, *rxq_lease;
+ struct netdev_rx_queue *rxq;
struct netdev_queue *txq;
void *hdr;
@@ -462,20 +493,8 @@ netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev,
goto nla_put_failure;
if (netdev_nl_queue_fill_lease(rsp, netdev, q_idx, q_type))
goto nla_put_failure;
-
- rxq_lease = netif_get_rx_queue_lease_locked(&netdev, &q_idx);
- if (rxq_lease)
- rxq = rxq_lease;
- params = &rxq->mp_params;
- if (params->mp_ops &&
- params->mp_ops->nl_fill(params->mp_priv, rsp, rxq))
- goto nla_put_failure_lease;
-#ifdef CONFIG_XDP_SOCKETS
- if (rxq->pool)
- if (nla_put_empty_nest(rsp, NETDEV_A_QUEUE_XSK))
- goto nla_put_failure_lease;
-#endif
- netif_put_rx_queue_lease_locked(orig_netdev, netdev);
+ if (netdev_nl_queue_fill_mp(rsp, netdev, rxq))
+ goto nla_put_failure;
break;
case NETDEV_QUEUE_TYPE_TX:
txq = netdev_get_tx_queue(netdev, q_idx);
@@ -493,8 +512,6 @@ netdev_nl_queue_fill_one(struct sk_buff *rsp, struct net_device *netdev,
return 0;
-nla_put_failure_lease:
- netif_put_rx_queue_lease_locked(orig_netdev, netdev);
nla_put_failure:
genlmsg_cancel(rsp, hdr);
return -EMSGSIZE;
diff --git a/net/core/netdev_queues.c b/net/core/netdev_queues.c
index 265161e12a9c..5597af86591b 100644
--- a/net/core/netdev_queues.c
+++ b/net/core/netdev_queues.c
@@ -37,18 +37,22 @@ struct device *netdev_queue_get_dma_dev(struct net_device *dev,
unsigned int idx,
enum netdev_queue_type type)
{
- struct net_device *orig_dev = dev;
+ struct netdev_rx_queue *hw_rxq;
struct device *dma_dev;
/* Only RX side supports queue leasing today. */
if (type != NETDEV_QUEUE_TYPE_RX || !netif_rxq_is_leased(dev, idx))
return __netdev_queue_get_dma_dev(dev, idx);
-
- if (!netif_get_rx_queue_lease_locked(&dev, &idx))
+ if (!netif_is_queue_leasee(dev))
return NULL;
- dma_dev = __netdev_queue_get_dma_dev(dev, idx);
- netif_put_rx_queue_lease_locked(orig_dev, dev);
+ hw_rxq = __netif_get_rx_queue(dev, idx)->lease;
+
+ netdev_lock(hw_rxq->dev);
+ idx = get_netdev_rx_queue_index(hw_rxq);
+ dma_dev = __netdev_queue_get_dma_dev(hw_rxq->dev, idx);
+ netdev_unlock(hw_rxq->dev);
+
return dma_dev;
}
diff --git a/net/core/netdev_rx_queue.c b/net/core/netdev_rx_queue.c
index 1d6e7e47bf0a..53cea4460768 100644
--- a/net/core/netdev_rx_queue.c
+++ b/net/core/netdev_rx_queue.c
@@ -57,6 +57,11 @@ static bool netif_lease_dir_ok(const struct net_device *dev,
return false;
}
+bool netif_is_queue_leasee(const struct net_device *dev)
+{
+ return netif_lease_dir_ok(dev, NETIF_VIRT_TO_PHYS);
+}
+
struct netdev_rx_queue *
__netif_get_rx_queue_lease(struct net_device **dev, unsigned int *rxq_idx,
enum netif_lease_dir dir)
@@ -74,29 +79,6 @@ __netif_get_rx_queue_lease(struct net_device **dev, unsigned int *rxq_idx,
return rxq;
}
-struct netdev_rx_queue *
-netif_get_rx_queue_lease_locked(struct net_device **dev, unsigned int *rxq_idx)
-{
- struct net_device *orig_dev = *dev;
- struct netdev_rx_queue *rxq;
-
- /* Locking order is always from the virtual to the physical device
- * see netdev_nl_queue_create_doit().
- */
- netdev_ops_assert_locked(orig_dev);
- rxq = __netif_get_rx_queue_lease(dev, rxq_idx, NETIF_VIRT_TO_PHYS);
- if (rxq && orig_dev != *dev)
- netdev_lock(*dev);
- return rxq;
-}
-
-void netif_put_rx_queue_lease_locked(struct net_device *orig_dev,
- struct net_device *dev)
-{
- if (orig_dev != dev)
- netdev_unlock(dev);
-}
-
/* See also page_pool_is_unreadable() */
bool netif_rxq_has_unreadable_mp(struct net_device *dev, unsigned int rxq_idx)
{
@@ -261,7 +243,6 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
const struct pp_memory_provider_params *p,
struct netlink_ext_ack *extack)
{
- struct net_device *orig_dev = dev;
int ret;
if (!netdev_need_ops_lock(dev))
@@ -276,19 +257,18 @@ int netif_mp_open_rxq(struct net_device *dev, unsigned int rxq_idx,
if (!netif_rxq_is_leased(dev, rxq_idx))
return __netif_mp_open_rxq(dev, rxq_idx, p, extack);
- if (!netif_get_rx_queue_lease_locked(&dev, &rxq_idx)) {
+ if (!__netif_get_rx_queue_lease(&dev, &rxq_idx, NETIF_VIRT_TO_PHYS)) {
NL_SET_ERR_MSG(extack, "rx queue leased to a virtual netdev");
return -EBUSY;
}
if (!dev->dev.parent) {
NL_SET_ERR_MSG(extack, "rx queue belongs to a virtual netdev");
- ret = -EOPNOTSUPP;
- goto out;
+ return -EOPNOTSUPP;
}
+ netdev_lock(dev);
ret = __netif_mp_open_rxq(dev, rxq_idx, p, extack);
-out:
- netif_put_rx_queue_lease_locked(orig_dev, dev);
+ netdev_unlock(dev);
return ret;
}
@@ -323,18 +303,18 @@ static void __netif_mp_close_rxq(struct net_device *dev, unsigned int ifq_idx,
void netif_mp_close_rxq(struct net_device *dev, unsigned int ifq_idx,
const struct pp_memory_provider_params *old_p)
{
- struct net_device *orig_dev = dev;
-
if (WARN_ON_ONCE(ifq_idx >= dev->real_num_rx_queues))
return;
if (!netif_rxq_is_leased(dev, ifq_idx))
return __netif_mp_close_rxq(dev, ifq_idx, old_p);
- if (WARN_ON_ONCE(!netif_get_rx_queue_lease_locked(&dev, &ifq_idx)))
+ if (!__netif_get_rx_queue_lease(&dev, &ifq_idx, NETIF_VIRT_TO_PHYS)) {
+ WARN_ON_ONCE(1);
return;
-
+ }
+ netdev_lock(dev);
__netif_mp_close_rxq(dev, ifq_idx, old_p);
- netif_put_rx_queue_lease_locked(orig_dev, dev);
+ netdev_unlock(dev);
}
void __netif_mp_uninstall_rxq(struct netdev_rx_queue *rxq,
diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c
index fe1c7899455e..616cd7b42502 100644
--- a/net/xdp/xsk.c
+++ b/net/xdp/xsk.c
@@ -31,6 +31,8 @@
#include <net/netdev_rx_queue.h>
#include <net/xdp.h>
+#include "../core/dev.h"
+
#include "xsk_queue.h"
#include "xdp_umem.h"
#include "xsk.h"
@@ -117,20 +119,42 @@ struct xsk_buff_pool *xsk_get_pool_from_qid(struct net_device *dev,
}
EXPORT_SYMBOL(xsk_get_pool_from_qid);
+static void __xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id)
+{
+ if (queue_id < dev->num_rx_queues)
+ dev->_rx[queue_id].pool = NULL;
+ if (queue_id < dev->num_tx_queues)
+ dev->_tx[queue_id].pool = NULL;
+}
+
void xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id)
{
- struct net_device *orig_dev = dev;
- unsigned int id = queue_id;
+ struct netdev_rx_queue *hw_rxq;
- if (id < dev->real_num_rx_queues)
- WARN_ON_ONCE(!netif_get_rx_queue_lease_locked(&dev, &id));
+ if (!netif_rxq_is_leased(dev, queue_id))
+ return __xsk_clear_pool_at_qid(dev, queue_id);
+ WARN_ON_ONCE(!netif_is_queue_leasee(dev));
- if (id < dev->num_rx_queues)
- dev->_rx[id].pool = NULL;
- if (id < dev->num_tx_queues)
- dev->_tx[id].pool = NULL;
+ hw_rxq = __netif_get_rx_queue(dev, queue_id)->lease;
- netif_put_rx_queue_lease_locked(orig_dev, dev);
+ netdev_lock(hw_rxq->dev);
+ queue_id = get_netdev_rx_queue_index(hw_rxq);
+ __xsk_clear_pool_at_qid(hw_rxq->dev, queue_id);
+ netdev_unlock(hw_rxq->dev);
+}
+
+static int __xsk_reg_pool_at_qid(struct net_device *dev,
+ struct xsk_buff_pool *pool, u16 queue_id)
+{
+ if (xsk_get_pool_from_qid(dev, queue_id))
+ return -EBUSY;
+
+ if (queue_id < dev->real_num_rx_queues)
+ dev->_rx[queue_id].pool = pool;
+ if (queue_id < dev->real_num_tx_queues)
+ dev->_tx[queue_id].pool = pool;
+
+ return 0;
}
/* The buffer pool is stored both in the _rx struct and the _tx struct as we do
@@ -140,29 +164,26 @@ void xsk_clear_pool_at_qid(struct net_device *dev, u16 queue_id)
int xsk_reg_pool_at_qid(struct net_device *dev, struct xsk_buff_pool *pool,
u16 queue_id)
{
- struct net_device *orig_dev = dev;
- unsigned int id = queue_id;
- int ret = 0;
+ struct netdev_rx_queue *hw_rxq;
+ int ret;
- if (id >= max(dev->real_num_rx_queues,
- dev->real_num_tx_queues))
+ if (queue_id >= max(dev->real_num_rx_queues,
+ dev->real_num_tx_queues))
return -EINVAL;
- if (id < dev->real_num_rx_queues) {
- if (!netif_get_rx_queue_lease_locked(&dev, &id))
- return -EBUSY;
- if (xsk_get_pool_from_qid(dev, id)) {
- ret = -EBUSY;
- goto out;
- }
- }
+ if (queue_id >= dev->real_num_rx_queues ||
+ !netif_rxq_is_leased(dev, queue_id))
+ return __xsk_reg_pool_at_qid(dev, pool, queue_id);
+ if (!netif_is_queue_leasee(dev))
+ return -EBUSY;
+
+ hw_rxq = __netif_get_rx_queue(dev, queue_id)->lease;
+
+ netdev_lock(hw_rxq->dev);
+ queue_id = get_netdev_rx_queue_index(hw_rxq);
+ ret = __xsk_reg_pool_at_qid(hw_rxq->dev, pool, queue_id);
+ netdev_unlock(hw_rxq->dev);
- if (id < dev->real_num_rx_queues)
- dev->_rx[id].pool = pool;
- if (id < dev->real_num_tx_queues)
- dev->_tx[id].pool = pool;
-out:
- netif_put_rx_queue_lease_locked(orig_dev, dev);
return ret;
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH net-next v2 3/3] net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
From: Andrew Lunn @ 2026-04-08 21:56 UTC (permalink / raw)
To: Carlo Szelinsky
Cc: o.rempel, kory.maincent, andrew+netdev, hkallweit1, linux, kuba,
davem, edumazet, pabeni, horms, netdev, linux-kernel
In-Reply-To: <20260408210711.439068-1-github@szelinsky.de>
On Wed, Apr 08, 2026 at 11:07:11PM +0200, Carlo Szelinsky wrote:
> So I went ahead and tested the phy_probe() approach on my setup (RTL930x
> DSA switch with an I2C Hasivo HS104 PSE controller as module).
>
> PoE itself works fine, but phydev->psec never gets set - ethtool just
> says "No PSE is attached" on all ports.
>
> Took me a while to figure out what's going on. The problem is how DSA
> handles PHYs: when phy_probe() returns -EPROBE_DEFER because the PSE
> controller hasn't probed yet, the PHY device is registered but sits
> there unprobed. Then the DSA switch comes along, sets up its ports, and
> phy_attach_direct() force-binds the generic PHY driver with
> device_bind_driver().
Yes, this is a known issue with phylib.
> What I'm seeing timing-wise:
> - MDIO scan registers PHYs, phy_probe() defers (no PSE yet)
> - DSA probes, phy_attach_direct() binds genphy
> - t=17s: HS104 finally probes
That is a long time. Does it actually start probing much earlier, but
it is busy download firmware, and the probe completes after 17
seconds.
> - deferred retry: nope, driver already bound
> - t=35s: regulator_late_cleanup (caught by admin_state_synced)
>
> Not sure what the best path forward is here. Should we look at fixing
> phy_attach_direct() to handle this case.
It is not easy to fix, because generally drivers call
phy_attach_direct() in their open() function, not probe(). It is too
late to return EPROBE_DEFFER, you can only do that in probe. phylib
knows the device exists, but it sees there is no driver, so it does
not have much choice. It can either use genphy, or it can error out
phy_attach_direct().
DSA is however atypical, and does phylink_connect() early. So there
might be a way out. In dsa_user_phy_connect() once you have the
phydev, you could look at phydev->drv and return EPROBE_DEFFER if it
is NULL. Ugly. And a bit of a layering violation. Maybe a helper in
phylib, phy_is_driver_bound() ?
Andrew
^ permalink raw reply
* [PATCH net-next 7/7] selftests/net: packetdrill: add test for SO_RCVLOWAT window clamp
From: Simon Baatz via B4 Relay @ 2026-04-08 21:50 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
David Ahern, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Simon Baatz
In-Reply-To: <20260408-tcp_rcv_exact_clamp_and_wnd-v1-0-76a6f212e153@gmail.com>
From: Simon Baatz <gmbnomis@gmail.com>
Add a packetdrill test to verify that setting SO_RCVLOWAT does not
raise window_clamp beyond the maximum value allowed by window
scaling.
Signed-off-by: Simon Baatz <gmbnomis@gmail.com>
---
.../net/packetdrill/tcp_rcv_sockopt_lowat.pkt | 24 ++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_lowat.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_lowat.pkt
new file mode 100644
index 0000000000000..c024f3953f5a4
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_lowat.pkt
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Verify that setting SO_RCVLOWAT does not set window_clamp higher than the
+// maximum value allowed by window scaling.
+--mss=1000
+
+`./defaults.sh`
+
+// Initialize connection
+ 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+
+ +0 < S 0:0(0) win 32792
+ +0 > S. 0:0(0) ack 1 win 65535 <mss 1460>
+ +0 < . 1:1(0) ack 1 win 32792
+
+ +0 accept(3, ..., ...) = 4
+
+ +0 getsockopt(4, IPPROTO_TCP, 10, [65535], [4]) = 0 // TCP_WINDOW_CLAMP == 10
+ +0 setsockopt(4, SOL_SOCKET, SO_RCVLOWAT, [1024000], 4) = 0
+ +0 getsockopt(4, SOL_SOCKET, SO_RCVLOWAT, [1024000], [4]) = 0
+ +0 getsockopt(4, IPPROTO_TCP, 10, [65535], [4]) = 0
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 6/7] tcp: use tcp_set_window_clamp() for SO_RCVLOWAT
From: Simon Baatz via B4 Relay @ 2026-04-08 21:50 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
David Ahern, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Simon Baatz
In-Reply-To: <20260408-tcp_rcv_exact_clamp_and_wnd-v1-0-76a6f212e153@gmail.com>
From: Simon Baatz <gmbnomis@gmail.com>
Setting the SO_RCVLOWAT socket option may raise the receive window
clamp. Currently this is done by assigning to window_clamp directly.
Use the tcp_set_window_clamp() helper instead, so that raising the
clamp is subject to the same constraints and rcv_ssthresh adjustments
as TCP_WINDOW_CLAMP.
Signed-off-by: Simon Baatz <gmbnomis@gmail.com>
---
net/ipv4/tcp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index bd03c99f793ae..567027bc86b3f 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1853,7 +1853,7 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
WRITE_ONCE(sk->sk_rcvbuf, space);
if (tp->window_clamp && tp->window_clamp < val)
- WRITE_ONCE(tp->window_clamp, val);
+ tcp_set_window_clamp(sk, val);
}
return 0;
}
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 4/7] selftests/net: packetdrill: add tcp_rcv_wnd_snd_ack_no_scaling.pkt
From: Simon Baatz via B4 Relay @ 2026-04-08 21:50 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
David Ahern, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Simon Baatz
In-Reply-To: <20260408-tcp_rcv_exact_clamp_and_wnd-v1-0-76a6f212e153@gmail.com>
From: Simon Baatz <gmbnomis@gmail.com>
Verify that, when no TCP window scaling is used, each packet that
substantially advances the right edge of the receive window is ACKed
immediately.
Multiple packets are used so that the scaling_ratio receive window
adaptation can settle and does not by itself cause immediate ACKs,
avoiding false positives.
Signed-off-by: Simon Baatz <gmbnomis@gmail.com>
---
.../packetdrill/tcp_rcv_wnd_snd_ack_no_scaling.pkt | 37 ++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_wnd_snd_ack_no_scaling.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_wnd_snd_ack_no_scaling.pkt
new file mode 100644
index 0000000000000..41561b026da85
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_wnd_snd_ack_no_scaling.pkt
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Every packet must be ACKed immediately if the right edge of receive window
+// advances substantially. This test verifies that behavior when the connection
+// does not use window scaling.
+--mss=1000
+
+`./defaults.sh`
+
+// Initialize connection
+ 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [256000], 4) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+
+ +0 < S 0:0(0) win 32792
+ +0 > S. 0:0(0) ack 1 win 65535 <mss 1460>
+ +0 < . 1:1(0) ack 1 win 32792
+
+ +0 accept(3, ..., ...) = 4
+
+ +0 < P. 1:65001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 65001 win 65535
+ +0 < P. 65001:130001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 130001 win 65535
+ +0 < P. 130001:195001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 195001 win 65535
+ +0 < P. 195001:260001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 260001 win 65535
+ +0 < P. 260001:325001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 325001 win 65535
+
+// reading all data does not open the window further -> no ACK
+ +0 read(4, ..., 325000) = 325000
+ +0.2 < P. 325001:390001(65000) ack 1 win 32792
+ +0 > . 1:1(0) ack 390001 win 65535
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 5/7] selftests/net: packetdrill: add TCP_WINDOW_CLAMP test
From: Simon Baatz via B4 Relay @ 2026-04-08 21:50 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
David Ahern, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Simon Baatz
In-Reply-To: <20260408-tcp_rcv_exact_clamp_and_wnd-v1-0-76a6f212e153@gmail.com>
From: Simon Baatz <gmbnomis@gmail.com>
Add a packetdrill test to verify that the socket option
TCP_WINDOW_CLAMP can be set to a large value on a listening socket,
but is clamped on an established socket to the maximum representable
advertised window.
Signed-off-by: Simon Baatz <gmbnomis@gmail.com>
---
.../net/packetdrill/tcp_rcv_sockopt_wnd_clamp.pkt | 28 ++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_wnd_clamp.pkt b/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_wnd_clamp.pkt
new file mode 100644
index 0000000000000..d7203f2893c3f
--- /dev/null
+++ b/tools/testing/selftests/net/packetdrill/tcp_rcv_sockopt_wnd_clamp.pkt
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Verify that TCP_WINDOW_CLAMP can be set to a high value on a LISTEN socket, but,
+// in an established connection, the value is clamped to the maximum representable
+// advertised window.
+--mss=1000
+
+`./defaults.sh`
+
+// Initialize connection
+ 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+ +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+ +0 setsockopt(3, SOL_SOCKET, SO_RCVBUF, [256000], 4) = 0
+ +0 setsockopt(3, IPPROTO_TCP, 10, [255999], 4) = 0 // TCP_WINDOW_CLAMP == 10
+ +0 getsockopt(3, IPPROTO_TCP, 10, [255999], [4]) = 0
+ +0 bind(3, ..., ...) = 0
+ +0 listen(3, 1) = 0
+
+ +0 < S 0:0(0) win 32792
+ +0 > S. 0:0(0) ack 1 win 65535 <mss 1460>
+ +0 < . 1:1(0) ack 1 win 32792
+
+ +0 accept(3, ..., ...) = 4
+
+ +0 getsockopt(4, IPPROTO_TCP, 10, [65535], [4]) = 0
+ +0 setsockopt(4, IPPROTO_TCP, 10, [255999], 4) = 0
+ +0 getsockopt(4, IPPROTO_TCP, 10, [65535], [4]) = 0
+ +0 getsockopt(3, IPPROTO_TCP, 10, [255999], [4]) = 0
--
2.53.0
^ permalink raw reply related
* [PATCH net-next 3/7] tcp: Ensure window_clamp is limited to representable window
From: Simon Baatz via B4 Relay @ 2026-04-08 21:50 UTC (permalink / raw)
To: Eric Dumazet, Neal Cardwell, Kuniyuki Iwashima, David S. Miller,
David Ahern, Jakub Kicinski, Paolo Abeni, Simon Horman,
Shuah Khan
Cc: netdev, linux-kernel, linux-kselftest, Simon Baatz
In-Reply-To: <20260408-tcp_rcv_exact_clamp_and_wnd-v1-0-76a6f212e153@gmail.com>
From: Simon Baatz <gmbnomis@gmail.com>
On connection initiation, window_clamp is limited to the maximum value
representable for the connection's window scale factor.
However, window_clamp may be changed later when:
- it needs to be adjusted due to scaling_ratio changes
- the receive buffer grows due to autotuning
- the TCP_WINDOW_CLAMP socket option is set
In all cases, window_clamp must not end up higher than the maximum
representable advertised window.
Thus, if the TCP connection state indicates that we can rely on
rx_opt.rcv_wscale, clamp the new window_clamp to the maximum window
for that scaling factor (including the "no window scaling" case where
rcv_wscale is zero).
This has visible consequences for calculations based on rcv_wnd. For
example, the logic in __tcp_ack_snd_check() uses the advance of the
right edge of the receive window to determine when to send an
immediate ACK. If rcv_wnd does not properly reflect the "on the wire"
advertised window (i.e. it is much higher than the maximum value
representable), this calculation will be wrong and ACKs may be delayed
when they should be sent immediately.
One concrete example is when the TCP receive buffer is much larger
than 64KB, but no window scaling is used. If window_clamp (and thus
rcv_wnd) are not limited to 65535, the "internal" window based on
rcv_wnd can extend far beyond the 16‑bit window actually advertised on
the wire.
After receiving a data segment, the right edge of the "on the wire"
window can be moved (as there is plenty of space in rcv_wnd) and an
immediate ACK should be sent. But, it won't do so if the calculation
based on rcv_wnd does not happen to change "internal" window right edge.
Signed-off-by: Simon Baatz <gmbnomis@gmail.com>
---
net/ipv4/tcp.c | 4 ++++
net/ipv4/tcp_input.c | 6 ++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index e57eaffc007a0..bd03c99f793ae 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -3802,6 +3802,10 @@ int tcp_set_window_clamp(struct sock *sk, int val)
old_window_clamp = tp->window_clamp;
new_window_clamp = max_t(int, SOCK_MIN_RCVBUF / 2, val);
+ if ((1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT |
+ TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2))
+ new_window_clamp = min_t(u32, U16_MAX << tp->rx_opt.rcv_wscale, new_window_clamp);
+
if (new_window_clamp == old_window_clamp)
return 0;
diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
index 505884dcb7a2b..6e9123c98152f 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -914,6 +914,7 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval)
struct tcp_sock *tp = tcp_sk(sk);
u32 rcvwin, rcvbuf, cap, oldval;
u32 rtt_threshold, rtt_us;
+ u32 window_clamp;
u64 grow;
oldval = tp->rcvq_space.space;
@@ -949,8 +950,9 @@ void tcp_rcvbuf_grow(struct sock *sk, u32 newval)
if (rcvbuf > sk->sk_rcvbuf) {
WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
/* Make the window clamp follow along. */
- WRITE_ONCE(tp->window_clamp,
- tcp_win_from_space(sk, rcvbuf));
+ window_clamp = tcp_win_from_space(sk, rcvbuf);
+ window_clamp = min_t(u32, U16_MAX << tp->rx_opt.rcv_wscale, window_clamp);
+ WRITE_ONCE(tp->window_clamp, window_clamp);
}
}
/*
--
2.53.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox