generic: 6.12: sync MxL862xx driver with upstream Linux

Swap pending with accepted patches, rebase remaining pending patches
on top of new upstream.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
This commit is contained in:
Daniel Golle 2026-04-22 03:56:43 +01:00
parent 0d21956ecd
commit f0f6fa2e28
30 changed files with 1256 additions and 1100 deletions

View File

@ -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 <daniel@makrotopia.org>
Date: Mon, 30 Mar 2026 23:40:53 +0100
Date: Mon, 30 Mar 2026 23:52:09 +0100
Subject: [PATCH] net: dsa: mxl862xx: cancel pending work on probe error
Call mxl862xx_host_shutdown() in case dsa_register_switch() returns
an error, so any still pending crc_err_work get canceled.
Fixes: a319d0c8c8ced ("net: dsa: mxl862xx: add CRC for MDIO communication)"
Fixes: a319d0c8c8ce ("net: dsa: mxl862xx: add CRC for MDIO communication")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Link: https://patch.msgid.link/3fd163f5bb88de426ca9847549f94b4296170ef0.1774911025.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:11 +0000
Subject: [PATCH 01/26] net: dsa: move dsa_bridge_ports() helper to dsa.h
Date: Wed, 1 Apr 2026 14:34:30 +0100
Subject: [PATCH 1/4] net: dsa: move dsa_bridge_ports() helper to dsa.h
The yt921x driver contains a helper to create a bitmap of ports
which are members of a bridge.
@ -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 <daniel@makrotopia.org>
Link: https://patch.msgid.link/4f8bbfce3e4e3a02064fc4dc366263136c6e0383.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
include/net/dsa.h | 13 +++++++++++++
2 files changed, 13 insertions(+), 13 deletions(-)
--- a/include/net/dsa.h
+++ b/include/net/dsa.h

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:41 +0000
Subject: [PATCH 02/26] net: dsa: add bridge member iteration macro
Date: Wed, 1 Apr 2026 14:34:42 +0100
Subject: [PATCH 2/4] net: dsa: add bridge member iteration macro
Drivers that offload bridges need to iterate over the ports that are
members of a given bridge, for example to rebuild per-port forwarding
@ -15,6 +15,8 @@ directly, and use it for the existing dsa_bridge_ports() inline
helper.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/e7136aaa26773f39e805a00fe4ecf13cd2b83fc0.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
include/net/dsa.h | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:54:52 +0000
Subject: [PATCH 03/26] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
Date: Wed, 1 Apr 2026 14:34:52 +0100
Subject: [PATCH 3/4] dsa: tag_mxl862xx: set dsa_default_offload_fwd_mark()
The MxL862xx offloads bridge forwarding in hardware, so set
dsa_default_offload_fwd_mark() to avoid duplicate forwarding of
@ -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 <daniel@makrotopia.org>
Link: https://patch.msgid.link/e1161c90894ddc519c57dc0224b3a0f6bfa1d2d6.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
net/dsa/tag_mxl862xx.c | 3 +++
1 file changed, 3 insertions(+)

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 17:55:08 +0000
Subject: [PATCH 04/26] net: dsa: mxl862xx: implement bridge offloading
Date: Wed, 1 Apr 2026 14:35:01 +0100
Subject: [PATCH 4/4] net: dsa: mxl862xx: implement bridge offloading
Implement joining and leaving bridges as well as add, delete and dump
operations on isolated FDBs, port MDB membership management, and
@ -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 <daniel@makrotopia.org>
Link: https://patch.msgid.link/dd079180e2098e5f9626fcd149b9bad9a1b5a1b2.1775049897.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 ++++++-
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 225 +++++++-
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 20 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 743 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 133 +++++
4 files changed, 1076 insertions(+), 45 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 736 ++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 99 ++++
4 files changed, 1022 insertions(+), 58 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@ -384,7 +394,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+
+ 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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+
+ 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 <daniel@makrotopia.org>
- 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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
}
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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ * 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
};
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -4,15 +4,148 @@
@@ -4,15 +4,114 @@
#define __MXL862XX_H
#include <linux/mdio.h>
@ -1246,37 +1261,6 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+#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 <daniel@makrotopia.org>
+ * @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 <daniel@makrotopia.org>
+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;

