Files
openwrt/target/linux/generic/pending-6.18/760-09-net-dsa-mxl862xx-add-link-aggregation-support.patch
T
Daniel Golle 028dc3f57a generic: 6.18: update MxL862xx DSA switch driver
Update driver to be ready for the upcoming firmware release.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
2026-05-27 19:01:52 +01:00

852 lines
27 KiB
Diff

From 4220567bd18b2174c74e90d22f3bf47422c1fdf6 Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Tue, 24 Mar 2026 03:44:41 +0000
Subject: [PATCH 09/19] 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
stable for the LAG's lifetime. All member CTPs redirect to it,
and FDB/MDB entries target it, so no entry migration is needed
when the LAG master (lowest-numbered member port) changes.
The firmware provides three cooperating mechanisms:
- PCE_TRUNK_CONF register: global 6-bit hash field selection
(SA, DA, SIP, DIP, sport, dport)
- CTP redirection: all member CTPs point bridge_port_id to the
LAG's dedicated bridge port
- P-mapper on the LAG bridge port: 64 hash-indexed entries
(indices 9..72) filled round-robin with active member ports
Hash and active-backup bond modes are supported. The global hash
register is widened monotonically on LAG join and recomputed from
stored per-port requirements on LAG leave.
The LAG master's full bridge port configuration (bridge_id, EVLAN,
VLAN filter, learning, portmap, flood metering) is pushed to the
LAG bridge port via __mxl862xx_set_bridge_port() whenever it
changes. Bridge portmaps use the LAG bridge port ID instead of
individual member port indices, ensuring correct cross-LAG
forwarding.
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 | 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
@@ -564,6 +564,28 @@ struct mxl862xx_pmapper {
} __packed;
/**
+ * struct mxl862xx_trunking_cfg - LAG hash algorithm configuration
+ * @ip_src: Include source IP address in trunk hash (1 = include)
+ * @ip_dst: Include destination IP address in trunk hash
+ * @mac_src: Include source MAC address in trunk hash
+ * @mac_dst: Include destination MAC address in trunk hash
+ * @src_port: Include TCP/UDP source port in trunk hash
+ * @dst_port: Include TCP/UDP destination port in trunk hash
+ *
+ * The firmware inverts the boolean sense when writing the hardware
+ * register (PCE_TRUNK_CONF): bit=0 means include, bit=1 means exclude.
+ * This struct uses the logical sense (1 = include).
+ */
+struct mxl862xx_trunking_cfg {
+ u8 ip_src;
+ u8 ip_dst;
+ u8 mac_src;
+ u8 mac_dst;
+ u8 src_port;
+ u8 dst_port;
+} __packed;
+
+/**
* struct mxl862xx_bridge_port_config - Bridge Port Configuration
* @bridge_port_id: Bridge Port ID allocated by bridge port allocation
* @mask: See &enum mxl862xx_bridge_port_config_mask
--- a/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx-cmd.h
@@ -51,6 +51,7 @@
#define MXL862XX_QOS_METERCFGSET (MXL862XX_QOS_MAGIC + 0x2)
#define MXL862XX_QOS_METERALLOC (MXL862XX_QOS_MAGIC + 0x2a)
+#define MXL862XX_QOS_PMAPPERTABLESET (MXL862XX_QOS_MAGIC + 0x2e)
#define MXL862XX_RMON_PORT_GET (MXL862XX_RMON_MAGIC + 0x1)
@@ -73,6 +74,9 @@
#define MXL862XX_SS_SPTAG_SET (MXL862XX_SS_MAGIC + 0x2)
+#define MXL862XX_TRUNKING_MAGIC 0xe00
+#define MXL862XX_TRUNKING_CFGSET (MXL862XX_TRUNKING_MAGIC + 0x2)
+
#define MXL862XX_STP_PORTCFGSET (MXL862XX_STP_MAGIC + 0x2)
#define INT_GPHY_READ (GPY_GPY2XX_MAGIC + 0x1)
--- a/drivers/net/dsa/mxl862xx/mxl862xx.c
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.c
@@ -695,7 +695,42 @@ static int mxl862xx_setup_snooping_traps
return mxl862xx_pce_rule_write(priv, &rule);
}
-static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
+static bool mxl862xx_is_lag_master(const struct mxl862xx_priv *priv, int port)
+{
+ struct dsa_lag *lag = priv->ports[port].lag;
+ int i;
+
+ if (!lag)
+ return true;
+
+ for (i = 0; i < port; i++) {
+ if (priv->ports[i].lag == lag)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * mxl862xx_lag_bridge_port - Get the effective bridge port ID for a port
+ * @priv: driver private data
+ * @port: port index
+ *
+ * If @port is a member of a LAG, returns the LAG's dedicated firmware
+ * bridge port ID. Otherwise returns @port itself.
+ */
+static u16 mxl862xx_lag_bridge_port(const struct mxl862xx_priv *priv, int port)
+{
+ struct dsa_lag *lag = priv->ports[port].lag;
+
+ if (lag && priv->lag_bridge_ports[lag->id])
+ return priv->lag_bridge_ports[lag->id];
+
+ return port;
+}
+
+static int __mxl862xx_set_bridge_port(struct dsa_switch *ds, int port,
+ u16 bp_id)
{
struct mxl862xx_bridge_port_config br_port_cfg = {};
struct dsa_port *dp = dsa_to_port(ds, port);
@@ -728,8 +763,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));
@@ -742,7 +781,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 |
@@ -823,11 +862,38 @@ static int mxl862xx_set_bridge_port(stru
br_port_cfg);
}
+static int mxl862xx_set_bridge_port(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p = &priv->ports[port];
+ u16 lag_bp;
+ int ret;
+
+ ret = __mxl862xx_set_bridge_port(ds, port, port);
+ if (ret)
+ return ret;
+
+ /* If this port is a LAG master, also push its config to the
+ * LAG's dedicated bridge port (which is the actual target of
+ * all member CTP redirections).
+ */
+ if (p->lag && mxl862xx_is_lag_master(priv, port)) {
+ lag_bp = priv->lag_bridge_ports[p->lag->id];
+ if (lag_bp)
+ ret = __mxl862xx_set_bridge_port(ds, port, lag_bp);
+ }
+
+ return ret;
+}
+
static int mxl862xx_sync_bridge_members(struct dsa_switch *ds,
const struct dsa_bridge *bridge)
{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_port *p;
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) {
err = mxl862xx_set_bridge_port(ds, dp->index);
@@ -835,6 +901,25 @@ static int mxl862xx_sync_bridge_members(
ret = err;
}
+ /* Push updated portmaps to LAG bridge ports. Each LAG master's
+ * portmap (which excludes itself) is used for the LAG bridge
+ * port -- this naturally avoids self-forwarding.
+ */
+ dsa_switch_for_each_bridge_member(dp, ds, bridge->dev) {
+ p = &priv->ports[dp->index];
+
+ if (!p->lag || !mxl862xx_is_lag_master(priv, dp->index))
+ continue;
+
+ lag_bp = priv->lag_bridge_ports[p->lag->id];
+ if (!lag_bp)
+ continue;
+
+ err = __mxl862xx_set_bridge_port(ds, dp->index, lag_bp);
+ if (err)
+ ret = err;
+ }
+
return ret;
}
@@ -1875,6 +1960,408 @@ static int mxl862xx_setup_cpu_bridge(str
return mxl862xx_set_bridge_port(ds, port);
}
+/**
+ * mxl862xx_lag_master_port - Find the LAG master (lowest-numbered member)
+ * @ds: DSA switch
+ * @lag: LAG to search
+ *
+ * The master's bridge port hosts the P-mapper and receives all ingress
+ * traffic via CTP redirection from other members.
+ *
+ * Return: port index of the master, or -ENOENT if no members.
+ */
+static int mxl862xx_lag_master_port(struct dsa_switch *ds,
+ const struct dsa_lag *lag)
+{
+ struct dsa_port *dp;
+ int master = -ENOENT;
+
+ dsa_lag_foreach_port(dp, ds->dst, lag) {
+ if (dp->ds != ds)
+ continue;
+ if (master < 0 || dp->index < master)
+ master = dp->index;
+ }
+
+ return master;
+}
+
+/**
+ * mxl862xx_lag_hash_bits - Translate Linux hash mode to firmware hash bitmask
+ * @info: bonding upper info (tx_type + hash_type)
+ *
+ * Return: 6-bit hash field bitmask (MXL862XX_TRUNK_HASH_*), or negative
+ * errno if the mode is unsupported.
+ */
+static int mxl862xx_lag_hash_bits(const struct netdev_lag_upper_info *info)
+{
+ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH)
+ return 0;
+
+ switch (info->hash_type) {
+ case NETDEV_LAG_HASH_L2:
+ return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA;
+ case NETDEV_LAG_HASH_L34:
+ return MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP |
+ MXL862XX_TRUNK_HASH_SPORT | MXL862XX_TRUNK_HASH_DPORT;
+ case NETDEV_LAG_HASH_L23:
+ case NETDEV_LAG_HASH_E23:
+ return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA |
+ MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP;
+ case NETDEV_LAG_HASH_E34:
+ return MXL862XX_TRUNK_HASH_SA | MXL862XX_TRUNK_HASH_DA |
+ MXL862XX_TRUNK_HASH_SIP | MXL862XX_TRUNK_HASH_DIP |
+ MXL862XX_TRUNK_HASH_SPORT | MXL862XX_TRUNK_HASH_DPORT;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/**
+ * mxl862xx_lag_set_hash - Push trunk hash configuration to firmware
+ * @priv: driver private data
+ * @hash_bits: 6-bit hash field bitmask (MXL862XX_TRUNK_HASH_*)
+ *
+ * Only issues a firmware command when @hash_bits differs from the
+ * currently active configuration.
+ */
+static int mxl862xx_lag_set_hash(struct mxl862xx_priv *priv, u8 hash_bits)
+{
+ struct mxl862xx_trunking_cfg cfg = {};
+
+ if (priv->trunk_hash == hash_bits)
+ return 0;
+
+ cfg.mac_src = !!(hash_bits & MXL862XX_TRUNK_HASH_SA);
+ cfg.mac_dst = !!(hash_bits & MXL862XX_TRUNK_HASH_DA);
+ cfg.ip_src = !!(hash_bits & MXL862XX_TRUNK_HASH_SIP);
+ cfg.ip_dst = !!(hash_bits & MXL862XX_TRUNK_HASH_DIP);
+ cfg.src_port = !!(hash_bits & MXL862XX_TRUNK_HASH_SPORT);
+ cfg.dst_port = !!(hash_bits & MXL862XX_TRUNK_HASH_DPORT);
+
+ priv->trunk_hash = hash_bits;
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_TRUNKING_CFGSET, cfg);
+}
+
+/**
+ * mxl862xx_lag_recompute_hash - Recompute global hash from all active LAGs
+ * @ds: DSA switch
+ *
+ * Scans all ports and ORs together the stored hash requirements of every
+ * active LAG member. Used after a LAG is destroyed to potentially narrow
+ * the global hash configuration.
+ *
+ * Return: union of all active LAGs' hash field bitmasks.
+ */
+static u8 mxl862xx_lag_recompute_hash(struct dsa_switch *ds)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ u8 hash = 0;
+ int port;
+
+ for (port = 0; port < ds->num_ports; port++) {
+ if (priv->ports[port].lag)
+ hash |= priv->ports[port].lag_hash_bits;
+ }
+
+ return hash;
+}
+
+/**
+ * mxl862xx_lag_build_pmapper - Fill P-mapper with round-robin LAG distribution
+ * @ds: DSA switch
+ * @lag: LAG group
+ * @pm: P-mapper struct to fill (entries 9..72)
+ *
+ * Only ports with lag_tx_enabled are included. Falls back to the
+ * master port if no members are active.
+ */
+static void mxl862xx_lag_build_pmapper(struct dsa_switch *ds,
+ const struct dsa_lag *lag,
+ struct mxl862xx_pmapper *pm)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ int active_ports[MXL862XX_MAX_PORTS];
+ int n_active = 0, master, port;
+ struct dsa_port *dp;
+ int i;
+
+ dsa_lag_foreach_port(dp, ds->dst, lag) {
+ if (dp->ds != ds)
+ continue;
+ if (priv->ports[dp->index].lag_tx_enabled)
+ active_ports[n_active++] = dp->index;
+ }
+
+ /* Fallback: if no members are active, use the master port */
+ if (!n_active) {
+ master = mxl862xx_lag_master_port(ds, lag);
+
+ if (master >= 0) {
+ active_ports[0] = master;
+ n_active = 1;
+ }
+ }
+
+ if (!n_active)
+ return;
+
+ for (i = 0; i < MXL862XX_PMAPPER_LAG_COUNT; i++) {
+ port = active_ports[i % n_active];
+
+ pm->dest_sub_if_id_group[MXL862XX_PMAPPER_LAG_FIRST + i] =
+ (port << 4) & 0xff;
+ }
+}
+
+/**
+ * mxl862xx_lag_redirect_ctp - Redirect a port's CTP to the LAG master
+ * @priv: driver private data
+ * @port: port whose CTP to redirect
+ * @master_port: LAG master port index
+ */
+static int mxl862xx_lag_redirect_ctp(struct mxl862xx_priv *priv,
+ int port, int master_port)
+{
+ struct mxl862xx_ctp_port_config ctp = {};
+
+ ctp.logical_port_id = port;
+ ctp.mask = cpu_to_le32(MXL862XX_CTP_PORT_CONFIG_MASK_BRIDGE_PORT_ID);
+ ctp.bridge_port_id = cpu_to_le16(master_port);
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_CTP_PORTCONFIGSET, ctp);
+}
+
+/**
+ * mxl862xx_lag_restore_ctp - Restore a port's CTP to point to itself
+ * @priv: driver private data
+ * @port: port whose CTP to restore
+ */
+static int mxl862xx_lag_restore_ctp(struct mxl862xx_priv *priv, int port)
+{
+ return mxl862xx_lag_redirect_ctp(priv, port, port);
+}
+
+/**
+ * mxl862xx_lag_disable_pmapper - Disable P-mapper on a bridge port
+ * @ds: DSA switch
+ * @bp_id: firmware bridge port ID to reconfigure
+ */
+static int mxl862xx_lag_disable_pmapper(struct dsa_switch *ds, u16 bp_id)
+{
+ struct mxl862xx_bridge_port_config bp_cfg = {};
+ struct mxl862xx_priv *priv = ds->priv;
+
+ bp_cfg.bridge_port_id = cpu_to_le16(bp_id);
+ bp_cfg.mask = cpu_to_le32(
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING);
+ bp_cfg.dest_logical_port_id = bp_id;
+ bp_cfg.pmapper_enable = 0;
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
+}
+
+/**
+ * mxl862xx_lag_sync - Synchronize LAG hardware state for a LAG group
+ * @ds: DSA switch
+ * @lag: LAG group to synchronize
+ *
+ * Finds the master (lowest-numbered member), redirects all member CTPs
+ * to the LAG's dedicated firmware bridge port, configures the P-mapper
+ * for hash distribution, and pushes the master's full bridge port
+ * configuration (EVLAN, VF, portmap, learning) to the LAG bridge port.
+ */
+static int mxl862xx_lag_sync(struct dsa_switch *ds, const struct dsa_lag *lag)
+{
+ struct mxl862xx_bridge_port_config bp_cfg = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ struct mxl862xx_pmapper pm = {};
+ struct dsa_port *dp;
+ int master, ret;
+ u16 lag_bp;
+
+ lag_bp = priv->lag_bridge_ports[lag->id];
+ if (!lag_bp)
+ return -ENOENT;
+
+ master = mxl862xx_lag_master_port(ds, lag);
+ if (master < 0)
+ return master;
+
+ /* Redirect all member CTPs to the LAG bridge port */
+ dsa_lag_foreach_port(dp, ds->dst, lag) {
+ if (dp->ds != ds)
+ continue;
+ ret = mxl862xx_lag_redirect_ctp(priv, dp->index, lag_bp);
+ if (ret)
+ return ret;
+ }
+
+ /* Push the master's full config to the LAG bridge port so it
+ * inherits the current bridge_id, EVLAN/VF blocks, portmap,
+ * learning and flood settings.
+ */
+ ret = __mxl862xx_set_bridge_port(ds, master, lag_bp);
+ if (ret)
+ return ret;
+
+ /* Build P-mapper with active members */
+ mxl862xx_lag_build_pmapper(ds, lag, &pm);
+
+ /* Enable P-mapper in LAG mode on the LAG bridge port */
+ bp_cfg.bridge_port_id = cpu_to_le16(lag_bp);
+ bp_cfg.mask = cpu_to_le32(
+ MXL862XX_BRIDGE_PORT_CONFIG_MASK_EGRESS_CTP_MAPPING);
+ bp_cfg.dest_logical_port_id = master;
+ bp_cfg.pmapper_enable = 1;
+ bp_cfg.pmapper_mapping_mode =
+ cpu_to_le32(MXL862XX_PMAPPER_MAPPING_LAG);
+ bp_cfg.pmapper = pm;
+
+ return MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_CONFIGSET, bp_cfg);
+}
+
+static int mxl862xx_port_lag_join(struct dsa_switch *ds, int port,
+ const struct dsa_lag lag,
+ struct netdev_lag_upper_info *info,
+ struct netlink_ext_ack *extack)
+{
+ struct mxl862xx_bridge_port_alloc bp_alloc = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ int hash_bits;
+ u8 new_hash;
+ int ret;
+
+ if (dsa_is_cpu_port(ds, port)) {
+ NL_SET_ERR_MSG_MOD(extack, "CPU port LAG not supported");
+ return -EOPNOTSUPP;
+ }
+
+ if (info->tx_type != NETDEV_LAG_TX_TYPE_HASH &&
+ info->tx_type != NETDEV_LAG_TX_TYPE_ACTIVEBACKUP) {
+ NL_SET_ERR_MSG_MOD(extack, "Only hash and active-backup LAG modes supported");
+ return -EOPNOTSUPP;
+ }
+
+ hash_bits = mxl862xx_lag_hash_bits(info);
+ if (hash_bits < 0) {
+ NL_SET_ERR_MSG_MOD(extack, "Unsupported LAG hash mode");
+ return hash_bits;
+ }
+
+ /* Allocate a dedicated firmware bridge port for this LAG on
+ * first member join. This bridge port is stable for the
+ * LAG's lifetime -- all CTP redirections, FDB and MDB entries
+ * target it, so no migration is needed on membership changes.
+ */
+ if (!priv->lag_bridge_ports[lag.id]) {
+ ret = MXL862XX_API_READ(priv, MXL862XX_BRIDGEPORT_ALLOC,
+ bp_alloc);
+ if (ret) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Failed to allocate LAG bridge port");
+ return ret;
+ }
+ priv->lag_bridge_ports[lag.id] =
+ le16_to_cpu(bp_alloc.bridge_port_id);
+ }
+
+ priv->ports[port].lag = dp->lag;
+ priv->ports[port].lag_tx_enabled = dp->lag_tx_enabled;
+ priv->ports[port].lag_hash_bits = hash_bits;
+
+ /* Widen global hash to include this LAG's requirements */
+ new_hash = priv->trunk_hash | hash_bits;
+ ret = mxl862xx_lag_set_hash(priv, new_hash);
+ if (ret)
+ goto err_undo;
+
+ ret = mxl862xx_lag_sync(ds, dp->lag);
+ if (ret)
+ goto err_undo;
+
+ return 0;
+
+err_undo:
+ priv->ports[port].lag = NULL;
+ priv->ports[port].lag_tx_enabled = false;
+ priv->ports[port].lag_hash_bits = 0;
+ return ret;
+}
+
+static int mxl862xx_port_lag_leave(struct dsa_switch *ds, int port,
+ const struct dsa_lag lag)
+{
+ struct mxl862xx_bridge_port_alloc bp_alloc = {};
+ struct mxl862xx_priv *priv = ds->priv;
+ u8 new_hash;
+ int ret;
+
+ /* Restore this port's CTP to point to itself */
+ ret = mxl862xx_lag_restore_ctp(priv, port);
+ if (ret)
+ dev_err(ds->dev, "failed to restore CTP for port %d: %pe\n",
+ port, ERR_PTR(ret));
+
+ priv->ports[port].lag = NULL;
+ priv->ports[port].lag_tx_enabled = false;
+ priv->ports[port].lag_hash_bits = 0;
+
+ /* If other members remain, re-sync the LAG */
+ if (mxl862xx_lag_master_port(ds, &lag) >= 0) {
+ ret = mxl862xx_lag_sync(ds, &lag);
+ if (ret)
+ dev_err(ds->dev,
+ "failed to re-sync LAG after port %d left: %pe\n",
+ port, ERR_PTR(ret));
+ } else if (priv->lag_bridge_ports[lag.id]) {
+ /* Last member left -- disable P-mapper and free the
+ * LAG's dedicated bridge port.
+ */
+ ret = mxl862xx_lag_disable_pmapper(ds,
+ priv->lag_bridge_ports[lag.id]);
+ if (ret)
+ dev_err(ds->dev,
+ "failed to disable P-mapper on LAG bridge port %u: %pe\n",
+ priv->lag_bridge_ports[lag.id], ERR_PTR(ret));
+
+ bp_alloc.bridge_port_id =
+ cpu_to_le16(priv->lag_bridge_ports[lag.id]);
+ ret = MXL862XX_API_WRITE(priv, MXL862XX_BRIDGEPORT_FREE,
+ bp_alloc);
+ if (ret)
+ dev_err(ds->dev,
+ "failed to free LAG bridge port %u: %pe\n",
+ priv->lag_bridge_ports[lag.id], ERR_PTR(ret));
+
+ priv->lag_bridge_ports[lag.id] = 0;
+ }
+
+ /* Recompute global hash from remaining LAGs */
+ new_hash = mxl862xx_lag_recompute_hash(ds);
+ ret = mxl862xx_lag_set_hash(priv, new_hash);
+ if (ret)
+ dev_err(ds->dev, "failed to update trunk hash: %pe\n",
+ ERR_PTR(ret));
+
+ return 0;
+}
+
+static int mxl862xx_port_lag_change(struct dsa_switch *ds, int port)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ struct dsa_port *dp = dsa_to_port(ds, port);
+
+ if (!priv->ports[port].lag)
+ return 0;
+
+ priv->ports[port].lag_tx_enabled = dp->lag_tx_enabled;
+
+ return mxl862xx_lag_sync(ds, priv->ports[port].lag);
+}
+
static int mxl862xx_port_bridge_join(struct dsa_switch *ds, int port,
const struct dsa_bridge bridge,
bool *tx_fwd_offload,
@@ -1900,7 +2387,18 @@ static int mxl862xx_port_bridge_join(str
return 0;
}
- return mxl862xx_sync_bridge_members(ds, &bridge);
+ ret = mxl862xx_sync_bridge_members(ds, &bridge);
+ if (ret)
+ return ret;
+
+ /* If this port is in a LAG, re-sync the LAG bridge port so it
+ * picks up the new bridge_id (switching from standalone FID to
+ * the shared bridge FID).
+ */
+ if (priv->ports[port].lag)
+ ret = mxl862xx_lag_sync(ds, priv->ports[port].lag);
+
+ return ret;
}
static void mxl862xx_port_bridge_leave(struct dsa_switch *ds, int port,
@@ -1951,6 +2449,17 @@ static void mxl862xx_port_bridge_leave(s
"failed to update CPU VBP for port %d: %pe\n", port,
ERR_PTR(err));
+ /* If this port is in a LAG, re-sync the LAG bridge port so it
+ * reverts to the standalone FID.
+ */
+ if (p->lag) {
+ err = mxl862xx_lag_sync(ds, p->lag);
+ if (err)
+ dev_err(ds->dev,
+ "failed to re-sync LAG after port %d left bridge: %pe\n",
+ port, ERR_PTR(err));
+ }
+
if (!dsa_bridge_ports(ds, bridge.dev))
mxl862xx_free_bridge(ds, &bridge);
}
@@ -2609,18 +3118,17 @@ static int mxl862xx_get_fid(struct dsa_s
}
/**
- * mxl862xx_fdb_bridge_port - Translate port for MAC table in tag_8021q mode
+ * mxl862xx_fdb_bridge_port - Translate port to effective bridge port ID
* @ds: DSA switch
* @port: port number passed by DSA (usually the CPU port for host entries)
* @db: database context identifying the user port or bridge
*
- * In tag_8021q mode, host FDB/MDB entries for standalone ports must use
- * the virtual bridge port (bridge_port_cpu) as the MAC table destination
- * so that known-unicast and known-multicast frames exit through the
- * virtual bridge port's egress EVLAN, which inserts the management VID.
- * Without this, the firmware forwards known traffic directly to the
- * physical CPU bridge port, bypassing management VID insertion, and DSA
- * drops the untagged frame.
+ * Returns the firmware bridge port ID that should be used for MAC table
+ * entries targeting @port:
+ * - CPU port in tag_8021q standalone mode: the virtual bridge port
+ * (bridge_port_cpu) so known traffic exits through egress EVLAN
+ * - User port in a LAG: the LAG's dedicated firmware bridge port
+ * - Otherwise: the port index itself
*/
static int mxl862xx_fdb_bridge_port(struct dsa_switch *ds, int port,
const struct dsa_db db)
@@ -2636,7 +3144,7 @@ static int mxl862xx_fdb_bridge_port(stru
return bp_cpu;
}
- return port;
+ return mxl862xx_lag_bridge_port(priv, port);
}
/**
@@ -2883,11 +3391,43 @@ static int mxl862xx_port_fdb_del(struct
return ret;
}
+static int mxl862xx_lag_fdb_add(struct dsa_switch *ds, const struct dsa_lag lag,
+ const unsigned char *addr, u16 vid,
+ const struct dsa_db db)
+{
+ struct mxl862xx_priv *priv = ds->priv;
+ u16 lag_bp = priv->lag_bridge_ports[lag.id];
+ int fid;
+
+ if (!lag_bp)
+ return -ENOENT;
+
+ fid = mxl862xx_get_fid(ds, db);
+ if (fid < 0)
+ return fid;
+
+ return mxl862xx_fdb_add_per_fid(ds, addr, vid, fid, lag_bp);
+}
+
+static int mxl862xx_lag_fdb_del(struct dsa_switch *ds, const struct dsa_lag lag,
+ const unsigned char *addr, u16 vid,
+ const struct dsa_db db)
+{
+ int fid;
+
+ fid = mxl862xx_get_fid(ds, db);
+ if (fid < 0)
+ return fid;
+
+ return mxl862xx_fdb_del_per_fid(ds, addr, vid, fid);
+}
+
static int mxl862xx_port_fdb_dump(struct dsa_switch *ds, int port,
dsa_fdb_dump_cb_t *cb, void *data)
{
struct mxl862xx_mac_table_read param = { .initial = 1 };
struct mxl862xx_priv *priv = ds->priv;
+ u16 lag_bp = mxl862xx_lag_bridge_port(priv, port);
u32 entry_port_id;
int ret;
@@ -2901,7 +3441,7 @@ static int mxl862xx_port_fdb_dump(struct
entry_port_id = le32_to_cpu(param.port_id);
- if (entry_port_id == port) {
+ if (entry_port_id == port || entry_port_id == lag_bp) {
ret = cb(param.mac, FIELD_GET(MXL862XX_TCI_VLAN_ID,
le16_to_cpu(param.tci)),
param.static_entry, data);
@@ -3572,6 +4112,11 @@ static const struct dsa_switch_ops mxl86
.port_fdb_dump = mxl862xx_port_fdb_dump,
.port_mdb_add = mxl862xx_port_mdb_add,
.port_mdb_del = mxl862xx_port_mdb_del,
+ .port_lag_join = mxl862xx_port_lag_join,
+ .port_lag_leave = mxl862xx_port_lag_leave,
+ .port_lag_change = mxl862xx_port_lag_change,
+ .lag_fdb_add = mxl862xx_lag_fdb_add,
+ .lag_fdb_del = mxl862xx_lag_fdb_del,
.port_vlan_filtering = mxl862xx_port_vlan_filtering,
.port_vlan_add = mxl862xx_port_vlan_add,
.port_vlan_del = mxl862xx_port_vlan_del,
@@ -3612,6 +4157,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;
+ ds->num_lag_ids = MXL862XX_MAX_LAG_IDS;
mxl862xx_host_init(priv);
--- a/drivers/net/dsa/mxl862xx/mxl862xx.h
+++ b/drivers/net/dsa/mxl862xx/mxl862xx.h
@@ -19,6 +19,19 @@ struct mxl862xx_priv;
#define MXL862XX_MAX_BRIDGE_PORTS 128
#define MXL862XX_TOTAL_EVLAN_ENTRIES 1024
#define MXL862XX_TOTAL_VF_ENTRIES 1024
+#define MXL862XX_MAX_LAG_IDS 16
+
+/* Trunk hash field bitmask (matches PCE_TRUNK_CONF layout) */
+#define MXL862XX_TRUNK_HASH_SA BIT(0)
+#define MXL862XX_TRUNK_HASH_DA BIT(1)
+#define MXL862XX_TRUNK_HASH_SIP BIT(2)
+#define MXL862XX_TRUNK_HASH_DIP BIT(3)
+#define MXL862XX_TRUNK_HASH_SPORT BIT(4)
+#define MXL862XX_TRUNK_HASH_DPORT BIT(5)
+
+/* P-mapper LAG entries occupy indices 9..72 (64 entries) */
+#define MXL862XX_PMAPPER_LAG_FIRST 9
+#define MXL862XX_PMAPPER_LAG_COUNT 64
/* Number of __le16 words in a firmware portmap (128-bit bitmap). */
#define MXL862XX_FW_PORTMAP_WORDS (MXL862XX_MAX_BRIDGE_PORTS / 16)
@@ -230,6 +243,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
+ * @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
+ * @lag_hash_bits: hash field bitmask (MXL862XX_TRUNK_HASH_*) requested
+ * when this port joined its LAG; used to recompute the
+ * global trunk_hash when a LAG is destroyed
*/
struct mxl862xx_port {
struct mxl862xx_priv *priv;
@@ -249,6 +268,9 @@ struct mxl862xx_port {
struct work_struct host_flood_work;
u16 tag_8021q_vid;
struct mxl862xx_evlan_block cpu_egress_evlan;
+ struct dsa_lag *lag;
+ bool lag_tx_enabled;
+ u8 lag_hash_bits;
struct mxl862xx_port_stats stats;
spinlock_t stats_lock; /* protects stats accumulators */
};
@@ -334,6 +356,15 @@ struct 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).
+ * The bridge port is stable for the LAG's lifetime
+ * so FDB/MDB entries never need migration on
+ * membership changes.
+ * @trunk_hash: current global hash field bitmask (6 bits,
+ * MXL862XX_TRUNK_HASH_*); union of all active LAGs'
+ * hash requirements
* @stats_work: periodic work item that polls RMON hardware counters
* and accumulates them into 64-bit per-port stats
*/
@@ -354,6 +385,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;
};