[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