Commit 15f7cfae authored by Vladimir Oltean's avatar Vladimir Oltean Committed by Jakub Kicinski

net: dsa: microchip: make learning configurable and keep it off while standalone

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: b987e98e ("dsa: add DSA switch driver for Microchip KSZ9477")
Link: https://lore.kernel.org/netdev/CAFZh4h-JVWt80CrQWkFji7tZJahMfOToUJQgKS5s0_=9zzpvYQ@mail.gmail.com/Reported-by: default avatarBrian Hutchinson <b.hutchman@gmail.com>
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Link: https://lore.kernel.org/r/20220818164809.3198039-1-vladimir.oltean@nxp.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 855a28f9
...@@ -968,6 +968,7 @@ static void ksz_update_port_member(struct ksz_device *dev, int port) ...@@ -968,6 +968,7 @@ static void ksz_update_port_member(struct ksz_device *dev, int port)
static int ksz_setup(struct dsa_switch *ds) static int ksz_setup(struct dsa_switch *ds)
{ {
struct ksz_device *dev = ds->priv; struct ksz_device *dev = ds->priv;
struct ksz_port *p;
const u16 *regs; const u16 *regs;
int ret; int ret;
...@@ -1007,6 +1008,14 @@ static int ksz_setup(struct dsa_switch *ds) ...@@ -1007,6 +1008,14 @@ static int ksz_setup(struct dsa_switch *ds)
return ret; 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 */ /* start switch */
regmap_update_bits(dev->regmap[0], regs[S_START_CTRL], regmap_update_bits(dev->regmap[0], regs[S_START_CTRL],
SW_START, SW_START); SW_START, SW_START);
...@@ -1283,6 +1292,8 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) ...@@ -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); ksz_pread8(dev, port, regs[P_STP_CTRL], &data);
data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE); data &= ~(PORT_TX_ENABLE | PORT_RX_ENABLE | PORT_LEARN_DISABLE);
p = &dev->ports[port];
switch (state) { switch (state) {
case BR_STATE_DISABLED: case BR_STATE_DISABLED:
data |= PORT_LEARN_DISABLE; data |= PORT_LEARN_DISABLE;
...@@ -1292,9 +1303,13 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) ...@@ -1292,9 +1303,13 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state)
break; break;
case BR_STATE_LEARNING: case BR_STATE_LEARNING:
data |= PORT_RX_ENABLE; data |= PORT_RX_ENABLE;
if (!p->learning)
data |= PORT_LEARN_DISABLE;
break; break;
case BR_STATE_FORWARDING: case BR_STATE_FORWARDING:
data |= (PORT_TX_ENABLE | PORT_RX_ENABLE); data |= (PORT_TX_ENABLE | PORT_RX_ENABLE);
if (!p->learning)
data |= PORT_LEARN_DISABLE;
break; break;
case BR_STATE_BLOCKING: case BR_STATE_BLOCKING:
data |= PORT_LEARN_DISABLE; data |= PORT_LEARN_DISABLE;
...@@ -1306,12 +1321,38 @@ void ksz_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) ...@@ -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); ksz_pwrite8(dev, port, regs[P_STP_CTRL], data);
p = &dev->ports[port];
p->stp_state = state; p->stp_state = state;
ksz_update_port_member(dev, port); 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, static enum dsa_tag_protocol ksz_get_tag_protocol(struct dsa_switch *ds,
int port, int port,
enum dsa_tag_protocol mp) enum dsa_tag_protocol mp)
...@@ -1725,6 +1766,8 @@ static const struct dsa_switch_ops ksz_switch_ops = { ...@@ -1725,6 +1766,8 @@ static const struct dsa_switch_ops ksz_switch_ops = {
.port_bridge_join = ksz_port_bridge_join, .port_bridge_join = ksz_port_bridge_join,
.port_bridge_leave = ksz_port_bridge_leave, .port_bridge_leave = ksz_port_bridge_leave,
.port_stp_state_set = ksz_port_stp_state_set, .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_fast_age = ksz_port_fast_age,
.port_vlan_filtering = ksz_port_vlan_filtering, .port_vlan_filtering = ksz_port_vlan_filtering,
.port_vlan_add = ksz_port_vlan_add, .port_vlan_add = ksz_port_vlan_add,
......
...@@ -65,6 +65,7 @@ struct ksz_chip_data { ...@@ -65,6 +65,7 @@ struct ksz_chip_data {
struct ksz_port { struct ksz_port {
bool remove_tag; /* Remove Tag flag set, for ksz8795 only */ bool remove_tag; /* Remove Tag flag set, for ksz8795 only */
bool learning;
int stp_state; int stp_state;
struct phy_device phydev; struct phy_device phydev;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment