Thread (10 messages) flat view 10 messages, 2 authors, 5d ago
COOLING5d

[RFC PATCH net-next v2 5/6] net: dsa: qca8k: support changing the CPU conduit

From: Brandon Mahdavi <hidden>
Date: 2026-08-02 03:47:29
Also in: lkml
Subsystem: networking drivers, networking [dsa], the rest · Maintainers: Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Andrew Lunn, Vladimir Oltean, Linus Torvalds

From: Christian Marangi <ansuelsmth@gmail.com>

Allow DSA to move a user port between the local QCA8K CPU ports or
onto a LAG made from those ports. Update both sides of lookup
membership so traffic can flow only between the user port and its
selected conduit.

A CPU port can join or leave a conduit LAG without DSA changing the
conduit netdev of user ports already assigned to that LAG. Wrap the
LAG callbacks and refresh those users after hardware trunk membership
changes. Derive bridge membership from the complete conduit mask as
well, so a later bridge rejoin does not reduce a conduit LAG to one
physical CPU port.

Snapshot both directions of the old lookup membership before a live
change and restore them if any register update fails. This keeps the
hardware on the old conduit when DSA rejects or unwinds the change.

Link: https://github.com/openwrt/openwrt/commit/500dbaefd2a6a1c9c0d81e275a28c6a8e26519e4
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Co-developed-by: Brandon Mahdavi <redacted>
Signed-off-by: Brandon Mahdavi <redacted>
---
 drivers/net/dsa/qca/qca8k-8xxx.c   | 138 ++++++++++++++++++++++++++++-
 drivers/net/dsa/qca/qca8k-common.c |  47 +++++++++-
 drivers/net/dsa/qca/qca8k.h        |   4 +
 3 files changed, 186 insertions(+), 3 deletions(-)
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index 9eae921ad716..32dc626399c9 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -1743,6 +1743,139 @@ qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
 	return DSA_TAG_PROTO_QCA;
 }
 
+static int qca8k_port_get_conduit_membership(struct dsa_switch *ds, int port,
+					     u32 *user_members,
+					     u32 *cpu_members)
+{
+	struct qca8k_priv *priv = ds->priv;
+	struct dsa_port *cpu_dp;
+	u32 val;
+	int ret;
+
+	ret = regmap_read(priv->regmap, QCA8K_PORT_LOOKUP_CTRL(port), &val);
+	if (ret)
+		return ret;
+
+	*user_members = val & dsa_cpu_ports(ds);
+	*cpu_members = 0;
+
+	dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+		ret = regmap_read(priv->regmap,
+				  QCA8K_PORT_LOOKUP_CTRL(cpu_dp->index), &val);
+		if (ret)
+			return ret;
+
+		if (val & BIT(port))
+			*cpu_members |= BIT(cpu_dp->index);
+	}
+
+	return 0;
+}
+
+static int qca8k_port_set_conduit_membership(struct dsa_switch *ds, int port,
+					     u32 user_members,
+					     u32 cpu_members)
+{
+	struct qca8k_priv *priv = ds->priv;
+	struct dsa_port *cpu_dp;
+	int ret;
+
+	ret = regmap_update_bits(priv->regmap,
+				 QCA8K_PORT_LOOKUP_CTRL(port),
+				 dsa_cpu_ports(ds), user_members);
+	if (ret)
+		return ret;
+
+	dsa_switch_for_each_cpu_port(cpu_dp, ds) {
+		u32 member = cpu_members & BIT(cpu_dp->index) ? BIT(port) : 0;
+
+		ret = regmap_update_bits(priv->regmap,
+					 QCA8K_PORT_LOOKUP_CTRL(cpu_dp->index),
+					 BIT(port), member);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int qca8k_port_change_conduit(struct dsa_switch *ds, int port,
+				     struct net_device *conduit,
+				     struct netlink_ext_ack *extack)
+{
+	struct qca8k_priv *priv = ds->priv;
+	u32 cpu_port_mask, old_cpu_members, old_user_members;
+	int restore_ret, ret;
+
+	ret = qca8k_conduit_port_mask(ds, conduit, &cpu_port_mask, extack);
+	if (ret)
+		return ret;
+
+	ret = qca8k_port_get_conduit_membership(ds, port, &old_user_members,
+						&old_cpu_members);
+	if (ret)
+		return ret;
+
+	ret = qca8k_port_set_conduit_membership(ds, port, cpu_port_mask,
+						cpu_port_mask);
+	if (!ret)
+		return 0;
+
+	restore_ret = qca8k_port_set_conduit_membership(ds, port,
+							old_user_members,
+							old_cpu_members);
+	if (restore_ret)
+		dev_err(priv->dev,
+			"failed to restore conduit membership for port %d: %pe\n",
+			port, ERR_PTR(restore_ret));
+
+	return ret;
+}
+
+static int qca8k_lag_refresh_conduit_ports(struct dsa_switch *ds,
+					   struct dsa_lag lag)
+{
+	struct dsa_port *dp;
+	int ret;
+
+	dsa_switch_for_each_user_port(dp, ds) {
+		if (dsa_port_to_conduit(dp) != lag.dev)
+			continue;
+
+		ret = qca8k_port_change_conduit(ds, dp->index, lag.dev, NULL);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int qca8k_port_lag_join_with_conduits(struct dsa_switch *ds, int port,
+					     struct dsa_lag lag,
+					     struct netdev_lag_upper_info *info,
+					     struct netlink_ext_ack *extack)
+{
+	int ret;
+
+	ret = qca8k_port_lag_join(ds, port, lag, info, extack);
+	if (ret)
+		return ret;
+
+	return qca8k_lag_refresh_conduit_ports(ds, lag);
+}
+
+static int qca8k_port_lag_leave_with_conduits(struct dsa_switch *ds, int port,
+					      struct dsa_lag lag)
+{
+	int ret;
+
+	ret = qca8k_port_lag_leave(ds, port, lag);
+	if (ret)
+		return ret;
+
+	return qca8k_lag_refresh_conduit_ports(ds, lag);
+}
+
 static void
 qca8k_conduit_change(struct dsa_switch *ds, const struct net_device *conduit,
 		     bool operational)
@@ -2032,8 +2165,9 @@ static const struct dsa_switch_ops qca8k_switch_ops = {
 	.port_vlan_del		= qca8k_port_vlan_del,
 	.phylink_get_caps	= qca8k_phylink_get_caps,
 	.get_phy_flags		= qca8k_get_phy_flags,
-	.port_lag_join		= qca8k_port_lag_join,
-	.port_lag_leave		= qca8k_port_lag_leave,
+	.port_lag_join		= qca8k_port_lag_join_with_conduits,
+	.port_lag_leave		= qca8k_port_lag_leave_with_conduits,
+	.port_change_conduit	= qca8k_port_change_conduit,
 	.conduit_state_change	= qca8k_conduit_change,
 	.connect_tag_protocol	= qca8k_connect_tag_protocol,
 };
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index c0a0d44e7c70..73e36f5fb358 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -637,15 +637,60 @@ void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 	qca8k_port_configure_learning(ds, port, learning);
 }
 
+int qca8k_conduit_port_mask(struct dsa_switch *ds,
+			    struct net_device *conduit,
+			    u32 *port_mask,
+			    struct netlink_ext_ack *extack)
+{
+	struct dsa_switch_tree *dst = ds->dst;
+	struct dsa_port *dp;
+	int id;
+
+	*port_mask = 0;
+
+	if (!netif_is_lag_master(conduit)) {
+		dp = conduit->dsa_ptr;
+		if (!dp || dp->ds != ds || !dsa_port_is_cpu(dp)) {
+			NL_SET_ERR_MSG_MOD(extack,
+					   "Conduit is not a local CPU port");
+			return -EOPNOTSUPP;
+		}
+
+		*port_mask = BIT(dp->index);
+		return 0;
+	}
+
+	id = dsa_lag_id(dst, conduit);
+	if (id < 0)
+		return id;
+
+	dsa_lag_foreach_port(dp, dst, dsa_lag_by_id(dst, id))
+		if (dp->ds == ds && dsa_port_is_cpu(dp))
+			*port_mask |= BIT(dp->index);
+
+	if (!*port_mask) {
+		NL_SET_ERR_MSG_MOD(extack,
+				   "Conduit LAG has no local CPU port");
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
 static int qca8k_update_port_member(struct qca8k_priv *priv, int port,
 				    const struct net_device *bridge_dev,
 				    bool join)
 {
 	bool isolated = !!(priv->port_isolated_map & BIT(port)), other_isolated;
 	struct dsa_port *dp = dsa_to_port(priv->ds, port), *other_dp;
-	u32 port_mask = BIT(dp->cpu_dp->index);
+	u32 port_mask;
 	int i, ret;
 
+	ret = qca8k_conduit_port_mask(priv->ds, dsa_port_to_conduit(dp),
+				      &port_mask, NULL);
+	if (ret)
+		return ret;
+
 	for (i = 0; i < QCA8K_NUM_PORTS; i++) {
 		if (i == port)
 			continue;
diff --git a/drivers/net/dsa/qca/qca8k.h b/drivers/net/dsa/qca/qca8k.h
index ab8ea261c0b9..333b343886de 100644
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/drivers/net/dsa/qca/qca8k.h
@@ -523,6 +523,10 @@ int qca8k_get_sset_count(struct dsa_switch *ds, int port, int sset);
 int qca8k_set_mac_eee(struct dsa_switch *ds, int port, struct ethtool_keee *eee);
 
 /* Common bridge function */
+int qca8k_conduit_port_mask(struct dsa_switch *ds,
+			    struct net_device *conduit,
+			    u32 *port_mask,
+			    struct netlink_ext_ack *extack);
 void qca8k_port_stp_state_set(struct dsa_switch *ds, int port, u8 state);
 int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
 				struct switchdev_brport_flags flags,
-- 
2.43.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help