netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [net-next v6 12/12] selftests: drv-net: Add USO test
From: Joe Damato @ 2026-03-26 23:52 UTC (permalink / raw)
  To: netdev, Shuah Khan
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, horms, michael.chan,
	pavan.chebbi, linux-kernel, leon, Joe Damato, linux-kselftest
In-Reply-To: <20260326235238.2940471-1-joe@dama.to>

Add a simple test for USO. Can be used with netdevsim or real hardware.
Tests both ipv4 and ipv6 with several full segments and a partial
segment.

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.

 v4:
   - Fix python linter issues (unused imports, docstring, etc).

 rfcv2:
   - new in rfcv2

 tools/testing/selftests/drivers/net/Makefile |  1 +
 tools/testing/selftests/drivers/net/uso.py   | 96 ++++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100755 tools/testing/selftests/drivers/net/uso.py

diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile
index 7c7fa75b80c2..335c2ce4b9ab 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -21,6 +21,7 @@ TEST_PROGS := \
 	ring_reconfig.py \
 	shaper.py \
 	stats.py \
+	uso.py \
 	xdp.py \
 # end of TEST_PROGS
 
diff --git a/tools/testing/selftests/drivers/net/uso.py b/tools/testing/selftests/drivers/net/uso.py
new file mode 100755
index 000000000000..2ddeae99b4d6
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/uso.py
@@ -0,0 +1,96 @@
+#!/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 correct number of individual segments with correct sizes.
+"""
+import socket
+import time
+
+from lib.py import ksft_run, ksft_exit, KsftSkipEx
+from lib.py import ksft_ge
+from lib.py import NetDrvEpEnv
+from lib.py import defer, ethtool, ip, rand_port
+
+# 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 = bytes(range(256)) * ((total_payload // 256) + 1)
+    payload = payload[:total_payload]
+    sock.sendto(payload, dst)
+    sock.close()
+    return payload
+
+
+def _get_rx_packets(cfg):
+    stats = ip(f"-s link show dev {cfg.remote_ifname}",
+               json=True, host=cfg.remote)[0]
+    return stats['stats64']['rx']['packets']
+
+
+def _test_uso(cfg, ipver, mss, total_payload):
+    cfg.require_ipver(ipver)
+
+    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
+    defer(ethtool, f"-K {cfg.ifname} tx-udp-segmentation off")
+
+    expected_segs = (total_payload + mss - 1) // mss
+
+    rx_before = _get_rx_packets(cfg)
+
+    port = rand_port(stype=socket.SOCK_DGRAM)
+    _send_uso(cfg, ipver, mss, total_payload, port)
+
+    time.sleep(0.5)
+
+    rx_after = _get_rx_packets(cfg)
+    rx_delta = rx_after - rx_before
+
+    ksft_ge(rx_delta, expected_segs,
+            comment=f"Expected >= {expected_segs} rx packets, got {rx_delta}")
+
+
+def test_uso_v4(cfg):
+    """USO IPv4: 11 segments (10 full + 1 partial)."""
+    _test_uso(cfg, "4", 1400, 1400 * 10 + 500)
+
+
+def test_uso_v6(cfg):
+    """USO IPv6: 11 segments (10 full + 1 partial)."""
+    _test_uso(cfg, "6", 1400, 1400 * 10 + 500)
+
+
+def test_uso_v4_exact(cfg):
+    """USO IPv4: exact multiple of MSS (5 full segments)."""
+    _test_uso(cfg, "4", 1400, 1400 * 5)
+
+
+def main() -> None:
+    """Run USO tests."""
+    with NetDrvEpEnv(__file__) as cfg:
+        ksft_run([test_uso_v4,
+                  test_uso_v6,
+                  test_uso_v4_exact],
+                 args=(cfg, ))
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()
-- 
2.52.0


^ permalink raw reply related

* [PATCH net v4] bnxt_en: validate firmware backing store types
From: Pengpeng Hou @ 2026-03-27  0:38 UTC (permalink / raw)
  To: michael.chan
  Cc: pavan.chebbi, andrew+netdev, davem, edumazet, kuba, pabeni,
	netdev, linux-kernel, pengpeng
In-Reply-To: <20260326142033.82313-1-pengpeng@iscas.ac.cn>

bnxt_hwrm_func_backing_store_qcaps_v2() stores resp->type from the
firmware response in ctxm->type and later uses that value to index
fixed backing-store metadata arrays such as ctx_arr[] and
bnxt_bstore_to_trace[].

The firmware response type is defined to match the queried request type.
Validate that resp->type matches the current request before storing it,
and keep next_valid_type in a dedicated variable so loop control stays
clear for non-valid or unchanged entries.

Fixes: 6a4d0774f02d ("bnxt_en: Add support for new backing store query firmware API")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
v4:
- validate that resp->type matches the queried type
- keep next_type only for loop control

 drivers/net/ethernet/broadcom/bnxt/bnxt.c | 30 +++++++++++++++++------
 1 file changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 0751c0e4581a..6dd35942640d 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -8692,15 +8692,18 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		u8 init_val, init_off, i;
 		u32 max_entries;
 		u16 entry_size;
+		u16 req_type;
 		__le32 *p;
+		u16 next_type;
 		u32 flags;
 
-		req->type = cpu_to_le16(type);
+		req_type = type;
+		req->type = cpu_to_le16(req_type);
 		rc = hwrm_req_send(bp, req);
 		if (rc)
 			goto ctx_done;
 		flags = le32_to_cpu(resp->flags);
-		type = le16_to_cpu(resp->next_valid_type);
+		next_type = le16_to_cpu(resp->next_valid_type);
 		if (!(flags & BNXT_CTX_MEM_TYPE_VALID)) {
 			bnxt_free_one_ctx_mem(bp, ctxm, true);
 			continue;
@@ -8708,14 +8711,23 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		entry_size = le16_to_cpu(resp->entry_size);
 		max_entries = le32_to_cpu(resp->max_num_entries);
 		if (ctxm->mem_valid) {
-			if (!(flags & BNXT_CTX_MEM_PERSIST) ||
-			    ctxm->entry_size != entry_size ||
-			    ctxm->max_entries != max_entries)
-				bnxt_free_one_ctx_mem(bp, ctxm, true);
-			else
+			if ((flags & BNXT_CTX_MEM_PERSIST) &&
+			    ctxm->entry_size == entry_size &&
+			    ctxm->max_entries == max_entries) {
+				type = next_type;
 				continue;
+			}
+
+			bnxt_free_one_ctx_mem(bp, ctxm, true);
 		}
-		ctxm->type = le16_to_cpu(resp->type);
+		if (le16_to_cpu(resp->type) != req_type) {
+			netdev_warn(bp->dev,
+				    "unexpected backing store type %u returned for request type %u\n",
+				    le16_to_cpu(resp->type), req_type);
+			rc = -EINVAL;
+			goto ctx_done;
+		}
+		ctxm->type = req_type;
 		ctxm->entry_size = entry_size;
 		ctxm->flags = flags;
 		ctxm->instance_bmap = le32_to_cpu(resp->instance_bit_map);
@@ -8731,6 +8743,8 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		for (i = 0, p = &resp->split_entry_0; i < ctxm->split_entry_cnt;
 		     i++, p++)
 			ctxm->split[i] = le32_to_cpu(*p);
+
+		type = next_type;
 	}
 	rc = bnxt_alloc_all_ctx_pg_info(bp, BNXT_CTX_V2_MAX);
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* Re: [PATCH] qed: fcoe: limit command queue array fill to the ramrod array size
From: Pengpeng Hou @ 2026-03-27  0:49 UTC (permalink / raw)
  To: andrew+netdev
  Cc: davem, edumazet, kuba, pabeni, netdev, linux-kernel, pengpeng
In-Reply-To: <20260324105647.92763-1-pengpeng@iscas.ac.cn>

Hi,

No, I do not think the existing feat_num-based check is sufficient here.

That check only verifies that num_cqs does not exceed the FCoE CQ
resources reported by the hardware:

    fcoe_pf_params->num_cqs <= p_hwfn->hw_info.feat_num[QED_FCOE_CQ]

But the ramrod field being filled afterwards is a fixed-size array:

    cq_cmdq_sb_num_arr[SCSI_MAX_NUM_OF_CMDQS]

and SCSI_MAX_NUM_OF_CMDQS is 64 in the current tree.

The in-tree qedf caller derives num_cqs from the device-reported CQ count
and the online CPU count:

    dev_info.num_cqs = FEAT_NUM(hwfn, QED_FCOE_CQ)
    num_queues = min(dev_info.num_cqs, num_online_cpus())
    fcoe_pf_params->num_cqs = num_queues

So the existing resource check and the ramrod array capacity are not the
same bound. If the hardware reports more than 64 FCoE CQs, current-tree
callers can still pass a num_cqs value that satisfies the feat_num check
but exceeds ARRAY_SIZE(cq_cmdq_sb_num_arr).

I can resend with this clarified in the commit message.

Best regards,
Pengpeng


^ permalink raw reply

* Re: [PATCH net v2] net/sched: sch_hfsc: fix divide-by-zero in rtsc_min()
From: Jakub Kicinski @ 2026-03-27  0:50 UTC (permalink / raw)
  To: Xiang Mei
  Cc: security, netdev, pabeni, jhs, jiri, davem, edumazet, horms,
	bestswngs
In-Reply-To: <CAPpSM+Sz_z2LpJwJrc9g+A+vRb=dRyMaDnBdZPP7TT952bgjpg@mail.gmail.com>

On Thu, 26 Mar 2026 13:44:37 -0700 Xiang Mei wrote:
> Thanks for the suggestion. A TDC selftest was added to v3.

Excellent, thanks!

^ permalink raw reply

* Re: [PATCH] qed: iscsi: limit command queue array fill to the ramrod array size
From: Pengpeng Hou @ 2026-03-27  0:53 UTC (permalink / raw)
  To: andrew+netdev
  Cc: davem, edumazet, kuba, pabeni, netdev, linux-kernel, pengpeng
In-Reply-To: <20260324105629.61282-1-pengpeng@iscas.ac.cn>

Hi,

No, I do not think the existing feat_num-based check is sufficient here.

That check only verifies that num_queues does not exceed the iSCSI CQ
resources reported by the hardware:

    p_params->num_queues <= p_hwfn->hw_info.feat_num[QED_ISCSI_CQ]

But the ramrod field being filled afterwards is a fixed-size array:

    cq_cmdq_sb_num_arr[SCSI_MAX_NUM_OF_CMDQS]

and SCSI_MAX_NUM_OF_CMDQS is 64 in the current tree.

The in-tree qedi caller derives num_queues from the device-reported CQ
count and the online CPU count:

    dev_info.num_cqs = FEAT_NUM(hwfn, QED_ISCSI_CQ)
    num_queues = min(dev_info.num_cqs, num_online_cpus())

So the existing resource check and the ramrod array capacity are not the
same bound. If the hardware reports more than 64 iSCSI CQs, current-tree
callers can still pass a num_queues value that satisfies the feat_num
check but exceeds ARRAY_SIZE(cq_cmdq_sb_num_arr).

I can resend with this clarified in the commit message.

Best regards,
Pengpeng


^ permalink raw reply

* Re: [PATCH net-next v6 0/6] TLS read_sock performance scalability
From: Jakub Kicinski @ 2026-03-27  0:54 UTC (permalink / raw)
  To: Chuck Lever
  Cc: john.fastabend, sd, netdev, kernel-tls-handshake, Chuck Lever,
	Hannes Reinecke, Alistair Francis
In-Reply-To: <20260326-tls-read-sock-v6-0-fd887b9e7f06@oracle.com>

On Thu, 26 Mar 2026 09:50:45 -0400 Chuck Lever wrote:
> I'd like to encourage in-kernel kTLS consumers (i.e., NFS and
> NVMe/TCP) to coalesce on the use of read_sock. When I suggested
> this to Hannes, he reported a number of nagging performance
> scalability issues with read_sock. This series is an attempt to
> run these issues down and get them fixed before we convert the
> above sock_recvmsg consumers over to read_sock.
> 
> Batch async decryption and its submit/deliver scaffolding were
> dropped from this series because async_capable is always false
> for TLS 1.3, which NFS and NVMe/TCP both require. Async crypto
> support for TLS 1.3 is a prerequisite for revisiting that work.

Please repost the remaining 5 patches

^ permalink raw reply

* Re: [PATCH net-next v11 06/15] quic: add stream management
From: Jakub Kicinski @ 2026-03-27  1:00 UTC (permalink / raw)
  To: Xin Long
  Cc: network dev, quic, davem, Eric Dumazet, Paolo Abeni, Simon Horman,
	Stefan Metzmacher, Moritz Buhl, Tyler Fanelli, Pengtao He,
	Thomas Dreibholz, linux-cifs, Steve French, Namjae Jeon,
	Paulo Alcantara, Tom Talpey, kernel-tls-handshake, Chuck Lever,
	Jeff Layton, Steve Dickson, Hannes Reinecke, Alexander Aring,
	David Howells, Matthieu Baerts, John Ericson, Cong Wang,
	D . Wythe, Jason Baron, illiliti, Sabrina Dubroca,
	Marcelo Ricardo Leitner, Daniel Stenberg, Andy Gospodarek,
	Marc E . Fiuczynski
In-Reply-To: <CADvbK_eyNtAfA=JPR5Y4arFLPpkWMs8io=TO83rHAa-JJkhdAA@mail.gmail.com>

On Thu, 26 Mar 2026 17:48:51 -0400 Xin Long wrote:
> > Can you also check the google one?
> >
> > https://sashiko.dev/#/patchset/4019cfba3ffd6f91aee24713d2adc778f858b4c4.1774410440.git.lucien.xin@gmail.com  
> 
> Will do. There are quite a few reports, and it may take me some time
> to review them all carefully.
> 
> BTW, what's the workflow for the Google AI feedback? should I reply to
> each item in this thread like we did with Claude one, or is there a
> different workflow?

It's a bit more chatty and we don't have any integrations there so it's
a bit hard to deal with :( At this point just look thru the reports and
address what's legit. We'll probably take your word for everything that
remains being a false positive.

^ permalink raw reply

* [PATCH net-next v8 0/4] net: dsa: mxl862xx: add support for bridge offloading
From: Daniel Golle @ 2026-03-27  1:00 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
	Simon Horman, Russell King, netdev, linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin

As a next step to complete the mxl862xx DSA driver, add support for
offloading forwarding between bridged ports to the switch hardware.

This works pretty much without any big surprises, apart from two
subtleties:
 * per-port control over flooding behavior has to be implemented by
   (ab)using a 0-rate QoS meters as stopper in lack of any better
   option.
 * STP state transition unconditionally enables learning on a port
   even if it was previously explicitely disabled (a firmware bug)

Note that as the driver is still lacking all VLAN features (which
are going to be added next), at this point some of the
bridge_vlan_aware.sh tests are failing after applying this series.

This is expected and cannot be avoided without implementing
port_vlan_filtering + port_vlan_add/del. And adding both bridge and
VLAN offloading at the same time would be too much for anyone to
review, so VLAN support is going to be submitted in a follow-up
series immediately after this series has been accepted.

All other relevant selftests (including bridge_vlan_unaware.sh) are
still passing.

Inspired by the comments received from Paolo Abeni as reply to v5
the driver now no longer caches bridge port membership in the
driver, but instead imports an existing helper from yt921x.c to dsa.h
in order to allow the driver to easily iterate over bridge members.
The mapping between DSA bridge num and firmware bridge ID is done
using a simple fixed-size array in mxl862xx_priv.

Changes since v7:
 * add missing fields to kernel-doc for struct mxl862xx_priv
 * const'ify some function parameters

Changes since v6:
 * drop wrong addition of 'priv' pointer to struct dsa_bridge
   -> use simple array to track firmware bridge to dsa bridge mapping
 * move existing dsa_bridge_ports() helper from yt921x.c to dsa.h
 * replace propesed dsa_bridge_for_each_member() by new
   dsa_switch_for_each_bridge_member() taking (struct net_device *)
   instead of (struct dsa_bridge *), aligned with the existing
   helpers
 * fix bridge allocation resource leak in case of
   mxl862xx_sync_bridge_members() failing for the first port to join a
   bridge
 * zero-initialize struct mxl862xx_cfg in mxl862xx_set_ageing_time
 * set initial=1 to always reset cursor at start when interating over
   FDB in .fdb_dump
 * hack zero-rate meter to truly always block
 * use asynchronous worker for port_set_host_flood which runs from
   atomic context and hence cannot sleep
 * reorder function to minimize diffstat of planned follow-up
   commits

Changes since v5:
 * add new DSA helpers to interate over bridge members
 * add driver-private pointer to struct dsa_bridge
 * eliminate struct mxl862xx_bridge and driver-private bridge list,
   store firmware bridge FID in new dsa_bridge->priv instead
 * rework sync_bridge_members() to use dsa_bridge_for_each_member()
   instead of for_each_set_bit() on driver-private portmap
 * rework port_bridge_join/leave to use dsa_bridge.priv for first-
   member detection and dsa_bridge_ports() for empty-bridge check
 * derive active FID from dp->bridge->priv in set_bridge_port()
 * simplify allocate_bridge()/free_bridge() to take struct dsa_bridge
   pointer directly, drop kzalloc/kfree/list management
 * simplify get_fid() to read db.bridge.priv directly
 * remove mxl862xx_find_bridge()
 * remove unnecessary default bridge config call in setup()

Changes since v4:
 * move link-local check before dsa_strip_etype_header()
 * introduce port_map helpers
 * properly implement port_mdb_{add,del} operations

Changes since v3:
 * add missing cpu_to_le32 in mxl862xx_bridge_config_fwd()
 * use little-endian 32-bit type for (unused) age_timer API field
 * better comment in port_set_host_flood() documenting architectural
   limitation
 * fix typo in comment "matche" should be "matches"
 * few whitespace fixes

Changes since v2:
 * refactor .port_bridge_join and .port_bridge_leave as requested
   by Vladimir Oltean
 * include linux/etherdevice.h which was missing and causing build
   to fail (it accidentally slipped into a follow-up patch)
 * remove left-over manual reset of learning state for port leaving
   bridge
 * remove unnecessary call to mxl862xx_port_fast_age() for port
   leaving bridge
 * add kernel-doc comments in mxl862xx.h instead of sporadic inline
   comments covering only some of the struct members
 * some other minor cosmetics (linebreaks, whitespace) here and there

Changes since v1:
 * don't set dsa_default_offload_fwd_mark() on link-local frames
 * fix kernel-doc comments in API header
 * use bitfield helpers for compound tci field in fdb API
 * add missing endian conversion for mxl862xx_stp_port_cfg.port_state
   as well as mxl862xx_mac_table_read.tci (spotted by AI review)
 * drop manually resetting port learning state on bridge<->standalone
   transitions, DSA framework takes care of that
 * don't abort updating bridge ports on error, return error at the end
 * report error in mxl862xx_port_bridge_leave()
 * create mxl862xx_get_fid() helper and use it in
   mxl862xx_port_fdb_add() and mxl862xx_port_fdb_del()
 * propagete error of callback function in mxl862xx_port_fdb_dump()
 * manually mxl862xx_port_fast_age() in mxl862xx_port_stp_state_set()
   to avoid FDB poisoning due to race condition


Daniel Golle (4):
  net: dsa: move dsa_bridge_ports() helper to dsa.h
  net: dsa: add bridge member iteration macro
  dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
  net: dsa: mxl862xx: implement bridge offloading

 drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |  20 +-
 drivers/net/dsa/mxl862xx/mxl862xx.c     | 752 ++++++++++++++++++++++--
 drivers/net/dsa/mxl862xx/mxl862xx.h     | 133 +++++
 drivers/net/dsa/yt921x.c                |  13 -
 include/net/dsa.h                       |  16 +
 net/dsa/tag_mxl862xx.c                  |   3 +
 7 files changed, 1106 insertions(+), 56 deletions(-)

-- 
2.53.0

^ permalink raw reply

* [PATCH net-next v8 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
From: Daniel Golle @ 2026-03-27  1:00 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
	Simon Horman, Russell King, netdev, linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin
In-Reply-To: <cover.1774572749.git.daniel@makrotopia.org>

The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.

Move the helper as static inline function into dsa.h, so other driver
can make use of it as well.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v8: no changes
v7: new patch

 drivers/net/dsa/yt921x.c | 13 -------------
 include/net/dsa.h        | 13 +++++++++++++
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/drivers/net/dsa/yt921x.c b/drivers/net/dsa/yt921x.c
index 904613f4694ad..5b66109ecc235 100644
--- a/drivers/net/dsa/yt921x.c
+++ b/drivers/net/dsa/yt921x.c
@@ -2154,19 +2154,6 @@ yt921x_bridge_join(struct yt921x_priv *priv, int port, u16 ports_mask)
 	return 0;
 }
 
-static u32
-dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
-{
-	struct dsa_port *dp;
-	u32 mask = 0;
-
-	dsa_switch_for_each_user_port(dp, ds)
-		if (dsa_port_offloads_bridge_dev(dp, bdev))
-			mask |= BIT(dp->index);
-
-	return mask;
-}
-
 static int
 yt921x_bridge_flags(struct yt921x_priv *priv, int port,
 		    struct switchdev_brport_flags flags)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6c17446f3dcc2..e93b4feaca966 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -831,6 +831,19 @@ dsa_tree_offloads_bridge_dev(struct dsa_switch_tree *dst,
 	return false;
 }
 
+static inline u32
+dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
+{
+	struct dsa_port *dp;
+	u32 mask = 0;
+
+	dsa_switch_for_each_user_port(dp, ds)
+		if (dsa_port_offloads_bridge_dev(dp, bdev))
+			mask |= BIT(dp->index);
+
+	return mask;
+}
+
 static inline bool dsa_port_tree_same(const struct dsa_port *a,
 				      const struct dsa_port *b)
 {
-- 
2.53.0

^ permalink raw reply related

* [PATCH net-next v8 2/4] net: dsa: add bridge member iteration macro
From: Daniel Golle @ 2026-03-27  1:00 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
	Simon Horman, Russell King, netdev, linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin
In-Reply-To: <cover.1774572749.git.daniel@makrotopia.org>

Drivers that offload bridges need to iterate over the ports that are
members of a given bridge, for example to rebuild per-port forwarding
bitmaps when membership changes. Currently drivers typically open-code
this by combining dsa_switch_for_each_user_port() with a
dsa_port_offloads_bridge_dev() check, or cache bridge membership
within the driver.

Add dsa_switch_for_each_bridge_member() macro to express this pattern
directly, and use it for the existing dsa_bridge_ports() inline
helper.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v8: no changes
v7: reuse existing helper
v6: new patch

 include/net/dsa.h | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/include/net/dsa.h b/include/net/dsa.h
index e93b4feaca966..8b6d34e8a6f0d 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -831,15 +831,18 @@ dsa_tree_offloads_bridge_dev(struct dsa_switch_tree *dst,
 	return false;
 }
 
+#define dsa_switch_for_each_bridge_member(_dp, _ds, _bdev) \
+	dsa_switch_for_each_user_port(_dp, _ds) \
+		if (dsa_port_offloads_bridge_dev(_dp, _bdev))
+
 static inline u32
 dsa_bridge_ports(struct dsa_switch *ds, const struct net_device *bdev)
 {
 	struct dsa_port *dp;
 	u32 mask = 0;
 
-	dsa_switch_for_each_user_port(dp, ds)
-		if (dsa_port_offloads_bridge_dev(dp, bdev))
-			mask |= BIT(dp->index);
+	dsa_switch_for_each_bridge_member(dp, ds, bdev)
+		mask |= BIT(dp->index);
 
 	return mask;
 }
-- 
2.53.0

^ permalink raw reply related

* [PATCH net-next v8 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
From: Daniel Golle @ 2026-03-27  1:00 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
	Simon Horman, Russell King, netdev, linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin
In-Reply-To: <cover.1774572749.git.daniel@makrotopia.org>

The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
packets of (eg. flooded) frames arriving at the CPU port.

Link-local frames are directly trapped to the CPU port only, so don't
set dsa_default_offload_fwd_mark() on those.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v8: no changes
v7: no changes
v6: no changes
v5: move link-local check before dsa_strip_etype_header()
v4: no changes
v3: no changes
v2: don't set dsa_default_offload_fwd_mark() on link-local frames

 net/dsa/tag_mxl862xx.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/dsa/tag_mxl862xx.c b/net/dsa/tag_mxl862xx.c
index 01f2158682718..8daefeb8d49df 100644
--- a/net/dsa/tag_mxl862xx.c
+++ b/net/dsa/tag_mxl862xx.c
@@ -86,6 +86,9 @@ static struct sk_buff *mxl862_tag_rcv(struct sk_buff *skb,
 		return NULL;
 	}
 
+	if (likely(!is_link_local_ether_addr(eth_hdr(skb)->h_dest)))
+		dsa_default_offload_fwd_mark(skb);
+
 	/* remove the MxL862xx special tag between the MAC addresses and the
 	 * current ethertype field.
 	 */
-- 
2.53.0

^ permalink raw reply related

* [PATCH net-next v8 4/4] net: dsa: mxl862xx: implement bridge offloading
From: Daniel Golle @ 2026-03-27  1:01 UTC (permalink / raw)
  To: Daniel Golle, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Yang,
	Simon Horman, Russell King, netdev, linux-kernel
  Cc: Frank Wunderlich, Chad Monroe, Cezary Wilmanski, Liang Xu,
	Benny (Ying-Tsan) Weng, Jose Maria Verdu Munoz, Avinash Jayaraman,
	John Crispin
In-Reply-To: <cover.1774572749.git.daniel@makrotopia.org>

Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
setting a port's STP state.

The switch supports a maximum of 63 bridges, however, up to 12 may
be used as "single-port bridges" to isolate standalone ports.
Allowing up to 48 bridges to be offloaded seems more than enough on
that hardware, hence that is set as max_num_bridges.

A total of 128 bridge ports are supported in the bridge portmap, and
virtual bridge ports have to be used eg. for link-aggregation, hence
potentially exceeding the number of hardware ports.

The firmware-assigned bridge identifier (FID) for each offloaded bridge
is stored in an array used to map DSA bridge num to firmware bridge ID,
avoiding the need for a driver-private bridge tracking structure.
Bridge member portmaps are rebuilt on join/leave using
dsa_switch_for_each_bridge_member().

As there are now more users of the BRIDGEPORT_CONFIG_SET API and the
state of each port is cached locally, introduce a helper function
mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which is
then used to replace the direct calls to the API in
mxl862xx_setup_cpu_bridge() and mxl862xx_add_single_port_bridge().

Note that there is no convenient way to control flooding on per-port
level, so the driver is using a 0-rate QoS meter setup as a stopper in
lack of any better option. In order to be perfect the firmware-enforced
minimum bucket size is bypassed by directly writing 0s to the relevant
registers -- without that at least one 64-byte packet could still
pass before the meter would change from 'yellow' into 'red' state.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
v8:
 * add missing fields to kernel-doc for struct mxl862xx_priv
 * const'ify some function parameters

v7:
 * use simple array to track firmware bridge to dsa bridge mapping
 * use prototype of existing dsa_bridge_ports() helper moved from
   yt921x.c to dsa.h which takes struct net_device * instead of
   struct dsa_bridge * parameter.
 * use new dsa_switch_for_each_bridge_member helper
 * fix bridge allocation resource leak in case of
   mxl862xx_sync_bridge_members() failing for the first port to join a
   bridge
 * zero-initialize struct mxl862xx_cfg in mxl862xx_set_ageing_time
 * set initial=1 to always reset cursor at start when interating over
   FDB in .fdb_dump
 * hack zero-rate meter to truly always block
 * use asynchronous worker for port_set_host_flood which runs from
   atomic context and hence cannot sleep
 * reorder function to minimize diffstat of planned follow-up
   commits

v6:
 * eliminate struct mxl862xx_bridge and driver-private bridge list,
   store firmware bridge FID in new dsa_bridge->priv instead
 * rework sync_bridge_members() to use dsa_bridge_for_each_member()
   instead of for_each_set_bit() on driver-private portmap
 * rework port_bridge_join/leave to use dsa_bridge.priv for first-
   member detection and dsa_bridge_ports() for empty-bridge check
 * derive active FID from dp->bridge->priv in set_bridge_port()
 * simplify allocate_bridge()/free_bridge() to take struct dsa_bridge
   pointer directly, drop kzalloc/kfree/list management
 * simplify get_fid() to read db.bridge.priv directly
 * remove mxl862xx_find_bridge()
 * remove unnecessary default bridge config call in setup()

v5:
 * introduce port_map helpers
 * properly implement port_mdb_{add,del} operations

v4:
 * add missing cpu_to_le32 in mxl862xx_bridge_config_fwd()
 * use little-endian 32-bit type for (unused) age_timer API field
 * better comment in port_set_host_flood() documenting architectural
   limitation
 * fix typo in comment "matche" should be "matches"
 * few whitespace fixes

v3:
 * refactor .port_bridge_join and .port_bridge_leave as requested
   by Vladimir Oltean
 * include linux/etherdevice.h which was missing and causing build
   to fail (it accidentally slipped into a follow-up patch)
 * remove left-over manual reset of learning state for port leaving
   bridge
 * remove unnecessary call to mxl862xx_port_fast_age() for port
   leaving bridge
 * add kernel-doc comments in mxl862xx.h instead of sporadic inline
   comments covering only some of the struct members
 * some other minor cosmetics (linebreaks, whitespace) here and there

v2:
 * fix kernel-doc comments in API header
 * use bitfield helpers for compound tci field in fdb API
 * add missing endian conversion for mxl862xx_stp_port_cfg.port_state
   as well as mxl862xx_mac_table_read.tci (spotted by AI review)
 * drop manually resetting port learning state on bridge<->standalone
   transitions, DSA framework takes care of that
 * don't abort updating bridge ports on error, return error at the end
 * report error in mxl862xx_port_bridge_leave()
 * create mxl862xx_get_fid() helper and use it in
   mxl862xx_port_fdb_add() and mxl862xx_port_fdb_del()
 * propagete error of callback function in mxl862xx_port_fdb_dump()
 * manually mxl862xx_port_fast_age() in mxl862xx_port_stp_state_set()
   to avoid FDB poisoning due to race condition

 drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
 drivers/net/dsa/mxl862xx/mxl862xx-cmd.h |  20 +-
 drivers/net/dsa/mxl862xx/mxl862xx.c     | 752 ++++++++++++++++++++++--
 drivers/net/dsa/mxl862xx/mxl862xx.h     | 133 +++++
 4 files changed, 1087 insertions(+), 43 deletions(-)

diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-api.h b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
index a9f599dbca25b..8677763544d78 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@@ -3,6 +3,7 @@
 #ifndef __MXL862XX_API_H
 #define __MXL862XX_API_H
 
+#include <linux/bits.h>
 #include <linux/if_ether.h>
 
 /**
@@ -34,6 +35,168 @@ struct mxl862xx_register_mod {
 	__le16 mask;
 } __packed;
 
+/**
+ * enum mxl862xx_mac_table_filter - Source/Destination MAC address filtering
+ *
+ * @MXL862XX_MAC_FILTER_NONE: no filter
+ * @MXL862XX_MAC_FILTER_SRC: source address filter
+ * @MXL862XX_MAC_FILTER_DEST: destination address filter
+ * @MXL862XX_MAC_FILTER_BOTH: both source and destination filter
+ */
+enum mxl862xx_mac_table_filter {
+	MXL862XX_MAC_FILTER_NONE = 0,
+	MXL862XX_MAC_FILTER_SRC = BIT(0),
+	MXL862XX_MAC_FILTER_DEST = BIT(1),
+	MXL862XX_MAC_FILTER_BOTH = BIT(0) | BIT(1),
+};
+
+#define MXL862XX_TCI_VLAN_ID		GENMASK(11, 0)
+#define MXL862XX_TCI_VLAN_CFI_DEI	BIT(12)
+#define MXL862XX_TCI_VLAN_PRI		GENMASK(15, 13)
+
+/* Set in port_id to use port_map[] as a portmap bitmap instead of a single
+ * port ID. When clear, port_id selects one port; when set, the firmware
+ * ignores the lower bits of port_id and writes port_map[] directly into
+ * the PCE bridge port map.
+ */
+#define MXL862XX_PORTMAP_FLAG		BIT(31)
+
+/**
+ * struct mxl862xx_mac_table_add - MAC Table Entry to be added
+ * @fid: Filtering Identifier (FID) (not supported by all switches)
+ * @port_id: Ethernet Port number
+ * @port_map: Bridge Port Map
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @age_timer: Aging Time in seconds
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Static Entry (value will be aged out if not set to static)
+ * @traffic_class: Egress queue traffic class
+ * @mac: MAC Address to add to the table
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
+ *                   address matches MAC in this entry
+ * @associated_mac: Associated Mac address
+ * @tci: TCI for B-Step
+ *	Bit [0:11] - VLAN ID
+ *	Bit [12] - VLAN CFI/DEI
+ *	Bit [13:15] - VLAN PRI
+ */
+struct mxl862xx_mac_table_add {
+	__le16 fid;
+	__le32 port_id;
+	__le16 port_map[8];
+	__le16 sub_if_id;
+	__le32 age_timer;
+	__le16 vlan_id;
+	u8 static_entry;
+	u8 traffic_class;
+	u8 mac[ETH_ALEN];
+	u8 filter_flag;
+	u8 igmp_controlled;
+	u8 associated_mac[ETH_ALEN];
+	__le16 tci;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_remove - MAC Table Entry to be removed
+ * @fid: Filtering Identifier (FID)
+ * @mac: MAC Address to be removed from the table.
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @tci: TCI for B-Step
+ *	Bit [0:11] - VLAN ID
+ *	Bit [12] - VLAN CFI/DEI
+ *	Bit [13:15] - VLAN PRI
+ */
+struct mxl862xx_mac_table_remove {
+	__le16 fid;
+	u8 mac[ETH_ALEN];
+	u8 filter_flag;
+	__le16 tci;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_read - MAC Table Entry to be read
+ * @initial: Restart the get operation from the beginning of the table
+ * @last: Indicates that the read operation returned last entry
+ * @fid: Get the MAC table entry belonging to the given Filtering Identifier
+ * @port_id: The Bridge Port ID
+ * @port_map: Bridge Port Map
+ * @age_timer: Aging Time
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Indicates if this is a Static Entry
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @mac: MAC Address. Filled out by the switch API implementation.
+ * @filter_flag: See &enum mxl862xx_mac_table_filter
+ * @igmp_controlled: Packet is marked as IGMP controlled if destination MAC
+ *                   address matches the MAC in this entry
+ * @entry_changed: Indicate if the Entry has Changed
+ * @associated_mac: Associated MAC address
+ * @hit_status: MAC Table Hit Status Update
+ * @tci: TCI for B-Step
+ *	Bit [0:11] - VLAN ID
+ *	Bit [12] - VLAN CFI/DEI
+ *	Bit [13:15] - VLAN PRI
+ * @first_bridge_port_id: The port this MAC address has first been learned.
+ *                        This is used for loop detection.
+ */
+struct mxl862xx_mac_table_read {
+	u8 initial;
+	u8 last;
+	__le16 fid;
+	__le32 port_id;
+	__le16 port_map[8];
+	__le32 age_timer;
+	__le16 vlan_id;
+	u8 static_entry;
+	__le16 sub_if_id;
+	u8 mac[ETH_ALEN];
+	u8 filter_flag;
+	u8 igmp_controlled;
+	u8 entry_changed;
+	u8 associated_mac[ETH_ALEN];
+	u8 hit_status;
+	__le16 tci;
+	__le16 first_bridge_port_id;
+} __packed;
+
+/**
+ * struct mxl862xx_mac_table_query - MAC Table Entry key-based lookup
+ * @mac: MAC Address to search for (input)
+ * @fid: Filtering Identifier (input)
+ * @found: Set by firmware: 1 if entry was found, 0 if not
+ * @port_id: Bridge Port ID (output; MSB set if portmap mode)
+ * @port_map: Bridge Port Map (output; valid for static entries)
+ * @sub_if_id: Sub-Interface Identifier Destination
+ * @age_timer: Aging Time
+ * @vlan_id: STAG VLAN Id
+ * @static_entry: Indicates if this is a Static Entry
+ * @filter_flag: See &enum mxl862xx_mac_table_filter (input+output)
+ * @igmp_controlled: IGMP controlled flag
+ * @entry_changed: Entry changed flag
+ * @associated_mac: Associated MAC address
+ * @hit_status: MAC Table Hit Status Update
+ * @tci: TCI (VLAN ID + CFI/DEI + PRI) (input)
+ * @first_bridge_port_id: First learned bridge port
+ */
+struct mxl862xx_mac_table_query {
+	u8 mac[ETH_ALEN];
+	__le16 fid;
+	u8 found;
+	__le32 port_id;
+	__le16 port_map[8];
+	__le16 sub_if_id;
+	__le32 age_timer;
+	__le16 vlan_id;
+	u8 static_entry;
+	u8 filter_flag;
+	u8 igmp_controlled;
+	u8 entry_changed;
+	u8 associated_mac[ETH_ALEN];
+	u8 hit_status;
+	__le16 tci;
+	__le16 first_bridge_port_id;
+} __packed;
+
 /**
  * enum mxl862xx_mac_clear_type - MAC table clear type
  * @MXL862XX_MAC_CLEAR_PHY_PORT: clear dynamic entries based on port_id
@@ -138,6 +301,40 @@ enum mxl862xx_bridge_port_egress_meter {
 	MXL862XX_BRIDGE_PORT_EGRESS_METER_MAX,
 };
 
+/**
+ * struct mxl862xx_qos_meter_cfg - Rate meter configuration
+ * @enable: Enable/disable meter
+ * @meter_id: Meter ID (assigned by firmware on alloc)
+ * @meter_name: Meter name string
+ * @meter_type: Meter algorithm type (srTCM = 0, trTCM = 1)
+ * @cbs: Committed Burst Size (in bytes)
+ * @res1: Reserved
+ * @ebs: Excess Burst Size (in bytes)
+ * @res2: Reserved
+ * @rate: Committed Information Rate (in kbit/s)
+ * @pi_rate: Peak Information Rate (in kbit/s)
+ * @colour_blind_mode: Colour-blind mode enable
+ * @pkt_mode: Packet mode enable
+ * @local_overhd: Local overhead accounting enable
+ * @local_overhd_val: Local overhead accounting value
+ */
+struct mxl862xx_qos_meter_cfg {
+	u8 enable;
+	__le16 meter_id;
+	char meter_name[32];
+	__le32 meter_type;
+	__le32 cbs;
+	__le32 res1;
+	__le32 ebs;
+	__le32 res2;
+	__le32 rate;
+	__le32 pi_rate;
+	u8 colour_blind_mode;
+	u8 pkt_mode;
+	u8 local_overhd;
+	__le16 local_overhd_val;
+} __packed;
+
 /**
  * enum mxl862xx_bridge_forward_mode - Bridge forwarding type of packet
  * @MXL862XX_BRIDGE_FORWARD_FLOOD: Packet is flooded to port members of
@@ -456,7 +653,7 @@ struct mxl862xx_pmapper {
  */
 struct mxl862xx_bridge_port_config {
 	__le16 bridge_port_id;
-	__le32 mask; /* enum mxl862xx_bridge_port_config_mask  */
+	__le32 mask; /* enum mxl862xx_bridge_port_config_mask */
 	__le16 bridge_id;
 	u8 ingress_extended_vlan_enable;
 	__le16 ingress_extended_vlan_block_id;
@@ -658,6 +855,32 @@ struct mxl862xx_ctp_port_assignment {
 	__le16 bridge_port_id;
 } __packed;
 
+/**
+ * enum mxl862xx_stp_port_state - Spanning Tree Protocol port states
+ * @MXL862XX_STP_PORT_STATE_FORWARD: Forwarding state
+ * @MXL862XX_STP_PORT_STATE_DISABLE: Disabled/Discarding state
+ * @MXL862XX_STP_PORT_STATE_LEARNING: Learning state
+ * @MXL862XX_STP_PORT_STATE_BLOCKING: Blocking/Listening
+ */
+enum mxl862xx_stp_port_state {
+	MXL862XX_STP_PORT_STATE_FORWARD = 0,
+	MXL862XX_STP_PORT_STATE_DISABLE,
+	MXL862XX_STP_PORT_STATE_LEARNING,
+	MXL862XX_STP_PORT_STATE_BLOCKING,
+};
+
+/**
+ * struct mxl862xx_stp_port_cfg - Configures the Spanning Tree Protocol state
+ * @port_id: Port number
+ * @fid: Filtering Identifier (FID)
+ * @port_state: See &enum mxl862xx_stp_port_state
+ */
+struct mxl862xx_stp_port_cfg {
+	__le16 port_id;
+	__le16 fid;
+	__le32 port_state; /* enum mxl862xx_stp_port_state */
+} __packed;
+
 /**
  * struct mxl862xx_sys_fw_image_version - Firmware version information
  * @iv_major: firmware major version
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
index f6852ade64e7c..9f6c5bf9fdf21 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -15,12 +15,15 @@
 #define MXL862XX_BRDG_MAGIC		0x300
 #define MXL862XX_BRDGPORT_MAGIC		0x400
 #define MXL862XX_CTP_MAGIC		0x500
+#define MXL862XX_QOS_MAGIC		0x600
 #define MXL862XX_SWMAC_MAGIC		0xa00
+#define MXL862XX_STP_MAGIC		0xf00
 #define MXL862XX_SS_MAGIC		0x1600
 #define GPY_GPY2XX_MAGIC		0x1800
 #define SYS_MISC_MAGIC			0x1900
 
 #define MXL862XX_COMMON_CFGGET		(MXL862XX_COMMON_MAGIC + 0x9)
+#define MXL862XX_COMMON_CFGSET		(MXL862XX_COMMON_MAGIC + 0xa)
 #define MXL862XX_COMMON_REGISTERMOD	(MXL862XX_COMMON_MAGIC + 0x11)
 
 #define MXL862XX_BRIDGE_ALLOC		(MXL862XX_BRDG_MAGIC + 0x1)
@@ -35,14 +38,23 @@
 
 #define MXL862XX_CTP_PORTASSIGNMENTSET	(MXL862XX_CTP_MAGIC + 0x3)
 
+#define MXL862XX_QOS_METERCFGSET	(MXL862XX_QOS_MAGIC + 0x2)
+#define MXL862XX_QOS_METERALLOC		(MXL862XX_QOS_MAGIC + 0x2a)
+
+#define MXL862XX_MAC_TABLEENTRYADD	(MXL862XX_SWMAC_MAGIC + 0x2)
+#define MXL862XX_MAC_TABLEENTRYREAD	(MXL862XX_SWMAC_MAGIC + 0x3)
+#define MXL862XX_MAC_TABLEENTRYQUERY	(MXL862XX_SWMAC_MAGIC + 0x4)
+#define MXL862XX_MAC_TABLEENTRYREMOVE	(MXL862XX_SWMAC_MAGIC + 0x5)
 #define MXL862XX_MAC_TABLECLEARCOND	(MXL862XX_SWMAC_MAGIC + 0x8)
 
-#define MXL862XX_SS_SPTAG_SET		(MXL862XX_SS_MAGIC + 0x02)
+#define MXL862XX_SS_SPTAG_SET		(MXL862XX_SS_MAGIC + 0x2)
+
+#define MXL862XX_STP_PORTCFGSET		(MXL862XX_STP_MAGIC + 0x2)
 
-#define INT_GPHY_READ			(GPY_GPY2XX_MAGIC + 0x01)
-#define INT_GPHY_WRITE			(GPY_GPY2XX_MAGIC + 0x02)
+#define INT_GPHY_READ			(GPY_GPY2XX_MAGIC + 0x1)
+#define INT_GPHY_WRITE			(GPY_GPY2XX_MAGIC + 0x2)
 
-#define SYS_MISC_FW_VERSION		(SYS_MISC_MAGIC + 0x02)
+#define SYS_MISC_FW_VERSION		(SYS_MISC_MAGIC + 0x2)
 
 #define MMD_API_MAXIMUM_ID		0x7fff
 
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.c b/drivers/net/dsa/mxl862xx/mxl862xx.c
index 78eef639628a0..9cbff61717118 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -7,8 +7,11 @@
  * Copyright (C) 2025 Daniel Golle <daniel@makrotopia.org>
  */
 
-#include <linux/module.h>
+#include <linux/bitfield.h>
 #include <linux/delay.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/of_mdio.h>
 #include <linux/phy.h>
@@ -36,6 +39,17 @@
 #define MXL862XX_READY_TIMEOUT_MS	10000
 #define MXL862XX_READY_POLL_MS		100
 
+#define MXL862XX_TCM_INST_SEL		0xe00
+#define MXL862XX_TCM_CBS		0xe12
+#define MXL862XX_TCM_EBS		0xe13
+
+static const int mxl862xx_flood_meters[] = {
+	MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC,
+	MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP,
+	MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP,
+	MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
+};
+
 static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
 						       int port,
 						       enum dsa_tag_protocol m)
@@ -168,6 +182,225 @@ static int mxl862xx_setup_mdio(struct dsa_switch *ds)
 	return ret;
 }
 
+static int mxl862xx_bridge_config_fwd(struct dsa_switch *ds, u16 bridge_id,
+				      bool ucast_flood, bool mcast_flood,
+				      bool bcast_flood)
+{
+	struct mxl862xx_bridge_config bridge_config = {};
+	struct mxl862xx_priv *priv = ds->priv;
+	int ret;
+
+	bridge_config.mask = cpu_to_le32(MXL862XX_BRIDGE_CONFIG_MASK_FORWARDING_MODE);
+	bridge_config.bridge_id = cpu_to_le16(bridge_id);
+
+	bridge_config.forward_unknown_unicast = cpu_to_le32(ucast_flood ?
+		MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+
+	bridge_config.forward_unknown_multicast_ip = cpu_to_le32(mcast_flood ?
+		MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+	bridge_config.forward_unknown_multicast_non_ip =
+		bridge_config.forward_unknown_multicast_ip;
+
+	bridge_config.forward_broadcast = cpu_to_le32(bcast_flood ?
+		MXL862XX_BRIDGE_FORWARD_FLOOD : MXL862XX_BRIDGE_FORWARD_DISCARD);
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_CONFIGSET, bridge_config);
+	if (ret)
+		dev_err(ds->dev, "failed to configure bridge %u forwarding: %d\n",
+			bridge_id, ret);
+
+	return ret;
+}
+
+/* Allocate a single zero-rate meter shared by all ports and flood types.
+ * All flood-blocking egress sub-meters point to this one meter so that any
+ * packet hitting this meter is unconditionally dropped.
+ *
+ * The firmware API requires CBS >= 64 (its bs2ls encoder clamps smaller
+ * values), so the meter is initially configured with CBS=EBS=64.
+ * A zero-rate bucket starts full at CBS bytes, which would let one packet
+ * through before the bucket empties. To eliminate this one-packet leak we
+ * override CBS and EBS to zero via direct register writes after the API call;
+ * the hardware accepts CBS=0 and immediately flags the bucket as exceeded,
+ * so no traffic can ever pass.
+ */
+static int mxl862xx_setup_drop_meter(struct dsa_switch *ds)
+{
+	struct mxl862xx_qos_meter_cfg meter = {};
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_register_mod reg;
+	int ret;
+
+	/* meter_id=0 means auto-alloc */
+	ret = MXL862XX_API_READ(priv, MXL862XX_QOS_METERALLOC, meter);
+	if (ret)
+		return ret;
+
+	meter.enable = true;
+	meter.cbs = cpu_to_le32(64);
+	meter.ebs = cpu_to_le32(64);
+	snprintf(meter.meter_name, sizeof(meter.meter_name), "drop");
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_QOS_METERCFGSET, meter);
+	if (ret)
+		return ret;
+
+	priv->drop_meter = le16_to_cpu(meter.meter_id);
+
+	/* Select the meter instance for subsequent TCM register access. */
+	reg.addr = cpu_to_le16(MXL862XX_TCM_INST_SEL);
+	reg.data = cpu_to_le16(priv->drop_meter);
+	reg.mask = cpu_to_le16(0xffff);
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+	if (ret)
+		return ret;
+
+	/* Zero CBS so the committed bucket starts empty (exceeded). */
+	reg.addr = cpu_to_le16(MXL862XX_TCM_CBS);
+	reg.data = 0;
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+	if (ret)
+		return ret;
+
+	/* Zero EBS so the excess bucket starts empty (exceeded). */
+	reg.addr = cpu_to_le16(MXL862XX_TCM_EBS);
+	return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
+}
+
+static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
+{
+	struct mxl862xx_bridge_port_config br_port_cfg = {};
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_port *p = &priv->ports[port];
+	u16 bridge_id = dp->bridge ?
+		priv->bridges[dp->bridge->num] : p->fid;
+	bool enable;
+	int i, idx;
+
+	br_port_cfg.bridge_port_id = cpu_to_le16(port);
+	br_port_cfg.bridge_id = cpu_to_le16(bridge_id);
+	br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
+				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
+				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
+				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_SUB_METER);
+	br_port_cfg.src_mac_learning_disable = !p->learning;
+
+	mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
+
+	for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
+		idx = mxl862xx_flood_meters[i];
+		enable = !!(p->flood_block & BIT(idx));
+
+		br_port_cfg.egress_traffic_sub_meter_id[idx] =
+			enable ? cpu_to_le16(priv->drop_meter) : 0;
+		br_port_cfg.egress_sub_metering_enable[idx] = enable;
+	}
+
+	return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET,
+				  br_port_cfg);
+}
+
+static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
+					const struct dsa_bridge *bridge)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct dsa_port *dp, *member_dp;
+	int port, err, ret = 0;
+
+	dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+		port = dp->index;
+
+		bitmap_zero(priv->ports[port].portmap,
+			    MXL862XX_MAX_BRIDGE_PORTS);
+
+		dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev) {
+			if (member_dp->index != port)
+				__set_bit(member_dp->index,
+					  priv->ports[port].portmap);
+		}
+		__set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+		err = mxl862xx_set_bridge_port(ds, port);
+		if (err)
+			ret = err;
+	}
+
+	return ret;
+}
+
+/**
+ * mxl862xx_allocate_bridge - Allocate a firmware bridge instance
+ * @priv: driver private data
+ * @bridge_id: output -- firmware bridge ID assigned by the firmware
+ *
+ * Newly allocated bridges default to flooding all traffic classes
+ * (unknown unicast, multicast, broadcast).  Callers that need
+ * different forwarding behavior must call mxl862xx_bridge_config_fwd()
+ * after allocation.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
+{
+	struct mxl862xx_bridge_alloc br_alloc = {};
+	int ret;
+
+	ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
+	if (ret)
+		return ret;
+
+	*bridge_id = le16_to_cpu(br_alloc.bridge_id);
+	return 0;
+}
+
+static void mxl862xx_free_bridge(struct dsa_switch *ds,
+				 const struct dsa_bridge *bridge)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	u16 fw_id = priv->bridges[bridge->num];
+	struct mxl862xx_bridge_alloc br_alloc = {
+		.bridge_id = cpu_to_le16(fw_id),
+	};
+	int ret;
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGE_FREE, br_alloc);
+	if (ret) {
+		dev_err(ds->dev, "failed to free fw bridge %u: %pe\n",
+			fw_id, ERR_PTR(ret));
+		return;
+	}
+
+	priv->bridges[bridge->num] = 0;
+}
+
+static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+{
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	struct mxl862xx_priv *priv = ds->priv;
+	int ret;
+
+	ret = mxl862xx_allocate_bridge(priv, &priv->ports[port].fid);
+	if (ret) {
+		dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
+		return ret;
+	}
+
+	priv->ports[port].learning = false;
+	bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
+	__set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
+
+	ret = mxl862xx_set_bridge_port(ds, port);
+	if (ret)
+		return ret;
+
+	/* Standalone ports should not flood unknown unicast or multicast
+	 * towards the CPU by default; only broadcast is needed initially.
+	 */
+	return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+					 false, false, true);
+}
+
 static int mxl862xx_setup(struct dsa_switch *ds)
 {
 	struct mxl862xx_priv *priv = ds->priv;
@@ -181,6 +414,10 @@ static int mxl862xx_setup(struct dsa_switch *ds)
 	if (ret)
 		return ret;
 
+	ret = mxl862xx_setup_drop_meter(ds);
+	if (ret)
+		return ret;
+
 	return mxl862xx_setup_mdio(ds);
 }
 
@@ -260,66 +497,87 @@ static int mxl862xx_configure_sp_tag_proto(struct dsa_switch *ds, int port,
 
 static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
 {
-	struct mxl862xx_bridge_port_config br_port_cfg = {};
 	struct mxl862xx_priv *priv = ds->priv;
-	u16 bridge_port_map = 0;
 	struct dsa_port *dp;
 
-	/* CPU port bridge setup */
-	br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
-				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
-				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
-
-	br_port_cfg.bridge_port_id = cpu_to_le16(port);
-	br_port_cfg.src_mac_learning_disable = false;
-	br_port_cfg.vlan_src_mac_vid_enable = true;
-	br_port_cfg.vlan_dst_mac_vid_enable = true;
+	priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
+	priv->ports[port].learning = true;
 
 	/* include all assigned user ports in the CPU portmap */
+	bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
 	dsa_switch_for_each_user_port(dp, ds) {
 		/* it's safe to rely on cpu_dp being valid for user ports */
 		if (dp->cpu_dp->index != port)
 			continue;
 
-		bridge_port_map |= BIT(dp->index);
+		__set_bit(dp->index, priv->ports[port].portmap);
 	}
-	br_port_cfg.bridge_port_map[0] |= cpu_to_le16(bridge_port_map);
 
-	return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+	return mxl862xx_set_bridge_port(ds, port);
 }
 
-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
+				     const struct dsa_bridge bridge,
+				     bool *tx_fwd_offload,
+				     struct netlink_ext_ack *extack)
 {
-	struct mxl862xx_bridge_port_config br_port_cfg = {};
-	struct dsa_port *dp = dsa_to_port(ds, port);
-	struct mxl862xx_bridge_alloc br_alloc = {};
+	struct mxl862xx_priv *priv = ds->priv;
+	u16 fw_id;
 	int ret;
 
-	ret = MXL862XX_API_READ(ds->priv, MXL862XX_BRIDGE_ALLOC, br_alloc);
-	if (ret) {
-		dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
-		return ret;
+	if (!priv->bridges[bridge.num]) {
+		ret = mxl862xx_allocate_bridge(priv, &fw_id);
+		if (ret)
+			return ret;
+
+		priv->bridges[bridge.num] = fw_id;
+
+		/* Free bridge here on error, DSA rollback won't. */
+		ret = mxl862xx_sync_bridge_members(ds, &bridge);
+		if (ret) {
+			mxl862xx_free_bridge(ds, &bridge);
+			return ret;
+		}
+
+		return 0;
 	}
 
-	br_port_cfg.bridge_id = br_alloc.bridge_id;
-	br_port_cfg.bridge_port_id = cpu_to_le16(port);
-	br_port_cfg.mask = cpu_to_le32(MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_ID |
-				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_BRIDGE_PORT_MAP |
-				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_MC_SRC_MAC_LEARNING |
-				       MXL862XX_BRIDGE_PORT_CONFIG_MASK_VLAN_BASED_MAC_LEARNING);
-	br_port_cfg.src_mac_learning_disable = true;
-	br_port_cfg.vlan_src_mac_vid_enable = false;
-	br_port_cfg.vlan_dst_mac_vid_enable = false;
-	/* As this function is only called for user ports it is safe to rely on
-	 * cpu_dp being valid
-	 */
-	br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
+	return mxl862xx_sync_bridge_members(ds, &bridge);
+}
+
+static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+				       const struct dsa_bridge bridge)
+{
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_port *p = &priv->ports[port];
+	int err;
+
+	err = mxl862xx_sync_bridge_members(ds, &bridge);
+	if (err)
+		dev_err(ds->dev,
+			"failed to sync bridge members after port %d left: %pe\n",
+			port, ERR_PTR(err));
 
-	return MXL862XX_API_WRITE(ds->priv, MXL862XX_BRIDGEPORT_CONFIGSET, br_port_cfg);
+	/* Revert leaving port, omitted by the sync above, to its
+	 * single-port bridge
+	 */
+	bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
+	__set_bit(dp->cpu_dp->index, p->portmap);
+	p->flood_block = 0;
+	err = mxl862xx_set_bridge_port(ds, port);
+	if (err)
+		dev_err(ds->dev,
+			"failed to update bridge port %d state: %pe\n", port,
+			ERR_PTR(err));
+
+	if (!dsa_bridge_ports(ds, bridge.dev))
+		mxl862xx_free_bridge(ds, &bridge);
 }
 
 static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
 {
+	struct mxl862xx_priv *priv = ds->priv;
 	struct dsa_port *dp = dsa_to_port(ds, port);
 	bool is_cpu_port = dsa_port_is_cpu(dp);
 	int ret;
@@ -352,7 +610,31 @@ static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
 		return mxl862xx_setup_cpu_bridge(ds, port);
 
 	/* setup single-port bridge for user ports */
-	return mxl862xx_add_single_port_bridge(ds, port);
+	ret = mxl862xx_add_single_port_bridge(ds, port);
+	if (ret)
+		return ret;
+
+	priv->ports[port].setup_done = true;
+
+	return 0;
+}
+
+static void mxl862xx_port_teardown(struct dsa_switch *ds, int port)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct dsa_port *dp = dsa_to_port(ds, port);
+
+	if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
+		return;
+
+	/* Prevent deferred host_flood_work from acting on stale state.
+	 * The flag is checked under rtnl_lock() by the worker; since
+	 * teardown also runs under RTNL, this is race-free.
+	 *
+	 * HW EVLAN/VF blocks are not freed here -- the firmware receives
+	 * a full reset on the next probe, which reclaims all resources.
+	 */
+	priv->ports[port].setup_done = false;
 }
 
 static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
@@ -365,14 +647,385 @@ static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
 		  config->supported_interfaces);
 }
 
+static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+
+	switch (db.type) {
+	case DSA_DB_PORT:
+		return priv->ports[db.dp->index].fid;
+
+	case DSA_DB_BRIDGE:
+		if (!priv->bridges[db.bridge.num])
+			return -ENOENT;
+		return priv->bridges[db.bridge.num];
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mxl862xx_port_fdb_add(struct dsa_switch *ds, int port,
+				 const unsigned char *addr, u16 vid, struct dsa_db db)
+{
+	struct mxl862xx_mac_table_add param = {};
+	int fid = mxl862xx_get_fid(ds, db), ret;
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (fid < 0)
+		return fid;
+
+	param.port_id = cpu_to_le32(port);
+	param.static_entry = true;
+	param.fid = cpu_to_le16(fid);
+	param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+	ether_addr_copy(param.mac, addr);
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, param);
+	if (ret)
+		dev_err(ds->dev, "failed to add FDB entry on port %d\n", port);
+
+	return ret;
+}
+
+static int mxl862xx_port_fdb_del(struct dsa_switch *ds, int port,
+				 const unsigned char *addr, u16 vid, const struct dsa_db db)
+{
+	struct mxl862xx_mac_table_remove param = {};
+	int fid = mxl862xx_get_fid(ds, db), ret;
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (fid < 0)
+		return fid;
+
+	param.fid = cpu_to_le16(fid);
+	param.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, vid));
+	ether_addr_copy(param.mac, addr);
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
+	if (ret)
+		dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port);
+
+	return ret;
+}
+
+static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port,
+				  dsa_fdb_dump_cb_t *cb, void *data)
+{
+	struct mxl862xx_mac_table_read param = { .initial = 1 };
+	struct mxl862xx_priv *priv = ds->priv;
+	u32 entry_port_id;
+	int ret;
+
+	while (true) {
+		ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYREAD, param);
+		if (ret)
+			return ret;
+
+		if (param.last)
+			break;
+
+		entry_port_id = le32_to_cpu(param.port_id);
+
+		if (entry_port_id == port) {
+			ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
+						      le16_to_cpu(param.tci)),
+				 param.static_entry, data);
+			if (ret)
+				return ret;
+		}
+
+		memset(&param, 0, sizeof(param));
+	}
+
+	return 0;
+}
+
+static int mxl862xx_port_mdb_add(struct dsa_switch *ds, int port,
+				 const struct switchdev_obj_port_mdb *mdb,
+				 const struct dsa_db db)
+{
+	struct mxl862xx_mac_table_query qparam = {};
+	struct mxl862xx_mac_table_add aparam = {};
+	struct mxl862xx_priv *priv = ds->priv;
+	int fid, ret;
+
+	fid = mxl862xx_get_fid(ds, db);
+	if (fid < 0)
+		return fid;
+
+	/* Look up existing entry by {MAC, FID, TCI} */
+	ether_addr_copy(qparam.mac, mdb->addr);
+	qparam.fid = cpu_to_le16(fid);
+	qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+
+	ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+	if (ret)
+		return ret;
+
+	/* Build the ADD command using portmap mode */
+	ether_addr_copy(aparam.mac, mdb->addr);
+	aparam.fid = cpu_to_le16(fid);
+	aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+	aparam.static_entry = true;
+	aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+
+	/* Merge with existing portmap if entry already exists */
+	if (qparam.found)
+		memcpy(aparam.port_map, qparam.port_map,
+		       sizeof(aparam.port_map));
+
+	mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
+
+	return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
+}
+
+static int mxl862xx_port_mdb_del(struct dsa_switch *ds, int port,
+				 const struct switchdev_obj_port_mdb *mdb,
+				 const struct dsa_db db)
+{
+	struct mxl862xx_mac_table_remove rparam = {};
+	struct mxl862xx_mac_table_query qparam = {};
+	struct mxl862xx_mac_table_add aparam = {};
+	int fid = mxl862xx_get_fid(ds, db), ret;
+	struct mxl862xx_priv *priv = ds->priv;
+
+	if (fid < 0)
+		return fid;
+
+	/* Look up existing entry */
+	qparam.fid = cpu_to_le16(fid);
+	qparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+	ether_addr_copy(qparam.mac, mdb->addr);
+
+	ret = MXL862XX_API_READ(priv, MXL862XX_MAC_TABLEENTRYQUERY, qparam);
+	if (ret)
+		return ret;
+
+	if (!qparam.found)
+		return 0;
+
+	mxl862xx_fw_portmap_clear_bit(qparam.port_map, port);
+
+	if (mxl862xx_fw_portmap_is_empty(qparam.port_map)) {
+		/* No ports left — remove the entry entirely */
+		rparam.fid = cpu_to_le16(fid);
+		rparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+		ether_addr_copy(rparam.mac, mdb->addr);
+		ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, rparam);
+	} else {
+		/* Write back with reduced portmap */
+		aparam.fid = cpu_to_le16(fid);
+		aparam.tci = cpu_to_le16(FIELD_PREP(MXL862XX_TCI_VLAN_ID, mdb->vid));
+		ether_addr_copy(aparam.mac, mdb->addr);
+		aparam.static_entry = true;
+		aparam.port_id = cpu_to_le32(MXL862XX_PORTMAP_FLAG);
+		memcpy(aparam.port_map, qparam.port_map, sizeof(aparam.port_map));
+		ret = MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
+	}
+
+	return ret;
+}
+
+static int mxl862xx_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
+{
+	struct mxl862xx_cfg param = {};
+	int ret;
+
+	ret = MXL862XX_API_READ(ds->priv, MXL862XX_COMMON_CFGGET, param);
+	if (ret) {
+		dev_err(ds->dev, "failed to read switch config\n");
+		return ret;
+	}
+
+	param.mac_table_age_timer = cpu_to_le32(MXL862XX_AGETIMER_CUSTOM);
+	param.age_timer = cpu_to_le32(msecs / 1000);
+	ret = MXL862XX_API_WRITE(ds->priv, MXL862XX_COMMON_CFGSET, param);
+	if (ret)
+		dev_err(ds->dev, "failed to set ageing\n");
+
+	return ret;
+}
+
+static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
+					u8 state)
+{
+	struct mxl862xx_stp_port_cfg param = {
+		.port_id = cpu_to_le16(port),
+	};
+	struct mxl862xx_priv *priv = ds->priv;
+	int ret;
+
+	switch (state) {
+	case BR_STATE_DISABLED:
+		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_DISABLE);
+		break;
+	case BR_STATE_BLOCKING:
+	case BR_STATE_LISTENING:
+		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_BLOCKING);
+		break;
+	case BR_STATE_LEARNING:
+		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_LEARNING);
+		break;
+	case BR_STATE_FORWARDING:
+		param.port_state = cpu_to_le32(MXL862XX_STP_PORT_STATE_FORWARD);
+		break;
+	default:
+		dev_err(ds->dev, "invalid STP state: %d\n", state);
+		return;
+	}
+
+	ret = MXL862XX_API_WRITE(priv, MXL862XX_STP_PORTCFGSET, param);
+	if (ret) {
+		dev_err(ds->dev, "failed to set STP state on port %d\n", port);
+		return;
+	}
+
+	/* The firmware may re-enable MAC learning as a side-effect of entering
+	 * LEARNING or FORWARDING state (per 802.1D defaults).
+	 * Re-apply the driver's intended learning and metering config so that
+	 * standalone ports keep learning disabled.
+	 * This is likely to get fixed in future firmware releases, however,
+	 * the additional API call even then doesn't hurt much.
+	 */
+	ret = mxl862xx_set_bridge_port(ds, port);
+	if (ret)
+		dev_err(ds->dev, "failed to reapply brport flags on port %d\n",
+			port);
+
+	mxl862xx_port_fast_age(ds, port);
+}
+
+/* Deferred work handler for host flood configuration.
+ *
+ * port_set_host_flood is called from atomic context (under
+ * netif_addr_lock), so firmware calls must be deferred.  The worker
+ * acquires rtnl_lock() to serialize with DSA callbacks that access the
+ * same driver state.
+ */
+static void mxl862xx_host_flood_work_fn(struct work_struct *work)
+{
+	struct mxl862xx_port *p = container_of(work, struct mxl862xx_port,
+					       host_flood_work);
+	struct mxl862xx_priv *priv = p->priv;
+	struct dsa_switch *ds = priv->ds;
+	int port = p - priv->ports;
+	bool uc, mc;
+
+	rtnl_lock();
+
+	/* Port may have been torn down between scheduling and now. */
+	if (!p->setup_done) {
+		rtnl_unlock();
+		return;
+	}
+
+	uc = p->host_flood_uc;
+	mc = p->host_flood_mc;
+
+	/* The hardware controls unknown-unicast/multicast forwarding per FID
+	 * (bridge), not per source port.  For bridged ports all members share
+	 * one FID, so we cannot selectively suppress flooding to the CPU for
+	 * one source port while allowing it for another.  Silently ignore the
+	 * request -- the excess flooding towards the CPU is harmless.
+	 */
+	if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
+		mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
+
+	rtnl_unlock();
+}
+
+static void mxl862xx_port_set_host_flood(struct dsa_switch *ds, int port,
+					 bool uc, bool mc)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	struct mxl862xx_port *p = &priv->ports[port];
+
+	p->host_flood_uc = uc;
+	p->host_flood_mc = mc;
+	schedule_work(&p->host_flood_work);
+}
+
+static int mxl862xx_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+					  const struct switchdev_brport_flags flags,
+					  struct netlink_ext_ack *extack)
+{
+	if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
+			   BR_LEARNING))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int mxl862xx_port_bridge_flags(struct dsa_switch *ds, int port,
+				      const struct switchdev_brport_flags flags,
+				      struct netlink_ext_ack *extack)
+{
+	struct mxl862xx_priv *priv = ds->priv;
+	unsigned long old_block = priv->ports[port].flood_block;
+	unsigned long block = old_block;
+	bool need_update = false;
+	int ret;
+
+	if (flags.mask & BR_FLOOD) {
+		if (flags.val & BR_FLOOD)
+			block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
+		else
+			block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
+	}
+
+	if (flags.mask & BR_MCAST_FLOOD) {
+		if (flags.val & BR_MCAST_FLOOD) {
+			block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
+			block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
+		} else {
+			block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
+			block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
+		}
+	}
+
+	if (flags.mask & BR_BCAST_FLOOD) {
+		if (flags.val & BR_BCAST_FLOOD)
+			block &= ~BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
+		else
+			block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST);
+	}
+
+	if (flags.mask & BR_LEARNING)
+		priv->ports[port].learning = !!(flags.val & BR_LEARNING);
+
+	need_update = (block != old_block) || (flags.mask & BR_LEARNING);
+	if (need_update) {
+		priv->ports[port].flood_block = block;
+		ret = mxl862xx_set_bridge_port(ds, port);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static const struct dsa_switch_ops mxl862xx_switch_ops = {
 	.get_tag_protocol = mxl862xx_get_tag_protocol,
 	.setup = mxl862xx_setup,
 	.port_setup = mxl862xx_port_setup,
+	.port_teardown = mxl862xx_port_teardown,
 	.phylink_get_caps = mxl862xx_phylink_get_caps,
 	.port_enable = mxl862xx_port_enable,
 	.port_disable = mxl862xx_port_disable,
 	.port_fast_age = mxl862xx_port_fast_age,
+	.set_ageing_time = mxl862xx_set_ageing_time,
+	.port_bridge_join = mxl862xx_port_bridge_join,
+	.port_bridge_leave = mxl862xx_port_bridge_leave,
+	.port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
+	.port_bridge_flags = mxl862xx_port_bridge_flags,
+	.port_stp_state_set = mxl862xx_port_stp_state_set,
+	.port_set_host_flood = mxl862xx_port_set_host_flood,
+	.port_fdb_add = mxl862xx_port_fdb_add,
+	.port_fdb_del = mxl862xx_port_fdb_del,
+	.port_fdb_dump = mxl862xx_port_fdb_dump,
+	.port_mdb_add = mxl862xx_port_mdb_add,
+	.port_mdb_del = mxl862xx_port_mdb_del,
 };
 
 static void mxl862xx_phylink_mac_config(struct phylink_config *config,
@@ -407,6 +1060,7 @@ static int mxl862xx_probe(struct mdio_device *mdiodev)
 	struct device *dev = &mdiodev->dev;
 	struct mxl862xx_priv *priv;
 	struct dsa_switch *ds;
+	int i;
 
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
@@ -424,8 +1078,17 @@ static int mxl862xx_probe(struct mdio_device *mdiodev)
 	ds->ops = &mxl862xx_switch_ops;
 	ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
 	ds->num_ports = MXL862XX_MAX_PORTS;
+	ds->fdb_isolation = true;
+	ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
+
 	mxl862xx_host_init(priv);
 
+	for (i = 0; i < MXL862XX_MAX_PORTS; i++) {
+		priv->ports[i].priv = priv;
+		INIT_WORK(&priv->ports[i].host_flood_work,
+			  mxl862xx_host_flood_work_fn);
+	}
+
 	dev_set_drvdata(dev, ds);
 
 	return dsa_register_switch(ds);
@@ -435,6 +1098,7 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
 {
 	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
 	struct mxl862xx_priv *priv;
+	int i;
 
 	if (!ds)
 		return;
@@ -444,12 +1108,21 @@ static void mxl862xx_remove(struct mdio_device *mdiodev)
 	dsa_unregister_switch(ds);
 
 	mxl862xx_host_shutdown(priv);
+
+	/* Cancel any pending host flood work.  dsa_unregister_switch()
+	 * has already called port_teardown (which sets setup_done=false),
+	 * but a worker could still be blocked on rtnl_lock().  Since we
+	 * are now outside RTNL, cancel_work_sync() will not deadlock.
+	 */
+	for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+		cancel_work_sync(&priv->ports[i].host_flood_work);
 }
 
 static void mxl862xx_shutdown(struct mdio_device *mdiodev)
 {
 	struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
 	struct mxl862xx_priv *priv;
+	int i;
 
 	if (!ds)
 		return;
@@ -460,6 +1133,9 @@ static void mxl862xx_shutdown(struct mdio_device *mdiodev)
 
 	mxl862xx_host_shutdown(priv);
 
+	for (i = 0; i < MXL862XX_MAX_PORTS; i++)
+		cancel_work_sync(&priv->ports[i].host_flood_work);
+
 	dev_set_drvdata(&mdiodev->dev, NULL);
 }
 
diff --git a/drivers/net/dsa/mxl862xx/mxl862xx.h b/drivers/net/dsa/mxl862xx/mxl862xx.h
index 3ca0d386f3e8f..35917204b483b 100644
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,15 +4,148 @@
 #define __MXL862XX_H
 
 #include <linux/mdio.h>
+#include <linux/workqueue.h>
 #include <net/dsa.h>
 
+struct mxl862xx_priv;
+
 #define MXL862XX_MAX_PORTS		17
+#define MXL862XX_DEFAULT_BRIDGE		0
+#define MXL862XX_MAX_BRIDGES		48
+#define MXL862XX_MAX_BRIDGE_PORTS	128
+
+/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
+#define MXL862XX_FW_PORTMAP_WORDS	(MXL862XX_MAX_BRIDGE_PORTS / 16)
+
+/**
+ * mxl862xx_fw_portmap_from_bitmap - convert a kernel bitmap to a firmware
+ *                                   portmap (__le16[8])
+ * @dst: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @src: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ */
+static inline void
+mxl862xx_fw_portmap_from_bitmap(__le16 *dst, const unsigned long *src)
+{
+	int i;
+
+	for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+		dst[i] = cpu_to_le16(bitmap_read(src, i * 16, 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_to_bitmap - convert a firmware portmap (__le16[8]) to
+ *                                 a kernel bitmap
+ * @dst: kernel bitmap of at least MXL862XX_MAX_BRIDGE_PORTS bits
+ * @src: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ */
+static inline void
+mxl862xx_fw_portmap_to_bitmap(unsigned long *dst, const __le16 *src)
+{
+	int i;
+
+	bitmap_zero(dst, MXL862XX_MAX_BRIDGE_PORTS);
+	for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+		bitmap_write(dst, le16_to_cpu(src[i]), i * 16, 16);
+}
+
+/**
+ * mxl862xx_fw_portmap_set_bit - set a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
+ */
+static inline void mxl862xx_fw_portmap_set_bit(__le16 *map, int port)
+{
+	map[port / 16] |= cpu_to_le16(BIT(port % 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_clear_bit - clear a single port bit in a firmware portmap
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ * @port: port index (0..MXL862XX_MAX_BRIDGE_PORTS-1)
+ */
+static inline void mxl862xx_fw_portmap_clear_bit(__le16 *map, int port)
+{
+	map[port / 16] &= ~cpu_to_le16(BIT(port % 16));
+}
+
+/**
+ * mxl862xx_fw_portmap_is_empty - check whether a firmware portmap has no
+ *                                bits set
+ * @map: firmware portmap array (MXL862XX_FW_PORTMAP_WORDS entries)
+ *
+ * Return: true if every word in @map is zero.
+ */
+static inline bool mxl862xx_fw_portmap_is_empty(const __le16 *map)
+{
+	int i;
+
+	for (i = 0; i < MXL862XX_FW_PORTMAP_WORDS; i++)
+		if (map[i])
+			return false;
+	return true;
+}
+
+/**
+ * struct mxl862xx_port - per-port state tracked by the driver
+ * @priv:                back-pointer to switch private data; needed by
+ *                       deferred work handlers to access ds and priv
+ * @fid:                 firmware FID for the permanent single-port bridge;
+ *                       kept alive for the lifetime of the port so traffic is
+ *                       never forwarded while the port is unbridged
+ * @portmap:             bitmap of switch port indices that share the current
+ *                       bridge with this port
+ * @flood_block:         bitmask of firmware meter indices that are currently
+ *                       rate-limiting flood traffic on this port (zero-rate
+ *                       meters used to block flooding)
+ * @learning:            true when address learning is enabled on this port
+ * @setup_done:          set at end of port_setup, cleared at start of
+ *                       port_teardown; guards deferred work against
+ *                       acting on torn-down state
+ * @host_flood_uc:       desired host unicast flood state (true = flood);
+ *                       updated atomically by port_set_host_flood, consumed
+ *                       by the deferred host_flood_work
+ * @host_flood_mc:       desired host multicast flood state (true = flood)
+ * @host_flood_work:     deferred work for applying host flood changes;
+ *                       port_set_host_flood runs in atomic context (under
+ *                       netif_addr_lock) so firmware calls must be deferred.
+ *                       The worker acquires rtnl_lock() to serialize with
+ *                       DSA callbacks and checks @setup_done to avoid
+ *                       acting on torn-down ports.
+ */
+struct mxl862xx_port {
+	struct mxl862xx_priv *priv;
+	u16 fid;
+	DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
+	unsigned long flood_block;
+	bool learning;
+	bool setup_done;
+	bool host_flood_uc;
+	bool host_flood_mc;
+	struct work_struct host_flood_work;
+};
 
+/**
+ * struct mxl862xx_priv - driver private data for an MxL862xx switch
+ * @ds:            pointer to the DSA switch instance
+ * @mdiodev:       MDIO device used to communicate with the switch firmware
+ * @crc_err_work:  deferred work for shutting down all ports on MDIO CRC errors
+ * @crc_err:       set atomically before CRC-triggered shutdown, cleared after
+ * @drop_meter:    index of the single shared zero-rate firmware meter used
+ *                 to unconditionally drop traffic (used to block flooding)
+ * @ports:         per-port state, indexed by switch port number
+ * @bridges:       maps DSA bridge number to firmware bridge ID;
+ *                 zero means no firmware bridge allocated for that
+ *                 DSA bridge number.  Indexed by dsa_bridge.num
+ *                 (0 .. ds->max_num_bridges).
+ */
 struct mxl862xx_priv {
 	struct dsa_switch *ds;
 	struct mdio_device *mdiodev;
 	struct work_struct crc_err_work;
 	unsigned long crc_err;
+	u16 drop_meter;
+	struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
+	u16 bridges[MXL862XX_MAX_BRIDGES + 1];
 };
 
 #endif /* __MXL862XX_H */
-- 
2.53.0

^ permalink raw reply related

* [PATCH net v5] bnxt_en: validate firmware backing store types
From: Pengpeng Hou @ 2026-03-27  1:02 UTC (permalink / raw)
  To: michael.chan
  Cc: pavan.chebbi, andrew+netdev, davem, edumazet, kuba, pabeni,
	netdev, linux-kernel, pengpeng

bnxt_hwrm_func_backing_store_qcaps_v2() stores resp->type from the
firmware response in ctxm->type and later uses that value to index
fixed backing-store metadata arrays such as ctx_arr[] and
bnxt_bstore_to_trace[].

The firmware response type is defined to match the queried request type.
Validate that resp->type matches the current request before storing it,
and keep next_valid_type in a dedicated variable so loop control stays
clear for non-valid or unchanged entries.

Fixes: 6a4d0774f02d ("bnxt_en: Add support for new backing store query firmware API")
Signed-off-by: Pengpeng Hou <pengpeng@iscas.ac.cn>
---
v5:
- resend in a new thread
- add a lore link to the previous posting

Link: https://lore.kernel.org/r/20260327003845.27877-1-pengpeng@iscas.ac.cn

v4:
- validate that resp->type matches the queried type
- keep next_type only for loop control

 drivers/net/ethernet/broadcom/bnxt/bnxt.c | 30 +++++++++++++++++------
 1 file changed, 22 insertions(+), 8 deletions(-)

diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
index 0751c0e4581a..6dd35942640d 100644
--- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c
+++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c
@@ -8692,15 +8692,18 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		u8 init_val, init_off, i;
 		u32 max_entries;
 		u16 entry_size;
+		u16 req_type;
 		__le32 *p;
+		u16 next_type;
 		u32 flags;
 
-		req->type = cpu_to_le16(type);
+		req_type = type;
+		req->type = cpu_to_le16(req_type);
 		rc = hwrm_req_send(bp, req);
 		if (rc)
 			goto ctx_done;
 		flags = le32_to_cpu(resp->flags);
-		type = le16_to_cpu(resp->next_valid_type);
+		next_type = le16_to_cpu(resp->next_valid_type);
 		if (!(flags & BNXT_CTX_MEM_TYPE_VALID)) {
 			bnxt_free_one_ctx_mem(bp, ctxm, true);
 			continue;
@@ -8708,14 +8711,23 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		entry_size = le16_to_cpu(resp->entry_size);
 		max_entries = le32_to_cpu(resp->max_num_entries);
 		if (ctxm->mem_valid) {
-			if (!(flags & BNXT_CTX_MEM_PERSIST) ||
-			    ctxm->entry_size != entry_size ||
-			    ctxm->max_entries != max_entries)
-				bnxt_free_one_ctx_mem(bp, ctxm, true);
-			else
+			if ((flags & BNXT_CTX_MEM_PERSIST) &&
+			    ctxm->entry_size == entry_size &&
+			    ctxm->max_entries == max_entries) {
+				type = next_type;
 				continue;
+			}
+
+			bnxt_free_one_ctx_mem(bp, ctxm, true);
 		}
-		ctxm->type = le16_to_cpu(resp->type);
+		if (le16_to_cpu(resp->type) != req_type) {
+			netdev_warn(bp->dev,
+				    "unexpected backing store type %u returned for request type %u\n",
+				    le16_to_cpu(resp->type), req_type);
+			rc = -EINVAL;
+			goto ctx_done;
+		}
+		ctxm->type = req_type;
 		ctxm->entry_size = entry_size;
 		ctxm->flags = flags;
 		ctxm->instance_bmap = le32_to_cpu(resp->instance_bit_map);
@@ -8731,6 +8743,8 @@ static int bnxt_hwrm_func_backing_store_qcaps_v2(struct bnxt *bp)
 		for (i = 0, p = &resp->split_entry_0; i < ctxm->split_entry_cnt;
 		     i++, p++)
 			ctxm->split[i] = le32_to_cpu(*p);
+
+		type = next_type;
 	}
 	rc = bnxt_alloc_all_ctx_pg_info(bp, BNXT_CTX_V2_MAX);
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related

* Re: [PATCH net v4] bnxt_en: validate firmware backing store types
From: Jakub Kicinski @ 2026-03-27  1:06 UTC (permalink / raw)
  To: Pengpeng Hou
  Cc: michael.chan, pavan.chebbi, andrew+netdev, davem, edumazet,
	pabeni, netdev, linux-kernel
In-Reply-To: <20260327003845.27877-1-pengpeng@iscas.ac.cn>

On Fri, 27 Mar 2026 08:38:45 +0800 Pengpeng Hou wrote:
> bnxt_hwrm_func_backing_store_qcaps_v2() stores resp->type from the
> firmware response in ctxm->type and later uses that value to index
> fixed backing-store metadata arrays such as ctx_arr[] and
> bnxt_bstore_to_trace[].

Again, please do not post in replay to previous posting.
And wait 24h before reposting. Read this:
https://www.kernel.org/doc/html/next/process/maintainer-netdev.html#tl-dr

^ permalink raw reply

* Re: [PATCH net v3 09/11] rxrpc: Fix keyring reference count leak in rxrpc_setsockopt()
From: Anderson Nascimento @ 2026-03-27  1:06 UTC (permalink / raw)
  To: David Howells
  Cc: netdev, Marc Dionne, Jakub Kicinski, David S. Miller,
	Eric Dumazet, Paolo Abeni, linux-afs, linux-kernel,
	Jeffrey Altman, Simon Horman, stable
In-Reply-To: <20260326131838.634095-10-dhowells@redhat.com>

On Thu, Mar 26, 2026 at 10:19 AM David Howells <dhowells@redhat.com> wrote:
>
> From: Anderson Nascimento <anderson@allelesecurity.com>
>
> In rxrpc_setsockopt(), the code checks 'rx->key' when handling the
> RXRPC_SECURITY_KEYRING option.  However, this appears to be a logic error.
> The code should be checking 'rx->securities' to determine if a keyring has
> already been defined for the socket.
>
> Currently, if a user calls setsockopt(RXRPC_SECURITY_KEYRING) multiple
> times on the same socket, the check 'if (rx->key)' fails to block
> subsequent calls because 'rx->key' has not been defined by the function.
> This results in a reference count leak on the keyring.
>
> This patch changes the check to 'rx->securities' to correctly identify if
> the socket security keyring has already been configured, returning -EINVAL
> on subsequent attempts.
>
> Before the patch:
>
> It shows the keyring reference counter elevated.
>
> $ cat /proc/keys | grep AFSkeys1
> 27aca8ae I--Q--- 24469721 perm 3f010000  1000  1000 keyring   AFSkeys1: empty
> $
>
> After the patch:
>
> The keyring reference counter remains stable and subsequent calls return an
> error:
>
> $ ./poc
> setsockopt: Invalid argument
> $
>
> Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
> Signed-off-by: Anderson Nascimento <anderson@allelesecurity.com>
> Signed-off-by: David Howells <dhowells@redhat.com>
> Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
> cc: Marc Dionne <marc.dionne@auristor.com>
> cc: Eric Dumazet <edumazet@google.com>
> cc: "David S. Miller" <davem@davemloft.net>
> cc: Jakub Kicinski <kuba@kernel.org>
> cc: Paolo Abeni <pabeni@redhat.com>
> cc: Simon Horman <horms@kernel.org>
> cc: linux-afs@lists.infradead.org
> cc: netdev@vger.kernel.org
> cc: stable@kernel.org
> ---
>  net/rxrpc/af_rxrpc.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/net/rxrpc/af_rxrpc.c b/net/rxrpc/af_rxrpc.c
> index 0f90272ac254..0b7ed99a3025 100644
> --- a/net/rxrpc/af_rxrpc.c
> +++ b/net/rxrpc/af_rxrpc.c
> @@ -665,7 +665,7 @@ static int rxrpc_setsockopt(struct socket *sock, int level, int optname,
>
>                 case RXRPC_SECURITY_KEYRING:
>                         ret = -EINVAL;
> -                       if (rx->key)
> +                       if (rx->securities)
>                                 goto error;
>                         ret = -EISCONN;
>                         if (rx->sk.sk_state != RXRPC_UNBOUND)
>

I am following this patchset along with sashiko's comments. This time,
it flagged this patch for an API order dependency. The setsockopt
RXRPC_SECURITY_KEY option checks for rx->securities before proceeding,
but after this change, the RXRPC_SECURITY_KEYRING option doesn't check
for rx->key. Consequently, it's possible to set both keys on a socket
depending on the setsockopt call order.

I noticed this and wondered about it before sending the patch, but I
decided to keep the patch as concise as possible rather than assuming
too much and potentially breaking code. To make the logic more
coherent, what if we check if (rx->key || rx->securities) in both
options and remove the rx->securities check from rxrpc_request_key()?

Based on my check, rxrpc_request_key() and rxrpc_server_keyring() are
only called by their setsockopt options. We could also add a comment
to rxrpc_request_key() and rxrpc_server_keyring() noting that key
validation should be handled by the caller. Is the -EINVAL error
appropriate for this case?

-- 
Anderson Nascimento
Allele Security Intelligence
https://www.allelesecurity.com

^ permalink raw reply

* Re: [RFC PATCH net-next 0/3] seg6: SRv6 L2 VPN with End.DT2U and srl2 device
From: Stefano Salsano @ 2026-03-27  1:09 UTC (permalink / raw)
  To: nicolas.dichtel, Andrea Mayer, netdev
  Cc: David S . Miller, David Ahern, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Paolo Lungaroni, Ahmed Abdelsalam,
	Justin Iurman, linux-kernel
In-Reply-To: <2bb55d10-38a9-4595-baef-678d59bf4059@6wind.com>

Il 26/03/2026 17:32, Nicolas Dichtel ha scritto:
> Le 22/03/2026 à 01:05, Andrea Mayer a écrit :
>> Hi all,
> Hi,
> 
> first, thank you for posting this RFC.
> 
>>
>> this RFC series adds support for the SRv6 End.DT2U behavior and
>> introduces the srl2 Ethernet pseudowire device, together with
>> corresponding selftests.
>>
>> Motivation
>> ==========
>>
>> The main goal is to enable L2 service delivery through an internal
>> SRv6 L2 endpoint, in a way similar to how VXLAN provides L2 services
>> through a VXLAN netdevice.
>>
>> Today, Linux supports SRv6 L2 decapsulation only through an
>> End.DX2-style model, where the exposed Ethernet frame is forwarded
>> to a configured egress interface. The current implementation does
>> not provide a native netdevice representing the SRv6 L2 service
>> endpoint. This is a significant limitation compared to VXLAN, where
>> the tunnel endpoint is represented by a netdevice that can be
>> attached to a bridge and integrated naturally into the local L2
>> data plane.
>>
>> On the encapsulation side, the existing H.L2.Encaps mode is
>> implemented as a route-based lwtunnel, which only intercepts packets
>> entering the IP routing path. Non-IP protocols such as ARP are
>> never routed and therefore cannot be encapsulated, making it
>> unsuitable for full L2 service delivery.
>>
>> Design
>> ======
>>
>> This series addresses that limitation by introducing srl2, a native
>> SRv6 L2 endpoint interface. The intent is to make SRv6 L2 delivery
>> usable in the same deployment model already familiar from VXLAN: an
>> internal interface that represents the L2 service endpoint and can
>> participate naturally in local L2 forwarding.
> The current design, with vxlan, uses two interfaces: one bridge and one vxlan.
> I presume this design was chosen because the vxlan protocol is implemented in
> the vxlan driver.
> I wonder if using the same design for SRv6 is the best choice. Why not use only
> a bridge interface? Instead of pointing to a port, the FDB entries could point
> to a nexthop id. This enables to associate a srv6 encap directly to a MAC> address. Something like:
> 
> $ ip nexthop list id 1234
> id 1234 encap seg6 mode encap segs ...
> $ bridge fdb show dev br0
> 02:03:04:05:06:07 dev br0 nhid 1234 ...
> 
> What is the gain of having two interfaces?
> In term of scalability, it is interesting to have only one interface.

among our medium-term design goals we have:

1) achieving the feature parity with vxlan
2) being at least isomorphic with vxlan in terms of exposed CLI mechanisms

in this way we can port the existing vxlan based solution for cloud 
infrastructures and container networking to srv6

on top of this, we can design srv6 specific machinery for higher efficiency

therefore, the bridge+sr6 solution has to be supported to achieve 
feature parity/isomorphism with vxlan

on top of this, we also include a solution that does not need the bridge 
and terminates an L2 tunnel directly into a single sr6 interface 
characterized by a mac address (this is for the decap side)

for the encap side, we are considering per-mac solutions (see our answer 
to Justin) in a way which is aligned with vxlan design for the reasons 
mentioned above

ciao
Stefano


> 
> Regards,
> Nicolas
> 
>>
>> On top of that, the series adds support for End.DT2U so that Ethernet
>> payloads carried over SRv6 can be decapsulated and delivered into an
>> L2 domain through either a bridge port or an srl2 interface.
>>
>> Relation to RFC 8986
>> ====================
>>
>> RFC 8986 describes End.DT2U in terms of an abstract L2 table
>> associated with the SID. That abstraction specifies the externally
>> observable forwarding behavior of the node, but it does not mandate a
>> specific internal implementation. What matters is functional
>> equivalence.
>>
>> This series enforces that property by restricting valid End.DT2U
>> decapsulation targets to endpoints that provide the required L2
>> forwarding semantics by construction.
>>
>> When the target device is a bridge port, the Linux bridge provides
>> the expected behavior directly: source MAC learning, destination MAC
>> lookup, and unknown-unicast flooding within the corresponding L2
>> domain.
>>
>> When the target device is an srl2 interface, the same model is
>> realized in a degenerate but valid form, corresponding to a minimal
>> two-port L2 domain in which one endpoint is srl2. In that case,
>> forwarding is trivial: frames are delivered to srl2 only when
>> addressed to its MAC address, or when they are broadcast or multicast.
>> No general FDB is required in this case because there are no
>> additional L2 destinations to learn.
>>
>> Any other type of decapsulation target is rejected at configuration
>> time, since it would not guarantee the forwarding behavior required by
>> End.DT2U.
>>
>> The srl2 device is the minimal baseline implementation providing
>> point-to-point L2 encapsulation with a single segment list.
>> Additional features can be added incrementally based on community
>> feedback and use cases.
>>
>> Usage
>> =====
>>
>>    ip link add srl2-0 type srl2 segs fc00::a,fc00::b
>>    ip link set srl2-0 master br0
>>
>>    ip -6 route add fc00::100/128 encap seg6local action End.DT2U \
>>        l2dev srl2-0 dev dum0
>>
>>    # encapsulating traffic from veth-hs into srl2-0
>>    ip link set veth-hs master br0
>>
>> Selftest topology
>> =================
>>
>>              cafe::1                                cafe::2
>>             10.0.0.1                               10.0.0.2
>>            +--------+                             +--------+
>>            |  hs-1  |                             |  hs-2  |
>>            +---+----+                             +----+---+
>>                |                                       |
>>          +-----+------+                         +------+-----+
>>          |   veth-hs  |                         |  veth-hs   |
>>          |      |     |   fcf0:0:1:2::/64       |     |      |
>>          |    br0     +-------------------------+   br0      |
>>          |      |     |                         |     |      |
>>          |   srl2-0   |                         |  srl2-0    |
>>          |    rt-1    |                         |   rt-2     |
>>          +------------+                         +------------+
>>
>> The test verifies IPv4/IPv6 host-to-host and host-to-gateway
>> connectivity over the SRv6 L2 VPN, including ARP resolution
>> through the tunnel.
>>
>> A companion iproute2 series provides userspace support:
>>
>>    [RFC PATCH iproute2-next 0/4] seg6: add SRv6 End.DT2U and srl2 support
>>
>>
>> This is an RFC posting to collect feedback on both the overall design
>> and the userspace API before moving forward with a formal submission.
>>
>> Comments are very welcome, especially on:
>> - the introduction of srl2 as an internal SRv6 L2 endpoint
>> - the choice of constraining End.DT2U targets to bridge ports
>>    and srl2 devices
>> - the netlink/uAPI exposure
>> - the selftest coverage and topology
>>
>> Thanks,
>> Andrea and Stefano
>>
>>
>> Andrea Mayer (3):
>>    seg6: add support for the SRv6 End.DT2U behavior
>>    seg6: add SRv6 L2 tunnel device (srl2)
>>    selftests: seg6: add SRv6 srl2 + End.DT2U L2 VPN test
>>
>>   include/linux/srl2.h                          |   7 +
>>   include/uapi/linux/seg6_local.h               |   3 +
>>   include/uapi/linux/srl2.h                     |  20 +
>>   net/ipv6/Kconfig                              |  16 +
>>   net/ipv6/Makefile                             |   1 +
>>   net/ipv6/seg6.c                               |   1 +
>>   net/ipv6/seg6_local.c                         | 160 ++++-
>>   net/ipv6/srl2.c                               | 269 ++++++++
>>   tools/testing/selftests/net/Makefile          |   1 +
>>   tools/testing/selftests/net/config            |   1 +
>>   .../selftests/net/srv6_srl2_l2vpn_test.sh     | 621 ++++++++++++++++++
>>   11 files changed, 1094 insertions(+), 6 deletions(-)
>>   create mode 100644 include/linux/srl2.h
>>   create mode 100644 include/uapi/linux/srl2.h
>>   create mode 100644 net/ipv6/srl2.c
>>   create mode 100755 tools/testing/selftests/net/srv6_srl2_l2vpn_test.sh
>>
> 


-- 
*******************************************************************
Prof. Stefano Salsano
Dipartimento Ingegneria Elettronica
Universita' di Roma Tor Vergata
Viale Politecnico, 1 - 00133 Roma - ITALY

http://netgroup.uniroma2.it/Stefano_Salsano/

E-mail  : stefano.salsano@uniroma2.it
Office  : (Tel.) +39 06 72597770 (Fax.) +39 06 72597435
*******************************************************************


^ permalink raw reply

* Re: [GIT PULL] wireless-2026-03-26
From: patchwork-bot+netdevbpf @ 2026-03-27  1:10 UTC (permalink / raw)
  To: Johannes Berg; +Cc: netdev, linux-wireless
In-Reply-To: <20260326093329.77815-3-johannes@sipsolutions.net>

Hello:

This pull request was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Thu, 26 Mar 2026 10:31:35 +0100 you wrote:
> Hi,
> 
> And ... more fixes from drivers came in, notably iwlwifi with
> a bunch of things (their maintainer had been on vacation).
> 
> I'm going to be on vacation next week, but I'm also not aware
> of anything missing from drivers etc. right now, so we'll see.
> I doubt this is the _last_ pull request for 7.0, but I think
> not much more will come in.
> 
> [...]

Here is the summary with links:
  - [GIT,PULL] wireless-2026-03-26
    https://git.kernel.org/netdev/net/c/45dbf8fcea4d

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next v8 4/4] tun/tap & vhost-net: avoid ptr_ring tail-drop when a qdisc is present
From: Jason Wang @ 2026-03-27  1:13 UTC (permalink / raw)
  To: Simon Schippers
  Cc: willemdebruijn.kernel, andrew+netdev, davem, edumazet, kuba,
	pabeni, mst, eperezma, leiyang, stephen, jon, tim.gebauer, netdev,
	linux-kernel, kvm, virtualization
In-Reply-To: <3d3274b4-f274-4cf9-9d0f-989d05148604@tu-dortmund.de>

On Thu, Mar 26, 2026 at 11:31 PM Simon Schippers
<simon.schippers@tu-dortmund.de> wrote:
>
> On 3/26/26 03:41, Jason Wang wrote:
> > On Wed, Mar 25, 2026 at 10:48 PM Simon Schippers
> > <simon.schippers@tu-dortmund.de> wrote:
> >>
> >> On 3/24/26 11:14, Simon Schippers wrote:
> >>> On 3/24/26 02:47, Jason Wang wrote:
> >>>> On Thu, Mar 12, 2026 at 9:07 PM Simon Schippers
> >>>> <simon.schippers@tu-dortmund.de> wrote:
> >>>>>
> >>>>> This commit prevents tail-drop when a qdisc is present and the ptr_ring
> >>>>> becomes full. Once an entry is successfully produced and the ptr_ring
> >>>>> reaches capacity, the netdev queue is stopped instead of dropping
> >>>>> subsequent packets.
> >>>>>
> >>>>> If producing an entry fails anyways due to a race, tun_net_xmit returns
> >>>>> NETDEV_TX_BUSY, again avoiding a drop. Such races are expected because
> >>>>> LLTX is enabled and the transmit path operates without the usual locking.
> >>>>>
> >>>>> The existing __tun_wake_queue() function wakes the netdev queue. Races
> >>>>> between this wakeup and the queue-stop logic could leave the queue
> >>>>> stopped indefinitely. To prevent this, a memory barrier is enforced
> >>>>> (as discussed in a similar implementation in [1]), followed by a recheck
> >>>>> that wakes the queue if space is already available.
> >>>>>
> >>>>> If no qdisc is present, the previous tail-drop behavior is preserved.
> >>>>
> >>>> I wonder if we need a dedicated TUN flag to enable this. With this new
> >>>> flag, we can even prevent TUN from using noqueue (not sure if it's
> >>>> possible or not).
> >>>>
> >>>
> >>> Except of the slight regressions because of this patchset I do not see
> >>> a reason for such a flag.
> >>>
> >>> I have never seen that the driver prevents noqueue. For example you can
> >>> set noqueue to your ethernet interface and under load you soon get
> >>>
> >>> net_crit_ratelimited("Virtual device %s asks to queue packet!\n",
> >>>                                            dev->name);
> >>>
> >>> followed by a -ENETDOWN. And this is not prevented even though it is
> >>> clearly not something a user wants.
> >>>
> >>>>>
> >>>>> Benchmarks:
> >>>>> The benchmarks show a slight regression in raw transmission performance,
> >>>>> though no packets are lost anymore.
> >>>>>
> >>>>> The previously introduced threshold to only wake after the queue stopped
> >>>>> and half of the ring was consumed showed to be a descent choice:
> >>>>> Waking the queue whenever a consume made space in the ring strongly
> >>>>> degrades performance for tap, while waking only when the ring is empty
> >>>>> is too late and also hurts throughput for tap & tap+vhost-net.
> >>>>> Other ratios (3/4, 7/8) showed similar results (not shown here), so
> >>>>> 1/2 was chosen for the sake of simplicity for both tun/tap and
> >>>>> tun/tap+vhost-net.
> >>>>>
> >>>>> Test setup:
> >>>>> AMD Ryzen 5 5600X at 4.3 GHz, 3200 MHz RAM, isolated QEMU threads;
> >>>>> Average over 20 runs @ 100,000,000 packets. SRSO and spectre v2
> >>>>> mitigations disabled.
> >>>>>
> >>>>> Note for tap+vhost-net:
> >>>>> XDP drop program active in VM -> ~2.5x faster, slower for tap due to
> >>>>> more syscalls (high utilization of entry_SYSRETQ_unsafe_stack in perf)
> >>>>>
> >>>>> +--------------------------+--------------+----------------+----------+
> >>>>> | 1 thread                 | Stock        | Patched with   | diff     |
> >>>>> | sending                  |              | fq_codel qdisc |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>> | TAP        | Transmitted | 1.151 Mpps   | 1.139 Mpps     | -1.1%    |
> >>>>> |            +-------------+--------------+----------------+----------+
> >>>>> |            | Lost/s      | 3.606 Mpps   | 0 pps          |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>> | TAP        | Transmitted | 3.948 Mpps   | 3.738 Mpps     | -5.3%    |
> >>>>> |            +-------------+--------------+----------------+----------+
> >>>>> | +vhost-net | Lost/s      | 496.5 Kpps   | 0 pps          |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>>
> >>>>> +--------------------------+--------------+----------------+----------+
> >>>>> | 2 threads                | Stock        | Patched with   | diff     |
> >>>>> | sending                  |              | fq_codel qdisc |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>> | TAP        | Transmitted | 1.133 Mpps   | 1.109 Mpps     | -2.1%    |
> >>>>> |            +-------------+--------------+----------------+----------+
> >>>>> |            | Lost/s      | 8.269 Mpps   | 0 pps          |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>> | TAP        | Transmitted | 3.820 Mpps   | 3.513 Mpps     | -8.0%    |
> >>>>> |            +-------------+--------------+----------------+----------+
> >>>>> | +vhost-net | Lost/s      | 4.961 Mpps   | 0 pps          |          |
> >>>>> +------------+-------------+--------------+----------------+----------+
> >>>>>
> >>>>> [1] Link: https://lore.kernel.org/all/20250424085358.75d817ae@kernel.org/
> >>>>>
> >>>>> Co-developed-by: Tim Gebauer <tim.gebauer@tu-dortmund.de>
> >>>>> Signed-off-by: Tim Gebauer <tim.gebauer@tu-dortmund.de>
> >>>>> Signed-off-by: Simon Schippers <simon.schippers@tu-dortmund.de>
> >>>>> ---
> >>>>>  drivers/net/tun.c | 30 ++++++++++++++++++++++++++++--
> >>>>>  1 file changed, 28 insertions(+), 2 deletions(-)
> >>>>>
> >>>>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> >>>>> index b86582cc6cb6..9b7daec69acd 100644
> >>>>> --- a/drivers/net/tun.c
> >>>>> +++ b/drivers/net/tun.c
> >>>>> @@ -1011,6 +1011,8 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
> >>>>>         struct netdev_queue *queue;
> >>>>>         struct tun_file *tfile;
> >>>>>         int len = skb->len;
> >>>>> +       bool qdisc_present;
> >>>>> +       int ret;
> >>>>>
> >>>>>         rcu_read_lock();
> >>>>>         tfile = rcu_dereference(tun->tfiles[txq]);
> >>>>> @@ -1063,13 +1065,37 @@ static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
> >>>>>
> >>>>>         nf_reset_ct(skb);
> >>>>>
> >>>>> -       if (ptr_ring_produce(&tfile->tx_ring, skb)) {
> >>>>> +       queue = netdev_get_tx_queue(dev, txq);
> >>>>> +       qdisc_present = !qdisc_txq_has_no_queue(queue);
> >>>>> +
> >>>>> +       spin_lock(&tfile->tx_ring.producer_lock);
> >>>>> +       ret = __ptr_ring_produce(&tfile->tx_ring, skb);
> >>>>> +       if (__ptr_ring_produce_peek(&tfile->tx_ring) && qdisc_present) {
> >>>>
> >>>> So, it's possible that the administrator is switching between noqueue
> >>>> and another qdisc. So ptr_ring_produce() can fail here, do we need to
> >>>> check that or not?
> >>>>
> >>>
> >>> Do you mean that? My thoughts:
> >>>
> >>> Switching from noqueue to some qdisc can cause a
> >>>
> >>> net_crit_ratelimited("Virtual device %s asks to queue packet!\n",
> >>>                                            dev->name);
> >>>
> >>> followed by a return of -ENETDOWN in __dev_queue_xmit().
> >>> This is because tun_net_xmit detects some qdisc with
> >>>
> >>> qdisc_present = !qdisc_txq_has_no_queue(queue);
> >>>
> >>> and returns NETDEV_TX_BUSY even though __dev_queue_xmit() did still
> >>> detect noqueue.
> >>>
> >>> I am not sure how to solve this/if this has to be solved.
> >>> I do not see a proper way to avoid parallel execution of ndo_start_xmit
> >>> and a qdisc change (dev_graft_qdisc only takes qdisc_skb_head lock).
> >>>
> >>> And from my understanding the veth implementation faces the same issue.
> >>
> >> How about rechecking if a qdisc is connected?
> >> This would avoid -ENETDOWN.
> >>
> >> diff --git a/net/core/dev.c b/net/core/dev.c
> >> index f48dc299e4b2..2731a1a70732 100644
> >> --- a/net/core/dev.c
> >> +++ b/net/core/dev.c
> >> @@ -4845,10 +4845,17 @@ int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev)
> >>                                 if (is_list)
> >>                                         rc = NETDEV_TX_OK;
> >>                         }
> >> +                       bool qdisc_present = !qdisc_txq_has_no_queue(txq);
> >>                         HARD_TX_UNLOCK(dev, txq);
> >>                         if (!skb) /* xmit completed */
> >>                                 goto out;
> >>
> >> +                       /* Maybe a qdisc was connected in the meantime */
> >> +                       if (qdisc_present) {
> >> +                               kfree_skb(skb);
> >> +                               goto out;
> >> +                       }
> >> +
> >>                         net_crit_ratelimited("Virtual device %s asks to queue packet!\n",
> >>                                              dev->name);
> >>                         /* NETDEV_TX_BUSY or queue was stopped */
> >>
> >
> > Probably not, and we likely won't hit this warning because qdisc could
> > not be changed during ndo_start_xmit().
>
> Okay.
>
> >
> > I meant something like this:
> >
> > 1) set noqueue to tuntap
> > 2) produce packets so tuntap is full
> > 3) set e.g fq_codel to tuntap
> > 4) then we can hit the failure of __ptr_ring_produce()
> >
> > Rethink of the code, it looks just fine.
>
> Yes, in this case it just returns NETDEV_TX_BUSY which is fine with a
> qdisc attached.
>
> >
> >>
> >>>
> >>>
> >>> Switching from some qdisc to noqueue is no problem I think.
> >>>
> >>>>> +               netif_tx_stop_queue(queue);
> >>>>> +               /* Avoid races with queue wake-ups in __tun_wake_queue by
> >>>>> +                * waking if space is available in a re-check.
> >>>>> +                * The barrier makes sure that the stop is visible before
> >>>>> +                * we re-check.
> >>>>> +                */
> >>>>> +               smp_mb__after_atomic();
> >>>>
> >>>> Let's document which barrier is paired with this.
> >>>>
> >>>
> >>> I am basically copying the (old) logic of veth [1] proposed by
> >>> Jakub Kicinski. I must admit I am not 100% sure what it pairs with.
> >>>
> >>> [1] Link: https://lore.kernel.org/all/20250424085358.75d817ae@kernel.org/
> >
> > So it looks like it implicitly tries to pair with tun_ring_consume():
> >
> > 1) spinlock(consumer_lock)
> > 2) store NULL to ptr_ring // STORE
> > 3) spinunlock(consumer_lock) // RELEASE
> > 4) spinlock(consumer_lock) // ACQURE
> > 5) check empty
> > 6) spinunlock(consumer_lock)
> > 7) netif_wakeup_queue() // test_and_set() which is an RMW
> >
> > RELEASE + ACQUIRE implies a full barrier
>
> Thanks.
>
> >
> > I see several problems
> >
> > 1) Due to batch consumption, we may get spurious wakeups under heavy
> > load (we can try disabling batch consuming to see if it helps).
>
> I assume that you mean the waking in the recheck of the producer happens
> too often and then wakes too often. But this would just take slightly
> more producer cpu as the SOFTIRQ runs on the producer cpu and not slow
> down the consumer?
>
> Why would disabling batch consume help here?

We could end up with.

1) consumer wakes up the producer but the slot is not cleaned
2) producer is woken up but see the ring is full, so it need to drop the packet

This probably defeat the goal of zero packet loss.

> Wouldn't it just decrease the consumer speed?

Not sure, we probably need a benchmark.

>
> Apart from that I do not see a different method to do this recheck.
> The ring producer is only safely able to do a !produce_peek (so a check
> for !full).
>
> The normal waking (after consuming half of the ring) should be fine IMO.
>
> > 2) So the barriers don't help but would slow down the consuming
> > 3) Two spinlocks were used instead of one, this is another reason we
> > will see a performance regression
>
> You are right, I can change it to a single spin_lock. Apart from that
> I do not see how the barriers/locking could be reduced further.
>
> > 4) Tricky code that needs to be understood or at least requires a comment tweak.
> >
> > Note that due to ~IFF_TX_SKB_SHARING, pktgen can't clone skbs, so we
> > may not notice the real degradation.
>
> So run pktgen with pg_set SHARED?

Probably (as a workaround).

> I am pretty sure that the vhost
> thread was always at 100% CPU so pktgen was not the bottleneck. And when
> I had perf enabled I always saw that in my patched version not the
> creation of SKB's took most CPU in pktgen but a different unnamed
> function (I assume this is a waiting function).

Let's try and see.

Thanks

>
>
> Thank you!
>
> >
> >>>
> >>>>> +               if (!__ptr_ring_produce_peek(&tfile->tx_ring))
> >>>>> +                       netif_tx_wake_queue(queue);
> >>>>> +       }
> >>>>> +       spin_unlock(&tfile->tx_ring.producer_lock);
> >>>>> +
> >>>>> +       if (ret) {
> >>>>> +               /* If a qdisc is attached to our virtual device,
> >>>>> +                * returning NETDEV_TX_BUSY is allowed.
> >>>>> +                */
> >>>>> +               if (qdisc_present) {
> >>>>> +                       rcu_read_unlock();
> >>>>> +                       return NETDEV_TX_BUSY;
> >>>>> +               }
> >>>>>                 drop_reason = SKB_DROP_REASON_FULL_RING;
> >>>>>                 goto drop;
> >>>>>         }
> >>>>>
> >>>>>         /* dev->lltx requires to do our own update of trans_start */
> >>>>> -       queue = netdev_get_tx_queue(dev, txq);
> >>>>>         txq_trans_cond_update(queue);
> >>>>>
> >>>>>         /* Notify and wake up reader process */
> >>>>> --
> >>>>> 2.43.0
> >>>>>
> >>>>
> >>>> Thanks
> >>>>
> >>
> >
> > Thanks
> >
>


^ permalink raw reply

* [PATCH] net: ns83820: check DMA mapping errors in hard_start_xmit
From: Wang Jun @ 2026-03-27  1:16 UTC (permalink / raw)
  To: kuba
  Cc: pabeni, andrew+netdev, davem, edumazet, simon.horman, netdev,
	linux-kernel, gszhai, 25125332, 25125283, 23120469, Wang Jun,
	stable

The ns83820 driver currently ignores the return values of dma_map_single()
and skb_frag_dma_map() in the transmit path. If DMA mapping fails due to
IOMMU exhaustion or SWIOTLB pressure, the driver may proceed with invalid
DMA addresses, potentially causing hardware errors, data corruption, or
system instability.

Additionally, if mapping fails midway through processing fragmented
packets,previously mapped DMA resources are not released, leading
to DMA resource leaks.

Fix this by:
1. Checking dma_mapping_error() after each DMA mapping call.
2. Implementing an error handling path to unmap successfully
mapped buffers(both linear and fragments) using
dma_unmap_single().
3. Freeing the skb using dev_kfree_skb_any() to safely handle
both process and softirq contexts, as hard_start_xmit may be
called with IRQs enabled.
4. Returning NETDEV_TX_OK to drop the packet gracefully and
prevent TX queue stagnation.

This ensures compliance with the DMA API guidelines and
improves driver stability under memory pressure.

Fixes: fd9e4d6fec15 ("natsemi: switch from 'pci_' to 'dma_' API")
Cc: stable@vger.kernel.org
Signed-off-by: Wang Jun <1742789905@qq.com>
---
 drivers/net/ethernet/natsemi/ns83820.c | 32 ++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/drivers/net/ethernet/natsemi/ns83820.c b/drivers/net/ethernet/natsemi/ns83820.c
index cdbf82affa7b..90465e4977c3 100644
--- a/drivers/net/ethernet/natsemi/ns83820.c
+++ b/drivers/net/ethernet/natsemi/ns83820.c
@@ -1051,6 +1051,12 @@ static netdev_tx_t ns83820_hard_start_xmit(struct sk_buff *skb,
 	int stopped = 0;
 	int do_intr = 0;
 	volatile __le32 *first_desc;
+	dma_addr_t frag_dma_addr[MAX_SKB_FRAGS];
+	unsigned int frag_dma_len[MAX_SKB_FRAGS];
+	int frag_mapped_count = 0;
+	dma_addr_t main_buf = 0;
+	unsigned int main_len = 0;
+	int i;
 
 	dprintk("ns83820_hard_start_xmit\n");
 
@@ -1121,6 +1127,13 @@ static netdev_tx_t ns83820_hard_start_xmit(struct sk_buff *skb,
 	buf = dma_map_single(&dev->pci_dev->dev, skb->data, len,
 			     DMA_TO_DEVICE);
 
+	if (dma_mapping_error(&dev->pci_dev->dev, buf)) {
+		dev_kfree_skb_any(skb);
+		return NETDEV_TX_OK;
+	}
+	main_buf = buf;
+	main_len = len;
+
 	first_desc = dev->tx_descs + (free_idx * DESC_SIZE);
 
 	for (;;) {
@@ -1144,6 +1157,15 @@ static netdev_tx_t ns83820_hard_start_xmit(struct sk_buff *skb,
 
 		buf = skb_frag_dma_map(&dev->pci_dev->dev, frag, 0,
 				       skb_frag_size(frag), DMA_TO_DEVICE);
+		if (dma_mapping_error(&dev->pci_dev->dev, buf))
+			goto dma_map_error;
+
+		if (frag_mapped_count < MAX_SKB_FRAGS) {
+			frag_dma_addr[frag_mapped_count] = buf;
+			frag_dma_len[frag_mapped_count] = skb_frag_size(frag);
+			frag_mapped_count++;
+		}
+
 		dprintk("frag: buf=%08Lx  page=%08lx offset=%08lx\n",
 			(long long)buf, (long) page_to_pfn(frag->page),
 			frag->page_offset);
@@ -1166,6 +1188,16 @@ static netdev_tx_t ns83820_hard_start_xmit(struct sk_buff *skb,
 	if (stopped && (dev->tx_done_idx != tx_done_idx) && start_tx_okay(dev))
 		netif_start_queue(ndev);
 
+	return NETDEV_TX_OK;
+dma_map_error:
+	dma_unmap_single(&dev->pci_dev->dev, main_buf, main_len, DMA_TO_DEVICE);
+	for (i = 0; i < frag_mapped_count; i++) {
+		dma_unmap_single(&dev->pci_dev->dev, frag_dma_addr[i], frag_dma_len[i],
+				 DMA_TO_DEVICE);
+	}
+
+	dev_kfree_skb_any(skb);
+
 	return NETDEV_TX_OK;
 }
 
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH net-next v15 0/3] Add support for Nuvoton MA35D1 GMAC
From: patchwork-bot+netdevbpf @ 2026-03-27  1:20 UTC (permalink / raw)
  To: Joey Lu
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, robh, krzk+dt,
	conor+dt, mcoquelin.stm32, richardcochran, alexandre.torgue,
	joabreu, ychuang3, schung, yclu4, peppe.cavallaro,
	linux-arm-kernel, netdev, devicetree, linux-kernel, openbmc,
	linux-stm32
In-Reply-To: <20260323101756.81849-1-a0987203069@gmail.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Mon, 23 Mar 2026 18:17:53 +0800 you wrote:
> This patch series is submitted to add GMAC support for Nuvoton MA35D1
> SoC platform. This work involves implementing a GMAC driver glue layer
> based on Synopsys DWMAC driver framework to leverage MA35D1's dual GMAC
> interface capabilities.
> 
> Overview:
>   1. Added a GMAC driver glue layer for MA35D1 SoC, providing support for
>   the platform's two GMAC interfaces.
>   2. Added device tree settings, with specific configurations for our
>   development boards:
>     a. SOM board: Configured for two RGMII interfaces.
>     b. IoT board: Configured with one RGMII and one RMII interface.
>   3. Added dt-bindings for the GMAC interfaces.
> 
> [...]

Here is the summary with links:
  - [net-next,v15,1/3] dt-bindings: net: nuvoton: Add schema for Nuvoton MA35 family GMAC
    https://git.kernel.org/netdev/net-next/c/8454478ef9ab
  - [net-next,v15,2/3] arm64: dts: nuvoton: Add Ethernet nodes
    (no matching commit)
  - [net-next,v15,3/3] net: stmmac: dwmac-nuvoton: Add dwmac glue for Nuvoton MA35 family
    https://git.kernel.org/netdev/net-next/c/4d7c557f58ef

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* [PATCH v3] selftests: net: broadcast_pmtu: Fix false failure from incorrect ping exit code logic
From: fffsqian @ 2026-03-27  1:27 UTC (permalink / raw)
  To: davem, edumazet
  Cc: kuba, pabeni, horms, shuah, netdev, linux-kselftest, linux-kernel,
	Qingshuang Fu

From: Qingshuang Fu <fuqingshuang@kylinos.cn>

The broadcast_pmtu.sh test verifies that broadcast route MTU is respected,
but it uses an incorrect criteria for test success: it relies solely on
the ping command's exit code, which leads to false failures.

When the kernel correctly blocks oversized broadcast packets due to the
configured MTU limit (1500), ping fails to transmit any packets and returns
exit code 1. The original script interprets this as a test failure, even
though the kernel is properly enforcing the MTU (the core goal of the test).

Fix this by checking ping's output for "0 packets transmitted" or
"message too long" instead of relying on exit code.

Signed-off-by: Qingshuang Fu <fuqingshuang@kylinos.cn>
---
 tools/testing/selftests/net/broadcast_pmtu.sh | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/tools/testing/selftests/net/broadcast_pmtu.sh b/tools/testing/selftests/net/broadcast_pmtu.sh
index 726eb5d25839..68b4c5e539e7 100755
--- a/tools/testing/selftests/net/broadcast_pmtu.sh
+++ b/tools/testing/selftests/net/broadcast_pmtu.sh
@@ -40,8 +40,15 @@ cleanup() {
 trap cleanup EXIT
 
 setup &&
-	echo "Testing for broadcast route MTU" &&
-	ip net exec "${CLIENT_NS}" ping -f -M want -q -c 1 -s 8000 -w 1 -b "${CLIENT_BROADCAST_ADDRESS}" > /dev/null 2>&1
-
-exit $?
+	echo "Testing for broadcast route MTU" && {
+		ping_output=$(ip net exec "${CLIENT_NS}" ping -f -M want -c 1 -s 8000 -w 1 -b \
+			"${CLIENT_BROADCAST_ADDRESS}" 2>&1)
+		if echo "${ping_output}" | grep -q -E "0 packets transmitted|message too long"; then
+			exit_code=0
+		else
+			exit_code=1
+		fi
+	}
+
+exit ${exit_code}
 

base-commit: bbeb83d3182abe0d245318e274e8531e5dd7a948
-- 
2.25.1


^ permalink raw reply related

* Re: [GIT PULL] wireless-next-2026-03-26
From: patchwork-bot+netdevbpf @ 2026-03-27  1:32 UTC (permalink / raw)
  To: Johannes Berg; +Cc: netdev, linux-wireless
In-Reply-To: <20260326152021.305959-3-johannes@sipsolutions.net>

Hello:

This pull request was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Thu, 26 Mar 2026 16:18:02 +0100 you wrote:
> Hi,
> 
> So couple more things for now, including the NAN (Neighbor
> Awareness Networking) APIs that we spent a lot of time on
> trying to get right :) Other than that changes all over.
> 
> We'll surely have more changes for NAN and also ranging
> (for proximity detection) is coming, it's looking to be
> a big 7.1 from a wifi perspective, but we'll see how it
> all comes together.
> 
> [...]

Here is the summary with links:
  - [GIT,PULL] wireless-next-2026-03-26
    https://git.kernel.org/netdev/net-next/c/dbd94b9831bc

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next 0/5] net: stmmac: dwmac-socfpga: Cleanup .fix_mac_speed
From: patchwork-bot+netdevbpf @ 2026-03-27  1:32 UTC (permalink / raw)
  To: Maxime Chevallier
  Cc: andrew, kuba, davem, edumazet, pabeni, horms, mcoquelin.stm32,
	alexandre.torgue, linux, thomas.petazzoni, alexis.lothore,
	rohan.g.thomas, mun.yew.tham, netdev, linux-kernel,
	linux-arm-kernel, linux-stm32
In-Reply-To: <20260324092102.687082-1-maxime.chevallier@bootlin.com>

Hello:

This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Tue, 24 Mar 2026 10:20:55 +0100 you wrote:
> Hi everyone,
> 
> This small series does a bit of cleanup in the dwmad-socfpga glue
> driver, especially around the .fix_mac_speed() operation.
> 
> It's mostly about re-using existing helpers from the glue driver, as
> well as reorganizing the code to make the local private structures a
> little bit smaller.
> 
> [...]

Here is the summary with links:
  - [net-next,1/5] net: stmmac: dwmac-socfpga: Move internal helpers
    https://git.kernel.org/netdev/net-next/c/845a04411118
  - [net-next,2/5] net: stmmac: dwmac-socfpga: Use the socfpga_sgmii_config() helper
    https://git.kernel.org/netdev/net-next/c/a7be7cc12442
  - [net-next,3/5] net: stmmac: dwmac-socfpga: Use the correct type for interface modes
    https://git.kernel.org/netdev/net-next/c/9b04ecdfb876
  - [net-next,4/5] net: stmmac: dwmac-socfpga: get the phy_mode with the dedicated helper
    https://git.kernel.org/netdev/net-next/c/adf1536f79a5
  - [net-next,5/5] net: stmmac: dwmac-sofcpga: Drop the struct device reference
    https://git.kernel.org/netdev/net-next/c/9bd1af853750

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next] net: phylink: use phylink_expects_phy() in phylink_fwnode_phy_connect()
From: patchwork-bot+netdevbpf @ 2026-03-27  1:32 UTC (permalink / raw)
  To: Russell King; +Cc: andrew, davem, edumazet, kuba, netdev, pabeni
In-Reply-To: <E1w4zHg-0000000DmC4-2oyb@rmk-PC.armlinux.org.uk>

Hello:

This patch was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Tue, 24 Mar 2026 10:46:52 +0000 you wrote:
> The tests in phylink_expects_phy() and phylink_fwnode_phy_connect() are
> identical (by intention). Use phylink_expects_phy() to decide whether
> to ignore a call to phylink_fwnode_phy_connect().
> 
> Signed-off-by: Russell King (Oracle) <rmk+kernel@armlinux.org.uk>
> ---
>  drivers/net/phy/phylink.c | 6 ++----
>  1 file changed, 2 insertions(+), 4 deletions(-)

Here is the summary with links:
  - [net-next] net: phylink: use phylink_expects_phy() in phylink_fwnode_phy_connect()
    https://git.kernel.org/netdev/net-next/c/b83c28328fee

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next] ipv6: move IFA_F_PERMANENT percpu allocation in process scope
From: Jakub Kicinski @ 2026-03-27  1:35 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: netdev, David S. Miller, David Ahern, Eric Dumazet, Simon Horman
In-Reply-To: <8c8bfe2e1a324e501f0e15fef404a77443fd8caf.1774365668.git.pabeni@redhat.com>

On Tue, 24 Mar 2026 16:22:08 +0100 Paolo Abeni wrote:
> +	while (!list_empty(&tmp_addr_list)) {
> +		ifp = list_first_entry(&tmp_addr_list,
> +				       struct inet6_ifaddr, if_list_aux);
> +		list_del(&ifp->if_list_aux);
> +
> +		if (fixup_permanent_addr(net, idev, ifp) < 0) {
>  			in6_ifa_hold(ifp);
>  			ipv6_del_addr(ifp);
> -			write_lock_bh(&idev->lock);
>  
>  			net_info_ratelimited("%s: Failed to add prefix route for address %pI6c; dropping\n",
>  					     idev->dev->name, &ifp->addr);
>  		}

Sashiko points out that there's a UAF on the ifp->addr in the print
statement. Could you reorder these in net first, to avoid the conflict?
-- 
pw-bot: cr

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).