net: dsa: microchip: make learning configurable and keep it off while standalone
authorVladimir Oltean <vladimir.oltean@nxp.com>
Thu, 18 Aug 2022 16:48:09 +0000 (19:48 +0300)
committerJakub Kicinski <kuba@kernel.org>
Tue, 23 Aug 2022 21:36:56 +0000 (14:36 -0700)
Address learning should initially be turned off by the driver for port
operation in standalone mode, then the DSA core handles changes to it
via ds->ops->port_bridge_flags().

Leaving address learning enabled while ports are standalone breaks any
kind of communication which involves port B receiving what port A has
sent. Notably it breaks the ksz9477 driver used with a (non offloaded,
ports act as if standalone) bonding interface in active-backup mode,
when the ports are connected together through external switches, for
redundancy purposes.

This fixes a major design flaw in the ksz9477 and ksz8795 drivers, which
unconditionally leave address learning enabled even while ports operate
as standalone.

Fixes: b987e98e50ab ("dsa: add DSA switch driver for Microchip KSZ9477")
Link: https://lore.kernel.org/netdev/CAFZh4h-JVWt80CrQWkFji7tZJahMfOToUJQgKS5s0_=9zzpvYQ@mail.gmail.com/
Reported-by: Brian Hutchinson <b.hutchman@gmail.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20220818164809.3198039-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/microchip/ksz_common.c
drivers/net/dsa/microchip/ksz_common.h

index 7461272..6bd69a7 100644 (file)
@@ -968,6 +968,7 @@ static void ksz_update_port_member(struct ksz_device *dev, int port)
 static int ksz_setup(struct dsa_switch *ds)
 {
        struct ksz_device *dev = ds->priv;
+       struct ksz_port *p;
        const u16 *regs;
        int ret;
 
@@ -1007,6 +1008,14 @@ static int ksz_setup(struct dsa_switch *ds)
                        return ret;
        }
 
+       /* Start with learning disabled on standalone user ports, and enabled
+        * on the CPU port. In lack of other finer mechanisms, learning on the
+        * CPU port will avoid flooding bridge local addresses on the network
+        * in some cases.
+        */
+       p = &dev->ports[dev->cpu_port];
+       p->learning = true;
+
        /* start switch */
        regmap_update_bits(dev->regmap[0], regs[S_START_CTRL],
                           SW_START, SW_START);
@@ -1283,6 +1292,8 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
        ksz_pread8(dev, port, regs[P_STP_CTRL], &data);
        data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
 
+       p = &dev->ports[port];
+
        switch (state) {
        case BR_STATE_DISABLED:
                data |= PORT_LEARN_DISABLE;
@@ -1292,9 +1303,13 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
                break;
        case BR_STATE_LEARNING:
                data |= PORT_RX_ENABLE;
+               if (!p->learning)
+                       data |= PORT_LEARN_DISABLE;
                break;
        case BR_STATE_FORWARDING:
                data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
+               if (!p->learning)
+                       data |= PORT_LEARN_DISABLE;
                break;
        case BR_STATE_BLOCKING:
                data |= PORT_LEARN_DISABLE;
@@ -1306,12 +1321,38 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
 
        ksz_pwrite8(dev, port, regs[P_STP_CTRL], data);
 
-       p = &dev->ports[port];
        p->stp_state = state;
 
        ksz_update_port_member(dev, port);
 }
 
+static int ksz_port_pre_bridge_flags(struct dsa_switch *ds, int port,
+                                    struct switchdev_brport_flags flags,
+                                    struct netlink_ext_ack *extack)
+{
+       if (flags.mask & ~BR_LEARNING)
+               return -EINVAL;
+
+       return 0;
+}
+
+static int ksz_port_bridge_flags(struct dsa_switch *ds, int port,
+                                struct switchdev_brport_flags flags,
+                                struct netlink_ext_ack *extack)
+{
+       struct ksz_device *dev = ds->priv;
+       struct ksz_port *p = &dev->ports[port];
+
+       if (flags.mask & BR_LEARNING) {
+               p->learning = !!(flags.val & BR_LEARNING);
+
+               /* Make the change take effect immediately */
+               ksz_port_stp_state_set(ds, port, p->stp_state);
+       }
+
+       return 0;
+}
+
 static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds,
                                                  int port,
                                                  enum dsa_tag_protocol mp)
@@ -1725,6 +1766,8 @@ static const struct dsa_switch_ops ksz_switch_ops = {
        .port_bridge_join       = ksz_port_bridge_join,
        .port_bridge_leave      = ksz_port_bridge_leave,
        .port_stp_state_set     = ksz_port_stp_state_set,
+       .port_pre_bridge_flags  = ksz_port_pre_bridge_flags,
+       .port_bridge_flags      = ksz_port_bridge_flags,
        .port_fast_age          = ksz_port_fast_age,
        .port_vlan_filtering    = ksz_port_vlan_filtering,
        .port_vlan_add          = ksz_port_vlan_add,
index 764ada3..0d9520d 100644 (file)
@@ -65,6 +65,7 @@ struct ksz_chip_data {
 
 struct ksz_port {
        bool remove_tag;                /* Remove Tag flag set, for ksz8795 only */
+       bool learning;
        int stp_state;
        struct phy_device phydev;