--- /dev/null
+From e85d3e6fea05c8ae21a40809a3c6b7adc97411c7 Mon Sep 17 00:00:00 2001
+Message-ID: <e85d3e6fea05c8ae21a40809a3c6b7adc97411c7.1728674648.git.mschiffer@universe-factory.net>
+From: Matthias Schiffer <mschiffer@universe-factory.net>
+Date: Thu, 20 Jun 2024 19:25:48 +0200
+Subject: [PATCH] net: dsa: qca8k: do not write port mask twice in bridge
+ join/leave
+
+qca8k_port_bridge_join() set QCA8K_PORT_LOOKUP_CTRL() for i == port twice,
+once in the loop handling all other port's masks, and finally at the end
+with the accumulated port_mask.
+
+The first time it would incorrectly set the port's own bit in the mask,
+only to correct the mistake a moment later. qca8k_port_bridge_leave() had
+the same issue, but here the regmap_clear_bits() was a no-op rather than
+setting an unintended value.
+
+Remove the duplicate assignment by skipping the whole loop iteration for
+i == port. The unintended bit setting doesn't seem to have any negative
+effects (even when not reverted right away), so the change is submitted
+as a simple cleanup rather than a fix.
+
+Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
+Reviewed-by: Wojciech Drewek <wojciech.drewek@intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-common.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -654,6 +654,8 @@ int qca8k_port_bridge_join(struct dsa_sw
+ port_mask = BIT(cpu_port);
+
+ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ if (i == port)
++ continue;
+ if (dsa_is_cpu_port(ds, i))
+ continue;
+ if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
+@@ -666,8 +668,7 @@ int qca8k_port_bridge_join(struct dsa_sw
+ BIT(port));
+ if (ret)
+ return ret;
+- if (i != port)
+- port_mask |= BIT(i);
++ port_mask |= BIT(i);
+ }
+
+ /* Add all other ports to this ports portvlan mask */
+@@ -686,6 +687,8 @@ void qca8k_port_bridge_leave(struct dsa_
+ cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+
+ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ if (i == port)
++ continue;
+ if (dsa_is_cpu_port(ds, i))
+ continue;
+ if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
--- /dev/null
+From 412e1775f413c944b8c51bdadb675be957d83dc8 Mon Sep 17 00:00:00 2001
+Message-ID: <412e1775f413c944b8c51bdadb675be957d83dc8.1728674648.git.mschiffer@universe-factory.net>
+In-Reply-To: <e85d3e6fea05c8ae21a40809a3c6b7adc97411c7.1728674648.git.mschiffer@universe-factory.net>
+References: <e85d3e6fea05c8ae21a40809a3c6b7adc97411c7.1728674648.git.mschiffer@universe-factory.net>
+From: Matthias Schiffer <mschiffer@universe-factory.net>
+Date: Thu, 20 Jun 2024 19:25:49 +0200
+Subject: [PATCH] net: dsa: qca8k: factor out bridge join/leave logic
+
+Most of the logic in qca8k_port_bridge_join() and qca8k_port_bridge_leave()
+is the same. Refactor to reduce duplication and prepare for reusing the
+code for implementing bridge port isolation.
+
+dsa_port_offloads_bridge_dev() is used instead of
+dsa_port_offloads_bridge(), passing the bridge in as a struct netdevice *,
+as we won't have a struct dsa_bridge in qca8k_port_bridge_flags().
+
+The error handling is changed slightly in the bridge leave case,
+returning early and emitting an error message when a regmap access fails.
+This shouldn't matter in practice, as there isn't much we can do if
+communication with the switch breaks down in the middle of reconfiguration.
+
+Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
+Reviewed-by: Wojciech Drewek <wojciech.drewek@intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-common.c | 101 ++++++++++++++---------------
+ 1 file changed, 50 insertions(+), 51 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -615,6 +615,49 @@ void qca8k_port_stp_state_set(struct dsa
+ qca8k_port_configure_learning(ds, port, learning);
+ }
+
++static int qca8k_update_port_member(struct qca8k_priv *priv, int port,
++ const struct net_device *bridge_dev,
++ bool join)
++{
++ struct dsa_port *dp = dsa_to_port(priv->ds, port), *other_dp;
++ u32 port_mask = BIT(dp->cpu_dp->index);
++ int i, ret;
++
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ if (i == port)
++ continue;
++ if (dsa_is_cpu_port(priv->ds, i))
++ continue;
++
++ other_dp = dsa_to_port(priv->ds, i);
++ if (!dsa_port_offloads_bridge_dev(other_dp, bridge_dev))
++ continue;
++
++ /* Add/remove this port to/from the portvlan mask of the other
++ * ports in the bridge
++ */
++ if (join) {
++ port_mask |= BIT(i);
++ ret = regmap_set_bits(priv->regmap,
++ QCA8K_PORT_LOOKUP_CTRL(i),
++ BIT(port));
++ } else {
++ ret = regmap_clear_bits(priv->regmap,
++ QCA8K_PORT_LOOKUP_CTRL(i),
++ BIT(port));
++ }
++
++ if (ret)
++ return ret;
++ }
++
++ /* Add/remove all other ports to/from this port's portvlan mask */
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_MEMBER, port_mask);
++
++ return ret;
++}
++
+ int qca8k_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+@@ -647,65 +690,21 @@ int qca8k_port_bridge_join(struct dsa_sw
+ struct netlink_ext_ack *extack)
+ {
+ struct qca8k_priv *priv = ds->priv;
+- int port_mask, cpu_port;
+- int i, ret;
+-
+- cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+- port_mask = BIT(cpu_port);
+
+- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+- if (i == port)
+- continue;
+- if (dsa_is_cpu_port(ds, i))
+- continue;
+- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
+- continue;
+- /* Add this port to the portvlan mask of the other ports
+- * in the bridge
+- */
+- ret = regmap_set_bits(priv->regmap,
+- QCA8K_PORT_LOOKUP_CTRL(i),
+- BIT(port));
+- if (ret)
+- return ret;
+- port_mask |= BIT(i);
+- }
+-
+- /* Add all other ports to this ports portvlan mask */
+- ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+- QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+-
+- return ret;
++ return qca8k_update_port_member(priv, port, bridge.dev, true);
+ }
+
+ void qca8k_port_bridge_leave(struct dsa_switch *ds, int port,
+ struct dsa_bridge bridge)
+ {
+ struct qca8k_priv *priv = ds->priv;
+- int cpu_port, i;
+-
+- cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
+-
+- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+- if (i == port)
+- continue;
+- if (dsa_is_cpu_port(ds, i))
+- continue;
+- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
+- continue;
+- /* Remove this port to the portvlan mask of the other ports
+- * in the bridge
+- */
+- regmap_clear_bits(priv->regmap,
+- QCA8K_PORT_LOOKUP_CTRL(i),
+- BIT(port));
+- }
++ int err;
+
+- /* Set the cpu port to be the only one in the portvlan mask of
+- * this port
+- */
+- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+- QCA8K_PORT_LOOKUP_MEMBER, BIT(cpu_port));
++ err = qca8k_update_port_member(priv, port, bridge.dev, false);
++ if (err)
++ dev_err(priv->dev,
++ "Failed to update switch config for bridge leave: %d\n",
++ err);
+ }
+
+ void qca8k_port_fast_age(struct dsa_switch *ds, int port)
--- /dev/null
+From 422b64025ec10981c48f9367311846bf4bd38042 Mon Sep 17 00:00:00 2001
+Message-ID: <422b64025ec10981c48f9367311846bf4bd38042.1728674648.git.mschiffer@universe-factory.net>
+In-Reply-To: <e85d3e6fea05c8ae21a40809a3c6b7adc97411c7.1728674648.git.mschiffer@universe-factory.net>
+References: <e85d3e6fea05c8ae21a40809a3c6b7adc97411c7.1728674648.git.mschiffer@universe-factory.net>
+From: Matthias Schiffer <mschiffer@universe-factory.net>
+Date: Thu, 20 Jun 2024 19:25:50 +0200
+Subject: [PATCH] net: dsa: qca8k: add support for bridge port isolation
+
+Remove a pair of ports from the port matrix when both ports have the
+isolated flag set.
+
+Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
+Reviewed-by: Wojciech Drewek <wojciech.drewek@intel.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-common.c | 22 ++++++++++++++++++++--
+ drivers/net/dsa/qca/qca8k.h | 1 +
+ 2 files changed, 21 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-common.c
++++ b/drivers/net/dsa/qca/qca8k-common.c
+@@ -619,6 +619,7 @@ static int qca8k_update_port_member(stru
+ 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);
+ int i, ret;
+@@ -633,10 +634,12 @@ static int qca8k_update_port_member(stru
+ if (!dsa_port_offloads_bridge_dev(other_dp, bridge_dev))
+ continue;
+
++ other_isolated = !!(priv->port_isolated_map & BIT(i));
++
+ /* Add/remove this port to/from the portvlan mask of the other
+ * ports in the bridge
+ */
+- if (join) {
++ if (join && !(isolated && other_isolated)) {
+ port_mask |= BIT(i);
+ ret = regmap_set_bits(priv->regmap,
+ QCA8K_PORT_LOOKUP_CTRL(i),
+@@ -662,7 +665,7 @@ int qca8k_port_pre_bridge_flags(struct d
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+ {
+- if (flags.mask & ~BR_LEARNING)
++ if (flags.mask & ~(BR_LEARNING | BR_ISOLATED))
+ return -EINVAL;
+
+ return 0;
+@@ -672,6 +675,7 @@ int qca8k_port_bridge_flags(struct dsa_s
+ struct switchdev_brport_flags flags,
+ struct netlink_ext_ack *extack)
+ {
++ struct qca8k_priv *priv = ds->priv;
+ int ret;
+
+ if (flags.mask & BR_LEARNING) {
+@@ -680,6 +684,20 @@ int qca8k_port_bridge_flags(struct dsa_s
+ if (ret)
+ return ret;
+ }
++
++ if (flags.mask & BR_ISOLATED) {
++ struct dsa_port *dp = dsa_to_port(ds, port);
++ struct net_device *bridge_dev = dsa_port_bridge_dev_get(dp);
++
++ if (flags.val & BR_ISOLATED)
++ priv->port_isolated_map |= BIT(port);
++ else
++ priv->port_isolated_map &= ~BIT(port);
++
++ ret = qca8k_update_port_member(priv, port, bridge_dev, true);
++ if (ret)
++ return ret;
++ }
+
+ return 0;
+ }
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -451,6 +451,7 @@ struct qca8k_priv {
+ * Bit 1: port enabled. Bit 0: port disabled.
+ */
+ u8 port_enabled_map;
++ u8 port_isolated_map;
+ struct qca8k_ports_config ports_config;
+ struct regmap *regmap;
+ struct mii_bus *bus;
.port_mirror_add = qca8k_port_mirror_add,
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
-@@ -1215,6 +1215,42 @@ int qca8k_port_lag_leave(struct dsa_swit
+@@ -1235,6 +1235,42 @@ int qca8k_port_lag_leave(struct dsa_swit
return qca8k_lag_refresh_portmap(ds, port, lag, true);
}
u32 val;
--- a/drivers/net/dsa/qca/qca8k.h
+++ b/drivers/net/dsa/qca/qca8k.h
-@@ -590,5 +590,11 @@ int qca8k_port_lag_join(struct dsa_switc
+@@ -591,5 +591,11 @@ int qca8k_port_lag_join(struct dsa_switc
struct netlink_ext_ack *extack);
int qca8k_port_lag_leave(struct dsa_switch *ds, int port,
struct dsa_lag lag);
mask = QCA8K_VTU_FUNC0_EG_MODE_PORT_NOT(i);
if ((reg & mask) != mask) {
-@@ -653,7 +653,7 @@ int qca8k_port_bridge_join(struct dsa_sw
- cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
- port_mask = BIT(cpu_port);
+@@ -624,7 +624,7 @@ static int qca8k_update_port_member(stru
+ u32 port_mask = BIT(dp->cpu_dp->index);
+ int i, ret;
- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-+ for (i = 0; i < ds->num_ports; i++) {
- if (dsa_is_cpu_port(ds, i))
- continue;
- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
-@@ -685,7 +685,7 @@ void qca8k_port_bridge_leave(struct dsa_
-
- cpu_port = dsa_to_port(ds, port)->cpu_dp->index;
-
-- for (i = 0; i < QCA8K_NUM_PORTS; i++) {
-+ for (i = 0; i < ds->num_ports; i++) {
- if (dsa_is_cpu_port(ds, i))
++ for (i = 0; i < priv->ds->num_ports; i++) {
+ if (i == port)
continue;
- if (!dsa_port_offloads_bridge(dsa_to_port(ds, i), &bridge))
+ if (dsa_is_cpu_port(priv->ds, i))
--- /dev/null
+++ b/drivers/net/dsa/qca/qca8k-ipq4019.c
@@ -0,0 +1,948 @@
enum {
QCA8K_PORT_SPEED_10M = 0,
QCA8K_PORT_SPEED_100M = 1,
-@@ -466,6 +518,10 @@ struct qca8k_priv {
+@@ -467,6 +519,10 @@ struct qca8k_priv {
struct qca8k_pcs pcs_port_6;
const struct qca8k_match_data *info;
struct qca8k_led ports_led[QCA8K_LED_COUNT];