From a4ffe09fc2d7138d28b225cc20893f506f2712cf Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 9 Aug 2021 01:56:48 +0300 Subject: [PATCH 1/2] net: dsa: still fast-age ports joining a bridge if they can't configure learning Commit 39f32101543b ("net: dsa: don't fast age standalone ports") assumed that all standalone ports disable address learning, but if the switch driver implements .port_fast_age but not .port_bridge_flags (like ksz9477, ksz8795, lantiq_gswip, lan9303), then that might not actually be true. So whereas before, the bridge temporarily walking us through the BLOCKING STP state meant that the standalone ports had a checkpoint to flush their baggage and start fresh when they join a bridge, after that commit they no longer do. Restore the old behavior for these drivers by checking if the switch can toggle address learning. If it can't, disregard the "do_fast_age" argument and unconditionally perform fast ageing on STP state changes. Signed-off-by: Vladimir Oltean Signed-off-by: David S. Miller --- net/dsa/port.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/net/dsa/port.c b/net/dsa/port.c index 96a4de67eccb..aac87ac989ed 100644 --- a/net/dsa/port.c +++ b/net/dsa/port.c @@ -60,6 +60,21 @@ static void dsa_port_fast_age(const struct dsa_port *dp) dsa_port_notify_bridge_fdb_flush(dp); } +static bool dsa_port_can_configure_learning(struct dsa_port *dp) +{ + struct switchdev_brport_flags flags = { + .mask = BR_LEARNING, + }; + struct dsa_switch *ds = dp->ds; + int err; + + if (!ds->ops->port_bridge_flags || !ds->ops->port_pre_bridge_flags) + return false; + + err = ds->ops->port_pre_bridge_flags(ds, dp->index, flags, NULL); + return !err; +} + int dsa_port_set_state(struct dsa_port *dp, u8 state, bool do_fast_age) { struct dsa_switch *ds = dp->ds; @@ -70,7 +85,8 @@ int dsa_port_set_state(struct dsa_port *dp, u8 state, bool do_fast_age) ds->ops->port_stp_state_set(ds, port, state); - if (do_fast_age && dp->learning) { + if (!dsa_port_can_configure_learning(dp) || + (do_fast_age && dp->learning)) { /* Fast age FDB entries or flush appropriate forwarding database * for the given port, if we are moving it from Learning or * Forwarding state, to Disabled or Blocking or Listening state. From bee7c577e6d7b51fa0d2b30747c2cd3499ef778e Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Mon, 9 Aug 2021 01:56:49 +0300 Subject: [PATCH 2/2] net: dsa: avoid fast ageing twice when port leaves a bridge Drivers that support both the toggling of address learning and dynamic FDB flushing (mv88e6xxx, b53, sja1105) currently need to fast-age a port twice when it leaves a bridge: - once, when del_nbp() calls br_stp_disable_port() which puts the port in the BLOCKING state - twice, when dsa_port_switchdev_unsync_attrs() calls dsa_port_clear_brport_flags() which disables address learning The knee-jerk reaction might be to say "dsa_port_clear_brport_flags does not need to fast-age the port at all", but the thing is, we still need both code paths to flush the dynamic FDB entries in different situations. When a DSA switch port leaves a bonding/team interface that is (still) a bridge port, no del_nbp() will be called, so we rely on dsa_port_clear_brport_flags() function to restore proper standalone port functionality with address learning disabled. So the solution is just to avoid double the work when both code paths are called in series. Luckily, DSA already caches the STP port state, so we can skip flushing the dynamic FDB when we disable address learning and the STP state is one where no address learning takes place at all. Under that condition, not flushing the FDB is safe because there is supposed to not be any dynamic FDB entry at all (they were flushed during the transition towards that state, and none were learned in the meanwhile). Signed-off-by: Vladimir Oltean Signed-off-by: David S. Miller --- net/dsa/port.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/net/dsa/port.c b/net/dsa/port.c index aac87ac989ed..831d50d28d59 100644 --- a/net/dsa/port.c +++ b/net/dsa/port.c @@ -699,7 +699,9 @@ int dsa_port_bridge_flags(struct dsa_port *dp, if (learning == dp->learning) return 0; - if (dp->learning && !learning) + if ((dp->learning && !learning) && + (dp->stp_state == BR_STATE_LEARNING || + dp->stp_state == BR_STATE_FORWARDING)) dsa_port_fast_age(dp); dp->learning = learning;