Skip to content

Commit a57d8c2

Browse files
vladimirolteankuba-moo
authored andcommitted
net: dsa: flush switchdev workqueue before tearing down CPU/DSA ports
Sometimes when unbinding the mv88e6xxx driver on Turris MOX, these error messages appear: mv88e6085 d0032004.mdio-mii:12: port 1 failed to delete be:79:b4:9e:9e:96 vid 1 from fdb: -2 mv88e6085 d0032004.mdio-mii:12: port 1 failed to delete be:79:b4:9e:9e:96 vid 0 from fdb: -2 mv88e6085 d0032004.mdio-mii:12: port 1 failed to delete d8:58:d7:00:ca:6d vid 100 from fdb: -2 mv88e6085 d0032004.mdio-mii:12: port 1 failed to delete d8:58:d7:00:ca:6d vid 1 from fdb: -2 mv88e6085 d0032004.mdio-mii:12: port 1 failed to delete d8:58:d7:00:ca:6d vid 0 from fdb: -2 (and similarly for other ports) What happens is that DSA has a policy "even if there are bugs, let's at least not leak memory" and dsa_port_teardown() clears the dp->fdbs and dp->mdbs lists, which are supposed to be empty. But deleting that cleanup code, the warnings go away. => the FDB and MDB lists (used for refcounting on shared ports, aka CPU and DSA ports) will eventually be empty, but are not empty by the time we tear down those ports. Aka we are deleting them too soon. The addresses that DSA complains about are host-trapped addresses: the local addresses of the ports, and the MAC address of the bridge device. The problem is that offloading those entries happens from a deferred work item scheduled by the SWITCHDEV_FDB_DEL_TO_DEVICE handler, and this races with the teardown of the CPU and DSA ports where the refcounting is kept. In fact, not only it races, but fundamentally speaking, if we iterate through the port list linearly, we might end up tearing down the shared ports even before we delete a DSA user port which has a bridge upper. So as it turns out, we need to first tear down the user ports (and the unused ones, for no better place of doing that), then the shared ports (the CPU and DSA ports). In between, we need to ensure that all work items scheduled by our switchdev handlers (which only run for user ports, hence the reason why we tear them down first) have finished. Fixes: 161ca59 ("net: dsa: reference count the MDB entries at the cross-chip notifier level") Signed-off-by: Vladimir Oltean <[email protected]> Reviewed-by: Florian Fainelli <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 301de69 commit a57d8c2

File tree

4 files changed

+42
-15
lines changed

4 files changed

+42
-15
lines changed

include/net/dsa.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,11 @@ static inline bool dsa_port_is_user(struct dsa_port *dp)
447447
return dp->type == DSA_PORT_TYPE_USER;
448448
}
449449

450+
static inline bool dsa_port_is_unused(struct dsa_port *dp)
451+
{
452+
return dp->type == DSA_PORT_TYPE_UNUSED;
453+
}
454+
450455
static inline bool dsa_is_unused_port(struct dsa_switch *ds, int p)
451456
{
452457
return dsa_to_port(ds, p)->type == DSA_PORT_TYPE_UNUSED;

net/dsa/dsa.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,11 @@ bool dsa_schedule_work(struct work_struct *work)
345345
return queue_work(dsa_owq, work);
346346
}
347347

348+
void dsa_flush_workqueue(void)
349+
{
350+
flush_workqueue(dsa_owq);
351+
}
352+
348353
int dsa_devlink_param_get(struct devlink *dl, u32 id,
349354
struct devlink_param_gset_ctx *ctx)
350355
{

net/dsa/dsa2.c

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,33 @@ static void dsa_switch_teardown(struct dsa_switch *ds)
897897
ds->setup = false;
898898
}
899899

900+
/* First tear down the non-shared, then the shared ports. This ensures that
901+
* all work items scheduled by our switchdev handlers for user ports have
902+
* completed before we destroy the refcounting kept on the shared ports.
903+
*/
904+
static void dsa_tree_teardown_ports(struct dsa_switch_tree *dst)
905+
{
906+
struct dsa_port *dp;
907+
908+
list_for_each_entry(dp, &dst->ports, list)
909+
if (dsa_port_is_user(dp) || dsa_port_is_unused(dp))
910+
dsa_port_teardown(dp);
911+
912+
dsa_flush_workqueue();
913+
914+
list_for_each_entry(dp, &dst->ports, list)
915+
if (dsa_port_is_dsa(dp) || dsa_port_is_cpu(dp))
916+
dsa_port_teardown(dp);
917+
}
918+
919+
static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)
920+
{
921+
struct dsa_port *dp;
922+
923+
list_for_each_entry(dp, &dst->ports, list)
924+
dsa_switch_teardown(dp->ds);
925+
}
926+
900927
static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
901928
{
902929
struct dsa_port *dp;
@@ -923,26 +950,13 @@ static int dsa_tree_setup_switches(struct dsa_switch_tree *dst)
923950
return 0;
924951

925952
teardown:
926-
list_for_each_entry(dp, &dst->ports, list)
927-
dsa_port_teardown(dp);
953+
dsa_tree_teardown_ports(dst);
928954

929-
list_for_each_entry(dp, &dst->ports, list)
930-
dsa_switch_teardown(dp->ds);
955+
dsa_tree_teardown_switches(dst);
931956

932957
return err;
933958
}
934959

935-
static void dsa_tree_teardown_switches(struct dsa_switch_tree *dst)
936-
{
937-
struct dsa_port *dp;
938-
939-
list_for_each_entry(dp, &dst->ports, list)
940-
dsa_port_teardown(dp);
941-
942-
list_for_each_entry(dp, &dst->ports, list)
943-
dsa_switch_teardown(dp->ds);
944-
}
945-
946960
static int dsa_tree_setup_master(struct dsa_switch_tree *dst)
947961
{
948962
struct dsa_port *dp;
@@ -1052,6 +1066,8 @@ static void dsa_tree_teardown(struct dsa_switch_tree *dst)
10521066

10531067
dsa_tree_teardown_master(dst);
10541068

1069+
dsa_tree_teardown_ports(dst);
1070+
10551071
dsa_tree_teardown_switches(dst);
10561072

10571073
dsa_tree_teardown_cpu_ports(dst);

net/dsa/dsa_priv.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ void dsa_tag_driver_put(const struct dsa_device_ops *ops);
170170
const struct dsa_device_ops *dsa_find_tagger_by_name(const char *buf);
171171

172172
bool dsa_schedule_work(struct work_struct *work);
173+
void dsa_flush_workqueue(void);
173174
const char *dsa_tag_protocol_to_str(const struct dsa_device_ops *ops);
174175

175176
static inline int dsa_tag_protocol_overhead(const struct dsa_device_ops *ops)

0 commit comments

Comments
 (0)