mirror of
https://github.com/openwrt/openwrt.git
synced 2026-06-10 11:48:56 +04:00
028dc3f57a
Update driver to be ready for the upcoming firmware release. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
852 lines
27 KiB
Diff
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;
|
|
};
|
|
|