-From 3fd163f5bb88de426ca9847549f94b4296170ef0 Mon Sep 17 00:00:00 2001
+From e9abf1da0af3f787a03b249945e5ca726c1b8013 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Mon, 30 Mar 2026 23:40:53 +0100
+Date: Mon, 30 Mar 2026 23:52:09 +0100
Subject: [PATCH] net: dsa: mxl862xx: cancel pending work on probe error
Call mxl862xx_host_shutdown() in case dsa_register_switch() returns
an error, so any still pending crc_err_work get canceled.
-Fixes: a319d0c8c8ced ("net: dsa: mxl862xx: add CRC for MDIO communication)"
+Fixes: a319d0c8c8ce ("net: dsa: mxl862xx: add CRC for MDIO communication")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://patch.msgid.link/3fd163f5bb88de426ca9847549f94b4296170ef0.1774911025.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
-From cd698f1ae94c16499e2714b31dd6048e6f9f068d Mon Sep 17 00:00:00 2001
+From b0a79590d10847f190ed377d2664377d7068191d Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Wed, 25 Mar 2026 17:54:11 +0000
-Subject: [PATCH 01/26] net: dsa: move dsa_bridge_ports() helper to dsa.h
+Date: Wed, 1 Apr 2026 14:34:30 +0100
+Subject: [PATCH 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.
can make use of it as well.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
- include/net/dsa.h | 13 +++++++++++++
- 1 file changed, 13 insertions(+)
+ include/net/dsa.h | 13 +++++++++++++
+ 2 files changed, 13 insertions(+), 13 deletions(-)
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
-From c161533e1605a7282563c139323a3913890fdb72 Mon Sep 17 00:00:00 2001
+From f259e08494c47c614ce7b6d3079d1e0d3f30ae66 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Wed, 25 Mar 2026 17:54:41 +0000
-Subject: [PATCH 02/26] net: dsa: add bridge member iteration macro
+Date: Wed, 1 Apr 2026 14:34:42 +0100
+Subject: [PATCH 2/4] net: dsa: add bridge member iteration macro
Drivers that offload bridges need to iterate over the ports that are
members of a given bridge, for example to rebuild per-port forwarding
helper.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/e7136aaa26773f39e805a00fe4ecf13cd2b83fc0.1775049897.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
-From 753efe27a9afee52c4ad42098a9b9278366d63cc Mon Sep 17 00:00:00 2001
+From 4250ff1640ea1ede99bfe02ca949acbcc6c0927f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Wed, 25 Mar 2026 17:54:52 +0000
-Subject: [PATCH 03/26] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
+Date: Wed, 1 Apr 2026 14:34:52 +0100
+Subject: [PATCH 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
set dsa_default_offload_fwd_mark() on those.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/e1161c90894ddc519c57dc0224b3a0f6bfa1d2d6.1775049897.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/dsa/tag_mxl862xx.c | 3 +++
1 file changed, 3 insertions(+)
-From ce0664ff8f75c3ab01101c3f0f8569924d948775 Mon Sep 17 00:00:00 2001
+From 340bdf984613c4a9241d678915e513824f5a9b19 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Wed, 25 Mar 2026 17:55:08 +0000
-Subject: [PATCH 04/26] net: dsa: mxl862xx: implement bridge offloading
+Date: Wed, 1 Apr 2026 14:35:01 +0100
+Subject: [PATCH 4/4] net: dsa: mxl862xx: implement bridge offloading
Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
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().
+mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which
+applies the cached per-port state to hardware. For standalone user
+ports (dp->bridge == NULL), it additionally resets the port to
+single-port bridge state: CPU-only portmap, learning and flooding
+disabled. The CPU port path sets its state explicitly before calling
+this helper and is therefore not affected by the reset.
+
+Note that MASK_VLAN_BASED_MAC_LEARNING is intentionally absent from
+the firmware write mask. After mxl862xx_reset(), the firmware
+initialises all VLAN-based MAC learning fields to 0 (disabled), so
+SVL is the active mode by default without having to set it explicitly.
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
pass before the meter would change from 'yellow' into 'red' state.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/dd079180e2098e5f9626fcd149b9bad9a1b5a1b2.1775049897.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
- drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
- drivers/net/dsa/mxl862xx/mxl862xx.c | 743 ++++++++++++++++++++++--
- drivers/net/dsa/mxl862xx/mxl862xx.h | 133 +++++
- 4 files changed, 1076 insertions(+), 45 deletions(-)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
+ 4 files changed, 1022 insertions(+), 58 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
-@@ -168,6 +182,213 @@ static int mxl862xx_setup_mdio(struct ds
+@@ -168,6 +182,199 @@ static int mxl862xx_setup_mdio(struct ds
return ret;
}
+ 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;
++ struct dsa_port *member_dp;
++ u16 bridge_id;
+ bool enable;
+ int i, idx;
+
++ if (!p->setup_done)
++ return 0;
++
++ if (dsa_port_is_cpu(dp)) {
++ dsa_switch_for_each_user_port(member_dp, ds) {
++ if (member_dp->cpu_dp->index != port)
++ continue;
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ member_dp->index);
++ }
++ } else if (dp->bridge) {
++ dsa_switch_for_each_bridge_member(member_dp, ds,
++ dp->bridge->dev) {
++ if (member_dp->index == port)
++ continue;
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ member_dp->index);
++ }
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ dp->cpu_dp->index);
++ } else {
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ dp->cpu_dp->index);
++ p->flood_block = 0;
++ p->learning = false;
++ }
++
++ bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
++
+ 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_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));
+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;
++ struct dsa_port *dp;
++ int ret = 0, err;
+
+ 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);
++ err = mxl862xx_set_bridge_port(ds, dp->index);
+ if (err)
+ ret = err;
+ }
+ return ret;
+}
+
-+static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
++static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
+{
+ struct mxl862xx_bridge_alloc br_alloc = {};
+ int ret;
+ if (ret)
+ return ret;
+
-+ *bridge_id = le16_to_cpu(br_alloc.bridge_id);
-+ return 0;
++ return le16_to_cpu(br_alloc.bridge_id);
+}
+
+static void mxl862xx_free_bridge(struct dsa_switch *ds,
+
+ 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 +402,10 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -181,6 +388,10 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
return mxl862xx_setup_mdio(ds);
}
-@@ -260,66 +485,87 @@ static int mxl862xx_configure_sp_tag_pro
+@@ -260,99 +471,137 @@ static int mxl862xx_configure_sp_tag_pro
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;
+- 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);
--
++ priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
++ priv->ports[port].learning = true;
+
- 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)
+- /* include all assigned user ports in the CPU portmap */
+- 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;
+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;
++ 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;
+- bridge_port_map |= BIT(dp->index);
+ if (!priv->bridges[bridge.num]) {
-+ ret = mxl862xx_allocate_bridge(priv, &fw_id);
-+ if (ret)
++ ret = mxl862xx_allocate_bridge(priv);
++ if (ret < 0)
+ return ret;
+
-+ priv->bridges[bridge.num] = fw_id;
++ priv->bridges[bridge.num] = ret;
+
+ /* Free bridge here on error, DSA rollback won't. */
+ ret = mxl862xx_sync_bridge_members(ds, &bridge);
+
+ return 0;
}
+- 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_sync_bridge_members(ds, &bridge);
+ }
+
+-static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
++static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
++ const struct dsa_bridge bridge)
+ {
+- struct mxl862xx_bridge_port_config br_port_cfg = {};
+- struct dsa_port *dp = dsa_to_port(ds, port);
+- struct mxl862xx_bridge_alloc br_alloc = {};
+- int ret;
++ int err;
+
+- 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;
+- }
++ 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));
- br_port_cfg.bridge_id = br_alloc.bridge_id;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
- 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
-+ 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));
-+
+ /* Revert leaving port, omitted by the sync above, to its
+ * single-port bridge
*/
- br_port_cfg.bridge_port_map[0] = cpu_to_le16(BIT(dp->cpu_dp->index));
-+ 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,
struct dsa_port *dp = dsa_to_port(ds, port);
bool is_cpu_port = dsa_port_is_cpu(dp);
int ret;
-@@ -352,7 +598,31 @@ static int mxl862xx_port_setup(struct ds
+
+- /* disable port and flush MAC entries */
+ ret = mxl862xx_port_state(ds, port, false);
+ if (ret)
+ return ret;
+
+ mxl862xx_port_fast_age(ds, port);
+
+- /* skip setup for unused and DSA ports */
+ if (dsa_port_is_unused(dp) ||
+ dsa_port_is_dsa(dp))
+ return 0;
+
+- /* configure tag protocol */
+ ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
+ if (ret)
+ return ret;
+
+- /* assign CTP port IDs */
+ ret = mxl862xx_configure_ctp_port(ds, port, port,
+ is_cpu_port ? 32 - port : 1);
+ if (ret)
+ return ret;
+
+ if (is_cpu_port)
+- /* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
- /* setup single-port bridge for user ports */
+- /* setup single-port bridge for user ports */
- return mxl862xx_add_single_port_bridge(ds, port);
-+ ret = mxl862xx_add_single_port_bridge(ds, port);
++ /* setup single-port bridge for user ports.
++ * If this fails, the FID is leaked -- but the port then transitions
++ * to unused, and the FID pool is sized to tolerate this.
++ */
++ ret = mxl862xx_allocate_bridge(priv);
++ if (ret < 0) {
++ dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
++ return ret;
++ }
++ priv->ports[port].fid = ret;
++ /* Standalone ports should not flood unknown unicast or multicast
++ * towards the CPU by default; only broadcast is needed initially.
++ */
++ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++ false, false, true);
++ if (ret)
++ return ret;
++ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
+ return ret;
+
}
static void mxl862xx_phylink_get_caps(struct dsa_switch *ds, int port,
-@@ -365,14 +635,383 @@ static void mxl862xx_phylink_get_caps(st
+@@ -365,14 +614,371 @@ static void mxl862xx_phylink_get_caps(st
config->supported_interfaces);
}
+ 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));
+ 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));
+ 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);
+ 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);
+ * 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)
+ 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();
+
+ 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.
++ /* Always write to the standalone FID. When standalone it takes effect
++ * immediately; when bridged the port uses the shared bridge FID so the
++ * write is a no-op for current forwarding, but the state is preserved
++ * in hardware and is ready once the port returns to standalone.
+ */
-+ if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
-+ mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
++ mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
++ p->host_flood_mc, true);
+
+ rtnl_unlock();
+}
+ if (flags.mask & BR_LEARNING)
+ priv->ports[port].learning = !!(flags.val & BR_LEARNING);
+
-+ if ((block != old_block) || (flags.mask & BR_LEARNING)) {
++ if (block != old_block || (flags.mask & BR_LEARNING)) {
+ priv->ports[port].flood_block = block;
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
-@@ -407,7 +1046,7 @@ static int mxl862xx_probe(struct mdio_de
+@@ -407,7 +1013,7 @@ static int mxl862xx_probe(struct mdio_de
struct device *dev = &mdiodev->dev;
struct mxl862xx_priv *priv;
struct dsa_switch *ds;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
-@@ -425,14 +1064,25 @@ static int mxl862xx_probe(struct mdio_de
+@@ -425,14 +1031,25 @@ static int mxl862xx_probe(struct mdio_de
ds->ops = &mxl862xx_switch_ops;
ds->phylink_mac_ops = &mxl862xx_phylink_mac_ops;
ds->num_ports = MXL862XX_MAX_PORTS;
return err;
}
-@@ -440,6 +1090,7 @@ static void mxl862xx_remove(struct mdio_
+@@ -440,6 +1057,7 @@ static void mxl862xx_remove(struct mdio_
{
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
struct mxl862xx_priv *priv;
if (!ds)
return;
-@@ -449,12 +1100,21 @@ static void mxl862xx_remove(struct mdio_
+@@ -449,12 +1067,21 @@ static void mxl862xx_remove(struct mdio_
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
if (!ds)
return;
-@@ -465,6 +1125,9 @@ static void mxl862xx_shutdown(struct mdi
+@@ -465,6 +1092,9 @@ static void mxl862xx_shutdown(struct mdi
mxl862xx_host_shutdown(priv);
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -4,15 +4,148 @@
+@@ -4,15 +4,114 @@
#define __MXL862XX_H
#include <linux/mdio.h>
+#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)
+ * @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)
+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;
--- /dev/null
+From 3a4056ec7ec8f71ae9722f86d3cfbc4589deeac4 Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 7 Apr 2026 18:30:27 +0100
+Subject: [PATCH 1/2] net: dsa: mxl862xx: reject DSA_PORT_TYPE_DSA
+
+DSA links aren't supported by the mxl862xx driver.
+
+Instead of returning early from .port_setup when called for
+DSA_PORT_TYPE_DSA ports rather return -EOPNOTSUPP and show an error
+message.
+
+The desired side-effect is that the framework will switch the port to
+DSA_PORT_TYPE_UNUSED, so we can stop caring about DSA_PORT_TYPE_DSA in
+all other places.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/b686f3a22d8a6e7d470e7aa98da811a996a229b9.1775581804.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -544,10 +544,14 @@ static int mxl862xx_port_setup(struct ds
+
+ mxl862xx_port_fast_age(ds, port);
+
+- if (dsa_port_is_unused(dp) ||
+- dsa_port_is_dsa(dp))
++ if (dsa_port_is_unused(dp))
+ return 0;
+
++ if (dsa_port_is_dsa(dp)) {
++ dev_err(ds->dev, "port %d: DSA links not supported\n", port);
++ return -EOPNOTSUPP;
++ }
++
+ ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
+ if (ret)
+ return ret;
+@@ -591,7 +595,7 @@ static void mxl862xx_port_teardown(struc
+ 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))
++ if (dsa_port_is_unused(dp))
+ return;
+
+ /* Prevent deferred host_flood_work from acting on stale state.
--- /dev/null
+From 71934b9e6f36b1786bd969c0e1d2de8f9bd65f0f Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Tue, 7 Apr 2026 18:30:35 +0100
+Subject: [PATCH 2/2] net: dsa: mxl862xx: don't skip early bridge port
+ configuration
+
+mxl862xx_bridge_port_set() is currently guarded by the
+mxl8622_port->setup_done flag, as the early call to
+mxl862xx_bridge_port_set() from mxl862xx_port_stp_state_set() would
+otherwise cause a NULL-pointer dereference on unused ports which don't
+have dp->cpu_dp despite not being a CPU port.
+
+Using the setup_done flag (which is never set for unused ports),
+however, also prevents mxl862xx_bridge_port_set() from configuring
+user ports' single-port bridges early, which was unintended.
+
+Fix this by returning early from mxl862xx_bridge_port_set() in case
+dsa_port_is_unused().
+
+Fixes: 340bdf984613c ("net: dsa: mxl862xx: implement bridge offloading")
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/15962aac29ebe0a6eb77565451acff880c41ef33.1775581804.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
+@@ -278,7 +278,7 @@ static int mxl862xx_set_bridge_port(stru
+ bool enable;
+ int i, idx;
+
+- if (!p->setup_done)
++ if (dsa_port_is_unused(dp))
+ return 0;
+
+ if (dsa_port_is_cpu(dp)) {
-From 0d88d02cc9dccad01ff88f54e1beee867107b942 Mon Sep 17 00:00:00 2001
+From d587f9b6dcc98c1e8aeb5c189a7bfac60d6d29ac Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Tue, 10 Mar 2026 02:36:00 +0000
-Subject: [PATCH 05/26] net: dsa: mxl862xx: implement VLAN functionality
+Date: Tue, 7 Apr 2026 18:31:01 +0100
+Subject: [PATCH] net: dsa: mxl862xx: implement VLAN functionality
Add VLAN support using both the Extended VLAN (EVLAN) engine and the
VLAN Filter (VF) engine in a hybrid architecture that allows a higher
Both engines draw from shared 1024-entry hardware pools. The VF pool
is divided equally among user ports for VID membership, while the
EVLAN pool is partitioned into small fixed-size ingress blocks (7
-entries of catchall rules per port) and variable-size egress blocks
-for tag stripping.
+entries of catchall rules per port) and fixed-size egress blocks for
+tag stripping.
With 5 user ports this yields up to 204 VIDs per port (limited by VF),
of which up to 98 can be untagged (limited by EVLAN egress budget).
VLAN Filter blocks across ports with identical VID sets.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/9be29637675342b109a85fa08f5378800d9f7b78.1775581804.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
- drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 +++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 ++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 12 +
- drivers/net/dsa/mxl862xx/mxl862xx.c | 915 +++++++++++++++++++++++-
- drivers/net/dsa/mxl862xx/mxl862xx.h | 104 ++-
- 4 files changed, 1344 insertions(+), 16 deletions(-)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 781 +++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 103 +++-
+ 4 files changed, 1211 insertions(+), 14 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+/**
+ * enum mxl862xx_extended_vlan_treatment_priority - Treatment priority mode
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL: Use explicit value
-+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY: Copy from inner tag
-+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY: Copy from outer tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY: Copy from inner tag
++ * @MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY: Copy from outer tag
+ * @MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP: Derive from DSCP
+ */
+enum mxl862xx_extended_vlan_treatment_priority {
+ MXL862XX_EXTENDEDVLAN_TREATMENT_PRIORITY_VAL = 0,
-+ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRORITY = 1,
-+ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRORITY = 2,
++ MXL862XX_EXTENDEDVLAN_TREATMENT_INNER_PRIORITY = 1,
++ MXL862XX_EXTENDEDVLAN_TREATMENT_OUTER_PRIORITY = 2,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_DSCP = 3,
+};
+
#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -50,6 +50,88 @@ static const int mxl862xx_flood_meters[]
+@@ -50,6 +50,85 @@ static const int mxl862xx_flood_meters[]
MXL862XX_BRIDGE_PORT_EGRESS_METER_BROADCAST,
};
+enum mxl862xx_evlan_action {
+ EVLAN_ACCEPT, /* pass-through, no tag removal */
+ EVLAN_STRIP_IF_UNTAGGED, /* remove 1 tag if entry's untagged flag set */
-+ EVLAN_DISCARD, /* discard upstream */
+ EVLAN_PVID_OR_DISCARD, /* insert PVID tag or discard if no PVID */
-+ EVLAN_PVID_OR_PASS, /* insert PVID tag or pass-through */
+ EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
+};
+
+ * regardless of TPID, so without the ACCEPT guard, it would also
+ * catch standard 802.1Q VID>0 frames and corrupt them. With the
+ * guard, 802.1Q VID>0 frames match the ACCEPT rules first and
-+ * pass through untouched; only non-8021Q TPID frames fall through
++ * pass through untouched; only non-8021Q TPID frames pass through
+ * to the NO_FILTER catchalls.
+ */
+static const struct mxl862xx_evlan_rule_desc ingress_aware_final[] = {
+};
+
+/*
-+ * VID-specific accept rules for VLAN-unaware egress.
-+ * The HW sees the MxL tag as outer, real VLAN tag as inner.
-+ * match on inner VID with outer=NO_FILTER.
++ * Egress tag-stripping rules for VLAN-unaware mode (2 per untagged VID).
++ * The HW sees the MxL tag as outer; the real VLAN tag, if any, is inner.
+ */
+static const struct mxl862xx_evlan_rule_desc vid_accept_egress_unaware[] = {
-+ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
-+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, true, EVLAN_STRIP_IF_UNTAGGED },
++ { FT_NO_FILTER, FT_NORMAL, TP_NONE, TP_8021Q, true, EVLAN_STRIP_IF_UNTAGGED },
++ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, false, EVLAN_STRIP_IF_UNTAGGED },
+};
+
static enum dsa_tag_protocol mxl862xx_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol m)
-@@ -275,6 +357,7 @@ static int mxl862xx_set_bridge_port(stru
+@@ -275,6 +354,7 @@ static int mxl862xx_set_bridge_port(stru
struct mxl862xx_port *p = &priv->ports[port];
- u16 bridge_id = dp->bridge ?
- priv->bridges[dp->bridge->num] : p->fid;
+ struct dsa_port *member_dp;
+ u16 bridge_id;
+ u16 vf_scan;
bool enable;
int i, idx;
-@@ -283,9 +366,69 @@ static int mxl862xx_set_bridge_port(stru
+@@ -312,9 +392,69 @@ static int mxl862xx_set_bridge_port(stru
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 |
+ br_port_cfg.vlan_src_mac_vid_enable = p->vlan_filtering;
+ br_port_cfg.vlan_dst_mac_vid_enable = p->vlan_filtering;
+
- mxl862xx_fw_portmap_from_bitmap(br_port_cfg.bridge_port_map, p->portmap);
-
for (i = 0; i < ARRAY_SIZE(mxl862xx_flood_meters); i++) {
-@@ -329,6 +472,91 @@ static int mxl862xx_sync_bridge_members(
+ idx = mxl862xx_flood_meters[i];
+ enable = !!(p->flood_block & BIT(idx));
+@@ -343,6 +483,72 @@ static int mxl862xx_sync_bridge_members(
return ret;
}
-+static void mxl862xx_evlan_block_init(struct mxl862xx_evlan_block *blk,
-+ u16 size)
-+{
-+ blk->allocated = false;
-+ blk->in_use = false;
-+ blk->block_id = 0;
-+ blk->block_size = size;
-+ blk->n_active = 0;
-+}
-+
+static int mxl862xx_evlan_block_alloc(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk)
+{
+ return 0;
+}
+
-+static void mxl862xx_vf_init(struct mxl862xx_vf_block *vf, u16 size)
-+{
-+ vf->allocated = false;
-+ vf->block_id = 0;
-+ vf->block_size = size;
-+ vf->active_count = 0;
-+ INIT_LIST_HEAD(&vf->vids);
-+}
-+
+static int mxl862xx_vf_block_alloc(struct mxl862xx_priv *priv,
+ u16 size, u16 *block_id)
+{
+ return mxl862xx_vf_entry_discard(priv, vf->block_id, 0);
+}
+
- static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv, u16 *bridge_id)
+ static int mxl862xx_allocate_bridge(struct mxl862xx_priv *priv)
{
struct mxl862xx_bridge_alloc br_alloc = {};
-@@ -392,6 +620,9 @@ static int mxl862xx_add_single_port_brid
+@@ -378,6 +584,9 @@ static void mxl862xx_free_bridge(struct
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
int ret;
ret = mxl862xx_reset(priv);
-@@ -402,6 +633,50 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -388,6 +597,50 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
+ * through without EVLAN processing.
+ *
+ * Total EVLAN budget:
-+ * n_user_ports * (ingress + egress) ≤ 1024.
++ * n_user_ports * (ingress + egress) <= 1024.
+ * Ingress blocks are small (7 entries), so almost all capacity
+ * goes to egress VID rules.
+ */
ret = mxl862xx_setup_drop_meter(ds);
if (ret)
return ret;
-@@ -483,27 +758,616 @@ static int mxl862xx_configure_sp_tag_pro
+@@ -469,12 +722,509 @@ static int mxl862xx_configure_sp_tag_pro
return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
}
-+/**
-+ * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
-+ * @priv: driver private data
-+ * @block_id: HW Extended VLAN block ID
-+ * @entry_index: entry index within the block
-+ * @desc: rule descriptor (filter type + action)
-+ * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
-+ * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
-+ * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
-+ *
-+ * Translates a compact rule descriptor into a full firmware
-+ * mxl862xx_extendedvlan_config struct and writes it via the API.
-+ */
+static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index,
+ const struct mxl862xx_evlan_rule_desc *desc,
+ MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
+ break;
+
-+ case EVLAN_DISCARD:
-+ cfg.treatment.remove_tag =
-+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
-+ break;
-+
+ case EVLAN_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
+ }
+ break;
+
-+ case EVLAN_PVID_OR_PASS:
-+ if (pvid) {
-+ cfg.treatment.remove_tag =
-+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
-+ cfg.treatment.add_outer_vlan = 1;
-+ cfg.treatment.outer_vlan.vid_mode =
-+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_VID_VAL);
-+ cfg.treatment.outer_vlan.vid_val = cpu_to_le32(pvid);
-+ cfg.treatment.outer_vlan.tpid =
-+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_8021Q);
-+ } else {
-+ cfg.treatment.remove_tag =
-+ cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_NOT_REMOVE_TAG);
-+ }
-+ break;
-+
+ case EVLAN_STRIP1_AND_PVID_OR_DISCARD:
+ if (pvid) {
+ cfg.treatment.remove_tag =
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
-+/**
-+ * mxl862xx_evlan_deactivate_entry - Reset an Extended VLAN entry to no-op
-+ * @priv: driver private data
-+ * @block_id: HW Extended VLAN block ID
-+ * @entry_index: entry index within the block
-+ *
-+ * Writes a zeroed-out config to the firmware, which deactivates the
-+ * rule (making it transparent / no-op).
-+ */
+static int mxl862xx_evlan_deactivate_entry(struct mxl862xx_priv *priv,
+ u16 block_id, u16 entry_index)
+{
+ return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
+}
+
-+/**
-+ * mxl862xx_evlan_write_final_rules - Write catchall rules to the ingress block
-+ * @priv: driver private data
-+ * @blk: Extended VLAN block (already allocated)
-+ * @rules: array of rule descriptors for the final rules
-+ * @n_rules: number of final rules
-+ * @pvid: port VLAN ID (for PVID insertion rules)
-+ *
-+ * Writes final catchall rules starting at block_size - n_rules. With
-+ * VLAN Filter handling VID membership, only the ingress block uses
-+ * finals, and the block is sized to exactly fit them (no VID entries),
-+ * so the rules fill the entire block.
-+ */
+static int mxl862xx_evlan_write_final_rules(struct mxl862xx_priv *priv,
+ struct mxl862xx_evlan_block *blk,
+ const struct mxl862xx_evlan_rule_desc *rules,
+ return 0;
+}
+
-+/**
-+ * mxl862xx_vf_entry_set - Write a single VLAN Filter entry
-+ * @priv: driver private data
-+ * @block_id: HW VLAN Filter block ID
-+ * @index: entry index within the block
-+ * @vid: VLAN ID to allow
-+ *
-+ * Writes an ALLOW entry (discard_matched=false) for the given VID.
-+ */
+static int mxl862xx_vf_entry_set(struct mxl862xx_priv *priv,
+ u16 block_id, u16 index, u16 vid)
+{
+ return MXL862XX_API_WRITE(priv, MXL862XX_VLANFILTER_SET, cfg);
+}
+
-+/**
-+ * mxl862xx_vf_find_vid - Find a VID entry in a VF block
-+ * @vf: VLAN Filter block to search
-+ * @vid: VLAN ID to find
-+ */
-+static struct mxl862xx_vf_vid *
-+mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf, u16 vid)
++static struct mxl862xx_vf_vid *mxl862xx_vf_find_vid(struct mxl862xx_vf_block *vf,
++ u16 vid)
+{
+ struct mxl862xx_vf_vid *ve;
+
+ return NULL;
+}
+
-+/**
-+ * mxl862xx_vf_add_vid - Add a VID to a port's VLAN Filter block
-+ * @priv: driver private data
-+ * @vf: VLAN Filter block
-+ * @vid: VLAN ID to add
-+ * @untagged: whether this VID should strip tags on egress
-+ *
-+ * Idempotent. Writes an ALLOW entry at active_count and increments
-+ * active_count. If the VID already exists, only the untagged flag
-+ * is updated. The HW block must be allocated before calling this.
-+ */
+static int mxl862xx_vf_add_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf,
+ u16 vid, bool untagged)
+ return 0;
+}
+
-+/**
-+ * mxl862xx_vf_del_vid - Remove a VID from a port's VLAN Filter block
-+ * @priv: driver private data
-+ * @vf: VLAN Filter block
-+ * @vid: VLAN ID to remove
-+ *
-+ * Swap-compacts: the last active entry is moved into the gap,
-+ * active_count is decremented, and the old last slot is plugged
-+ * with DISCARD. When active_count drops to 0, a DISCARD sentinel
-+ * is restored at index 0.
-+ */
+static int mxl862xx_vf_del_vid(struct mxl862xx_priv *priv,
+ struct mxl862xx_vf_block *vf, u16 vid)
+{
+ return ret;
+ } else if (gap < last) {
+ /* Swap: move the last ALLOW entry into the gap */
-+ last_ve = NULL;
+ list_for_each_entry(last_ve, &vf->vids, list)
+ if (last_ve->index == last)
+ break;
+
-+ if (WARN_ON(!last_ve || last_ve->index != last))
++ if (WARN_ON(list_entry_is_head(last_ve, &vf->vids, list)))
+ return -EINVAL;
+
+ ret = mxl862xx_vf_entry_set(priv, vf->block_id,
+ return ret;
+
+ last_ve->index = gap;
-+
-+ /* Plug the old last slot with DISCARD */
-+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
-+ if (ret)
-+ return ret;
-+ } else {
-+ /* Deleting the last entry -- just plug it */
-+ ret = mxl862xx_vf_entry_discard(priv, vf->block_id, last);
-+ if (ret)
-+ return ret;
+ }
+
+ list_del(&ve->list);
+ return 0;
+}
+
-+/**
-+ * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
-+ * @priv: driver private data
-+ * @port: port number
-+ *
-+ * In VLAN-aware mode the ingress EVLAN block handles PVID insertion for
-+ * untagged/priority-tagged frames, passes through standard 802.1Q
-+ * tagged frames for VF membership checking, and treats non-8021Q TPID
-+ * frames as untagged. The block is sized to exactly fit the 7 catchall
-+ * rules and is rewritten whenever PVID changes.
-+ *
-+ * In VLAN-unaware mode the firmware passes frames through unchanged when
-+ * no ingress block is assigned, so nothing is programmed.
-+ */
+static int mxl862xx_evlan_program_ingress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
+ p->pvid);
+}
+
-+/**
-+ * mxl862xx_evlan_program_egress - Reprogram all egress tag-stripping rules
-+ * @priv: driver private data
-+ * @port: port number
-+ *
-+ * Walks the port's VF VID list and writes 2 EVLAN rules per VID that
-+ * needs egress tag stripping. In VLAN-aware mode only untagged VIDs
-+ * need rules (tagged VIDs pass through EVLAN untouched). In unaware
-+ * mode every VID gets rules.
-+ *
-+ * Entries are packed starting at index 0, and the scan window
-+ * (n_active) is narrowed so stale entries beyond it are never matched.
-+ */
+static int mxl862xx_evlan_program_egress(struct mxl862xx_priv *priv, int port)
+{
+ struct mxl862xx_port *p = &priv->ports[port];
+ }
+
+ list_for_each_entry(vfv, &p->vf.vids, list) {
-+ /* In VLAN-aware mode tagged-only VIDs need no EVLAN
-+ * rules -- VLAN Filter handles membership.
-+ */
-+ if (p->vlan_filtering && !vfv->untagged)
++ if (!vfv->untagged)
+ continue;
+
+ if (idx + n_vid > blk->block_size)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
++ bool old_vlan_filtering = p->vlan_filtering;
++ bool old_in_use = p->ingress_evlan.in_use;
+ bool changed = (p->vlan_filtering != vlan_filtering);
+ int ret;
+
+ p->vlan_filtering = vlan_filtering;
+
-+ /* Reprogram Extended VLAN rules if filtering mode changed */
+ if (changed) {
+ /* When leaving VLAN-aware mode, release the ingress HW
+ * block. The firmware passes frames through unchanged
+
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
-+ return ret;
++ goto err_restore;
+
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
-+ return ret;
++ goto err_restore;
+ }
+
-+ /* Push VLAN-based MAC learning flags and (possibly newly
-+ * allocated) ingress block to hardware.
-+ */
+ return mxl862xx_set_bridge_port(ds, port);
++
++ /* No HW rollback -- restoring SW state is sufficient for a correct retry. */
++err_restore:
++ p->vlan_filtering = old_vlan_filtering;
++ p->ingress_evlan.in_use = old_in_use;
++ return ret;
+}
+
+static int mxl862xx_port_vlan_add(struct dsa_switch *ds, int port,
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
-+ goto err_pvid;
++ goto err_rollback;
+ }
+
+ /* Reprogram egress tag-stripping rules (walks VF VID list) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
-+ goto err_pvid;
++ goto err_rollback;
+
+ /* Apply VLAN block IDs and MAC learning flags to bridge port */
+ ret = mxl862xx_set_bridge_port(ds, port);
+ if (ret)
-+ goto err_pvid;
++ goto err_rollback;
+
+ return 0;
+
++err_rollback:
++ /* Best-effort: undo VF add and restore consistent hardware state.
++ * A retry of port_vlan_add will converge since vf_add_vid is
++ * idempotent.
++ */
++ p->pvid = old_pvid;
++ mxl862xx_vf_del_vid(priv, &p->vf, vid);
++ mxl862xx_evlan_program_ingress(priv, port);
++ mxl862xx_evlan_program_egress(priv, port);
++ mxl862xx_set_bridge_port(ds, port);
++ return ret;
+err_pvid:
+ p->pvid = old_pvid;
+ return ret;
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
-+ u16 vid = vlan->vid;
++ struct mxl862xx_vf_vid *ve;
+ bool pvid_changed = false;
++ u16 vid = vlan->vid;
++ bool old_untagged;
++ u16 old_pvid;
+ int ret;
+
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
++ ve = mxl862xx_vf_find_vid(&p->vf, vid);
++ if (!ve)
++ return 0;
++ old_untagged = ve->untagged;
++ old_pvid = p->pvid;
++
+ /* Clear PVID if we're deleting it */
+ if (p->pvid == vid) {
+ p->pvid = 0;
+ */
+ ret = mxl862xx_vf_del_vid(priv, &p->vf, vid);
+ if (ret)
-+ return ret;
++ goto err_pvid;
+
+ /* Reprogram egress tag-stripping rules (VID is now gone) */
+ ret = mxl862xx_evlan_program_egress(priv, port);
+ if (ret)
-+ return ret;
++ goto err_rollback;
+
+ /* If PVID changed, reprogram ingress finals */
+ if (pvid_changed) {
+ ret = mxl862xx_evlan_program_ingress(priv, port);
+ if (ret)
-+ return ret;
++ goto err_rollback;
+ }
+
-+ return mxl862xx_set_bridge_port(ds, port);
++ ret = mxl862xx_set_bridge_port(ds, port);
++ if (ret)
++ goto err_rollback;
++
++ return 0;
++
++err_rollback:
++ /* Best-effort: re-add the VID and restore consistent hardware
++ * state. A retry of port_vlan_del will converge.
++ */
++ p->pvid = old_pvid;
++ mxl862xx_vf_add_vid(priv, &p->vf, vid, old_untagged);
++ mxl862xx_evlan_program_egress(priv, port);
++ mxl862xx_evlan_program_ingress(priv, port);
++ mxl862xx_set_bridge_port(ds, port);
++ return ret;
++err_pvid:
++ p->pvid = old_pvid;
++ return ret;
+}
+
static int mxl862xx_setup_cpu_bridge(struct dsa_switch *ds, int port)
{
struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
- struct dsa_port *dp;
- priv->ports[port].fid = MXL862XX_DEFAULT_BRIDGE;
- priv->ports[port].learning = true;
+ * assignment need to be configured.
+ */
- /* include all assigned user ports in the CPU portmap */
-- bitmap_zero(priv->ports[port].portmap, MXL862XX_MAX_BRIDGE_PORTS);
-+ bitmap_zero(p->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;
-
-- __set_bit(dp->index, priv->ports[port].portmap);
-+ __set_bit(dp->index, p->portmap);
- }
-
return mxl862xx_set_bridge_port(ds, port);
}
+@@ -510,6 +1260,8 @@ static int mxl862xx_port_bridge_join(str
+ static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
+ const struct dsa_bridge bridge)
+ {
++ struct mxl862xx_priv *priv = ds->priv;
++ struct mxl862xx_port *p = &priv->ports[port];
+ int err;
-+
- static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
- const struct dsa_bridge bridge,
- bool *tx_fwd_offload,
-@@ -553,6 +1417,22 @@ static void mxl862xx_port_bridge_leave(s
- bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
- __set_bit(dp->cpu_dp->index, p->portmap);
- p->flood_block = 0;
-+
-+ /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
-+ * them. The firmware tracks a usage count per block and rejects
-+ * FREE while the count is non-zero.
-+ *
-+ * For EVLAN: setting in_use=false makes set_bridge_port send
-+ * enable=false, which decrements the firmware refcount.
-+ *
-+ * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
-+ * cleared it) and sends vlan_filter_enable=0, which decrements
-+ * the firmware VF refcount.
-+ */
+ err = mxl862xx_sync_bridge_members(ds, &bridge);
+@@ -521,6 +1273,10 @@ static void mxl862xx_port_bridge_leave(s
+ /* Revert leaving port, omitted by the sync above, to its
+ * single-port bridge
+ */
+ p->pvid = 0;
+ p->ingress_evlan.in_use = false;
+ p->egress_evlan.in_use = false;
err = mxl862xx_set_bridge_port(ds, port);
if (err)
dev_err(ds->dev,
-@@ -602,6 +1482,28 @@ static int mxl862xx_port_setup(struct ds
+@@ -585,6 +1341,22 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
-+ /* Initialize and pre-allocate per-port EVLAN and VF blocks for
-+ * user ports. CPU ports do not use EVLAN or VF -- frames pass
-+ * through without processing. Pre-allocation avoids firmware
-+ * EVLAN table fragmentation and simplifies control flow.
-+ */
-+ mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
-+ priv->evlan_ingress_size);
++ priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
+ if (ret)
+ return ret;
+
-+ mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
-+ priv->evlan_egress_size);
++ priv->ports[port].egress_evlan.block_size = priv->evlan_egress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
+ if (ret)
+ return ret;
+
-+ mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
++ priv->ports[port].vf.block_size = priv->vf_block_size;
++ INIT_LIST_HEAD(&priv->ports[port].vf.vids);
+ ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
+ if (ret)
+ return ret;
priv->ports[port].setup_done = true;
return 0;
-@@ -1012,6 +1914,9 @@ static const struct dsa_switch_ops mxl86
+@@ -983,6 +1755,9 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
-@@ -86,6 +88,66 @@ static inline bool mxl862xx_fw_portmap_i
+@@ -55,6 +57,66 @@ static inline bool mxl862xx_fw_portmap_i
}
/**
* 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
-@@ -101,6 +163,11 @@ static inline bool mxl862xx_fw_portmap_i
+@@ -68,6 +130,11 @@ static inline bool mxl862xx_fw_portmap_i
* @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
-@@ -119,6 +186,12 @@ struct mxl862xx_port {
+@@ -85,6 +152,11 @@ struct mxl862xx_port {
unsigned long flood_block;
bool learning;
bool setup_done;
-+ /* VLAN state */
+ u16 pvid;
+ bool vlan_filtering;
+ struct mxl862xx_vf_block vf;
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
-@@ -126,17 +199,23 @@ struct mxl862xx_port {
+@@ -92,17 +164,23 @@ struct mxl862xx_port {
/**
* struct mxl862xx_priv - driver private data for an MxL862xx switch
*/
struct mxl862xx_priv {
struct dsa_switch *ds;
-@@ -146,6 +225,9 @@ struct mxl862xx_priv {
+@@ -112,6 +190,9 @@ struct mxl862xx_priv {
u16 drop_meter;
struct mxl862xx_port ports[MXL862XX_MAX_PORTS];
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
-From 0067d79d10becfc5779fb50d5c0ac152cc5dc303 Mon Sep 17 00:00:00 2001
+From e6295d124644b14a12b55edf5d3e89cf86a4a2ce Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Sun, 22 Mar 2026 00:57:33 +0000
-Subject: [PATCH 06/26] net: dsa: mxl862xx: add ethtool statistics support
+Date: Sun, 12 Apr 2026 01:01:57 +0100
+Subject: [PATCH 1/2] net: dsa: mxl862xx: add ethtool statistics support
The MxL862xx firmware exposes per-port RMON counters through the
RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
IEEE 802.3 statistics interface.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
- drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 +++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 3 +
- drivers/net/dsa/mxl862xx/mxl862xx.c | 168 ++++++++++++++++++++++++
- 3 files changed, 313 insertions(+)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 173 ++++++++++++++++++++++++
+ 3 files changed, 318 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+ * @tx_good_bytes: Transmitted good byte count (64-bit)
+ */
+struct mxl862xx_rmon_port_cnt {
-+ enum mxl862xx_port_type port_type;
++ __le32 port_type; /* enum mxl862xx_port_type */
+ __le16 port_id;
+ __le16 sub_if_id_group;
+ u8 pce_bypass;
#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -30,6 +30,64 @@
+@@ -30,6 +30,38 @@
#define MXL862XX_API_READ_QUIET(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), true, true)
+ .offset = offsetof(struct mxl862xx_rmon_port_cnt, _element) \
+}
+
++/* Hardware-specific counters not covered by any standardized stats callback. */
+static const struct mxl862xx_mib_desc mxl862xx_mib[] = {
-+ MIB_DESC(1, "TxGoodPkts", tx_good_pkts),
-+ MIB_DESC(1, "TxUnicastPkts", tx_unicast_pkts),
-+ MIB_DESC(1, "TxBroadcastPkts", tx_broadcast_pkts),
-+ MIB_DESC(1, "TxMulticastPkts", tx_multicast_pkts),
-+ MIB_DESC(1, "Tx64BytePkts", tx64byte_pkts),
-+ MIB_DESC(1, "Tx127BytePkts", tx127byte_pkts),
-+ MIB_DESC(1, "Tx255BytePkts", tx255byte_pkts),
-+ MIB_DESC(1, "Tx511BytePkts", tx511byte_pkts),
-+ MIB_DESC(1, "Tx1023BytePkts", tx1023byte_pkts),
-+ MIB_DESC(1, "TxMaxBytePkts", tx_max_byte_pkts),
-+ MIB_DESC(1, "TxDroppedPkts", tx_dropped_pkts),
+ MIB_DESC(1, "TxAcmDroppedPkts", tx_acm_dropped_pkts),
-+ MIB_DESC(2, "TxGoodBytes", tx_good_bytes),
-+ MIB_DESC(1, "TxSingleCollCount", tx_single_coll_count),
-+ MIB_DESC(1, "TxMultCollCount", tx_mult_coll_count),
-+ MIB_DESC(1, "TxLateCollCount", tx_late_coll_count),
-+ MIB_DESC(1, "TxExcessCollCount", tx_excess_coll_count),
-+ MIB_DESC(1, "TxCollCount", tx_coll_count),
-+ MIB_DESC(1, "TxPauseCount", tx_pause_count),
-+ MIB_DESC(1, "RxGoodPkts", rx_good_pkts),
-+ MIB_DESC(1, "RxUnicastPkts", rx_unicast_pkts),
-+ MIB_DESC(1, "RxBroadcastPkts", rx_broadcast_pkts),
-+ MIB_DESC(1, "RxMulticastPkts", rx_multicast_pkts),
-+ MIB_DESC(1, "RxFCSErrorPkts", rx_fcserror_pkts),
-+ MIB_DESC(1, "RxUnderSizeGoodPkts", rx_under_size_good_pkts),
-+ MIB_DESC(1, "RxOversizeGoodPkts", rx_oversize_good_pkts),
-+ MIB_DESC(1, "RxUnderSizeErrorPkts", rx_under_size_error_pkts),
-+ MIB_DESC(1, "RxOversizeErrorPkts", rx_oversize_error_pkts),
+ MIB_DESC(1, "RxFilteredPkts", rx_filtered_pkts),
-+ MIB_DESC(1, "Rx64BytePkts", rx64byte_pkts),
-+ MIB_DESC(1, "Rx127BytePkts", rx127byte_pkts),
-+ MIB_DESC(1, "Rx255BytePkts", rx255byte_pkts),
-+ MIB_DESC(1, "Rx511BytePkts", rx511byte_pkts),
-+ MIB_DESC(1, "Rx1023BytePkts", rx1023byte_pkts),
-+ MIB_DESC(1, "RxMaxBytePkts", rx_max_byte_pkts),
-+ MIB_DESC(1, "RxDroppedPkts", rx_dropped_pkts),
+ MIB_DESC(1, "RxExtendedVlanDiscardPkts", rx_extended_vlan_discard_pkts),
+ MIB_DESC(1, "MtuExceedDiscardPkts", mtu_exceed_discard_pkts),
-+ MIB_DESC(2, "RxGoodBytes", rx_good_bytes),
+ MIB_DESC(2, "RxBadBytes", rx_bad_bytes),
-+ MIB_DESC(1, "RxGoodPausePkts", rx_good_pause_pkts),
-+ MIB_DESC(1, "RxAlignErrorPkts", rx_align_error_pkts),
++};
++
++static const struct ethtool_rmon_hist_range mxl862xx_rmon_ranges[] = {
++ { 0, 64 },
++ { 65, 127 },
++ { 128, 255 },
++ { 256, 511 },
++ { 512, 1023 },
++ { 1024, 10240 },
++ {}
+};
+
#define MXL862XX_SDMA_PCTRLP(p) (0xbc0 + ((p) * 0x6))
#define MXL862XX_SDMA_PCTRL_EN BIT(0)
-@@ -1893,6 +1951,110 @@ static int mxl862xx_port_bridge_flags(st
+@@ -1734,6 +1766,140 @@ static int mxl862xx_port_bridge_flags(st
return 0;
}
+static void mxl862xx_get_strings(struct dsa_switch *ds, int port,
-+ u32 stringset, u8 *data)
++ u32 stringset, u8 *data)
+{
+ int i;
+
+ struct mxl862xx_rmon_port_cnt *cnt)
+{
+ memset(cnt, 0, sizeof(*cnt));
-+ cnt->port_type = MXL862XX_CTP_PORT;
++ cnt->port_type = cpu_to_le32(MXL862XX_CTP_PORT);
+ cnt->port_id = cpu_to_le16(port);
+
+ return MXL862XX_API_READ(ds->priv, MXL862XX_RMON_PORT_GET, *cnt);
+ pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
+}
+
++static void mxl862xx_get_rmon_stats(struct dsa_switch *ds, int port,
++ struct ethtool_rmon_stats *rmon_stats,
++ const struct ethtool_rmon_hist_range **ranges)
++{
++ struct mxl862xx_rmon_port_cnt cnt;
++
++ if (mxl862xx_read_rmon(ds, port, &cnt))
++ return;
++
++ rmon_stats->undersize_pkts = le32_to_cpu(cnt.rx_under_size_good_pkts);
++ rmon_stats->oversize_pkts = le32_to_cpu(cnt.rx_oversize_good_pkts);
++ rmon_stats->fragments = le32_to_cpu(cnt.rx_under_size_error_pkts);
++ rmon_stats->jabbers = le32_to_cpu(cnt.rx_oversize_error_pkts);
++
++ rmon_stats->hist[0] = le32_to_cpu(cnt.rx64byte_pkts);
++ rmon_stats->hist[1] = le32_to_cpu(cnt.rx127byte_pkts);
++ rmon_stats->hist[2] = le32_to_cpu(cnt.rx255byte_pkts);
++ rmon_stats->hist[3] = le32_to_cpu(cnt.rx511byte_pkts);
++ rmon_stats->hist[4] = le32_to_cpu(cnt.rx1023byte_pkts);
++ rmon_stats->hist[5] = le32_to_cpu(cnt.rx_max_byte_pkts);
++
++ rmon_stats->hist_tx[0] = le32_to_cpu(cnt.tx64byte_pkts);
++ rmon_stats->hist_tx[1] = le32_to_cpu(cnt.tx127byte_pkts);
++ rmon_stats->hist_tx[2] = le32_to_cpu(cnt.tx255byte_pkts);
++ rmon_stats->hist_tx[3] = le32_to_cpu(cnt.tx511byte_pkts);
++ rmon_stats->hist_tx[4] = le32_to_cpu(cnt.tx1023byte_pkts);
++ rmon_stats->hist_tx[5] = le32_to_cpu(cnt.tx_max_byte_pkts);
++
++ *ranges = mxl862xx_rmon_ranges;
++}
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
-@@ -1917,6 +2079,12 @@ static const struct dsa_switch_ops mxl86
+@@ -1758,6 +1924,13 @@ static const struct dsa_switch_ops mxl86
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
+ .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+ .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+ .get_pause_stats = mxl862xx_get_pause_stats,
++ .get_rmon_stats = mxl862xx_get_rmon_stats,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
-From bab5a69e3872a693069e430a1fa0d2825ea83b4f Mon Sep 17 00:00:00 2001
+From a21d33a5265f0b31d935a8b9b2b6faefb5185911 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Tue, 24 Mar 2026 04:14:38 +0000
-Subject: [PATCH 07/26] net: dsa: mxl862xx: implement .get_stats64
+Date: Sun, 12 Apr 2026 01:02:05 +0100
+Subject: [PATCH 2/2] net: dsa: mxl862xx: implement .get_stats64
Poll free-running firmware RMON counters every 2 seconds and accumulate
deltas into 64-bit per-port statistics. 32-bit packet counters wrap
-in ~880s at 2.5 Gbps line rate; the 2s polling interval provides a
-comfortable margin. The .get_stats64 callback forces a fresh poll so
-that counters are always up to date when queried.
+in ~220s at 10 Gbps line rate with minimum-size frames; the 2s polling
+interval provides a comfortable margin. The .get_stats64 callback
+forces a fresh poll so that counters are always up to date when queried.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+Link: https://patch.msgid.link/fa38548ba05866879e8912721edc91947ce4ff12.1775951347.git.daniel@makrotopia.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 +-
- drivers/net/dsa/mxl862xx/mxl862xx.c | 174 +++++++++++++++++++++++
- drivers/net/dsa/mxl862xx/mxl862xx.h | 63 +++++++-
- 3 files changed, 238 insertions(+), 7 deletions(-)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 175 +++++++++++++++++++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 94 +++++++++++-
+ 3 files changed, 270 insertions(+), 7 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
struct mxl862xx_mib_desc {
unsigned int size;
unsigned int offset;
-@@ -739,6 +745,9 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -677,6 +683,9 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
return mxl862xx_setup_mdio(ds);
}
-@@ -2055,6 +2064,158 @@ static void mxl862xx_get_pause_stats(str
- pause_stats->rx_pause_frames = le32_to_cpu(cnt.rx_good_pause_pkts);
- }
+@@ -1900,6 +1909,159 @@ static void mxl862xx_get_rmon_stats(stru
+ *ranges = mxl862xx_rmon_ranges;
+ }
++
+/* Compute the delta between two 32-bit free-running counter snapshots,
+ * handling a single wrap-around correctly via unsigned subtraction.
+ */
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.setup = mxl862xx_setup,
-@@ -2085,6 +2246,7 @@ static const struct dsa_switch_ops mxl86
- .get_eth_mac_stats = mxl862xx_get_eth_mac_stats,
+@@ -1931,6 +2093,7 @@ static const struct dsa_switch_ops mxl86
.get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
.get_pause_stats = mxl862xx_get_pause_stats,
+ .get_rmon_stats = mxl862xx_get_rmon_stats,
+ .get_stats64 = mxl862xx_get_stats64,
};
static void mxl862xx_phylink_mac_config(struct phylink_config *config,
-@@ -2146,16 +2308,22 @@ static int mxl862xx_probe(struct mdio_de
+@@ -1992,16 +2155,22 @@ static int mxl862xx_probe(struct mdio_de
priv->ports[i].priv = priv;
INIT_WORK(&priv->ports[i].host_flood_work,
mxl862xx_host_flood_work_fn);
return err;
}
-@@ -2170,6 +2338,9 @@ static void mxl862xx_remove(struct mdio_
+@@ -2016,6 +2185,9 @@ static void mxl862xx_remove(struct mdio_
priv = ds->priv;
dsa_unregister_switch(ds);
mxl862xx_host_shutdown(priv);
-@@ -2196,6 +2367,9 @@ static void mxl862xx_shutdown(struct mdi
+@@ -2042,6 +2214,9 @@ static void mxl862xx_shutdown(struct mdi
dsa_switch_shutdown(ds);
for (i = 0; i < MXL862XX_MAX_PORTS; i++)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -148,6 +148,47 @@ struct mxl862xx_evlan_block {
+@@ -117,6 +117,79 @@ struct mxl862xx_evlan_block {
};
/**
+ * struct mxl862xx_port_stats - 64-bit accumulated hardware port statistics
++ * @rx_packets: total received packets
++ * @tx_packets: total transmitted packets
++ * @rx_bytes: total received bytes
++ * @tx_bytes: total transmitted bytes
++ * @rx_errors: total receive errors
++ * @tx_errors: total transmit errors
++ * @rx_dropped: total received packets dropped
++ * @tx_dropped: total transmitted packets dropped
++ * @multicast: total received multicast packets
++ * @collisions: total transmit collisions
++ * @rx_length_errors: received length errors (undersize + oversize)
++ * @rx_crc_errors: received FCS errors
++ * @rx_frame_errors: received alignment errors
++ * @prev_rx_good_pkts: previous snapshot of rx good packet counter
++ * @prev_tx_good_pkts: previous snapshot of tx good packet counter
++ * @prev_rx_good_bytes: previous snapshot of rx good byte counter
++ * @prev_tx_good_bytes: previous snapshot of tx good byte counter
++ * @prev_rx_fcserror_pkts: previous snapshot of rx FCS error counter
++ * @prev_rx_under_size_error_pkts: previous snapshot of rx undersize
++ * error counter
++ * @prev_rx_oversize_error_pkts: previous snapshot of rx oversize
++ * error counter
++ * @prev_rx_align_error_pkts: previous snapshot of rx alignment
++ * error counter
++ * @prev_tx_dropped_pkts: previous snapshot of tx dropped counter
++ * @prev_rx_dropped_pkts: previous snapshot of rx dropped counter
++ * @prev_rx_evlan_discard_pkts: previous snapshot of extended VLAN
++ * discard counter
++ * @prev_mtu_exceed_discard_pkts: previous snapshot of MTU exceed
++ * discard counter
++ * @prev_tx_acm_dropped_pkts: previous snapshot of tx ACM dropped
++ * counter
++ * @prev_rx_multicast_pkts: previous snapshot of rx multicast counter
++ * @prev_tx_coll_count: previous snapshot of tx collision counter
+ *
+ * The firmware RMON counters are 32-bit free-running (64-bit for byte
+ * counters). This structure holds 64-bit accumulators alongside the
+ * handling 32-bit wrap correctly via unsigned subtraction.
+ */
+struct mxl862xx_port_stats {
-+ /* 64-bit accumulators */
+ u64 rx_packets;
+ u64 tx_packets;
+ u64 rx_bytes;
+ u64 rx_length_errors;
+ u64 rx_crc_errors;
+ u64 rx_frame_errors;
-+ /* Previous raw RMON values for delta computation */
+ u32 prev_rx_good_pkts;
+ u32 prev_tx_good_pkts;
+ u64 prev_rx_good_bytes;
* 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
-@@ -178,6 +219,10 @@ struct mxl862xx_evlan_block {
+@@ -145,6 +218,10 @@ struct mxl862xx_evlan_block {
* 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;
-@@ -195,16 +240,25 @@ struct mxl862xx_port {
+@@ -160,16 +237,24 @@ struct mxl862xx_port {
bool host_flood_uc;
bool host_flood_mc;
struct work_struct host_flood_work;
-+ /* Hardware stats accumulation */
+ struct mxl862xx_port_stats stats;
-+ spinlock_t stats_lock;
++ spinlock_t stats_lock; /* protects stats accumulators */
};
+/* Bit indices for struct mxl862xx_priv::flags */
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
-@@ -216,18 +270,21 @@ struct mxl862xx_port {
+@@ -181,18 +266,21 @@ struct mxl862xx_port {
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
* @vf_block_size: per-port VLAN Filter block size
-From da12469e73282da814163125153f381823e33f20 Mon Sep 17 00:00:00 2001
+From 60a23e663e0c607ae4ed871aaa24d257051ad557 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 17:56:35 +0000
-Subject: [PATCH 08/26] net: dsa: mxl862xx: store firmware version for feature
+Subject: [PATCH 01/19] net: dsa: mxl862xx: store firmware version for feature
gating
Query the firmware version at init (already done in wait_ready),
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -286,6 +286,9 @@ static int mxl862xx_wait_ready(struct ds
+@@ -257,6 +257,9 @@ static int mxl862xx_wait_ready(struct ds
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
-@@ -245,6 +246,38 @@ struct mxl862xx_port {
- spinlock_t stats_lock;
+@@ -241,6 +242,38 @@ struct mxl862xx_port {
+ spinlock_t stats_lock; /* protects stats accumulators */
};
+/**
/* Bit indices for struct mxl862xx_priv::flags */
#define MXL862XX_FLAG_CRC_ERR 0
#define MXL862XX_FLAG_WORK_STOPPED 1
-@@ -262,6 +295,8 @@ struct mxl862xx_port {
+@@ -258,6 +291,8 @@ struct mxl862xx_port {
* @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
-@@ -279,6 +314,7 @@ struct mxl862xx_priv {
+@@ -275,6 +310,7 @@ struct mxl862xx_priv {
struct work_struct crc_err_work;
unsigned long flags;
u16 drop_meter;
-From f7606470d398e4091e1bc405bf2125dc5fc99919 Mon Sep 17 00:00:00 2001
+From cefa0447dc95a4ddd5093f7b8cf35e654870283f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 21:39:30 +0000
-Subject: [PATCH 09/26] net: dsa: mxl862xx: move phylink stubs to
+Subject: [PATCH 02/19] net: dsa: mxl862xx: move phylink stubs to
mxl862xx-phylink.c
Move the phylink MAC operations and get_caps callback from mxl862xx.c
#define MXL862XX_API_WRITE(dev, cmd, data) \
mxl862xx_api_wrap(dev, cmd, &(data), sizeof((data)), false, false)
-@@ -1597,16 +1598,6 @@ static void mxl862xx_port_teardown(struc
+@@ -1424,16 +1425,6 @@ static void mxl862xx_port_teardown(struc
priv->ports[port].setup_done = false;
}
static int mxl862xx_get_fid(struct dsa_switch *ds, struct dsa_db db)
{
struct mxl862xx_priv *priv = ds->priv;
-@@ -2252,33 +2243,6 @@ static const struct dsa_switch_ops mxl86
+@@ -2099,33 +2090,6 @@ static const struct dsa_switch_ops mxl86
.get_stats64 = mxl862xx_get_stats64,
};
-From e583eeeb907f0abeef2082162293a5d63b9fd6fa Mon Sep 17 00:00:00 2001
+From 3c1d77006daca1df20d612850535bc6050e266ee Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 26 Mar 2026 01:50:00 +0000
-Subject: [PATCH 10/26] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
+Subject: [PATCH 03/19] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
Move the MXL862XX_API_WRITE, MXL862XX_API_READ and
MXL862XX_API_READ_QUIET convenience macros from mxl862xx.c to
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -684,7 +684,7 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -622,7 +622,7 @@ static int mxl862xx_setup(struct dsa_swi
int n_user_ports = 0, max_vlans;
int ingress_finals, vid_rules;
struct dsa_port *dp;
ret = mxl862xx_reset(priv);
if (ret)
-@@ -694,6 +694,9 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -632,6 +632,9 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
* Ingress: only final catchall rules (PVID insertion, 802.1Q
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -247,6 +247,22 @@ struct mxl862xx_port {
+@@ -243,6 +243,22 @@ struct mxl862xx_port {
};
/**
* union mxl862xx_fw_version - firmware version for comparison and display
* @major: firmware major version
* @minor: firmware minor version
-@@ -297,6 +313,8 @@ union mxl862xx_fw_version {
+@@ -293,6 +309,8 @@ union mxl862xx_fw_version {
* flooding)
* @fw_version: cached firmware version, populated at probe and
* compared with MXL862XX_FW_VER_MIN()
* @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
-@@ -315,6 +333,7 @@ struct mxl862xx_priv {
+@@ -311,6 +329,7 @@ struct mxl862xx_priv {
unsigned long flags;
u16 drop_meter;
union mxl862xx_fw_version fw_version;
-From 24d752291784e30d7329bed15744bbbc6a3e2485 Mon Sep 17 00:00:00 2001
+From 3659914c43a587a1ca6418867834831aa518ac35 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:14:33 +0000
-Subject: [PATCH 12/26] net: dsa: mxl862xx: add SerDes ethtool statistics
+Subject: [PATCH 05/19] net: dsa: mxl862xx: add SerDes ethtool statistics
Expose SerDes equalization and signal detect parameters as ethtool
statistics on ports 9-16 (XPCS-backed ports). Uses the XPCS EQ_GET
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -1960,6 +1960,8 @@ static void mxl862xx_get_strings(struct
+@@ -1775,6 +1775,8 @@ static void mxl862xx_get_strings(struct
for (i = 0; i < ARRAY_SIZE(mxl862xx_mib); i++)
ethtool_puts(&data, mxl862xx_mib[i].name);
}
static int mxl862xx_get_sset_count(struct dsa_switch *ds, int port, int sset)
-@@ -1967,7 +1969,7 @@ static int mxl862xx_get_sset_count(struc
+@@ -1782,7 +1784,7 @@ static int mxl862xx_get_sset_count(struc
if (sset != ETH_SS_STATS)
return 0;
}
static int mxl862xx_read_rmon(struct dsa_switch *ds, int port,
-@@ -2003,6 +2005,8 @@ static void mxl862xx_get_ethtool_stats(s
+@@ -1818,6 +1820,8 @@ static void mxl862xx_get_ethtool_stats(s
else
*data++ = le64_to_cpu(*(__le64 *)field);
}
-From ee227a5e4c74f599cc1b34578b32214d5873ad2f Mon Sep 17 00:00:00 2001
+From ce66c0be462c8500dfc483395e68be4326ebf296 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:15:32 +0000
-Subject: [PATCH 13/26] net: dsa: mxl862xx: add SerDes self-test via PRBS and
+Subject: [PATCH 06/19] net: dsa: mxl862xx: add SerDes self-test via PRBS and
BERT
Implement the dsa_switch_ops.self_test callback for SerDes ports
#endif /* __MXL862XX_PHYLINK_H */
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -2241,6 +2241,7 @@ static const struct dsa_switch_ops mxl86
- .get_eth_ctrl_stats = mxl862xx_get_eth_ctrl_stats,
+@@ -2088,6 +2088,7 @@ static const struct dsa_switch_ops mxl86
.get_pause_stats = mxl862xx_get_pause_stats,
+ .get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
+ .self_test = mxl862xx_serdes_self_test,
};
-From 43eb3eed250ea4e7e83371fcbf2bfb8d626eade6 Mon Sep 17 00:00:00 2001
+From e6defbd42db6e64c2bb203f82b7d7f8c0691a052 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:13 +0000
-Subject: [PATCH 14/26] net: dsa: mxl862xx: trap link-local frames to the CPU
- port
+Subject: [PATCH 07/19] net: dsa: mxl862xx: trap link-local and multicast
+ snooping frames to CPU
Install per-CTP PCE rules on each user port that trap IEEE 802.1D
-link-local frames (01:80:c2:00:00:0x) to the CPU port via an
-explicit forwarding portmap with cross-state enabled, ensuring the
-frames reach the host even when the bridge port is in BLOCKING or
-LEARNING state.
+link-local frames (01:80:c2:00:00:0x) and IP multicast snooping
+frames (IGMP, MLDv1, MLDv2) to the CPU port.
+
+All trap rules share a common action helper,
+mxl862xx_fill_cpu_trap_action(), which sets PORTMAP_ALTERNATIVE to
+redirect frames to the CPU and enables cross-state forwarding so
+that frames reach the host even when the bridge port is in BLOCKING
+or LEARNING state.
+
+A dedicated bridge FID (cpu_trap_fid) is allocated during setup with
+all flood modes enabled. Each trap rule points the bridge engine at
+this FID via bFidEnable so that IGMP and MLD frames are never
+silently dropped by the ingress port's private flood policy.
+
+Three multicast snooping rules are installed per port:
+ offset 2 -- IPv4 IGMP (IP protocol 2, all versions)
+ offset 3 -- ICMPv6 types 130-132 (MLDv1 query, report, done)
+ offset 4 -- ICMPv6 type 143 (MLDv2 Listener Report)
Add the PCE rule firmware API structures, command definitions, and
the rule block allocation interface.
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 684 ++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 5 +
- drivers/net/dsa/mxl862xx/mxl862xx.c | 69 +++
- 3 files changed, 758 insertions(+)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 186 +++++++
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 8 +
+ 4 files changed, 883 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -280,9 +280,11 @@ static int mxl862xx_wait_ready(struct ds
+@@ -10,6 +10,7 @@
+ #include <linux/bitfield.h>
+ #include <linux/delay.h>
+ #include <linux/etherdevice.h>
++#include <linux/icmpv6.h>
+ #include <linux/if_bridge.h>
+ #include <linux/module.h>
+ #include <linux/of_device.h>
+@@ -251,9 +252,11 @@ static int mxl862xx_wait_ready(struct ds
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
return 0;
not_ready_yet:
-@@ -410,6 +412,68 @@ static int mxl862xx_setup_drop_meter(str
+@@ -381,6 +384,158 @@ static int mxl862xx_setup_drop_meter(str
return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
}
+
-+/* Per-CTP offset used for the link-local trap rule. Each port's CTP
-+ * flow-table block is pre-allocated by the firmware during init (44
-+ * entries per port on a 10-port SKU, of which offset 0 is reserved
-+ * for flow-control marking). Offset 1 is the first unused slot.
++/* Per-CTP offsets for protocol trap rules. Each port's CTP flow-table
++ * block is pre-allocated by the firmware during init (44 entries per
++ * port on a 10-port SKU, of which offset 0 is reserved for flow-control
++ * marking). Offsets 1-4 are used for link-local and multicast snooping
++ * traps; all others remain free.
+ */
+#define MXL862XX_LINK_LOCAL_CTP_OFFSET 1
++#define MXL862XX_IGMP_CTP_OFFSET 2
++#define MXL862XX_MLDV1_CTP_OFFSET 3
++#define MXL862XX_MLDV2_CTP_OFFSET 4
++
++/* Fill the action fields of a PCE rule that traps ingress frames to
++ * the CPU port. Used by both the link-local trap and the multicast
++ * snooping traps. The caller must already have set the rule header
++ * (logicalportid, subifidgroup, region) and the pattern fields.
++ *
++ * PORTMAP_ALTERNATIVE redirects the frame to the CPU port but does
++ * not by itself bypass downstream flood gates. In SpTag mode the
++ * ingress port's private FID may have forward_unknown_multicast=false,
++ * which silently drops IGMP/MLD before they reach the CPU.
++ * Setting bFidEnable to cpu_trap_fid (a dedicated bridge with all
++ * flood modes enabled) overrides the FID used by the bridge engine,
++ * so the frame is never classified as blocked unknown MC regardless
++ * of the ingress port's standalone flood policy.
++ *
++ * Cross-state is enabled so trapped frames bypass STP port state.
++ */
++static void mxl862xx_fill_cpu_trap_action(struct dsa_switch *ds, int port,
++ struct mxl862xx_pce_rule *rule)
++{
++ struct mxl862xx_priv *priv = ds->priv;
++ int cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
++
++ rule->action.port_map_action =
++ cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
++ mxl862xx_fw_portmap_set_bit(rule->action.forward_port_map, cpu_port);
++
++ rule->action.cross_state_action =
++ cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
++
++ rule->action.fid_enable = 1;
++ rule->action.fid = priv->cpu_trap_fid;
++}
+
+/* Install a PCE rule that traps IEEE 802.1D link-local frames
+ * (01:80:c2:00:00:0x) to the CPU port for a single user port,
+ */
+static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port)
+{
-+ DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
-+ struct dsa_port *dp = dsa_to_port(ds, port);
++ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_pce_rule rule = {};
-+ int cpu_port = dp->cpu_dp->index;
-+ int i;
+
-+ /* Address this port's CTP flow-table block */
+ rule.logicalportid = port;
-+ rule.subifidgroup = 0;
+ rule.region = cpu_to_le32(MXL862XX_PCE_RULE_CTP);
+
-+ /* Pattern: link-local MAC on this specific ingress port */
+ rule.pattern.index = cpu_to_le16(MXL862XX_LINK_LOCAL_CTP_OFFSET);
+ rule.pattern.enable = 1;
+ rule.pattern.mac_dst_enable = 1;
+ memcpy(rule.pattern.mac_dst, eth_reserved_addr_base, ETH_ALEN);
+ rule.pattern.mac_dst_mask = cpu_to_le16(0x0001);
+
-+ /* Action: forward to the CPU port via explicit portmap */
-+ rule.action.port_map_action =
-+ cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
++ mxl862xx_fill_cpu_trap_action(ds, port, &rule);
+
-+ bitmap_zero(portmap, MXL862XX_MAX_BRIDGE_PORTS);
-+ __set_bit(cpu_port, portmap);
-+ for (i = 0; i < ARRAY_SIZE(rule.action.forward_port_map); i++)
-+ rule.action.forward_port_map[i] =
-+ cpu_to_le16(bitmap_read(portmap, i * 16, 16));
++ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
++}
+
-+ /* Bypass STP port state */
-+ rule.action.cross_state_action =
-+ cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
++/* Install PCE rules that trap IGMP and MLD frames to the CPU port for
++ * a single user port. PORTMAP_ALTERNATIVE overrides the bridge
++ * forwarding portmap to the CPU port. bFidEnable points the bridge
++ * engine at cpu_trap_fid (all flood modes enabled) so the frames are
++ * never classified as blocked unknown MC regardless of the ingress
++ * port's standalone flood policy.
++ *
++ * Three rules are installed per port:
++ * offset 2 -- IPv4 IGMP (IP protocol 2, all versions)
++ * offset 3 -- ICMPv6 types 130-132 (MLDv1 query, report, done)
++ * offset 4 -- ICMPv6 type 143 (MLDv2 Listener Report)
++ *
++ * The MLDv1 rule uses range mode on the first two bytes after the IP
++ * header (ICMPv6 type + code): lower bound 0x8200 (type 130, code 0)
++ * to upper bound 0x84ff (type 132, code 255). The MLDv2 rule uses
++ * nibble mask 0x3 to match type 143 with any code byte.
++ */
++static int mxl862xx_setup_snooping_traps(struct dsa_switch *ds, int port)
++{
++ struct mxl862xx_priv *priv = ds->priv;
++ struct mxl862xx_pce_rule rule = {};
++ int ret;
++
++ rule.logicalportid = port;
++ rule.region = cpu_to_le32(MXL862XX_PCE_RULE_CTP);
++ mxl862xx_fill_cpu_trap_action(ds, port, &rule);
++
++ /* IGMP: IPv4 protocol 2, all versions */
++ rule.pattern.index = cpu_to_le16(MXL862XX_IGMP_CTP_OFFSET);
++ rule.pattern.enable = 1;
++ rule.pattern.protocol = IPPROTO_IGMP;
++ rule.pattern.protocol_enable = 1;
+
-+ return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE,
-+ rule);
++ ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
++ if (ret)
++ return ret;
++
++ /* MLDv1: ICMPv6 types 130 (query), 131 (report), 132 (done).
++ * Range mode covers all three types with any code value.
++ */
++ memset(&rule.pattern, 0, sizeof(rule.pattern));
++ rule.pattern.index = cpu_to_le16(MXL862XX_MLDV1_CTP_OFFSET);
++ rule.pattern.enable = 1;
++ rule.pattern.protocol = IPPROTO_ICMPV6;
++ rule.pattern.protocol_enable = 1;
++ rule.pattern.app_data_msb =
++ cpu_to_le16((u16)ICMPV6_MGM_QUERY << 8);
++ rule.pattern.app_mask_range_msb =
++ cpu_to_le16(((u16)ICMPV6_MGM_REDUCTION << 8) | 0xff);
++ rule.pattern.app_data_msb_enable = 1;
++ rule.pattern.app_mask_range_msb_select = 1; /* range mode */
++
++ ret = MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
++ if (ret)
++ return ret;
++
++ /* MLDv2: ICMPv6 type 143 (Listener Report v2), any code byte.
++ * Nibble mask 0x3 masks nibbles 0-1 (lower byte = code field).
++ */
++ memset(&rule.pattern, 0, sizeof(rule.pattern));
++ rule.pattern.index = cpu_to_le16(MXL862XX_MLDV2_CTP_OFFSET);
++ rule.pattern.enable = 1;
++ rule.pattern.protocol = IPPROTO_ICMPV6;
++ rule.pattern.protocol_enable = 1;
++ rule.pattern.app_data_msb = cpu_to_le16((u16)ICMPV6_MLD2_REPORT << 8);
++ rule.pattern.app_mask_range_msb = cpu_to_le16(0x0003);
++ rule.pattern.app_data_msb_enable = 1;
++ /* app_mask_range_msb_select = 0: nibble mask mode (default) */
++
++ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
+}
+
static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
-@@ -1549,6 +1613,11 @@ static int mxl862xx_port_setup(struct ds
+@@ -683,6 +838,28 @@ static int mxl862xx_setup(struct dsa_swi
+ if (ret)
+ return ret;
+
++
++ /* Allocate a dedicated PCE snooping FID with all flood modes enabled.
++ * Per-port PCE trap rules (link-local, IGMP, MLD) set bFidEnable to
++ * this FID so that the bridge engine uses it for its flood-permission
++ * check instead of the ingress port's private FID (which has
++ * mc_flood=false to restrict unknown MC from reaching the CPU in the
++ * normal path). The hardware PCE FID action field is 6 bits wide, so
++ * the allocated ID must be in range 0..63.
++ */
++ ret = mxl862xx_allocate_bridge(priv);
++ if (ret < 0)
++ return ret;
++
++ if (WARN_ON_ONCE(ret > 0x3F))
++ return -ERANGE;
++
++ priv->cpu_trap_fid = ret;
++
++ ret = mxl862xx_bridge_config_fwd(ds, priv->cpu_trap_fid,
++ true, true, true);
++ if (ret)
++ return ret;
+ schedule_delayed_work(&priv->stats_work,
+ MXL862XX_STATS_POLL_INTERVAL);
+
+@@ -1382,6 +1559,15 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
-+ /* install link-local trap for this user port */
++ /* install link-local and multicast snooping traps */
+ ret = mxl862xx_setup_link_local_trap(ds, port);
+ if (ret)
+ return ret;
+
- /* Initialize and pre-allocate per-port EVLAN and VF blocks for
- * user ports. CPU ports do not use EVLAN or VF -- frames pass
- * through without processing. Pre-allocation avoids firmware
++ ret = mxl862xx_setup_snooping_traps(ds, port);
++ if (ret)
++ return ret;
++
+ priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
+ if (ret)
+--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
++++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
+@@ -319,6 +319,13 @@ union mxl862xx_fw_version {
+ * @evlan_ingress_size: per-port ingress Extended VLAN block size
+ * @evlan_egress_size: per-port egress Extended VLAN block size
+ * @vf_block_size: per-port VLAN Filter block size
++ * @cpu_trap_fid: firmware bridge FID allocated for PCE-trapped frames;
++ * configured with uc/mc/bc flood all enabled so that
++ * IGMP, MLD, and link-local frames always reach the CPU
++ * regardless of the ingress port's private FID flood
++ * policy. Set once in setup() and referenced by
++ * fill_cpu_trap_action() via bFidEnable. The PCE FID
++ * action field is 6 bits, so this value must be <= 63.
+ * @stats_work: periodic work item that polls RMON hardware counters
+ * and accumulates them into 64-bit per-port stats
+ */
+@@ -335,6 +342,7 @@ struct mxl862xx_priv {
+ u16 evlan_ingress_size;
+ u16 evlan_egress_size;
+ u16 vf_block_size;
++ u16 cpu_trap_fid;
+ struct delayed_work stats_work;
+ };
+
-From e18f5b235d8df21209c73f4f0bbc00cc3a1973ba Mon Sep 17 00:00:00 2001
+From 264838ee8ee3ad611a84df96d889429f1ded2148 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:21 +0000
-Subject: [PATCH 15/26] net: dsa: mxl862xx: warn about old firmware default PCE
+Subject: [PATCH 08/19] net: dsa: mxl862xx: warn about old firmware default PCE
rules
Firmware versions older than 1.0.80 install global PCE rules at
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -809,6 +809,10 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -860,6 +860,10 @@ static int mxl862xx_setup(struct dsa_swi
+ true, true, true);
if (ret)
return ret;
-
++
+ if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
+ dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
+ "that interfere with DSA operation, please update\n");
-+
schedule_delayed_work(&priv->stats_work,
MXL862XX_STATS_POLL_INTERVAL);
-From 04929904d3a7d824593587e52e3ed21d6f0f109a Mon Sep 17 00:00:00 2001
+From d7ed9b681298d206d81e9cf3c93558d6e912ae88 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Sun, 22 Mar 2026 00:58:04 +0000
-Subject: [PATCH 16/26] net: dsa: add 802.1Q VLAN-based tag driver for MxL862xx
+Subject: [PATCH 09/20] net: dsa: add 802.1Q VLAN-based tag driver for MxL862xx
The MxL862xx native 8-byte special tag (SpTag) requires firmware
support on the switch CPU and is not compatible with all SoC Ethernet
drivers/net/dsa/mxl862xx/Kconfig | 1 +
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 221 +++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 2 +
- drivers/net/dsa/mxl862xx/mxl862xx.c | 1626 ++++++++++++++++++++---
- drivers/net/dsa/mxl862xx/mxl862xx.h | 13 +
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 1678 ++++++++++++++++++++---
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 12 +
include/net/dsa.h | 2 +
net/dsa/Kconfig | 7 +
net/dsa/Makefile | 1 +
- net/dsa/tag_mxl862xx_8021q.c | 59 +
- 9 files changed, 1736 insertions(+), 196 deletions(-)
+ net/dsa/tag_mxl862xx_8021q.c | 65 +
+ 9 files changed, 1803 insertions(+), 186 deletions(-)
create mode 100644 net/dsa/tag_mxl862xx_8021q.c
--- a/drivers/net/dsa/mxl862xx/Kconfig
#define MXL862XX_MAC_TABLEENTRYQUERY (MXL862XX_SWMAC_MAGIC + 0x4)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -16,6 +16,7 @@
+@@ -17,6 +17,7 @@
#include <linux/of_mdio.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <net/dsa.h>
#include "mxl862xx.h"
-@@ -115,6 +116,9 @@ enum mxl862xx_evlan_action {
+@@ -88,6 +89,9 @@ enum mxl862xx_evlan_action {
+ EVLAN_STRIP_IF_UNTAGGED, /* remove 1 tag if entry's untagged flag set */
EVLAN_PVID_OR_DISCARD, /* insert PVID tag or discard if no PVID */
- EVLAN_PVID_OR_PASS, /* insert PVID tag or pass-through */
EVLAN_STRIP1_AND_PVID_OR_DISCARD,/* strip 1 tag + insert PVID, or discard */
+ EVLAN_INSERT_OUTER, /* insert outer tag with mgmt_vid */
+ EVLAN_STRIP1, /* strip 1 tag unconditionally */
};
struct mxl862xx_evlan_rule_desc {
-@@ -124,6 +128,7 @@ struct mxl862xx_evlan_rule_desc {
+@@ -97,6 +101,7 @@ struct mxl862xx_evlan_rule_desc {
u8 inner_tpid; /* enum mxl862xx_extended_vlan_filter_tpid */
bool match_vid; /* true: match on VID from the vid parameter */
u8 action; /* enum mxl862xx_evlan_action */
};
/* Shorthand constants for readability */
-@@ -190,11 +195,69 @@ static const struct mxl862xx_evlan_rule_
- { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, true, EVLAN_STRIP_IF_UNTAGGED },
+@@ -162,11 +167,69 @@ static const struct mxl862xx_evlan_rule_
+ { FT_NO_FILTER, FT_NO_TAG, TP_NONE, TP_NONE, false, EVLAN_STRIP_IF_UNTAGGED },
};
+/*
}
/* PHY access via firmware relay */
-@@ -420,6 +483,78 @@ static int mxl862xx_setup_drop_meter(str
- */
- #define MXL862XX_LINK_LOCAL_CTP_OFFSET 1
+@@ -396,6 +459,78 @@ static int mxl862xx_setup_drop_meter(str
+ #define MXL862XX_MLDV1_CTP_OFFSET 3
+ #define MXL862XX_MLDV2_CTP_OFFSET 4
+/**
+ * mxl862xx_cpu_bridge_port_id - Get the bridge port ID for CPU-side forwarding
+ return MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
+}
+
- /* Install a PCE rule that traps IEEE 802.1D link-local frames
- * (01:80:c2:00:00:0x) to the CPU port for a single user port,
- * preventing the hardware bridge from flooding them to other ports.
-@@ -440,10 +575,14 @@ static int mxl862xx_setup_link_local_tra
+ /* Fill the action fields of a PCE rule that traps ingress frames to
+ * the CPU port. Used by both the link-local trap and the multicast
+ * snooping traps. The caller must already have set the rule header
+@@ -404,24 +539,36 @@ static int mxl862xx_setup_drop_meter(str
+ * PORTMAP_ALTERNATIVE redirects the frame to the CPU port but does
+ * not by itself bypass downstream flood gates. In SpTag mode the
+ * ingress port's private FID may have forward_unknown_multicast=false,
+- * which silently drops IGMP/MLD before they reach the CPU.
++ * which silently drops IGMP/MLD before they reach the CPU. In
++ * tag_8021q mode the VBP egress sub-meters can have the same effect.
+ * Setting bFidEnable to cpu_trap_fid (a dedicated bridge with all
+ * flood modes enabled) overrides the FID used by the bridge engine,
+ * so the frame is never classified as blocked unknown MC regardless
+ * of the ingress port's standalone flood policy.
+ *
+- * Cross-state is enabled so trapped frames bypass STP port state.
++ * In tag_8021q mode the VBP egress EVLAN block is also attached so
++ * that the management VID is inserted before the frame reaches the
++ * CPU. Cross-state is enabled so trapped frames bypass STP port
++ * state.
+ */
+ static void mxl862xx_fill_cpu_trap_action(struct dsa_switch *ds, int port,
+ struct mxl862xx_pce_rule *rule)
{
- DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
- struct dsa_port *dp = dsa_to_port(ds, port);
-+ struct mxl862xx_priv *priv = ds->priv;
- struct mxl862xx_pce_rule rule = {};
- int cpu_port = dp->cpu_dp->index;
-+ struct mxl862xx_port *p;
- int i;
+ struct mxl862xx_priv *priv = ds->priv;
++ struct mxl862xx_port *p = &priv->ports[port];
+ int cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-+ p = &priv->ports[port];
-+
- /* Address this port's CTP flow-table block */
- rule.logicalportid = port;
- rule.subifidgroup = 0;
-@@ -466,11 +605,18 @@ static int mxl862xx_setup_link_local_tra
- rule.action.forward_port_map[i] =
- cpu_to_le16(bitmap_read(portmap, i * 16, 16));
+ rule->action.port_map_action =
+ cpu_to_le32(MXL862XX_PCE_ACTION_PORTMAP_ALTERNATIVE);
+ mxl862xx_fw_portmap_set_bit(rule->action.forward_port_map, cpu_port);
+ if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q &&
+ p->cpu_egress_evlan.in_use) {
-+ rule.action.extended_vlan_enable = 1;
-+ rule.action.extended_vlan_block_id =
++ rule->action.extended_vlan_enable = 1;
++ rule->action.extended_vlan_block_id =
+ cpu_to_le16(p->cpu_egress_evlan.block_id);
+ }
+
- /* Bypass STP port state */
- rule.action.cross_state_action =
+ rule->action.cross_state_action =
cpu_to_le32(MXL862XX_PCE_ACTION_CROSS_STATE_CROSS);
-- return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE,
-+ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE,
- rule);
- }
-
-@@ -587,7 +733,8 @@ static int mxl862xx_sync_bridge_members(
- __set_bit(member_dp->index,
- priv->ports[port].portmap);
- }
-- __set_bit(dp->cpu_dp->index, priv->ports[port].portmap);
-+ __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
-+ priv->ports[port].portmap);
-
- err = mxl862xx_set_bridge_port(ds, port);
- if (err)
-@@ -717,7 +864,6 @@ static void mxl862xx_free_bridge(struct
-
- static int mxl862xx_add_single_port_bridge(struct dsa_switch *ds, int port)
+@@ -441,9 +588,6 @@ static void mxl862xx_fill_cpu_trap_actio
+ * allocation via PCERULEALLOC is needed. Using region=CTP causes the
+ * firmware to translate the CTP-relative offset into an absolute
+ * hardware index.
+- *
+- * Cross-state is enabled so that link-local frames reach the CPU even
+- * when the bridge port is in BLOCKING or LEARNING state.
+ */
+ static int mxl862xx_setup_link_local_trap(struct dsa_switch *ds, int port)
{
-- struct dsa_port *dp = dsa_to_port(ds, port);
- struct mxl862xx_priv *priv = ds->priv;
- int ret;
-
-@@ -729,15 +875,27 @@ static int mxl862xx_add_single_port_brid
-
- 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);
-+ __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
-+ priv->ports[port].portmap);
-
- ret = mxl862xx_set_bridge_port(ds, port);
- if (ret)
- return ret;
+@@ -552,11 +696,17 @@ static int mxl862xx_set_bridge_port(stru
+ return 0;
-- /* Standalone ports should not flood unknown unicast or multicast
-- * towards the CPU by default; only broadcast is needed initially.
-+ /* In tag_8021q mode the TX path goes through the bridge engine
-+ * (CTP ingress EVLAN reassigns to a virtual bridge port which
-+ * then forwards via the bridge). With learning disabled on
-+ * standalone ports, unknown unicast must be flooded so that
-+ * frames from the host can reach the user port.
-+ *
-+ * In native SpTag mode, TX bypasses the bridge engine entirely
-+ * (the special tag selects the egress port directly), so flood
-+ * control only affects CPU-bound traffic and can be restrictive.
- */
-+ if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q)
-+ return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
-+ true, true, true);
-+
- return mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
- false, false, true);
- }
-@@ -745,10 +903,12 @@ static int mxl862xx_add_single_port_brid
+ if (dsa_port_is_cpu(dp)) {
+- dsa_switch_for_each_user_port(member_dp, ds) {
+- if (member_dp->cpu_dp->index != port)
+- continue;
+- mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+- member_dp->index);
++ /* In tag_8021q mode the CPU TX path uses per-user-port virtual
++ * bridge ports; leave the physical CPU bridge port map empty to
++ * prevent FID 0 flooding back to user ports.
++ */
++ if (priv->tag_proto != DSA_TAG_PROTO_MXL862_8021Q) {
++ dsa_switch_for_each_user_port(member_dp, ds) {
++ if (member_dp->cpu_dp->index != port)
++ continue;
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ member_dp->index);
++ }
+ }
+ } else if (dp->bridge) {
+ dsa_switch_for_each_bridge_member(member_dp, ds,
+@@ -567,10 +717,10 @@ static int mxl862xx_set_bridge_port(stru
+ member_dp->index);
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+- dp->cpu_dp->index);
++ mxl862xx_cpu_bridge_port_id(ds, port));
+ } else {
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+- dp->cpu_dp->index);
++ mxl862xx_cpu_bridge_port_id(ds, port));
+ p->flood_block = 0;
+ p->learning = false;
+ }
+@@ -774,10 +924,12 @@ static void mxl862xx_free_bridge(struct
static int mxl862xx_setup(struct dsa_switch *ds)
{
struct mxl862xx_priv *priv = ds->priv;
ret = mxl862xx_reset(priv);
if (ret)
-@@ -761,7 +921,7 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -790,7 +942,7 @@ static int mxl862xx_setup(struct dsa_swi
for (i = 0; i < 8; i++)
mxl862xx_setup_pcs(priv, &priv->serdes_ports[i], i + 9);
* With VLAN Filter handling VID membership checks:
* Ingress: only final catchall rules (PVID insertion, 802.1Q
* accept, non-8021Q TPID handling, discard).
-@@ -769,40 +929,67 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -798,46 +950,151 @@ static int mxl862xx_setup(struct dsa_swi
* ingress EVLAN rules are needed. (7 entries.)
* Egress: 2 rules per VID that needs tag stripping (untagged VIDs).
* No egress final catchalls -- VLAN Filter does the discard.
+ * VF: CPU port needs its own VF block for management VIDs.
*
* Total EVLAN budget:
-- * n_user_ports * (ingress + egress) ≤ 1024.
-- * Ingress blocks are small (7 entries), so almost all capacity
-- * goes to egress VID rules.
+- * n_user_ports * (ingress + egress) <= 1024.
+ * n_user_ports * (ingress + egress + cpu_egress + cpu_ingress_share)
+ * <= 1024.
+ * Ingress blocks are small (7 entries), so almost all capacity
+ * goes to egress VID rules.
+ * Total VF budget:
+ * (n_user_ports + n_cpu_ports) * vf_block_size <= 1024.
*/
}
ret = mxl862xx_setup_drop_meter(ds);
-@@ -813,6 +1000,68 @@ static int mxl862xx_setup(struct dsa_swi
- dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
- "that interfere with DSA operation, please update\n");
+ if (ret)
+ return ret;
++ if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
++ dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
++ "that interfere with DSA operation, please update\n");
++
+ /* Pre-allocate firmware resources for all ports. The DSA core
+ * calls change_tag_protocol() between setup() and port_setup(),
+ * and in tag_8021q mode that triggers dsa_tag_8021q_register()
+ * which fires tag_8021q_vlan_add callbacks that need EVLAN and
+ * VF blocks. complete_tag_8021q_setup() also needs per-port
-+ * FIDs from add_single_port_bridge().
++ * FIDs allocated before port_setup() runs.
+ *
+ * Per-port configuration (SpTag, CTP, portmaps, link-local
+ * traps) is deferred to port_setup().
+ dsa_switch_for_each_cpu_port(dp, ds) {
+ port = dp->index;
+
-+ mxl862xx_vf_init(&priv->ports[port].vf,
-+ priv->vf_block_size);
-+ mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
-+ priv->cpu_evlan_ingress_size);
++ priv->ports[port].vf.block_size = priv->vf_block_size;
++ INIT_LIST_HEAD(&priv->ports[port].vf.vids);
++ priv->ports[port].ingress_evlan.block_size =
++ priv->cpu_evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv,
+ &priv->ports[port].ingress_evlan);
+ if (ret)
+ dsa_switch_for_each_user_port(dp, ds) {
+ port = dp->index;
+
-+ mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
-+ priv->evlan_ingress_size);
++ priv->ports[port].ingress_evlan.block_size =
++ priv->evlan_ingress_size;
+ ret = mxl862xx_evlan_block_alloc(priv,
+ &priv->ports[port].ingress_evlan);
+ if (ret)
+ return ret;
+
-+ mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
-+ priv->evlan_egress_size);
++ priv->ports[port].egress_evlan.block_size =
++ priv->evlan_egress_size;
+ ret = mxl862xx_evlan_block_alloc(priv,
+ &priv->ports[port].egress_evlan);
+ if (ret)
+ return ret;
+
-+ mxl862xx_vf_init(&priv->ports[port].vf,
-+ priv->vf_block_size);
++ priv->ports[port].vf.block_size = priv->vf_block_size;
++ INIT_LIST_HEAD(&priv->ports[port].vf.vids);
+ ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
+ if (ret)
+ return ret;
+
-+ mxl862xx_evlan_block_init(&priv->ports[port].cpu_egress_evlan,
-+ ARRAY_SIZE(cpu_egress_tag_8021q));
++ priv->ports[port].cpu_egress_evlan.block_size =
++ ARRAY_SIZE(cpu_egress_tag_8021q);
+ ret = mxl862xx_evlan_block_alloc(priv,
+ &priv->ports[port].cpu_egress_evlan);
+ if (ret)
+ return ret;
+
-+ ret = mxl862xx_add_single_port_bridge(ds, port);
++ ret = mxl862xx_allocate_bridge(priv);
++ if (ret < 0)
++ return ret;
++ priv->ports[port].fid = ret;
++
++ /* Initialize flood forwarding for the private FID.
++ * change_tag_protocol() runs between setup() and port_setup();
++ * ports must be in a clean standalone state before that window.
++ */
++ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++ false, false, true);
+ if (ret)
+ return ret;
+ }
+
+
+ /* Allocate a dedicated PCE snooping FID with all flood modes enabled.
+ * Per-port PCE trap rules (link-local, IGMP, MLD) set bFidEnable to
+@@ -860,10 +1117,6 @@ static int mxl862xx_setup(struct dsa_swi
+ true, true, true);
+ if (ret)
+ return ret;
+-
+- if (!MXL862XX_FW_VER_MIN(priv, 1, 0, 80))
+- dev_warn(ds->dev, "firmware < 1.0.80 installs global PCE rules "
+- "that interfere with DSA operation, please update\n");
schedule_delayed_work(&priv->stats_work,
MXL862XX_STATS_POLL_INTERVAL);
-@@ -894,6 +1143,52 @@ static int mxl862xx_configure_sp_tag_pro
+@@ -944,10 +1197,71 @@ static int mxl862xx_configure_sp_tag_pro
+ return MXL862XX_API_WRITE(ds->priv, MXL862XX_SS_SPTAG_SET, tag);
}
- /**
++/**
+ * mxl862xx_set_cpu_vbp - Push CPU-side virtual bridge port config to firmware
+ * @ds: DSA switch
+ * @port: user port index whose VBP to configure
+}
+
+/**
- * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
- * @priv: driver private data
- * @block_id: HW Extended VLAN block ID
-@@ -902,6 +1197,7 @@ static int mxl862xx_configure_sp_tag_pro
- * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
- * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
- * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
++ * mxl862xx_evlan_write_rule - Write a single Extended VLAN rule to hardware
++ * @priv: driver private data
++ * @block_id: HW Extended VLAN block ID
++ * @entry_index: entry index within the block
++ * @desc: rule descriptor (filter type + action)
++ * @vid: VLAN ID for VID-specific rules (ignored when !desc->match_vid)
++ * @untagged: strip tag on egress for EVLAN_STRIP_IF_UNTAGGED action
++ * @pvid: port VLAN ID for PVID insertion rules (0 = no PVID)
+ * @mgmt_vid: tag_8021q management VID for outer tag insertion (0 = unused)
- *
- * Translates a compact rule descriptor into a full firmware
- * mxl862xx_extendedvlan_config struct and writes it via the API.
-@@ -909,7 +1205,8 @@ static int mxl862xx_configure_sp_tag_pro
++ *
++ * Translates a compact rule descriptor into a full firmware
++ * mxl862xx_extendedvlan_config struct and writes it via the API.
++ */
static int mxl862xx_evlan_write_rule(struct mxl862xx_priv *priv,
u16 block_id, u16 entry_index,
const struct mxl862xx_evlan_rule_desc *desc,
{
struct mxl862xx_extendedvlan_config cfg = {};
struct mxl862xx_extendedvlan_filter_vlan *fv;
-@@ -999,6 +1296,31 @@ static int mxl862xx_evlan_write_rule(str
+@@ -1016,6 +1330,31 @@ static int mxl862xx_evlan_write_rule(str
cpu_to_le32(MXL862XX_EXTENDEDVLAN_TREATMENT_DISCARD_UPSTREAM);
}
break;
}
return MXL862XX_API_WRITE(priv, MXL862XX_EXTENDEDVLAN_SET, cfg);
-@@ -1059,7 +1381,7 @@ static int mxl862xx_evlan_write_final_ru
+@@ -1054,7 +1393,7 @@ static int mxl862xx_evlan_write_final_ru
for (i = 0; i < n_rules; i++) {
ret = mxl862xx_evlan_write_rule(priv, blk->block_id,
start_idx + i, &rules[i],
if (ret)
return ret;
}
-@@ -1228,6 +1550,27 @@ static int mxl862xx_vf_del_vid(struct mx
+@@ -1175,6 +1514,41 @@ static int mxl862xx_vf_del_vid(struct mx
+ return 0;
}
- /**
++/**
+ * mxl862xx_vf_clear_vids - Remove all VID entries without freeing the HW block
+ * @priv: driver private data
+ * @vf: VLAN Filter block
+}
+
+/**
- * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
- * @priv: driver private data
- * @port: port number
-@@ -1278,8 +1621,8 @@ static int mxl862xx_evlan_program_egress
++ * mxl862xx_evlan_program_ingress - Write the fixed ingress catchall rules
++ * @priv: driver private data
++ * @port: port number
++ *
++ * In VLAN-aware mode the ingress EVLAN block handles PVID insertion for
++ * untagged/priority-tagged frames, passes through standard 802.1Q
++ * tagged frames for VF membership checking, and treats non-8021Q TPID
++ * frames as untagged. The block is sized to exactly fit the 7 catchall
++ * rules and is rewritten whenever PVID changes.
++ *
++ * In VLAN-unaware mode the firmware passes frames through unchanged when
++ * no ingress block is assigned, so nothing is programmed.
++ */
+ static int mxl862xx_evlan_program_ingress(struct mxl862xx_priv *priv, int port)
+ {
+ struct mxl862xx_port *p = &priv->ports[port];
+@@ -1199,8 +1573,8 @@ static int mxl862xx_evlan_program_egress
const struct mxl862xx_evlan_rule_desc *vid_rules;
struct mxl862xx_vf_vid *vfv;
u16 old_active = blk->n_active;
if (p->vlan_filtering) {
vid_rules = vid_accept_standard;
-@@ -1296,13 +1639,23 @@ static int mxl862xx_evlan_program_egress
- if (p->vlan_filtering && !vfv->untagged)
+@@ -1214,13 +1588,23 @@ static int mxl862xx_evlan_program_egress
+ if (!vfv->untagged)
continue;
+ /* Skip the tag_8021q management VID -- it must NOT get
if (ret)
return ret;
-@@ -1311,7 +1664,29 @@ static int mxl862xx_evlan_program_egress
+@@ -1229,7 +1613,29 @@ static int mxl862xx_evlan_program_egress
idx++, &vid_rules[1],
vfv->vid,
vfv->untagged,
if (ret)
return ret;
}
-@@ -1323,8 +1698,7 @@ static int mxl862xx_evlan_program_egress
+@@ -1241,8 +1647,7 @@ static int mxl862xx_evlan_program_egress
*/
for (i = idx; i < old_active; i++) {
ret = mxl862xx_evlan_deactivate_entry(priv,
if (ret)
return ret;
}
-@@ -1348,13 +1722,16 @@ static int mxl862xx_port_vlan_filtering(
+@@ -1267,13 +1672,16 @@ static int mxl862xx_port_vlan_filtering(
+ p->vlan_filtering = vlan_filtering;
- /* Reprogram Extended VLAN rules if filtering mode changed */
if (changed) {
- /* When leaving VLAN-aware mode, release the ingress HW
- * block. The firmware passes frames through unchanged
ret = mxl862xx_evlan_program_ingress(priv, port);
if (ret)
-@@ -1491,18 +1868,19 @@ static int mxl862xx_setup_cpu_bridge(str
-
- /* include all assigned user ports in the CPU portmap */
- bitmap_zero(p->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;
-+ if (priv->tag_proto != DSA_TAG_PROTO_MXL862_8021Q) {
-+ 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;
-
-- __set_bit(dp->index, p->portmap);
-+ __set_bit(dp->index, p->portmap);
-+ }
- }
-
- return mxl862xx_set_bridge_port(ds, port);
- }
-
--
- static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
- const struct dsa_bridge bridge,
- bool *tx_fwd_offload,
-@@ -1535,7 +1913,6 @@ static int mxl862xx_port_bridge_join(str
- 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;
-@@ -1550,34 +1927,587 @@ static void mxl862xx_port_bridge_leave(s
- * single-port bridge
- */
- bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
-- __set_bit(dp->cpu_dp->index, p->portmap);
-+ __set_bit(mxl862xx_cpu_bridge_port_id(ds, port), p->portmap);
- p->flood_block = 0;
-+ p->host_flood_block = 0;
-
-- /* Detach EVLAN and VF blocks from the bridge port BEFORE freeing
-- * them. The firmware tracks a usage count per block and rejects
-- * FREE while the count is non-zero.
-- *
-- * For EVLAN: setting in_use=false makes set_bridge_port send
-- * enable=false, which decrements the firmware refcount.
-+ /* Reset VLAN state for standalone mode. Ingress EVLAN is not
-+ * needed outside a VLAN-aware bridge. Egress EVLAN is
-+ * reprogrammed below -- in tag_8021q mode it gets the
-+ * management VID strip catchalls, in SpTag mode it is cleared.
- *
-- * For VF: set_bridge_port sees dp->bridge == NULL (DSA already
-- * cleared it) and sends vlan_filter_enable=0, which decrements
-- * the firmware VF refcount.
+@@ -1493,22 +1901,575 @@ static void mxl862xx_port_bridge_leave(s
+ port, ERR_PTR(err));
+
+ /* Revert leaving port, omitted by the sync above, to its
+- * single-port bridge
++ * single-port bridge state. Egress EVLAN is reprogrammed below
++ * -- in tag_8021q mode it gets management VID strip catchalls,
++ * in SpTag mode it is cleared.
++ *
+ * Do NOT clear the VF VID list here. Bridge VLANs are already
+ * removed by port_vlan_del during the switchdev replay in
+ * dsa_port_pre_bridge_leave. The remaining VIDs (e.g. the
+}
+
+/**
-+ * mxl862xx_refresh_cpu_targets - Update portmaps and traps for new CPU target
++ * mxl862xx_refresh_cpu_targets - Update bridge ports and traps for new CPU target
+ * @ds: DSA switch
+ *
+ * After switching between SpTag and tag_8021q, the CPU-side target in
-+ * each user port's portmap changes (physical CPU port vs. virtual
-+ * bridge port). This rebuilds every user port's portmap with the
-+ * correct CPU target and reinstalls the link-local PCE trap.
++ * each user port's bridge port map changes (physical CPU port vs. virtual
++ * bridge port). Reinstalls bridge port config and link-local PCE traps.
+ */
+static int mxl862xx_refresh_cpu_targets(struct dsa_switch *ds)
+{
+ struct mxl862xx_priv *priv = ds->priv;
-+ struct dsa_port *dp, *member_dp;
-+ struct mxl862xx_port *p;
++ struct dsa_port *dp;
+ int ret, port;
+
+ dsa_switch_for_each_user_port(dp, ds) {
+ port = dp->index;
-+ p = &priv->ports[port];
-+
-+ bitmap_zero(p->portmap, MXL862XX_MAX_BRIDGE_PORTS);
-+ if (dp->bridge) {
-+
-+ dsa_switch_for_each_bridge_member(member_dp, ds, dp->bridge->dev) {
-+ if (member_dp->index != port)
-+ __set_bit(member_dp->index, p->portmap);
-+ }
-+ }
-+ __set_bit(mxl862xx_cpu_bridge_port_id(ds, port), p->portmap);
+
+ /* Reprogram user port egress EVLAN to add or remove the
+ * tag_8021q management VID strip catchalls.
+ ret = mxl862xx_setup_link_local_trap(ds, port);
+ if (ret)
+ return ret;
++
++ ret = mxl862xx_setup_snooping_traps(ds, port);
++ if (ret)
++ return ret;
+ }
+
+ return 0;
+
+ mxl862xx_bridge_config_fwd(ds,
+ priv->ports[port].fid,
-+ false, false, true);
++ priv->ports[port].host_flood_uc,
++ priv->ports[port].host_flood_mc,
++ true);
+ }
+ }
+ dsa_switch_for_each_cpu_port(dp, ds) {
static int mxl862xx_port_setup(struct dsa_switch *ds, int port)
{
struct mxl862xx_priv *priv = ds->priv;
-@@ -1597,55 +2527,30 @@ static int mxl862xx_port_setup(struct ds
- dsa_port_is_dsa(dp))
- return 0;
+@@ -1530,33 +2491,52 @@ static int mxl862xx_port_setup(struct ds
+ return -EOPNOTSUPP;
+ }
-- /* configure tag protocol */
- ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
+ /* configure tag protocol: SpTag for native, disable for 8021q */
+ ret = mxl862xx_configure_sp_tag_proto(ds, port,
if (ret)
return ret;
- /* assign CTP port IDs */
ret = mxl862xx_configure_ctp_port(ds, port, port,
- is_cpu_port ? 32 - port : 1);
+ (is_cpu_port &&
return ret;
if (is_cpu_port)
-- /* assign user ports to CPU port bridge */
return mxl862xx_setup_cpu_bridge(ds, port);
-- /* setup single-port bridge for user ports */
-- ret = mxl862xx_add_single_port_bridge(ds, port);
-- if (ret)
+- /* setup single-port bridge for user ports.
+- * If this fails, the FID is leaked -- but the port then transitions
+- * to unused, and the FID pool is sized to tolerate this.
+- */
+- ret = mxl862xx_allocate_bridge(priv);
+- if (ret < 0) {
+- dev_err(ds->dev, "failed to allocate a bridge for port %d\n", port);
- return ret;
--
- /* install link-local trap for this user port */
- ret = mxl862xx_setup_link_local_trap(ds, port);
++ /* The FID and initial bridge port config were set up in setup()
++ * before change_tag_protocol() runs. Reconfigure here now that
++ * the per-port CTP and SpTag settings are in place.
++ *
++ * In tag_8021q mode the TX path goes through the bridge engine
++ * (CTP ingress EVLAN reassigns to a virtual bridge port which
++ * then forwards via the bridge). With learning disabled on
++ * standalone ports, unknown unicast must be flooded so that
++ * frames from the host can reach the user port.
++ *
++ * In SpTag mode TX bypasses the bridge engine entirely (the
++ * special tag selects the egress port directly), so flood
++ * control only affects CPU-bound traffic and can be restrictive.
++ * Block unknown UC/MC on the VBP egress meters in tag_8021q
++ * mode so frames to unknown destinations are not forwarded to
++ * the host. The DSA core re-enables selectively via
++ * port_set_host_flood when needed (e.g. promisc mode).
++ */
++ if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q) {
++ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++ true, true, true);
++ priv->ports[port].host_flood_block =
++ BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC) |
++ BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP) |
++ BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
++ } else {
++ ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
++ false, false, true);
+ }
+- priv->ports[port].fid = ret;
+- /* Standalone ports should not flood unknown unicast or multicast
+- * towards the CPU by default; only broadcast is needed initially.
+- */
+- ret = mxl862xx_bridge_config_fwd(ds, priv->ports[port].fid,
+- false, false, true);
+ if (ret)
+ return ret;
+ ret = mxl862xx_set_bridge_port(ds, port);
+@@ -1572,24 +2552,7 @@ static int mxl862xx_port_setup(struct ds
if (ret)
return ret;
-- /* Initialize and pre-allocate per-port EVLAN and VF blocks for
-- * user ports. CPU ports do not use EVLAN or VF -- frames pass
-- * through without processing. Pre-allocation avoids firmware
-- * EVLAN table fragmentation and simplifies control flow.
-- */
-- mxl862xx_evlan_block_init(&priv->ports[port].ingress_evlan,
-- priv->evlan_ingress_size);
+- priv->ports[port].ingress_evlan.block_size = priv->evlan_ingress_size;
- ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].ingress_evlan);
- if (ret)
- return ret;
-
-- mxl862xx_evlan_block_init(&priv->ports[port].egress_evlan,
-- priv->evlan_egress_size);
+- priv->ports[port].egress_evlan.block_size = priv->evlan_egress_size;
- ret = mxl862xx_evlan_block_alloc(priv, &priv->ports[port].egress_evlan);
- if (ret)
- return ret;
-
-- mxl862xx_vf_init(&priv->ports[port].vf, priv->vf_block_size);
+- priv->ports[port].vf.block_size = priv->vf_block_size;
+- INIT_LIST_HEAD(&priv->ports[port].vf.vids);
- ret = mxl862xx_vf_alloc(priv, &priv->ports[port].vf);
- if (ret)
- return ret;
return 0;
}
-@@ -1667,7 +2572,7 @@ static void mxl862xx_port_teardown(struc
+@@ -1611,7 +2574,7 @@ static void mxl862xx_port_teardown(struc
priv->ports[port].setup_done = false;
}
{
struct mxl862xx_priv *priv = ds->priv;
-@@ -1685,23 +2590,244 @@ static int mxl862xx_get_fid(struct dsa_s
+@@ -1629,23 +2592,247 @@ static int mxl862xx_get_fid(struct dsa_s
}
}
+ if (dsa_is_cpu_port(ds, port) && priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q &&
+ db.type == DSA_DB_PORT) {
+ bp_cpu = priv->ports[db.dp->index].bridge_port_cpu;
-
-- param.port_id = cpu_to_le32(port);
++
+ if (bp_cpu)
+ return bp_cpu;
+ }
-+
+
+- param.port_id = cpu_to_le32(port);
+ return port;
+}
+
+ * mxl862xx_mac_portmap_add - Set port bits in a MAC table entry's portmap
+ * @priv: driver private data
+ * @addr: MAC address
-+ * @fid: firmware FID
++ * @fid: FID
+ * @vid: VLAN ID
+ * @add_map: firmware-format portmap of bits to set
+ *
+ * mxl862xx_mac_portmap_del - Clear port bits from a MAC table entry's portmap
+ * @priv: driver private data
+ * @addr: MAC address
-+ * @fid: firmware FID
++ * @fid: FID
+ * @vid: VLAN ID
+ * @del_map: firmware-format portmap of bits to clear
+ *
+ * @ds: DSA switch
+ * @addr: MAC address
+ * @vid: VLAN ID
-+ * @bridge: bridge whose members' VBPs to include
++ * @bridge: bridge the entry is scoped to (used to pick the FID)
+ *
-+ * In tag_8021q mode, host FDB/MDB entries in a shared bridge FID must use
-+ * portmap mode targeting ALL bridge members' virtual bridge ports (VBPs).
-+ * The firmware ANDs the entry's portmap with each ingress port's
-+ * bridge_port_map, which contains only that port's own VBP. This
-+ * selects the correct VBP per ingress port, ensuring frames exit
-+ * through the right egress EVLAN (which inserts the per-port management
-+ * VID that identifies the source port to DSA on the CPU side).
++ * The entry's portmap lists every user port's VBP unconditionally. The
++ * bridging engine ANDs it with the ingress bridge port's bridge_port_map,
++ * which already encodes the current bridge membership, so only the
++ * ingress port's own VBP survives the intersection -- that selects the
++ * correct egress EVLAN (inserting the per-port management VID that
++ * identifies the source port to DSA on the CPU side) without any need
++ * to track bridge membership changes in this MAC entry.
+ */
+static int mxl862xx_mac_add_host_bridge(struct dsa_switch *ds,
+ const unsigned char *addr, u16 vid,
+ __le16 add_map[MXL862XX_FW_PORTMAP_WORDS] = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 fid = priv->bridges[bridge->num];
-+ struct dsa_port *member_dp;
++ struct dsa_port *dp;
++ u16 vbp;
+
-+ dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev)
-+ mxl862xx_fw_portmap_set_bit(add_map,
-+ priv->ports[member_dp->index].bridge_port_cpu);
++ dsa_switch_for_each_user_port(dp, ds) {
++ vbp = priv->ports[dp->index].bridge_port_cpu;
++ if (vbp)
++ mxl862xx_fw_portmap_set_bit(add_map, vbp);
++ }
+
+ return mxl862xx_mac_portmap_add(priv, addr, fid, vid, add_map);
+}
if (ret)
dev_err(ds->dev, "failed to add FDB entry on port %d\n", port);
-@@ -1711,18 +2837,25 @@ static int mxl862xx_port_fdb_add(struct
+@@ -1655,18 +2842,25 @@ static int mxl862xx_port_fdb_add(struct
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_priv *priv = ds->priv;
+ struct dsa_port *target_dp;
+ int fid, ret;
-
++
+ /* Mirror of the standalone->bridge FID path in fdb_add */
+ if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q && dsa_is_cpu_port(ds, port) &&
+ db.type == DSA_DB_PORT && vid > 0) {
+ mxl862xx_fdb_del_per_fid(ds, addr, vid,
+ priv->bridges[target_dp->bridge->num]);
+ }
-+
+
+ fid = mxl862xx_get_fid(ds, db);
if (fid < 0)
return fid;
if (ret)
dev_err(ds->dev, "failed to remove FDB entry on port %d\n", port);
-@@ -1761,88 +2894,147 @@ static int mxl862xx_port_fdb_dump(struct
+@@ -1705,84 +2899,153 @@ static int mxl862xx_port_fdb_dump(struct
return 0;
}
+ * mxl862xx_mdb_add_to_fid - Add a port bit to an MDB entry in one FID
+ * @ds: DSA switch
+ * @mdb: multicast group address and VID
-+ * @fid: firmware FID to operate on
++ * @fid: FID to operate on
+ * @port_bit: port index to set in the portmap
+ * @vid: VLAN ID for the MAC table entry
+ */
+ * mxl862xx_mdb_del_from_fid - Remove a port bit from an MDB entry in one FID
+ * @ds: DSA switch
+ * @mdb: multicast group address
-+ * @fid: firmware FID to operate on
++ * @fid: FID to operate on
+ * @port_bit: port index to clear from the portmap
+ * @vid: VLAN ID for the MAC table entry (0 for SVL/tag_8021q mode)
+ */
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_mdb_add_to_fid(ds, mdb, fid, db.dp->index,
+ mdb->vid);
-- /* Merge with existing portmap if entry already exists */
- if (qparam.found)
- memcpy(aparam.port_map, qparam.port_map,
- sizeof(aparam.port_map));
+ return ret;
+}
-
-- mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
++
+/**
-+ * mxl862xx_mac_del_host_bridge - Remove VBP bits from a host FDB/MDB entry
++ * mxl862xx_mac_del_host_bridge - Remove every user-port VBP bit from a host
++ * FDB/MDB entry, dropping the entry when its
++ * portmap becomes empty
+ * @ds: DSA switch
+ * @addr: MAC address
+ * @vid: VLAN ID
-+ * @bridge: bridge whose members' VBPs to clear
++ * @bridge: bridge the entry is scoped to (used to pick the FID)
+ *
-+ * Clears all bridge member VBP bits from the portmap. If the portmap
-+ * becomes empty (no user-port bits remain), removes the entry entirely.
++ * Counterpart of mxl862xx_mac_add_host_bridge(): the add path sets every
++ * user port's VBP in the portmap, so the delete path clears the same
++ * set of bits.
+ */
+static int mxl862xx_mac_del_host_bridge(struct dsa_switch *ds,
+ const unsigned char *addr, u16 vid,
+ __le16 del_map[MXL862XX_FW_PORTMAP_WORDS] = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 fid = priv->bridges[bridge->num];
-+ struct dsa_port *member_dp;
++ struct dsa_port *dp;
++ u16 vbp;
+
+- mxl862xx_fw_portmap_set_bit(aparam.port_map, port);
++ dsa_switch_for_each_user_port(dp, ds) {
++ vbp = priv->ports[dp->index].bridge_port_cpu;
++ if (vbp)
++ mxl862xx_fw_portmap_set_bit(del_map, vbp);
++ }
- return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYADD, aparam);
-+ dsa_switch_for_each_bridge_member(member_dp, ds, bridge->dev)
-+ mxl862xx_fw_portmap_set_bit(del_map,
-+ priv->ports[member_dp->index].bridge_port_cpu);
-+
+ return mxl862xx_mac_portmap_del(priv, addr, fid, vid, del_map);
}
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);
- 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);
return ret;
}
-@@ -1930,7 +3122,9 @@ static void mxl862xx_host_flood_work_fn(
+@@ -1867,6 +3130,9 @@ static void mxl862xx_host_flood_work_fn(
+ host_flood_work);
struct mxl862xx_priv *priv = p->priv;
struct dsa_switch *ds = priv->ds;
- int port = p - priv->ports;
++ int port = p - priv->ports;
+ unsigned long block;
- bool uc, mc;
+ int ret;
rtnl_lock();
-@@ -1943,14 +3137,35 @@ static void mxl862xx_host_flood_work_fn(
- uc = p->host_flood_uc;
- mc = p->host_flood_mc;
+@@ -1876,13 +3142,34 @@ static void mxl862xx_host_flood_work_fn(
+ return;
+ }
-- /* 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.
+- /* Always write to the standalone FID. When standalone it takes effect
+- * immediately; when bridged the port uses the shared bridge FID so the
+- * write is a no-op for current forwarding, but the state is preserved
+- * in hardware and is ready once the port returns to standalone.
- */
-- if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
-- mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
+- mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
+- p->host_flood_mc, true);
+ if (priv->tag_proto == DSA_TAG_PROTO_MXL862_8021Q) {
+ block = 0;
+
-+ if (!uc)
++ if (!p->host_flood_uc)
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_UC);
-+ if (!mc) {
++ if (!p->host_flood_mc) {
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_IP);
+ block |= BIT(MXL862XX_BRIDGE_PORT_EGRESS_METER_UNKNOWN_MC_NON_IP);
+ }
+ port, ERR_PTR(ret));
+ }
+ } else {
-+ /* 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.
++ /* Always write to the standalone FID. When standalone it takes
++ * effect immediately; when bridged the port uses the shared
++ * bridge FID so the write is a no-op for current forwarding,
++ * but the state is preserved in hardware and is ready once the
++ * port returns to standalone.
+ */
-+ if (!dsa_port_bridge_dev_get(dsa_to_port(ds, port)))
-+ mxl862xx_bridge_config_fwd(ds, p->fid, uc, mc, true);
++ mxl862xx_bridge_config_fwd(ds, p->fid, p->host_flood_uc,
++ p->host_flood_mc, true);
+ }
rtnl_unlock();
}
-@@ -2285,7 +3500,9 @@ static void mxl862xx_get_stats64(struct
+@@ -2248,7 +3535,9 @@ static void mxl862xx_get_stats64(struct
static const struct dsa_switch_ops mxl862xx_switch_ops = {
.get_tag_protocol = mxl862xx_get_tag_protocol,
.port_setup = mxl862xx_port_setup,
.port_teardown = mxl862xx_port_teardown,
.phylink_get_caps = mxl862xx_phylink_get_caps,
-@@ -2307,6 +3524,8 @@ static const struct dsa_switch_ops mxl86
+@@ -2270,6 +3559,8 @@ static const struct dsa_switch_ops mxl86
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
.get_strings = mxl862xx_get_strings,
.get_sset_count = mxl862xx_get_sset_count,
.get_ethtool_stats = mxl862xx_get_ethtool_stats,
-@@ -2354,6 +3573,8 @@ static int mxl862xx_probe(struct mdio_de
+@@ -2318,6 +3609,8 @@ static int mxl862xx_probe(struct mdio_de
INIT_DELAYED_WORK(&priv->stats_work, mxl862xx_stats_work_fn);
dev_set_drvdata(dev, ds);
err = dsa_register_switch(ds);
-@@ -2382,6 +3603,19 @@ static void mxl862xx_remove(struct mdio_
+@@ -2346,6 +3639,19 @@ static void mxl862xx_remove(struct mdio_
set_bit(MXL862XX_FLAG_WORK_STOPPED, &priv->flags);
cancel_delayed_work_sync(&priv->stats_work);
mxl862xx_host_shutdown(priv);
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -210,6 +210,9 @@ struct mxl862xx_port_stats {
+@@ -209,6 +209,9 @@ struct mxl862xx_port_stats {
* @vf: per-port VLAN Filter block state
* @ingress_evlan: ingress extended VLAN block state
* @egress_evlan: egress extended VLAN block 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
-@@ -224,6 +227,7 @@ struct mxl862xx_port_stats {
+@@ -223,6 +226,7 @@ struct mxl862xx_port_stats {
* periodically by the stats polling work
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
-@@ -238,9 +242,14 @@ struct mxl862xx_port {
+@@ -235,9 +239,13 @@ struct mxl862xx_port {
struct mxl862xx_vf_block vf;
struct mxl862xx_evlan_block ingress_evlan;
struct mxl862xx_evlan_block egress_evlan;
-+ /* tag_8021q state */
+ u16 bridge_port_cpu;
+ unsigned long host_flood_block;
bool host_flood_uc;
struct work_struct host_flood_work;
+ u16 tag_8021q_vid;
+ struct mxl862xx_evlan_block cpu_egress_evlan;
- /* Hardware stats accumulation */
struct mxl862xx_port_stats stats;
- spinlock_t stats_lock;
-@@ -308,6 +317,7 @@ union mxl862xx_fw_version {
+ spinlock_t stats_lock; /* protects stats accumulators */
+ };
+@@ -304,6 +312,7 @@ union mxl862xx_fw_version {
* before CRC-triggered shutdown and cleared after;
* %MXL862XX_FLAG_WORK_STOPPED is set before cancelling
* stats_work to prevent rescheduling during teardown
* @drop_meter: index of the single shared zero-rate firmware meter
* used to unconditionally drop traffic (used to block
* flooding)
-@@ -322,6 +332,7 @@ union mxl862xx_fw_version {
+@@ -318,6 +327,7 @@ union mxl862xx_fw_version {
* (0 .. ds->max_num_bridges).
* @evlan_ingress_size: per-port ingress Extended VLAN block size
* @evlan_egress_size: per-port egress Extended VLAN block size
+ * @cpu_evlan_ingress_size: CPU port ingress EVLAN block size (tag_8021q)
* @vf_block_size: per-port VLAN Filter block size
- * @stats_work: periodic work item that polls RMON hardware counters
- * and accumulates them into 64-bit per-port stats
-@@ -331,6 +342,7 @@ struct mxl862xx_priv {
+ * @cpu_trap_fid: firmware bridge FID allocated for PCE-trapped frames;
+ * configured with uc/mc/bc flood all enabled so that
+@@ -334,6 +344,7 @@ struct mxl862xx_priv {
struct mdio_device *mdiodev;
struct work_struct crc_err_work;
unsigned long flags;
u16 drop_meter;
union mxl862xx_fw_version fw_version;
struct mxl862xx_pcs serdes_ports[8];
-@@ -338,6 +350,7 @@ struct mxl862xx_priv {
+@@ -341,6 +352,7 @@ struct mxl862xx_priv {
u16 bridges[MXL862XX_MAX_BRIDGES + 1];
u16 evlan_ingress_size;
u16 evlan_egress_size;
+ u16 cpu_evlan_ingress_size;
u16 vf_block_size;
+ u16 cpu_trap_fid;
struct delayed_work stats_work;
- };
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -56,6 +56,7 @@ struct tc_action;
obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o
--- /dev/null
+++ b/net/dsa/tag_mxl862xx_8021q.c
-@@ -0,0 +1,59 @@
+@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DSA 802.1Q-based tag driver for MaxLinear MxL862xx switches
+static struct sk_buff *mxl862_8021q_rcv(struct sk_buff *skb,
+ struct net_device *netdev)
+{
-+ int src_port = -1, switch_id = -1;
++ int src_port = -1, switch_id = -1, vbid = -1;
++ int vid = -1;
+
-+ dsa_8021q_rcv(skb, &src_port, &switch_id, NULL, NULL);
++ dsa_8021q_rcv(skb, &src_port, &switch_id, &vbid, &vid);
+
-+ skb->dev = dsa_conduit_find_user(netdev, switch_id, src_port);
-+ if (!skb->dev)
++ skb->dev = dsa_tag_8021q_find_user(netdev, src_port, switch_id,
++ vid, vbid);
++ if (!skb->dev) {
++ net_warn_ratelimited("%s: dropped frame: src_port=%d switch_id=%d vid=%d vbid=%d\n",
++ netdev->name, src_port, switch_id,
++ vid, vbid);
+ return NULL;
++ }
+
+ dsa_default_offload_fwd_mark(skb);
+
-From 4e1d854199c166f617b93b7542e863e6a8ad2ccb Mon Sep 17 00:00:00 2001
+From a32f6a9c09b90e767a03ddac34d061545a0cf15e Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 03:44:41 +0000
-Subject: [PATCH 17/26] net: dsa: mxl862xx: add link aggregation support
+Subject: [PATCH 10/20] net: dsa: mxl862xx: add link aggregation support
Implement LAG offloading via the firmware's trunking engine. A
dedicated firmware bridge port is allocated per LAG and remains
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 22 +
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 4 +
- drivers/net/dsa/mxl862xx/mxl862xx.c | 587 +++++++++++++++++++++++-
- drivers/net/dsa/mxl862xx/mxl862xx.h | 34 ++
- 4 files changed, 630 insertions(+), 17 deletions(-)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 578 +++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 33 ++
+ 4 files changed, 621 insertions(+), 16 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -620,7 +620,42 @@ static int mxl862xx_setup_link_local_tra
- rule);
+@@ -680,7 +680,42 @@ static int mxl862xx_setup_snooping_traps
+ return MXL862XX_API_WRITE(priv, MXL862XX_TFLOW_PCERULEWRITE, rule);
}
-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);
-@@ -632,7 +667,7 @@ static int mxl862xx_set_bridge_port(stru
- bool enable;
- int i, idx;
+@@ -713,8 +748,12 @@ static int mxl862xx_set_bridge_port(stru
+ dp->bridge->dev) {
+ if (member_dp->index == port)
+ continue;
+- mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+- member_dp->index);
++ if (!mxl862xx_is_lag_master(priv, member_dp->index))
++ continue;
++ mxl862xx_fw_portmap_set_bit(
++ br_port_cfg.bridge_port_map,
++ mxl862xx_lag_bridge_port(priv,
++ member_dp->index));
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ mxl862xx_cpu_bridge_port_id(ds, port));
+@@ -727,7 +766,7 @@ static int mxl862xx_set_bridge_port(stru
+
+ bridge_id = dp->bridge ? priv->bridges[dp->bridge->num] : p->fid;
- br_port_cfg.bridge_port_id = cpu_to_le16(port);
+ br_port_cfg.bridge_port_id = cpu_to_le16(bp_id);
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 |
-@@ -715,12 +750,38 @@ static int mxl862xx_set_bridge_port(stru
+@@ -808,11 +847,38 @@ static int mxl862xx_set_bridge_port(stru
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;
++ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p;
-+ int port, member, err, ret = 0;
-+ u16 lag_bp, bp;
+ struct dsa_port *dp;
+- int ret = 0, err;
++ u16 lag_bp;
++ int err, ret = 0;
dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
- port = dp->index;
-@@ -729,9 +790,21 @@ static int mxl862xx_sync_bridge_members(
- 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);
-+ member = member_dp->index;
-+
-+ /* For LAG members, only include the LAG's
-+ * dedicated bridge port in the portmap.
-+ * Non-master members are skipped to avoid
-+ * duplicates (they share the same LAG bridge
-+ * port).
-+ */
-+ if (!mxl862xx_is_lag_master(priv, member))
-+ continue;
-+ if (member != port) {
-+ bp = mxl862xx_lag_bridge_port(priv,
-+ member);
-+ __set_bit(bp, priv->ports[port].portmap);
-+ }
- }
- __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
- priv->ports[port].portmap);
-@@ -741,6 +814,25 @@ static int mxl862xx_sync_bridge_members(
+ err = mxl862xx_set_bridge_port(ds, dp->index);
+@@ -820,6 +886,25 @@ static int mxl862xx_sync_bridge_members(
ret = err;
}
return ret;
}
-@@ -1881,6 +1973,408 @@ static int mxl862xx_setup_cpu_bridge(str
+@@ -1859,6 +1944,408 @@ static int mxl862xx_setup_cpu_bridge(str
return mxl862xx_set_bridge_port(ds, port);
}
static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge,
bool *tx_fwd_offload,
-@@ -1907,7 +2401,18 @@ static int mxl862xx_port_bridge_join(str
+@@ -1884,7 +2371,18 @@ static int mxl862xx_port_bridge_join(str
return 0;
}
}
static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
-@@ -1966,6 +2471,17 @@ static void mxl862xx_port_bridge_leave(s
+@@ -1935,6 +2433,17 @@ static void mxl862xx_port_bridge_leave(s
"failed to update CPU VBP for port %d: %pe\n", port,
ERR_PTR(err));
if (!dsa_bridge_ports(ds, bridge.dev))
mxl862xx_free_bridge(ds, &bridge);
}
-@@ -2591,18 +3107,17 @@ static int mxl862xx_get_fid(struct dsa_s
+@@ -2593,18 +3102,17 @@ static int mxl862xx_get_fid(struct dsa_s
}
/**
*/
static int mxl862xx_fdb_bridge_port(struct dsa_switch *ds, int port,
const struct dsa_db db)
-@@ -2618,7 +3133,7 @@ static int mxl862xx_fdb_bridge_port(stru
+@@ -2620,7 +3128,7 @@ static int mxl862xx_fdb_bridge_port(stru
return bp_cpu;
}
}
/**
-@@ -2862,11 +3377,43 @@ static int mxl862xx_port_fdb_del(struct
+@@ -2867,11 +3375,43 @@ static int mxl862xx_port_fdb_del(struct
return ret;
}
u32 entry_port_id;
int ret;
-@@ -2880,7 +3427,7 @@ static int mxl862xx_port_fdb_dump(struct
+@@ -2885,7 +3425,7 @@ static int mxl862xx_port_fdb_dump(struct
entry_port_id = le32_to_cpu(param.port_id);
ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
le16_to_cpu(param.tci)),
param.static_entry, data);
-@@ -3521,6 +4068,11 @@ static const struct dsa_switch_ops mxl86
+@@ -3556,6 +4096,11 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
-@@ -3561,6 +4113,7 @@ static int mxl862xx_probe(struct mdio_de
+@@ -3597,6 +4142,7 @@ static int mxl862xx_probe(struct mdio_de
ds->num_ports = MXL862XX_MAX_PORTS;
ds->fdb_isolation = true;
ds->max_num_bridges = MXL862XX_MAX_BRIDGES;
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
-@@ -228,6 +241,12 @@ struct mxl862xx_port_stats {
+@@ -227,6 +240,12 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
-@@ -250,6 +269,10 @@ struct mxl862xx_port {
+@@ -246,6 +265,9 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
-+ /* LAG state */
+ struct dsa_lag *lag;
+ bool lag_tx_enabled;
+ u8 lag_hash_bits;
- /* Hardware stats accumulation */
struct mxl862xx_port_stats stats;
- spinlock_t stats_lock;
-@@ -334,6 +357,15 @@ union mxl862xx_fw_version {
- * @evlan_egress_size: per-port egress Extended VLAN block size
- * @cpu_evlan_ingress_size: CPU port ingress EVLAN block size (tag_8021q)
- * @vf_block_size: per-port VLAN Filter block size
+ spinlock_t stats_lock; /* protects stats accumulators */
+ };
+@@ -336,6 +358,15 @@ union mxl862xx_fw_version {
+ * policy. Set once in setup() and referenced by
+ * fill_cpu_trap_action() via bFidEnable. The PCE FID
+ * action field is 6 bits, so this value must be <= 63.
+ * @lag_bridge_ports: maps DSA LAG ID to firmware bridge port ID;
+ * zero means no bridge port allocated for that LAG.
+ * Indexed by lag->id (entry 0 is unused).
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
-@@ -352,6 +384,8 @@ struct mxl862xx_priv {
- u16 evlan_egress_size;
+@@ -355,6 +386,8 @@ struct mxl862xx_priv {
u16 cpu_evlan_ingress_size;
u16 vf_block_size;
+ u16 cpu_trap_fid;
+ u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
+ u8 trunk_hash;
struct delayed_work stats_work;
-From 5528f38c3d709417625eb7f36628be31727a8221 Mon Sep 17 00:00:00 2001
+From d919c2f8da9dbd4dda57ceebb5c8b103805b58d1 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 12:05:29 +0000
-Subject: [PATCH 18/26] net: dsa: mxl862xx: add support for mirror port
+Subject: [PATCH 11/19] net: dsa: mxl862xx: add support for mirror port
The MxL862xx hardware supports a single monitor port which can be
configured to mirror any other port's ingress and/or egress traffic.
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 12 +++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 1 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 118 ++++++++++++++++++++++++
- drivers/net/dsa/mxl862xx/mxl862xx.h | 8 ++
- 4 files changed, 139 insertions(+)
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 7 ++
+ 4 files changed, 138 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
#define MXL862XX_TFLOW_PCERULEWRITE (MXL862XX_TFLOW_MAGIC + 0x2)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -1084,6 +1084,8 @@ static int mxl862xx_setup(struct dsa_swi
+@@ -1100,6 +1100,8 @@ static int mxl862xx_setup(struct dsa_swi
(n_user_ports + n_cpu_ports);
}
ret = mxl862xx_setup_drop_meter(ds);
if (ret)
return ret;
-@@ -1973,6 +1975,120 @@ static int mxl862xx_setup_cpu_bridge(str
- return mxl862xx_set_bridge_port(ds, port);
+@@ -3167,6 +3169,120 @@ static int mxl862xx_fdb_del_per_fid(stru
+ return MXL862XX_API_WRITE(priv, MXL862XX_MAC_TABLEENTRYREMOVE, param);
}
+static int mxl862xx_port_mirror_add(struct dsa_switch *ds, int port,
+}
+
/**
- * mxl862xx_lag_master_port - Find the LAG master (lowest-numbered member)
- * @ds: DSA switch
-@@ -4068,6 +4184,8 @@ static const struct dsa_switch_ops mxl86
+ * mxl862xx_mac_portmap_add - Set port bits in a MAC table entry's portmap
+ * @priv: driver private data
+@@ -4096,6 +4212,8 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
.port_lag_change = mxl862xx_port_lag_change,
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -241,6 +241,8 @@ struct mxl862xx_port_stats {
+@@ -240,6 +240,8 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
* @lag: non-NULL when port is member of a LAG group;
* points to the DSA LAG structure
* @lag_tx_enabled: true when this port is active for TX in its LAG
-@@ -269,6 +271,9 @@ struct mxl862xx_port {
+@@ -265,6 +267,8 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
-+ /* Mirror state */
+ bool ingress_mirror;
+ bool egress_mirror;
- /* LAG state */
struct dsa_lag *lag;
bool lag_tx_enabled;
-@@ -366,6 +371,8 @@ union mxl862xx_fw_version {
+ u8 lag_hash_bits;
+@@ -367,6 +371,8 @@ union mxl862xx_fw_version {
* @trunk_hash: current global hash field bitmask (6 bits,
* MXL862XX_TRUNK_HASH_*); union of all active LAGs'
* hash requirements
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
-@@ -386,6 +393,7 @@ struct mxl862xx_priv {
- u16 vf_block_size;
+@@ -388,6 +394,7 @@ struct mxl862xx_priv {
+ u16 cpu_trap_fid;
u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
u8 trunk_hash;
+ int mirror_dest;
-From 4059d35a5bbf1901b2e0eb7126369cd713cacfce Mon Sep 17 00:00:00 2001
+From 64269d9d809962a0f7e68e9b618d81e561e3eb6f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:08 +0000
-Subject: [PATCH 19/26] net: dsa: wire flash_update devlink callback to drivers
+Subject: [PATCH 12/19] net: dsa: wire flash_update devlink callback to drivers
Add a devlink_flash_update callback to dsa_switch_ops so that DSA
drivers can support devlink dev flash without open-coding the devlink
-From 0145151dc68aa318d8addb6fe7f12c0967f951da Mon Sep 17 00:00:00 2001
+From fed4225f75b6fe6898e48f472cbbee0aaa046760 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:17 +0000
-Subject: [PATCH 20/26] net: dsa: mxl862xx: add SMDIO clause-22 register access
+Subject: [PATCH 13/19] net: dsa: mxl862xx: add SMDIO clause-22 register access
Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
SMDIO register access. MCUboot rescue mode only exposes clause-22
-From bdbca48510e3e96ed9210f20fa4244dd6df5d44a Mon Sep 17 00:00:00 2001
+From fa186b09e346e0b7f2504232731bc9e4dee0c600 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:31 +0000
-Subject: [PATCH 21/26] net: dsa: mxl862xx: add devlink flash_update and
+Subject: [PATCH 14/19] net: dsa: mxl862xx: add devlink flash_update and
info_get
Implement runtime firmware upgrade via "devlink dev flash" and version
mutex_lock_nested(&priv->mdiodev->bus->mdio_lock, MDIO_MUTEX_NESTED);
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -22,6 +22,7 @@
+@@ -23,6 +23,7 @@
#include "mxl862xx.h"
#include "mxl862xx-api.h"
#include "mxl862xx-cmd.h"
#include "mxl862xx-host.h"
#include "mxl862xx-phylink.h"
-@@ -4204,6 +4205,9 @@ static const struct dsa_switch_ops mxl86
- .get_pause_stats = mxl862xx_get_pause_stats,
+@@ -4233,6 +4234,9 @@ static const struct dsa_switch_ops mxl86
+ .get_rmon_stats = mxl862xx_get_rmon_stats,
.get_stats64 = mxl862xx_get_stats64,
.self_test = mxl862xx_serdes_self_test,
+ .devlink_info_get = mxl862xx_devlink_info_get,
static int mxl862xx_probe(struct mdio_device *mdiodev)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -394,6 +394,8 @@ struct mxl862xx_priv {
+@@ -395,6 +395,8 @@ struct mxl862xx_priv {
u16 lag_bridge_ports[MXL862XX_MAX_LAG_IDS + 1];
u8 trunk_hash;
int mirror_dest;
-From 8deb5be9638f7eb3009ed3eb619eedadee1df523 Mon Sep 17 00:00:00 2001
+From 05bc981690d6ef03143a094f28714aa0171ab571 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Tue, 24 Mar 2026 23:42:18 +0000
-Subject: [PATCH 22/26] net: dsa: mxl862xx: implement port MTU configuration
+Date: Wed, 1 Apr 2026 13:43:08 +0100
+Subject: [PATCH 15/19] net: dsa: mxl862xx: implement port MTU configuration
The firmware exposes a global max_packet_len register via
MXL862XX_COMMON_CFGSET. Since this is switch-wide rather than
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 50 +++++++++++++++++++++++++++++
- drivers/net/dsa/mxl862xx/mxl862xx.h | 4 +++
- 2 files changed, 54 insertions(+)
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 3 ++
+ 2 files changed, 53 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -11,6 +11,7 @@
- #include <linux/delay.h>
+@@ -12,6 +12,7 @@
#include <linux/etherdevice.h>
+ #include <linux/icmpv6.h>
#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
-@@ -3723,6 +3724,53 @@ static int mxl862xx_set_ageing_time(stru
+@@ -3727,6 +3728,53 @@ static int mxl862xx_set_ageing_time(stru
return ret;
}
static void mxl862xx_port_stp_state_set(struct dsa_switch *ds, int port,
u8 state)
{
-@@ -4174,6 +4222,8 @@ static const struct dsa_switch_ops mxl86
+@@ -4202,6 +4250,8 @@ static const struct dsa_switch_ops mxl86
.port_disable = mxl862xx_port_disable,
.port_fast_age = mxl862xx_port_fast_age,
.set_ageing_time = mxl862xx_set_ageing_time,
.port_pre_bridge_flags = mxl862xx_port_pre_bridge_flags,
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -249,6 +249,8 @@ struct mxl862xx_port_stats {
+@@ -248,6 +248,8 @@ struct mxl862xx_port_stats {
* @lag_hash_bits: hash field bitmask (MXL862XX_TRUNK_HASH_*) requested
* when this port joined its LAG; used to recompute the
* global trunk_hash when a LAG is destroyed
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
-@@ -278,6 +280,8 @@ struct mxl862xx_port {
+@@ -272,6 +274,7 @@ struct mxl862xx_port {
struct dsa_lag *lag;
bool lag_tx_enabled;
u8 lag_hash_bits;
-+ /* MTU */
+ int mtu;
- /* Hardware stats accumulation */
struct mxl862xx_port_stats stats;
- spinlock_t stats_lock;
+ spinlock_t stats_lock; /* protects stats accumulators */
+ };
-From 13a4c918cd9ded7207f38033511ab13f7aff9bd2 Mon Sep 17 00:00:00 2001
+From b723f7484006aadbaa51e16b870f3c98390605b1 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
-Date: Wed, 25 Mar 2026 01:47:19 +0000
-Subject: [PATCH 23/26] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
+Date: Wed, 1 Apr 2026 13:43:12 +0100
+Subject: [PATCH 16/19] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
Implement hairpin mode by including the port's own bridge port ID in
its forwarding portmap. When hairpin is enabled, bridged frames whose
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
- drivers/net/dsa/mxl862xx/mxl862xx.c | 29 +++++++++++++++++++++++++++--
- drivers/net/dsa/mxl862xx/mxl862xx.h | 6 ++++++
- 2 files changed, 33 insertions(+), 2 deletions(-)
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 12 ++++++++++--
+ drivers/net/dsa/mxl862xx/mxl862xx.h | 5 +++++
+ 2 files changed, 15 insertions(+), 2 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -811,6 +811,15 @@ static int mxl862xx_sync_bridge_members(
- __set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
- priv->ports[port].portmap);
-
-+ /* Hairpin: include the port's own bridge port so bridged
-+ * frames can egress the ingress port.
-+ * For LAG ports this adds the LAG bridge port, which
-+ * propagates to the LAG BP in the second loop below.
-+ */
-+ if (priv->ports[port].hairpin)
-+ __set_bit(mxl862xx_lag_bridge_port(priv, port),
-+ priv->ports[port].portmap);
-+
- err = mxl862xx_set_bridge_port(ds, port);
- if (err)
- ret = err;
-@@ -3898,7 +3907,7 @@ static int mxl862xx_port_pre_bridge_flag
+@@ -759,6 +759,10 @@ static int __mxl862xx_set_bridge_port(st
+ }
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ mxl862xx_cpu_bridge_port_id(ds, port));
++ if (p->hairpin)
++ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
++ mxl862xx_lag_bridge_port(priv,
++ port));
+ } else {
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ mxl862xx_cpu_bridge_port_id(ds, port));
+@@ -3895,7 +3899,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
return -EINVAL;
return 0;
-@@ -3912,6 +3921,7 @@ static int mxl862xx_port_bridge_flags(st
- unsigned long old_block = priv->ports[port].flood_block;
- unsigned long block = old_block;
- int ret;
-+ u16 bp;
-
- if (flags.mask & BR_FLOOD) {
- if (flags.val & BR_FLOOD)
-@@ -3940,7 +3950,22 @@ static int mxl862xx_port_bridge_flags(st
+@@ -3937,7 +3941,11 @@ static int mxl862xx_port_bridge_flags(st
if (flags.mask & BR_LEARNING)
priv->ports[port].learning = !!(flags.val & BR_LEARNING);
-- if ((block != old_block) || (flags.mask & BR_LEARNING)) {
-+ if (flags.mask & BR_HAIRPIN_MODE) {
-+ bp = mxl862xx_lag_bridge_port(priv, port);
+- if (block != old_block || (flags.mask & BR_LEARNING)) {
++ if (flags.mask & BR_HAIRPIN_MODE)
+ priv->ports[port].hairpin = !!(flags.val & BR_HAIRPIN_MODE);
+
-+ /* Hairpin adds/removes the port's own bridge port from its
-+ * cached portmap. Only this port is affected -- push the
-+ * updated portmap directly.
-+ */
-+ if (flags.val & BR_HAIRPIN_MODE)
-+ __set_bit(bp, priv->ports[port].portmap);
-+ else
-+ __clear_bit(bp, priv->ports[port].portmap);
-+ }
-+
+ if ((block != old_block) ||
+ (flags.mask & (BR_LEARNING | BR_HAIRPIN_MODE))) {
priv->ports[port].flood_block = block;
if (ret)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -241,6 +241,10 @@ struct mxl862xx_port_stats {
+@@ -240,6 +240,10 @@ struct mxl862xx_port_stats {
* @stats_lock: protects accumulator reads in .get_stats64 against
* concurrent updates from the polling work
* @tag_8021q_vid: currently assigned tag_8021q management VID
* @ingress_mirror: true when ingress mirroring is active on this port
* @egress_mirror: true when egress mirroring is active on this port
* @lag: non-NULL when port is member of a LAG group;
-@@ -273,6 +277,8 @@ struct mxl862xx_port {
+@@ -269,6 +273,7 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
-+ /* Hairpin state */
+ bool hairpin;
- /* Mirror state */
bool ingress_mirror;
bool egress_mirror;
+ struct dsa_lag *lag;
-From d49d1f8bee29269def7593f980d0e08bfb5c3ef8 Mon Sep 17 00:00:00 2001
+From d8f20ba50ce0f93f34a41a9b833be76a5d1d2714 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 01:51:33 +0000
-Subject: [PATCH 24/26] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
+Subject: [PATCH 17/19] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
Implement port isolation by excluding isolated ports from each other's
forwarding portmaps in sync_bridge_members. Non-isolated ports can
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
- drivers/net/dsa/mxl862xx/mxl862xx.c | 26 +++++++++++++++++++++++++-
+ drivers/net/dsa/mxl862xx/mxl862xx.c | 20 +++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 4 ++++
- 2 files changed, 29 insertions(+), 1 deletion(-)
+ 2 files changed, 23 insertions(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -802,6 +802,14 @@ static int mxl862xx_sync_bridge_members(
- */
- if (!mxl862xx_is_lag_master(priv, member))
+@@ -752,6 +752,8 @@ static int __mxl862xx_set_bridge_port(st
continue;
-+
-+ /* Isolated ports cannot forward to each other.
-+ * Non-isolated ports can reach everyone.
-+ */
-+ if (priv->ports[port].isolated &&
-+ priv->ports[member].isolated)
+ if (!mxl862xx_is_lag_master(priv, member_dp->index))
+ continue;
++ if (p->isolated && priv->ports[member_dp->index].isolated)
+ continue;
-+
- if (member != port) {
- bp = mxl862xx_lag_bridge_port(priv,
- member);
-@@ -3907,7 +3915,7 @@ static int mxl862xx_port_pre_bridge_flag
+ mxl862xx_fw_portmap_set_bit(
+ br_port_cfg.bridge_port_map,
+ mxl862xx_lag_bridge_port(priv,
+@@ -3899,7 +3901,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
return -EINVAL;
return 0;
-@@ -3920,6 +3928,7 @@ static int mxl862xx_port_bridge_flags(st
+@@ -3912,6 +3914,7 @@ static int mxl862xx_port_bridge_flags(st
struct mxl862xx_priv *priv = ds->priv;
unsigned long old_block = priv->ports[port].flood_block;
unsigned long block = old_block;
+ struct dsa_port *dp;
int ret;
- u16 bp;
-@@ -3972,6 +3981,21 @@ static int mxl862xx_port_bridge_flags(st
+ if (flags.mask & BR_FLOOD) {
+@@ -3952,6 +3955,21 @@ static int mxl862xx_port_bridge_flags(st
return ret;
}
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
-@@ -214,6 +214,9 @@ struct mxl862xx_port_stats {
+@@ -213,6 +213,9 @@ struct mxl862xx_port_stats {
* @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
-@@ -261,6 +264,7 @@ struct mxl862xx_port {
+@@ -259,6 +262,7 @@ struct mxl862xx_port {
+ struct mxl862xx_priv *priv;
u16 fid;
- DECLARE_BITMAP(portmap, MXL862XX_MAX_BRIDGE_PORTS);
unsigned long flood_block;
+ bool isolated;
bool learning;
bool setup_done;
- /* VLAN state */
+ u16 pvid;
-From c2fb7f0df63ac994850f766e7f2eb50c6c5ef2cf Mon Sep 17 00:00:00 2001
+From 8856f7610167a3005ecef401c8528111d153b554 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:17:49 +0000
-Subject: [PATCH 25/26] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
+Subject: [PATCH 18/19] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
workaround for old firmware
Re-introduce the mxl862xx_disable_fw_global_rules() function that
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
-@@ -477,6 +477,43 @@ static int mxl862xx_setup_drop_meter(str
+@@ -449,6 +449,43 @@ static int mxl862xx_setup_drop_meter(str
return MXL862XX_API_WRITE(priv, MXL862XX_COMMON_REGISTERMOD, reg);
}
+ return 0;
+}
- /* Per-CTP offset used for the link-local trap rule. Each port's CTP
- * flow-table block is pre-allocated by the firmware during init (44
-@@ -1109,9 +1146,11 @@ static int mxl862xx_setup(struct dsa_swi
+ /* Per-CTP offsets for protocol trap rules. Each port's CTP flow-table
+ * block is pre-allocated by the firmware during init (44 entries per
+@@ -1114,9 +1151,11 @@ static int mxl862xx_setup(struct dsa_swi
if (ret)
return ret;
--- /dev/null
+From df0c747216063041ba0d786b01f9b1e2aba5316a Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 22 Apr 2026 15:15:52 +0100
+Subject: [PATCH] DO NOT SUBMIT: net: dsa: mxl862xx: increase CMD timeout
+
+Lift the command timeout by 10x from 500ms to 5s.
+This is done because older firmware can be extremely slow to respond
+and cause -ETIMEOUT during setup, or crash the PHY state machine.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/net/dsa/mxl862xx/mxl862xx-host.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
++++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+@@ -207,7 +207,7 @@ static int mxl862xx_busy_wait(struct mxl
+ int val;
+
+ return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
+- !(val & CTRL_BUSY_MASK), 15, 500000);
++ !(val & CTRL_BUSY_MASK), 50, 5000000);
+ }
+
+ /* Issue a firmware command with CRC-6 protection on the ctrl and len_ret