View File

@ -0,0 +1,50 @@
From 3a4056ec7ec8f71ae9722f86d3cfbc4589deeac4 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:27 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: reject DSA_PORT_TYPE_DSA
DSA links aren't supported by the mxl862xx driver.
Instead of returning early from .port_setup when called for
DSA_PORT_TYPE_DSA ports rather return -EOPNOTSUPP and show an error
message.
The desired side-effect is that the framework will switch the port to
DSA_PORT_TYPE_UNUSED, so we can stop caring about DSA_PORT_TYPE_DSA in
all other places.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/b686f3a22d8a6e7d470e7aa98da811a996a229b9.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -544,10 +544,14 @@ static int mxl862xx_port_setup(struct ds
mxl862xx_port_fast_age(ds, port);
- if (dsa_port_is_unused(dp) ||
- dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return 0;
+ if (dsa_port_is_dsa(dp)) {
+ dev_err(ds->dev, "port %d: DSA links not supported\n", port);
+ return -EOPNOTSUPP;
+ }
+
ret = mxl862xx_configure_sp_tag_proto(ds, port, is_cpu_port);
if (ret)
return ret;
@@ -591,7 +595,7 @@ static void mxl862xx_port_teardown(struc
struct mxl862xx_priv *priv = ds->priv;
struct dsa_port *dp = dsa_to_port(ds, port);
- if (dsa_port_is_unused(dp) || dsa_port_is_dsa(dp))
+ if (dsa_port_is_unused(dp))
return;
/* Prevent deferred host_flood_work from acting on stale state.

View File

@ -0,0 +1,38 @@
From 71934b9e6f36b1786bd969c0e1d2de8f9bd65f0f Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 7 Apr 2026 18:30:35 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: don't skip early bridge port
configuration
mxl862xx_bridge_port_set() is currently guarded by the
mxl8622_port->setup_done flag, as the early call to
mxl862xx_bridge_port_set() from mxl862xx_port_stp_state_set() would
otherwise cause a NULL-pointer dereference on unused ports which don't
have dp->cpu_dp despite not being a CPU port.
Using the setup_done flag (which is never set for unused ports),
however, also prevents mxl862xx_bridge_port_set() from configuring
user ports' single-port bridges early, which was unintended.
Fix this by returning early from mxl862xx_bridge_port_set() in case
dsa_port_is_unused().
Fixes: 340bdf984613c ("net: dsa: mxl862xx: implement bridge offloading")
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/15962aac29ebe0a6eb77565451acff880c41ef33.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -278,7 +278,7 @@ static int mxl862xx_set_bridge_port(stru
bool enable;
int i, idx;
- if (!p->setup_done)
+ if (dsa_port_is_unused(dp))
return 0;
if (dsa_port_is_cpu(dp)) {

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 10 Mar 2026 02:36:00 +0000
Subject: [PATCH 05/26] net: dsa: mxl862xx: implement VLAN functionality
Date: Tue, 7 Apr 2026 18:31:01 +0100
Subject: [PATCH] net: dsa: mxl862xx: implement VLAN functionality
Add VLAN support using both the Extended VLAN (EVLAN) engine and the
VLAN Filter (VF) engine in a hybrid architecture that allows a higher
@ -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 <daniel@makrotopia.org>
Link: https://patch.msgid.link/9be29637675342b109a85fa08f5378800d9f7b78.1775581804.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 +++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 329 ++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 12 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 915 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 104 ++-
4 files changed, 1344 insertions(+), 16 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 781 +++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 103 +++-
4 files changed, 1211 insertions(+), 14 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@ -101,14 +103,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+/**
+ * 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 <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
+ * 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 <daniel@makrotopia.org>
+};
+
+/*
+ * 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+ * 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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ }
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ }
+
+ 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 <daniel@makrotopia.org>
+{
+ 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 <daniel@makrotopia.org>
+
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+{
+ 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 <daniel@makrotopia.org>
+ */
+ 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 <daniel@makrotopia.org>
+ * 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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
/* 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 <daniel@makrotopia.org>
* 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 <daniel@makrotopia.org>
* @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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
*/
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];

View File

@ -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 <daniel@makrotopia.org>
Date: Sun, 22 Mar 2026 00:57:33 +0000
Subject: [PATCH 06/26] net: dsa: mxl862xx: add ethtool statistics support
Date: Sun, 12 Apr 2026 01:01:57 +0100
Subject: [PATCH 1/2] net: dsa: mxl862xx: add ethtool statistics support
The MxL862xx firmware exposes per-port RMON counters through the
RMON_PORT_GET command, covering standard IEEE 802.3 MAC statistics
@ -16,11 +16,13 @@ legacy ethtool -S support. Implement .get_eth_mac_stats,
IEEE 802.3 statistics interface.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/480be14d5ed51f3db7b1681b298044dbf8e87494.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 ++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-api.h | 142 +++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx-cmd.h | 3 +
drivers/net/dsa/mxl862xx/mxl862xx.c | 168 ++++++++++++++++++++++++
3 files changed, 313 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.c | 173 ++++++++++++++++++++++++
3 files changed, 318 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-api.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-api.h
@ -120,7 +122,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+ * @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 <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
+ .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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
+ .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,

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 04:14:38 +0000
Subject: [PATCH 07/26] net: dsa: mxl862xx: implement .get_stats64
Date: Sun, 12 Apr 2026 01:02:05 +0100
Subject: [PATCH 2/2] net: dsa: mxl862xx: implement .get_stats64
Poll free-running firmware RMON counters every 2 seconds and accumulate
deltas into 64-bit per-port statistics. 32-bit packet counters wrap
in ~880s at 2.5 Gbps line rate; the 2s polling interval provides a
comfortable margin. The .get_stats64 callback forces a fresh poll so
that counters are always up to date when queried.
in ~220s at 10 Gbps line rate with minimum-size frames; the 2s polling
interval provides a comfortable margin. The .get_stats64 callback
forces a fresh poll so that counters are always up to date when queried.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
Link: https://patch.msgid.link/fa38548ba05866879e8912721edc91947ce4ff12.1775951347.git.daniel@makrotopia.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 8 +-
drivers/net/dsa/mxl862xx/mxl862xx.c | 174 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 63 +++++++-
3 files changed, 238 insertions(+), 7 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 175 +++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 94 +++++++++++-
3 files changed, 270 insertions(+), 7 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@ -69,7 +71,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+ * 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 <daniel@makrotopia.org>
+ 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 <daniel@makrotopia.org>
* 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 <daniel@makrotopia.org>
*/
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 <daniel@makrotopia.org>
* @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

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 17:56:35 +0000
Subject: [PATCH 08/26] net: dsa: mxl862xx: store firmware version for feature
Subject: [PATCH 01/19] net: dsa: mxl862xx: store firmware version for feature
gating
Query the firmware version at init (already done in wait_ready),
@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- 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 <daniel@makrotopia.org>
#include <linux/mdio.h>
#include <linux/workqueue.h>
#include <net/dsa.h>
@@ -245,6 +246,38 @@ struct mxl862xx_port {
spinlock_t stats_lock;
@@ -241,6 +242,38 @@ struct mxl862xx_port {
spinlock_t stats_lock; /* protects stats accumulators */
};
+/**
@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
/* 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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 21:39:30 +0000
Subject: [PATCH 09/26] net: dsa: mxl862xx: move phylink stubs to
Subject: [PATCH 02/19] net: dsa: mxl862xx: move phylink stubs to
mxl862xx-phylink.c
Move the phylink MAC operations and get_caps callback from mxl862xx.c
@ -110,7 +110,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
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,
};

View File

@ -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 <daniel@makrotopia.org>
Date: Thu, 26 Mar 2026 01:50:00 +0000
Subject: [PATCH 10/26] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
Subject: [PATCH 03/19] net: dsa: mxl862xx: move API macros to mxl862xx-host.h
Move the MXL862XX_API_WRITE, MXL862XX_API_READ and
MXL862XX_API_READ_QUIET convenience macros from mxl862xx.c to

View File

@ -990,7 +990,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
* 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 <daniel@makrotopia.org>
* 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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:14:33 +0000
Subject: [PATCH 12/26] net: dsa: mxl862xx: add SerDes ethtool statistics
Subject: [PATCH 05/19] net: dsa: mxl862xx: add SerDes ethtool statistics
Expose SerDes equalization and signal detect parameters as ethtool
statistics on ports 9-16 (XPCS-backed ports). Uses the XPCS EQ_GET
@ -239,7 +239,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
}
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 <daniel@makrotopia.org>
}
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);
}

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:15:32 +0000
Subject: [PATCH 13/26] net: dsa: mxl862xx: add SerDes self-test via PRBS and
Subject: [PATCH 06/19] net: dsa: mxl862xx: add SerDes self-test via PRBS and
BERT
Implement the dsa_switch_ops.self_test callback for SerDes ports
@ -198,9 +198,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
#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,
};

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:13 +0000
Subject: [PATCH 14/26] net: dsa: mxl862xx: trap link-local frames to the CPU
port
Subject: [PATCH 07/19] net: dsa: mxl862xx: trap link-local and multicast
snooping frames to CPU
Install per-CTP PCE rules on each user port that trap IEEE 802.1D
link-local frames (01:80:c2:00:00:0x) to the CPU port via an
explicit forwarding portmap with cross-state enabled, ensuring the
frames reach the host even when the bridge port is in BLOCKING or
LEARNING state.
link-local frames (01:80:c2:00:00:0x) and IP multicast snooping
frames (IGMP, MLDv1, MLDv2) to the CPU port.
All trap rules share a common action helper,
mxl862xx_fill_cpu_trap_action(), which sets PORTMAP_ALTERNATIVE to
redirect frames to the CPU and enables cross-state forwarding so
that frames reach the host even when the bridge port is in BLOCKING
or LEARNING state.
A dedicated bridge FID (cpu_trap_fid) is allocated during setup with
all flood modes enabled. Each trap rule points the bridge engine at
this FID via bFidEnable so that IGMP and MLD frames are never
silently dropped by the ingress port's private flood policy.
Three multicast snooping rules are installed per port:
offset 2 -- IPv4 IGMP (IP protocol 2, all versions)
offset 3 -- ICMPv6 types 130-132 (MLDv1 query, report, done)
offset 4 -- ICMPv6 type 143 (MLDv2 Listener Report)
Add the PCE rule firmware API structures, command definitions, and
the rule block allocation interface.
@ -17,8 +31,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
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 <daniel@makrotopia.org>
#define MXL862XX_BRIDGE_CONFIGGET (MXL862XX_BRDG_MAGIC + 0x3)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -280,9 +280,11 @@ static int mxl862xx_wait_ready(struct ds
@@ -10,6 +10,7 @@
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/etherdevice.h>
+#include <linux/icmpv6.h>
#include <linux/if_bridge.h>
#include <linux/module.h>
#include <linux/of_device.h>
@@ -251,9 +252,11 @@ static int mxl862xx_wait_ready(struct ds
ver.iv_major, ver.iv_minor,
le16_to_cpu(ver.iv_revision),
le32_to_cpu(ver.iv_build_num));
@ -748,17 +771,54 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+ */
+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;
};

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:51:21 +0000
Subject: [PATCH 15/26] net: dsa: mxl862xx: warn about old firmware default PCE
Subject: [PATCH 08/19] net: dsa: mxl862xx: warn about old firmware default PCE
rules
Firmware versions older than 1.0.80 install global PCE rules at
@ -19,14 +19,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- 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);

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 03:44:41 +0000
Subject: [PATCH 17/26] net: dsa: mxl862xx: add link aggregation support
Subject: [PATCH 10/20] net: dsa: mxl862xx: add link aggregation support
Implement LAG offloading via the firmware's trunking engine. A
dedicated firmware bridge port is allocated per LAG and remains
@ -33,9 +33,9 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
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 <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
{
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
}
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
*/
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 <daniel@makrotopia.org>
}
/**
@@ -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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
.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 <daniel@makrotopia.org>
/* 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 <daniel@makrotopia.org>
*/
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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 12:05:29 +0000
Subject: [PATCH 18/26] net: dsa: mxl862xx: add support for mirror port
Subject: [PATCH 11/19] net: dsa: mxl862xx: add support for mirror port
The MxL862xx hardware supports a single monitor port which can be
configured to mirror any other port's ingress and/or egress traffic.
@ -14,8 +14,8 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
+}
+
/**
* 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 <daniel@makrotopia.org>
.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 <daniel@makrotopia.org>
* @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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:08 +0000
Subject: [PATCH 19/26] net: dsa: wire flash_update devlink callback to drivers
Subject: [PATCH 12/19] net: dsa: wire flash_update devlink callback to drivers
Add a devlink_flash_update callback to dsa_switch_ops so that DSA
drivers can support devlink dev flash without open-coding the devlink

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:17 +0000
Subject: [PATCH 20/26] net: dsa: mxl862xx: add SMDIO clause-22 register access
Subject: [PATCH 13/19] net: dsa: mxl862xx: add SMDIO clause-22 register access
Add mxl862xx_smdio_read() and mxl862xx_smdio_write() for clause-22
SMDIO register access. MCUboot rescue mode only exposes clause-22

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 16:30:31 +0000
Subject: [PATCH 21/26] net: dsa: mxl862xx: add devlink flash_update and
Subject: [PATCH 14/19] net: dsa: mxl862xx: add devlink flash_update and
info_get
Implement runtime firmware upgrade via "devlink dev flash" and version
@ -538,7 +538,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
#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 <daniel@makrotopia.org>
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;

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 23:42:18 +0000
Subject: [PATCH 22/26] net: dsa: mxl862xx: implement port MTU configuration
Date: Wed, 1 Apr 2026 13:43:08 +0100
Subject: [PATCH 15/19] net: dsa: mxl862xx: implement port MTU configuration
The firmware exposes a global max_packet_len register via
MXL862XX_COMMON_CFGSET. Since this is switch-wide rather than
@ -12,20 +12,20 @@ the effective maximum does not change.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 50 +++++++++++++++++++++++++++++
drivers/net/dsa/mxl862xx/mxl862xx.h | 4 +++
2 files changed, 54 insertions(+)
drivers/net/dsa/mxl862xx/mxl862xx.h | 3 ++
2 files changed, 53 insertions(+)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -11,6 +11,7 @@
#include <linux/delay.h>
@@ -12,6 +12,7 @@
#include <linux/etherdevice.h>
#include <linux/icmpv6.h>
#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_mdio.h>
@@ -3723,6 +3724,53 @@ static int mxl862xx_set_ageing_time(stru
@@ -3727,6 +3728,53 @@ static int mxl862xx_set_ageing_time(stru
return ret;
}
@ -79,7 +79,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
.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 <daniel@makrotopia.org>
*/
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 */
};

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 01:47:19 +0000
Subject: [PATCH 23/26] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
Date: Wed, 1 Apr 2026 13:43:12 +0100
Subject: [PATCH 16/19] net: dsa: mxl862xx: support BR_HAIRPIN_MODE bridge flag
Implement hairpin mode by including the port's own bridge port ID in
its forwarding portmap. When hairpin is enabled, bridged frames whose
@ -18,29 +18,24 @@ bridge member rebuild since only the calling port is affected.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 29 +++++++++++++++++++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 6 ++++++
2 files changed, 33 insertions(+), 2 deletions(-)
drivers/net/dsa/mxl862xx/mxl862xx.c | 12 ++++++++++--
drivers/net/dsa/mxl862xx/mxl862xx.h | 5 +++++
2 files changed, 15 insertions(+), 2 deletions(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -811,6 +811,15 @@ static int mxl862xx_sync_bridge_members(
__set_bit(mxl862xx_cpu_bridge_port_id(ds, port),
priv->ports[port].portmap);
+ /* Hairpin: include the port's own bridge port so bridged
+ * frames can egress the ingress port.
+ * For LAG ports this adds the LAG bridge port, which
+ * propagates to the LAG BP in the second loop below.
+ */
+ if (priv->ports[port].hairpin)
+ __set_bit(mxl862xx_lag_bridge_port(priv, port),
+ priv->ports[port].portmap);
+
err = mxl862xx_set_bridge_port(ds, port);
if (err)
ret = err;
@@ -3898,7 +3907,7 @@ static int mxl862xx_port_pre_bridge_flag
@@ -759,6 +759,10 @@ static int __mxl862xx_set_bridge_port(st
}
mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
mxl862xx_cpu_bridge_port_id(ds, port));
+ if (p->hairpin)
+ mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
+ mxl862xx_lag_bridge_port(priv,
+ port));
} else {
mxl862xx_fw_portmap_set_bit(br_port_cfg.bridge_port_map,
mxl862xx_cpu_bridge_port_id(ds, port));
@@ -3895,7 +3899,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
@ -49,33 +44,14 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Wed, 25 Mar 2026 01:51:33 +0000
Subject: [PATCH 24/26] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
Subject: [PATCH 17/19] net: dsa: mxl862xx: support BR_ISOLATED bridge flag
Implement port isolation by excluding isolated ports from each other's
forwarding portmaps in sync_bridge_members. Non-isolated ports can
@ -13,28 +13,22 @@ rebuilt via sync_bridge_members since multiple ports are affected.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx.c | 26 +++++++++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.c | 20 +++++++++++++++++++-
drivers/net/dsa/mxl862xx/mxl862xx.h | 4 ++++
2 files changed, 29 insertions(+), 1 deletion(-)
2 files changed, 23 insertions(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -802,6 +802,14 @@ static int mxl862xx_sync_bridge_members(
*/
if (!mxl862xx_is_lag_master(priv, member))
@@ -752,6 +752,8 @@ static int __mxl862xx_set_bridge_port(st
continue;
+
+ /* Isolated ports cannot forward to each other.
+ * Non-isolated ports can reach everyone.
+ */
+ if (priv->ports[port].isolated &&
+ priv->ports[member].isolated)
if (!mxl862xx_is_lag_master(priv, member_dp->index))
continue;
+ if (p->isolated && priv->ports[member_dp->index].isolated)
+ continue;
+
if (member != port) {
bp = mxl862xx_lag_bridge_port(priv,
member);
@@ -3907,7 +3915,7 @@ static int mxl862xx_port_pre_bridge_flag
mxl862xx_fw_portmap_set_bit(
br_port_cfg.bridge_port_map,
mxl862xx_lag_bridge_port(priv,
@@ -3899,7 +3901,7 @@ static int mxl862xx_port_pre_bridge_flag
struct netlink_ext_ack *extack)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD |
@ -43,15 +37,15 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
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 <daniel@makrotopia.org>
--- 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 <daniel@makrotopia.org>
* @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;

View File

@ -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 <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 18:17:49 +0000
Subject: [PATCH 25/26] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
Subject: [PATCH 18/19] DO NOT SUBMIT: net: dsa: mxl862xx: re-introduce PCE
workaround for old firmware
Re-introduce the mxl862xx_disable_fw_global_rules() function that
@ -20,7 +20,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
--- 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 <daniel@makrotopia.org>
+ 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;

View File

@ -0,0 +1,25 @@
From df0c747216063041ba0d786b01f9b1e2aba5316a Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Wed, 22 Apr 2026 15:15:52 +0100
Subject: [PATCH] DO NOT SUBMIT: net: dsa: mxl862xx: increase CMD timeout
Lift the command timeout by 10x from 500ms to 5s.
This is done because older firmware can be extremely slow to respond
and cause -ETIMEOUT during setup, or crash the PHY state machine.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
drivers/net/dsa/mxl862xx/mxl862xx-host.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--- a/drivers/net/dsa/mxl862xx/mxl862xx-host.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-host.c
@@ -207,7 +207,7 @@ static int mxl862xx_busy_wait(struct mxl
int val;
return readx_poll_timeout(mxl862xx_ctrl_read, priv, val,
- !(val & CTRL_BUSY_MASK), 15, 500000);
+ !(val & CTRL_BUSY_MASK), 50, 5000000);
}
/* Issue a firmware command with CRC-6 protection on the ctrl and len_ret