diff --git a/target/linux/generic/pending-6.12/760-00-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch b/target/linux/generic/backport-6.12/771-v7.1-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch similarity index 72% rename from target/linux/generic/pending-6.12/760-00-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch rename to target/linux/generic/backport-6.12/771-v7.1-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch index d215cbbb8d..ad42878eb7 100644 --- a/target/linux/generic/pending-6.12/760-00-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch +++ b/target/linux/generic/backport-6.12/771-v7.1-net-dsa-mxl862xx-cancel-pending-work-on-probe-error.patch @@ -1,13 +1,16 @@ -From 3fd163f5bb88de426ca9847549f94b4296170ef0 Mon Sep 17 00:00:00 2001 +From e9abf1da0af3f787a03b249945e5ca726c1b8013 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 +Reviewed-by: Andrew Lunn +Link: https://patch.msgid.link/3fd163f5bb88de426ca9847549f94b4296170ef0.1774911025.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- drivers/net/dsa/mxl862xx/mxl862xx.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch b/target/linux/generic/backport-6.12/772-v7.1-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch similarity index 65% rename from target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch rename to target/linux/generic/backport-6.12/772-v7.1-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch index c203174a8a..c0390aae96 100644 --- a/target/linux/generic/pending-6.12/760-01-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch +++ b/target/linux/generic/backport-6.12/772-v7.1-net-dsa-move-dsa_bridge_ports-helper-to-dsa.h.patch @@ -1,7 +1,7 @@ -From cd698f1ae94c16499e2714b31dd6048e6f9f068d Mon Sep 17 00:00:00 2001 +From b0a79590d10847f190ed377d2664377d7068191d Mon Sep 17 00:00:00 2001 From: Daniel Golle -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. @@ -10,9 +10,11 @@ Move the helper as static inline function into dsa.h, so other driver can make use of it as well. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- - 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 diff --git a/target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch b/target/linux/generic/backport-6.12/773-v7.1-net-dsa-add-bridge-member-iteration-macro.patch similarity index 80% rename from target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch rename to target/linux/generic/backport-6.12/773-v7.1-net-dsa-add-bridge-member-iteration-macro.patch index 67dc948461..26bd74ce1e 100644 --- a/target/linux/generic/pending-6.12/760-02-net-dsa-add-bridge-member-iteration-macro.patch +++ b/target/linux/generic/backport-6.12/773-v7.1-net-dsa-add-bridge-member-iteration-macro.patch @@ -1,7 +1,7 @@ -From c161533e1605a7282563c139323a3913890fdb72 Mon Sep 17 00:00:00 2001 +From f259e08494c47c614ce7b6d3079d1e0d3f30ae66 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -15,6 +15,8 @@ directly, and use it for the existing dsa_bridge_ports() inline helper. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/e7136aaa26773f39e805a00fe4ecf13cd2b83fc0.1775049897.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- include/net/dsa.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch b/target/linux/generic/backport-6.12/774-v7.1-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch similarity index 71% rename from target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch rename to target/linux/generic/backport-6.12/774-v7.1-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch index e47f4e44c8..574a396c82 100644 --- a/target/linux/generic/pending-6.12/760-03-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch +++ b/target/linux/generic/backport-6.12/774-v7.1-dsa-tag_mxl862xx-set-dsa_default_offload_fwd_mark.patch @@ -1,7 +1,7 @@ -From 753efe27a9afee52c4ad42098a9b9278366d63cc Mon Sep 17 00:00:00 2001 +From 4250ff1640ea1ede99bfe02ca949acbcc6c0927f Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -11,6 +11,8 @@ Link-local frames are directly trapped to the CPU port only, so don't set dsa_default_offload_fwd_mark() on those. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/e1161c90894ddc519c57dc0224b3a0f6bfa1d2d6.1775049897.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- net/dsa/tag_mxl862xx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch b/target/linux/generic/backport-6.12/775-v7.1-net-dsa-mxl862xx-implement-bridge-offloading.patch similarity index 87% rename from target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch rename to target/linux/generic/backport-6.12/775-v7.1-net-dsa-mxl862xx-implement-bridge-offloading.patch index a545e70677..9b41f35c1a 100644 --- a/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-implement-bridge-offloading.patch +++ b/target/linux/generic/backport-6.12/775-v7.1-net-dsa-mxl862xx-implement-bridge-offloading.patch @@ -1,7 +1,7 @@ -From ce0664ff8f75c3ab01101c3f0f8569924d948775 Mon Sep 17 00:00:00 2001 +From 340bdf984613c4a9241d678915e513824f5a9b19 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -24,9 +24,17 @@ dsa_switch_for_each_bridge_member(). As there are now more users of the BRIDGEPORT_CONFIG_SET API and the state of each port is cached locally, introduce a helper function -mxl862xx_set_bridge_port(struct dsa_switch *ds, int port) which is -then used to replace the direct calls to the API in -mxl862xx_setup_cpu_bridge() and mxl862xx_add_single_port_bridge(). +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 @@ -36,12 +44,14 @@ registers -- without that at least one 64-byte packet could still pass before the meter would change from 'yellow' into 'red' state. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/dd079180e2098e5f9626fcd149b9bad9a1b5a1b2.1775049897.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- - 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 @@ -384,7 +394,7 @@ Signed-off-by: Daniel Golle 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; } @@ -479,11 +489,40 @@ Signed-off-by: Daniel Golle + 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 | @@ -492,8 +531,6 @@ Signed-off-by: Daniel Golle + 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)); @@ -510,24 +547,11 @@ Signed-off-by: Daniel Golle +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; + } @@ -535,7 +559,7 @@ Signed-off-by: Daniel Golle + 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; @@ -544,8 +568,7 @@ Signed-off-by: Daniel Golle + 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, @@ -567,38 +590,11 @@ Signed-off-by: Daniel Golle + + 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; @@ -609,66 +605,49 @@ Signed-off-by: Daniel Golle 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); @@ -679,6 +658,32 @@ Signed-off-by: Daniel Golle + + 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); @@ -691,30 +696,10 @@ Signed-off-by: Daniel Golle - 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, @@ -732,12 +717,54 @@ Signed-off-by: Daniel Golle 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; + @@ -765,7 +792,7 @@ Signed-off-by: Daniel Golle } 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); } @@ -876,7 +903,6 @@ Signed-off-by: Daniel Golle + 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)); @@ -892,7 +918,6 @@ Signed-off-by: Daniel Golle + 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)); @@ -915,7 +940,6 @@ Signed-off-by: Daniel Golle + 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); @@ -930,7 +954,6 @@ Signed-off-by: Daniel Golle + 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); @@ -1007,8 +1030,6 @@ Signed-off-by: Daniel Golle + * 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) @@ -1031,8 +1052,6 @@ Signed-off-by: Daniel Golle + 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(); + @@ -1042,17 +1061,13 @@ Signed-off-by: Daniel Golle + 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(); +} @@ -1115,7 +1130,7 @@ Signed-off-by: Daniel Golle + 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) @@ -1149,7 +1164,7 @@ Signed-off-by: Daniel Golle }; 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; @@ -1158,7 +1173,7 @@ Signed-off-by: Daniel Golle 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; @@ -1186,7 +1201,7 @@ Signed-off-by: Daniel Golle 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; @@ -1194,7 +1209,7 @@ Signed-off-by: Daniel Golle 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); @@ -1216,7 +1231,7 @@ Signed-off-by: Daniel Golle 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); @@ -1228,7 +1243,7 @@ Signed-off-by: Daniel Golle --- 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 @@ -1246,37 +1261,6 @@ Signed-off-by: Daniel Golle +#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) @@ -1320,8 +1304,6 @@ Signed-off-by: Daniel Golle + * @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) @@ -1343,7 +1325,6 @@ Signed-off-by: Daniel Golle +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; diff --git a/target/linux/generic/backport-6.12/776-v7.1-net-dsa-mxl862xx-reject-DSA_PORT_TYPE_DSA.patch b/target/linux/generic/backport-6.12/776-v7.1-net-dsa-mxl862xx-reject-DSA_PORT_TYPE_DSA.patch new file mode 100644 index 0000000000..0550b57353 --- /dev/null +++ b/target/linux/generic/backport-6.12/776-v7.1-net-dsa-mxl862xx-reject-DSA_PORT_TYPE_DSA.patch @@ -0,0 +1,50 @@ +From 3a4056ec7ec8f71ae9722f86d3cfbc4589deeac4 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +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 +Link: https://patch.msgid.link/b686f3a22d8a6e7d470e7aa98da811a996a229b9.1775581804.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski +--- + 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. diff --git a/target/linux/generic/backport-6.12/777-v7.1-net-dsa-mxl862xx-don-t-skip-early-bridge-port-config.patch b/target/linux/generic/backport-6.12/777-v7.1-net-dsa-mxl862xx-don-t-skip-early-bridge-port-config.patch new file mode 100644 index 0000000000..5020526777 --- /dev/null +++ b/target/linux/generic/backport-6.12/777-v7.1-net-dsa-mxl862xx-don-t-skip-early-bridge-port-config.patch @@ -0,0 +1,38 @@ +From 71934b9e6f36b1786bd969c0e1d2de8f9bd65f0f Mon Sep 17 00:00:00 2001 +From: Daniel Golle +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 +Link: https://patch.msgid.link/15962aac29ebe0a6eb77565451acff880c41ef33.1775581804.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski +--- + 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)) { diff --git a/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch b/target/linux/generic/backport-6.12/778-v7.1-net-dsa-mxl862xx-implement-VLAN-functionality.patch similarity index 82% rename from target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch rename to target/linux/generic/backport-6.12/778-v7.1-net-dsa-mxl862xx-implement-VLAN-functionality.patch index c9e1769d98..5708dec414 100644 --- a/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-implement-VLAN-functionality.patch +++ b/target/linux/generic/backport-6.12/778-v7.1-net-dsa-mxl862xx-implement-VLAN-functionality.patch @@ -1,7 +1,7 @@ -From 0d88d02cc9dccad01ff88f54e1beee867107b942 Mon Sep 17 00:00:00 2001 +From d587f9b6dcc98c1e8aeb5c189a7bfac60d6d29ac Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -16,8 +16,8 @@ egress rules at all, so they consume only a VF entry. 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). @@ -34,12 +34,14 @@ rather than worst-case pre-allocation, or by sharing EVLAN egress and VLAN Filter blocks across ports with identical VID sets. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/9be29637675342b109a85fa08f5378800d9f7b78.1775581804.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- - 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 @@ -101,14 +103,14 @@ Signed-off-by: Daniel Golle +/** + * 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, +}; + @@ -409,16 +411,14 @@ Signed-off-by: Daniel Golle #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 */ +}; + @@ -457,7 +457,7 @@ Signed-off-by: Daniel Golle + * 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[] = { @@ -486,27 +486,26 @@ Signed-off-by: Daniel Golle +}; + +/* -+ * 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 | @@ -574,23 +573,13 @@ Signed-off-by: Daniel Golle + 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) +{ @@ -609,15 +598,6 @@ Signed-off-by: Daniel Golle + 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) +{ @@ -666,10 +646,10 @@ Signed-off-by: Daniel Golle + 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; @@ -679,7 +659,7 @@ Signed-off-by: Daniel Golle 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; @@ -695,7 +675,7 @@ Signed-off-by: Daniel Golle + * 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. + */ @@ -730,23 +710,10 @@ Signed-off-by: Daniel Golle 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, @@ -788,11 +755,6 @@ Signed-off-by: Daniel Golle + 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 = @@ -809,22 +771,6 @@ Signed-off-by: Daniel Golle + } + 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 = @@ -845,15 +791,6 @@ Signed-off-by: Daniel Golle + 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) +{ @@ -876,19 +813,6 @@ Signed-off-by: Daniel Golle + 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, @@ -908,15 +832,6 @@ Signed-off-by: Daniel Golle + 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) +{ @@ -931,13 +846,8 @@ Signed-off-by: Daniel Golle + 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; + @@ -948,17 +858,6 @@ Signed-off-by: Daniel Golle + 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) @@ -995,17 +894,6 @@ Signed-off-by: Daniel Golle + 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) +{ @@ -1035,12 +923,11 @@ Signed-off-by: Daniel Golle + 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, @@ -1049,16 +936,6 @@ Signed-off-by: Daniel Golle + 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); @@ -1068,20 +945,6 @@ Signed-off-by: Daniel Golle + 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]; @@ -1099,19 +962,6 @@ Signed-off-by: Daniel Golle + 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]; @@ -1131,10 +981,7 @@ Signed-off-by: Daniel Golle + } + + 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) @@ -1182,12 +1029,13 @@ Signed-off-by: Daniel Golle +{ + 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 @@ -1199,17 +1047,20 @@ Signed-off-by: Daniel Golle + + 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, @@ -1255,21 +1106,32 @@ Signed-off-by: Daniel Golle + 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; @@ -1280,13 +1142,22 @@ Signed-off-by: Daniel Golle +{ + 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; @@ -1299,28 +1170,45 @@ Signed-off-by: Daniel Golle + */ + 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; @@ -1332,41 +1220,21 @@ Signed-off-by: Daniel Golle + * 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; @@ -1374,28 +1242,22 @@ Signed-off-by: Daniel Golle 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; @@ -1403,7 +1265,7 @@ Signed-off-by: Daniel Golle 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, @@ -1424,7 +1286,7 @@ Signed-off-by: Daniel Golle /* 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 } /** @@ -1491,7 +1353,7 @@ Signed-off-by: Daniel Golle * 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 @@ -1503,11 +1365,10 @@ Signed-off-by: Daniel Golle * @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; @@ -1516,7 +1377,7 @@ Signed-off-by: Daniel Golle 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 @@ -1551,7 +1412,7 @@ Signed-off-by: Daniel Golle */ 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]; diff --git a/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch b/target/linux/generic/backport-6.12/779-v7.1-net-dsa-mxl862xx-add-ethtool-statistics-support.patch similarity index 83% rename from target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch rename to target/linux/generic/backport-6.12/779-v7.1-net-dsa-mxl862xx-add-ethtool-statistics-support.patch index 9788fb4021..e733713bd3 100644 --- a/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-ethtool-statistics-support.patch +++ b/target/linux/generic/backport-6.12/779-v7.1-net-dsa-mxl862xx-add-ethtool-statistics-support.patch @@ -1,7 +1,7 @@ -From 0067d79d10becfc5779fb50d5c0ac152cc5dc303 Mon Sep 17 00:00:00 2001 +From e6295d124644b14a12b55edf5d3e89cf86a4a2ce Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -16,11 +16,13 @@ legacy ethtool -S support. Implement .get_eth_mac_stats, IEEE 802.3 statistics interface. Signed-off-by: Daniel Golle +Link: https://patch.msgid.link/480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- - 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 @@ -120,7 +122,7 @@ Signed-off-by: Daniel Golle + * @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; @@ -192,7 +194,7 @@ Signed-off-by: Daniel Golle #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) @@ -209,60 +211,34 @@ Signed-off-by: Daniel Golle + .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; + @@ -285,7 +261,7 @@ Signed-off-by: Daniel Golle + 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); @@ -365,10 +341,40 @@ Signed-off-by: Daniel Golle + 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, @@ -378,6 +384,7 @@ Signed-off-by: Daniel Golle + .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, diff --git a/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch b/target/linux/generic/backport-6.12/780-v7.1-net-dsa-mxl862xx-implement-.get_stats64.patch similarity index 80% rename from target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch rename to target/linux/generic/backport-6.12/780-v7.1-net-dsa-mxl862xx-implement-.get_stats64.patch index 55b81a7023..b985f9ecbc 100644 --- a/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-implement-.get_stats64.patch +++ b/target/linux/generic/backport-6.12/780-v7.1-net-dsa-mxl862xx-implement-.get_stats64.patch @@ -1,20 +1,22 @@ -From bab5a69e3872a693069e430a1fa0d2825ea83b4f Mon Sep 17 00:00:00 2001 +From a21d33a5265f0b31d935a8b9b2b6faefb5185911 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 +Link: https://patch.msgid.link/fa38548ba05866879e8912721edc91947ce4ff12.1775951347.git.daniel@makrotopia.org +Signed-off-by: Jakub Kicinski --- 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 @@ -69,7 +71,7 @@ Signed-off-by: Daniel Golle 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; @@ -79,10 +81,11 @@ Signed-off-by: Daniel Golle 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. + */ @@ -238,15 +241,15 @@ Signed-off-by: Daniel Golle 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); @@ -269,7 +272,7 @@ Signed-off-by: Daniel Golle 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; @@ -279,7 +282,7 @@ Signed-off-by: Daniel Golle 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); @@ -291,11 +294,45 @@ Signed-off-by: Daniel Golle 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 @@ -303,7 +340,6 @@ Signed-off-by: Daniel Golle + * handling 32-bit wrap correctly via unsigned subtraction. + */ +struct mxl862xx_port_stats { -+ /* 64-bit accumulators */ + u64 rx_packets; + u64 tx_packets; + u64 rx_bytes; @@ -317,7 +353,6 @@ Signed-off-by: Daniel Golle + 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; @@ -339,7 +374,7 @@ Signed-off-by: Daniel Golle * 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. @@ -350,13 +385,12 @@ Signed-off-by: Daniel Golle */ 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 */ @@ -378,7 +412,7 @@ Signed-off-by: Daniel Golle * @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 diff --git a/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch b/target/linux/generic/pending-6.12/760-01-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch similarity index 89% rename from target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch rename to target/linux/generic/pending-6.12/760-01-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch index 03aeb8c9b7..1559511d98 100644 --- a/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch +++ b/target/linux/generic/pending-6.12/760-01-net-dsa-mxl862xx-store-firmware-version-for-feature-.patch @@ -1,7 +1,7 @@ -From da12469e73282da814163125153f381823e33f20 Mon Sep 17 00:00:00 2001 +From 60a23e663e0c607ae4ed871aaa24d257051ad557 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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), @@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle --- 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)); @@ -40,8 +40,8 @@ Signed-off-by: Daniel Golle #include #include #include -@@ -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 */ }; +/** @@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle /* 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) @@ -88,7 +88,7 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch b/target/linux/generic/pending-6.12/760-02-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch similarity index 95% rename from target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch rename to target/linux/generic/pending-6.12/760-02-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch index 2ce4d1fb0e..6d36ad4893 100644 --- a/target/linux/generic/pending-6.12/760-09-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch +++ b/target/linux/generic/pending-6.12/760-02-net-dsa-mxl862xx-move-phylink-stubs-to-mxl862xx-phyl.patch @@ -1,7 +1,7 @@ -From f7606470d398e4091e1bc405bf2125dc5fc99919 Mon Sep 17 00:00:00 2001 +From cefa0447dc95a4ddd5093f7b8cf35e654870283f Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -110,7 +110,7 @@ Signed-off-by: Daniel Golle #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; } @@ -127,7 +127,7 @@ Signed-off-by: Daniel Golle 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, }; diff --git a/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch b/target/linux/generic/pending-6.12/760-03-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch similarity index 94% rename from target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch rename to target/linux/generic/pending-6.12/760-03-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch index 7ac42cb3a6..50af43a019 100644 --- a/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch +++ b/target/linux/generic/pending-6.12/760-03-net-dsa-mxl862xx-move-API-macros-to-mxl862xx-host.h.patch @@ -1,7 +1,7 @@ -From e583eeeb907f0abeef2082162293a5d63b9fd6fa Mon Sep 17 00:00:00 2001 +From 3c1d77006daca1df20d612850535bc6050e266ee Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 diff --git a/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch b/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch similarity index 99% rename from target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch rename to target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch index 6fe50aeb90..38d6729997 100644 --- a/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch +++ b/target/linux/generic/pending-6.12/760-04-net-dsa-mxl862xx-add-support-for-SerDes-ports.patch @@ -990,7 +990,7 @@ Signed-off-by: Daniel Golle #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; @@ -999,7 +999,7 @@ Signed-off-by: Daniel Golle 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; @@ -1011,7 +1011,7 @@ Signed-off-by: Daniel Golle * 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 { }; /** @@ -1034,7 +1034,7 @@ Signed-off-by: Daniel Golle * 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() @@ -1043,7 +1043,7 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch b/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch similarity index 96% rename from target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch rename to target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch index 749a1ee2a6..1afbc17f15 100644 --- a/target/linux/generic/pending-6.12/760-12-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch +++ b/target/linux/generic/pending-6.12/760-05-net-dsa-mxl862xx-add-SerDes-ethtool-statistics.patch @@ -1,7 +1,7 @@ -From 24d752291784e30d7329bed15744bbbc6a3e2485 Mon Sep 17 00:00:00 2001 +From 3659914c43a587a1ca6418867834831aa518ac35 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -239,7 +239,7 @@ Signed-off-by: Daniel Golle #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); @@ -248,7 +248,7 @@ Signed-off-by: Daniel Golle } 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; @@ -257,7 +257,7 @@ Signed-off-by: Daniel Golle } 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); } diff --git a/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch b/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch similarity index 96% rename from target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch rename to target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch index baba8311c6..1fdcff672e 100644 --- a/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch +++ b/target/linux/generic/pending-6.12/760-06-net-dsa-mxl862xx-add-SerDes-self-test-via-PRBS-and-B.patch @@ -1,7 +1,7 @@ -From ee227a5e4c74f599cc1b34578b32214d5873ad2f Mon Sep 17 00:00:00 2001 +From ce66c0be462c8500dfc483395e68be4326ebf296 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -198,9 +198,9 @@ Signed-off-by: Daniel Golle #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, }; diff --git a/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch b/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-trap-link-local-and-multicast-snoop.patch similarity index 78% rename from target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch rename to target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-trap-link-local-and-multicast-snoop.patch index 56d3612310..935a82d168 100644 --- a/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-trap-link-local-frames-to-the-CPU-p.patch +++ b/target/linux/generic/pending-6.12/760-07-net-dsa-mxl862xx-trap-link-local-and-multicast-snoop.patch @@ -1,14 +1,28 @@ -From 43eb3eed250ea4e7e83371fcbf2bfb8d626eade6 Mon Sep 17 00:00:00 2001 +From e6defbd42db6e64c2bb203f82b7d7f8c0691a052 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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. @@ -17,8 +31,9 @@ Signed-off-by: Daniel Golle --- 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 @@ -736,7 +751,15 @@ Signed-off-by: Daniel Golle #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 + #include + #include ++#include + #include + #include + #include +@@ -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)); @@ -748,17 +771,54 @@ Signed-off-by: Daniel Golle 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, @@ -778,54 +838,164 @@ Signed-off-by: Daniel Golle + */ +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; + -+ return MXL862XX_API_WRITE(ds->priv, MXL862XX_TFLOW_PCERULEWRITE, -+ rule); ++ 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; ++ ++ 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; -+ /* install link-local trap for this user port */ ++ ++ /* 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 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; + }; + diff --git a/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch b/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch similarity index 81% rename from target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch rename to target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch index d590f54f47..48ffab50ec 100644 --- a/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch +++ b/target/linux/generic/pending-6.12/760-08-net-dsa-mxl862xx-warn-about-old-firmware-default-PCE.patch @@ -1,7 +1,7 @@ -From e18f5b235d8df21209c73f4f0bbc00cc3a1973ba Mon Sep 17 00:00:00 2001 +From 264838ee8ee3ad611a84df96d889429f1ded2148 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -19,14 +19,14 @@ Signed-off-by: Daniel Golle --- 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); diff --git a/target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch b/target/linux/generic/pending-6.12/760-09-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch similarity index 83% rename from target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch rename to target/linux/generic/pending-6.12/760-09-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch index 9b7a719db0..6cebfacd98 100644 --- a/target/linux/generic/pending-6.12/760-16-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch +++ b/target/linux/generic/pending-6.12/760-09-net-dsa-add-802.1Q-VLAN-based-tag-driver-for-MxL862x.patch @@ -1,7 +1,7 @@ -From 04929904d3a7d824593587e52e3ed21d6f0f109a Mon Sep 17 00:00:00 2001 +From d7ed9b681298d206d81e9cf3c93558d6e912ae88 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -22,13 +22,13 @@ Signed-off-by: Daniel Golle 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 @@ -290,7 +290,7 @@ Signed-off-by: Daniel Golle #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 #include #include @@ -298,9 +298,9 @@ Signed-off-by: Daniel Golle #include #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 */ @@ -308,7 +308,7 @@ Signed-off-by: Daniel Golle }; 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 */ @@ -316,8 +316,8 @@ Signed-off-by: Daniel Golle }; /* 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 }, }; +/* @@ -387,9 +387,9 @@ Signed-off-by: Daniel Golle } /* 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 @@ -463,94 +463,95 @@ Signed-off-by: Daniel Golle + 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; +@@ -552,11 +696,17 @@ static int mxl862xx_set_bridge_port(stru + return 0; -@@ -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; - -- /* 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; @@ -565,7 +566,7 @@ Signed-off-by: Daniel Golle 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); @@ -574,7 +575,7 @@ Signed-off-by: Daniel Golle * 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. @@ -589,11 +590,11 @@ Signed-off-by: Daniel Golle + * 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. */ @@ -656,16 +657,19 @@ Signed-off-by: Daniel Golle } 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(). @@ -673,10 +677,10 @@ Signed-off-by: Daniel Golle + 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) @@ -690,45 +694,67 @@ Signed-off-by: Daniel Golle + 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 @@ -775,18 +801,19 @@ Signed-off-by: Daniel Golle +} + +/** - * 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, @@ -796,7 +823,7 @@ Signed-off-by: Daniel Golle { 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; @@ -828,7 +855,7 @@ Signed-off-by: Daniel Golle } 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], @@ -837,10 +864,11 @@ Signed-off-by: Daniel Golle 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 @@ -862,10 +890,23 @@ Signed-off-by: Daniel Golle +} + +/** - * 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; @@ -875,8 +916,8 @@ Signed-off-by: Daniel Golle 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 @@ -900,7 +941,7 @@ Signed-off-by: Daniel Golle 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, @@ -931,7 +972,7 @@ Signed-off-by: Daniel Golle 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, @@ -941,9 +982,9 @@ Signed-off-by: Daniel Golle 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 @@ -963,63 +1004,15 @@ Signed-off-by: Daniel Golle ret = mxl862xx_evlan_program_ingress(priv, port); if (ret) -@@ -1491,18 +1868,19 @@ static int mxl862xx_setup_cpu_bridge(str +@@ -1493,22 +1901,575 @@ static void mxl862xx_port_bridge_leave(s + port, ERR_PTR(err)); - /* 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. + /* 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 @@ -1343,34 +1336,21 @@ Signed-off-by: Daniel Golle +} + +/** -+ * 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. @@ -1386,6 +1366,10 @@ Signed-off-by: Daniel Golle + 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; @@ -1516,7 +1500,9 @@ Signed-off-by: Daniel Golle + + 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) { @@ -1596,11 +1582,10 @@ Signed-off-by: Daniel Golle 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, @@ -1609,7 +1594,6 @@ Signed-off-by: Daniel Golle 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 && @@ -1619,37 +1603,70 @@ Signed-off-by: Daniel Golle 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; @@ -1659,7 +1676,7 @@ Signed-off-by: Daniel Golle 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; } @@ -1668,7 +1685,7 @@ Signed-off-by: Daniel Golle { 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 } } @@ -1701,12 +1718,12 @@ Signed-off-by: Daniel Golle + 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; +} + @@ -1751,7 +1768,7 @@ Signed-off-by: Daniel Golle + * 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 + * @@ -1796,7 +1813,7 @@ Signed-off-by: Daniel Golle + * 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 + * @@ -1852,15 +1869,15 @@ Signed-off-by: Daniel Golle + * @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, @@ -1869,11 +1886,14 @@ Signed-off-by: Daniel Golle + __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); +} @@ -1921,7 +1941,7 @@ Signed-off-by: Daniel Golle 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) { @@ -1930,7 +1950,7 @@ Signed-off-by: Daniel Golle 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) { @@ -1940,7 +1960,7 @@ Signed-off-by: Daniel Golle + 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; @@ -1954,7 +1974,7 @@ Signed-off-by: Daniel Golle 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; } @@ -1962,7 +1982,7 @@ Signed-off-by: Daniel Golle + * 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 + */ @@ -1982,7 +2002,7 @@ Signed-off-by: Daniel Golle + * 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) + */ @@ -2021,7 +2041,6 @@ Signed-off-by: Daniel Golle 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)); @@ -2051,23 +2070,24 @@ Signed-off-by: Daniel Golle + 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, @@ -2076,13 +2096,17 @@ Signed-off-by: Daniel Golle + __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); } @@ -2111,7 +2135,6 @@ Signed-off-by: Daniel Golle 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); @@ -2129,7 +2152,6 @@ Signed-off-by: Daniel Golle - 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); @@ -2153,34 +2175,33 @@ Signed-off-by: Daniel Golle 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); + } @@ -2194,20 +2215,19 @@ Signed-off-by: Daniel Golle + 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, @@ -2217,7 +2237,7 @@ Signed-off-by: Daniel Golle .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, @@ -2226,7 +2246,7 @@ Signed-off-by: Daniel Golle .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); @@ -2235,7 +2255,7 @@ Signed-off-by: Daniel Golle 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); @@ -2257,7 +2277,7 @@ Signed-off-by: Daniel Golle 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 @@ -2267,7 +2287,7 @@ Signed-off-by: Daniel Golle * @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 @@ -2275,11 +2295,10 @@ Signed-off-by: Daniel Golle */ 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; @@ -2287,10 +2306,10 @@ Signed-off-by: Daniel Golle 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 @@ -2298,15 +2317,15 @@ Signed-off-by: Daniel Golle * @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; @@ -2314,14 +2333,14 @@ Signed-off-by: Daniel Golle 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; @@ -2368,7 +2387,7 @@ Signed-off-by: Daniel Golle 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 @@ -2401,13 +2420,19 @@ Signed-off-by: Daniel Golle +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); + diff --git a/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch b/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-add-link-aggregation-support.patch similarity index 90% rename from target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch rename to target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-add-link-aggregation-support.patch index 0e4074254b..3b50fae970 100644 --- a/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-add-link-aggregation-support.patch +++ b/target/linux/generic/pending-6.12/760-10-net-dsa-mxl862xx-add-link-aggregation-support.patch @@ -1,7 +1,7 @@ -From 4e1d854199c166f617b93b7542e863e6a8ad2ccb Mon Sep 17 00:00:00 2001 +From a32f6a9c09b90e767a03ddac34d061545a0cf15e Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -33,9 +33,9 @@ Signed-off-by: Daniel Golle --- 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 @@ -90,8 +90,8 @@ Signed-off-by: Daniel Golle #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) @@ -134,16 +134,31 @@ Signed-off-by: Daniel Golle { 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); } @@ -174,41 +189,16 @@ Signed-off-by: Daniel Golle 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; } @@ -234,7 +224,7 @@ Signed-off-by: Daniel Golle 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); } @@ -643,7 +633,7 @@ Signed-off-by: Daniel Golle 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; } @@ -663,7 +653,7 @@ Signed-off-by: Daniel Golle } 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)); @@ -681,7 +671,7 @@ Signed-off-by: Daniel Golle 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 } /** @@ -707,7 +697,7 @@ Signed-off-by: Daniel Golle */ 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; } @@ -716,7 +706,7 @@ Signed-off-by: Daniel Golle } /** -@@ -2862,11 +3377,43 @@ static int mxl862xx_port_fdb_del(struct +@@ -2867,11 +3375,43 @@ static int mxl862xx_port_fdb_del(struct return ret; } @@ -760,7 +750,7 @@ Signed-off-by: Daniel Golle 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); @@ -769,7 +759,7 @@ Signed-off-by: Daniel Golle 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, @@ -781,7 +771,7 @@ Signed-off-by: Daniel Golle .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; @@ -811,7 +801,7 @@ Signed-off-by: Daniel Golle /* 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 @@ -824,21 +814,20 @@ Signed-off-by: Daniel Golle */ 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). @@ -851,10 +840,10 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch b/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-mirror-port.patch similarity index 89% rename from target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch rename to target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-mirror-port.patch index cdcbaee869..045abd1bc2 100644 --- a/target/linux/generic/pending-6.12/760-18-net-dsa-mxl862xx-add-support-for-mirror-port.patch +++ b/target/linux/generic/pending-6.12/760-11-net-dsa-mxl862xx-add-support-for-mirror-port.patch @@ -1,7 +1,7 @@ -From 5528f38c3d709417625eb7f36628be31727a8221 Mon Sep 17 00:00:00 2001 +From d919c2f8da9dbd4dda57ceebb5c8b103805b58d1 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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. @@ -14,8 +14,8 @@ Signed-off-by: Daniel Golle 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 @@ -50,7 +50,7 @@ Signed-off-by: Daniel Golle #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); } @@ -59,8 +59,8 @@ Signed-off-by: Daniel Golle 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, @@ -178,9 +178,9 @@ Signed-off-by: Daniel Golle +} + /** - * 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, @@ -191,7 +191,7 @@ Signed-off-by: Daniel Golle .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 @@ -200,17 +200,16 @@ Signed-off-by: Daniel Golle * @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 @@ -219,8 +218,8 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch b/target/linux/generic/pending-6.12/760-12-net-dsa-wire-flash_update-devlink-callback-to-driver.patch similarity index 93% rename from target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch rename to target/linux/generic/pending-6.12/760-12-net-dsa-wire-flash_update-devlink-callback-to-driver.patch index 2adad2d11c..b150749917 100644 --- a/target/linux/generic/pending-6.12/760-19-net-dsa-wire-flash_update-devlink-callback-to-driver.patch +++ b/target/linux/generic/pending-6.12/760-12-net-dsa-wire-flash_update-devlink-callback-to-driver.patch @@ -1,7 +1,7 @@ -From 4059d35a5bbf1901b2e0eb7126369cd713cacfce Mon Sep 17 00:00:00 2001 +From 64269d9d809962a0f7e68e9b618d81e561e3eb6f Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 diff --git a/target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch b/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch similarity index 94% rename from target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch rename to target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch index 2986143175..53a102904f 100644 --- a/target/linux/generic/pending-6.12/760-20-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch +++ b/target/linux/generic/pending-6.12/760-13-net-dsa-mxl862xx-add-SMDIO-clause-22-register-access.patch @@ -1,7 +1,7 @@ -From 0145151dc68aa318d8addb6fe7f12c0967f951da Mon Sep 17 00:00:00 2001 +From fed4225f75b6fe6898e48f472cbbee0aaa046760 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 diff --git a/target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch b/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch similarity index 98% rename from target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch rename to target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch index d731ec1b08..9bfc096301 100644 --- a/target/linux/generic/pending-6.12/760-21-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch +++ b/target/linux/generic/pending-6.12/760-14-net-dsa-mxl862xx-add-devlink-flash_update-and-info_g.patch @@ -1,7 +1,7 @@ -From bdbca48510e3e96ed9210f20fa4244dd6df5d44a Mon Sep 17 00:00:00 2001 +From fa186b09e346e0b7f2504232731bc9e4dee0c600 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -538,7 +538,7 @@ Signed-off-by: Daniel Golle 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" @@ -546,8 +546,8 @@ Signed-off-by: Daniel Golle #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, @@ -558,7 +558,7 @@ Signed-off-by: Daniel Golle 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; diff --git a/target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch b/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-implement-port-MTU-configuration.patch similarity index 83% rename from target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch rename to target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-implement-port-MTU-configuration.patch index 1a22bba1f1..0be764173c 100644 --- a/target/linux/generic/pending-6.12/760-22-net-dsa-mxl862xx-implement-port-MTU-configuration.patch +++ b/target/linux/generic/pending-6.12/760-15-net-dsa-mxl862xx-implement-port-MTU-configuration.patch @@ -1,7 +1,7 @@ -From 8deb5be9638f7eb3009ed3eb619eedadee1df523 Mon Sep 17 00:00:00 2001 +From 05bc981690d6ef03143a094f28714aa0171ab571 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -12,20 +12,20 @@ the effective maximum does not change. Signed-off-by: Daniel Golle --- 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 +@@ -12,6 +12,7 @@ #include + #include #include +#include #include #include #include -@@ -3723,6 +3724,53 @@ static int mxl862xx_set_ageing_time(stru +@@ -3727,6 +3728,53 @@ static int mxl862xx_set_ageing_time(stru return ret; } @@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle 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, @@ -90,7 +90,7 @@ Signed-off-by: Daniel Golle .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 @@ -99,12 +99,11 @@ Signed-off-by: Daniel Golle */ 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 */ + }; diff --git a/target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch b/target/linux/generic/pending-6.12/760-16-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch similarity index 55% rename from target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch rename to target/linux/generic/pending-6.12/760-16-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch index dfbf953754..07a5f43a42 100644 --- a/target/linux/generic/pending-6.12/760-23-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch +++ b/target/linux/generic/pending-6.12/760-16-net-dsa-mxl862xx-support-BR_HAIRPIN_MODE-bridge-flag.patch @@ -1,7 +1,7 @@ -From 13a4c918cd9ded7207f38033511ab13f7aff9bd2 Mon Sep 17 00:00:00 2001 +From b723f7484006aadbaa51e16b870f3c98390605b1 Mon Sep 17 00:00:00 2001 From: Daniel Golle -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 @@ -18,29 +18,24 @@ bridge member rebuild since only the calling port is affected. Signed-off-by: Daniel Golle --- - 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 | @@ -49,33 +44,14 @@ Signed-off-by: Daniel Golle 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; @@ -83,7 +59,7 @@ Signed-off-by: Daniel Golle 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 @@ -94,12 +70,11 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch b/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch similarity index 70% rename from target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch rename to target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch index c67d95d5c5..445384aed5 100644 --- a/target/linux/generic/pending-6.12/760-24-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch +++ b/target/linux/generic/pending-6.12/760-17-net-dsa-mxl862xx-support-BR_ISOLATED-bridge-flag.patch @@ -1,7 +1,7 @@ -From d49d1f8bee29269def7593f980d0e08bfb5c3ef8 Mon Sep 17 00:00:00 2001 +From d8f20ba50ce0f93f34a41a9b833be76a5d1d2714 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -13,28 +13,22 @@ rebuilt via sync_bridge_members since multiple ports are affected. Signed-off-by: Daniel Golle --- - 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 | @@ -43,15 +37,15 @@ Signed-off-by: Daniel Golle 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; } @@ -75,7 +69,7 @@ Signed-off-by: Daniel Golle --- 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) @@ -85,11 +79,11 @@ Signed-off-by: Daniel Golle * @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; diff --git a/target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch b/target/linux/generic/pending-6.12/760-18-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch similarity index 87% rename from target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch rename to target/linux/generic/pending-6.12/760-18-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch index 6e5d25069d..bd2f15ca08 100644 --- a/target/linux/generic/pending-6.12/760-25-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch +++ b/target/linux/generic/pending-6.12/760-18-DO-NOT-SUBMIT-net-dsa-mxl862xx-re-introduce-PCE-work.patch @@ -1,7 +1,7 @@ -From c2fb7f0df63ac994850f766e7f2eb50c6c5ef2cf Mon Sep 17 00:00:00 2001 +From 8856f7610167a3005ecef401c8528111d153b554 Mon Sep 17 00:00:00 2001 From: Daniel Golle 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 @@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle --- 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); } @@ -62,9 +62,9 @@ Signed-off-by: Daniel Golle + 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; diff --git a/target/linux/generic/pending-6.12/760-26-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch b/target/linux/generic/pending-6.12/760-19-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch similarity index 100% rename from target/linux/generic/pending-6.12/760-26-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch rename to target/linux/generic/pending-6.12/760-19-DO-NOT-SUBMIT-net-dsa-mxl862xx-legacy-SFP-API-fallba.patch diff --git a/target/linux/generic/pending-6.12/760-27-DO-NOT-SUBMIT-net-dsa-mxl862xx-increase-CMD-timeout.patch b/target/linux/generic/pending-6.12/760-27-DO-NOT-SUBMIT-net-dsa-mxl862xx-increase-CMD-timeout.patch new file mode 100644 index 0000000000..d0ad2eb2e6 --- /dev/null +++ b/target/linux/generic/pending-6.12/760-27-DO-NOT-SUBMIT-net-dsa-mxl862xx-increase-CMD-timeout.patch @@ -0,0 +1,25 @@ +From df0c747216063041ba0d786b01f9b1e2aba5316a Mon Sep 17 00:00:00 2001 +From: Daniel Golle +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 +--- + 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