* [PATCH net-next 1/2] net: bridge: switchdev: add br_switchdev_fdb_find_port()
2026-11-08 11:16 [PATCH net-next 0/2] net: dsa: fix host fdb refcount when an entry roams Julius Bairaktaris
@ 2026-11-08 11:16 ` Julius Bairaktaris
2026-11-08 11:16 ` [PATCH net-next 2/2] net: dsa: fix host fdb refcount when a bridge fdb entry roams Julius Bairaktaris
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-08 11:16 UTC (permalink / raw)
To: Nikolay Aleksandrov, Ido Schimmel, Andrew Lunn, Vladimir Oltean
Cc: netdev, bridge, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, linux-kernel
Add an RCU lookup that returns the port of a non-local FDB entry that
switchdev is notified of: not locked, not added by the user as a
dynamic entry, and not learned by a switchdev driver.
A switchdev driver that handles FDB events from a workqueue cannot take
RTNL for br_fdb_find_port(), and has to ignore the entries whose
deletion it will not be notified of. The filter of
br_switchdev_fdb_notify() moves into a helper that both use, with no
functional change; the learning path does not change.
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
include/linux/if_bridge.h | 13 ++++++++
net/bridge/br_switchdev.c | 63 +++++++++++++++++++++++++++++++++------
2 files changed, 67 insertions(+), 9 deletions(-)
diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index 75673b8bffcb8..e050b7e698fc9 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -201,6 +201,19 @@ static inline int br_mst_get_state(const struct net_device *dev, u16 msti,
}
#endif
+#if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_NET_SWITCHDEV)
+struct net_device *br_switchdev_fdb_find_port(const struct net_device *br_dev,
+ const unsigned char *addr,
+ u16 vid);
+#else
+static inline struct net_device *
+br_switchdev_fdb_find_port(const struct net_device *br_dev,
+ const unsigned char *addr, u16 vid)
+{
+ return NULL;
+}
+#endif
+
#if IS_ENABLED(CONFIG_BRIDGE)
struct net_device *br_fdb_find_port(const struct net_device *br_dev,
const unsigned char *addr,
diff --git a/net/bridge/br_switchdev.c b/net/bridge/br_switchdev.c
index 10f3aea6a1f82..02ca1cbcfede0 100644
--- a/net/bridge/br_switchdev.c
+++ b/net/bridge/br_switchdev.c
@@ -141,14 +141,10 @@ static void br_switchdev_fdb_populate(struct net_bridge *br,
item->info.ctx = ctx;
}
-void
-br_switchdev_fdb_notify(struct net_bridge *br,
- const struct net_bridge_fdb_entry *fdb, int type)
+static bool br_switchdev_fdb_notifies(const struct net_bridge_fdb_entry *fdb)
{
- struct switchdev_notifier_fdb_info item;
-
if (test_bit(BR_FDB_LOCKED, &fdb->flags))
- return;
+ return false;
/* Entries with these flags were created using ndm_state == NUD_REACHABLE,
* ndm_flags == NTF_MASTER( | NTF_STICKY), ext_flags == 0 by something
@@ -156,9 +152,58 @@ br_switchdev_fdb_notify(struct net_bridge *br,
* Drivers don't know how to deal with these, so don't notify them to
* avoid confusing them.
*/
- if (test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags) &&
- !test_bit(BR_FDB_STATIC, &fdb->flags) &&
- !test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags))
+ return !test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags) ||
+ test_bit(BR_FDB_STATIC, &fdb->flags) ||
+ test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags);
+}
+
+/**
+ * br_switchdev_fdb_find_port - find the port of an entry switchdev knows of
+ * @br_dev: the bridge device
+ * @addr: the MAC address of the entry
+ * @vid: the VLAN of the entry
+ *
+ * Must be called under RCU; the device is valid until the read-side critical
+ * section ends.
+ *
+ * Return: the port of the non-local entry for @addr and @vid, or NULL if
+ * there is none or switchdev is not notified of it.
+ */
+struct net_device *br_switchdev_fdb_find_port(const struct net_device *br_dev,
+ const unsigned char *addr,
+ u16 vid)
+{
+ const struct net_bridge_port *p;
+ struct net_bridge_fdb_entry *f;
+
+ RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious use outside RCU");
+
+ if (!netif_is_bridge_master(br_dev))
+ return NULL;
+
+ f = br_fdb_find_rcu(netdev_priv(br_dev), addr, vid);
+ if (!f || test_bit(BR_FDB_LOCAL, &f->flags) ||
+ !br_switchdev_fdb_notifies(f))
+ return NULL;
+
+ /* learned by a switchdev driver, which is not notified back */
+ if (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags) &&
+ !test_bit(BR_FDB_ADDED_BY_USER, &f->flags))
+ return NULL;
+
+ p = br_fdb_dst_port(f);
+
+ return p ? p->dev : NULL;
+}
+EXPORT_SYMBOL_GPL(br_switchdev_fdb_find_port);
+
+void
+br_switchdev_fdb_notify(struct net_bridge *br,
+ const struct net_bridge_fdb_entry *fdb, int type)
+{
+ struct switchdev_notifier_fdb_info item;
+
+ if (!br_switchdev_fdb_notifies(fdb))
return;
br_switchdev_fdb_populate(br, &item, fdb, NULL);
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH net-next 2/2] net: dsa: fix host fdb refcount when a bridge fdb entry roams
2026-11-08 11:16 [PATCH net-next 0/2] net: dsa: fix host fdb refcount when an entry roams Julius Bairaktaris
2026-11-08 11:16 ` [PATCH net-next 1/2] net: bridge: switchdev: add br_switchdev_fdb_find_port() Julius Bairaktaris
@ 2026-11-08 11:16 ` Julius Bairaktaris
2026-11-08 11:22 ` [PATCH net-next 0/2] net: dsa: fix host fdb refcount when an " netdev-bot+sinfo
2026-11-08 11:47 ` Julius Bairaktaris
3 siblings, 0 replies; 5+ messages in thread
From: Julius Bairaktaris @ 2026-11-08 11:16 UTC (permalink / raw)
To: Nikolay Aleksandrov, Ido Schimmel, Andrew Lunn, Vladimir Oltean
Cc: netdev, bridge, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, linux-kernel
A bridge FDB entry on a foreign port is a host address on every user
port in the bridge. When the entry roams, br_fdb_update() notifies the
deletion from the old port and the addition on the new one without a
lock. With two CPUs receiving the address on different ports, DSA can
get two deletions for one addition, an addition for a port the entry
already left, or the deletion before the addition. Counting these
events either makes a deletion find no reference left on the CPU port,
or leaks a reference, which keeps the address on the CPU port after the
entry moved to a user port or was deleted. The first ends in this
error, here from dsa_loop:
port 1 failed to delete 02:5a:0b:a2:1a:46 vid 0 from fdb: -2
Instead of counting, each user port records the host addresses it
holds, and on every event holds the address while the bridge has the
entry on a foreign port and notifies switchdev of it. A deletion that
still finds the entry on its own port was sent before the bridge
changed the port, so the address is released. An entry on a user port,
also one of another tree, is notified only to that port and is not
held; an addition there releases the address on the other ports of the
bridge, which also covers a "bridge fdb replace" onto a user port that
notifies no deletion. Replayed deletions release the address without
the lookup. When the port leaves the bridge, or fails to join it, the
workqueue is flushed before the bridge is freed and the records left
are released. Addresses local to the bridge do not roam and are still
counted. The software bridge is not changed.
In QEMU, with dsa_loop given FDB operations and assisted learning, two
veth ports in its bridge and one source address sent from both on two
CPUs for 5 s, the deletion fails and the address stays on the CPU port
after both veth ports go down in 4 of 5 boots. With this change neither
happens in 5 of 5 boots, and the address is removed when the user ports
leave the bridge. A "bridge fdb replace" of the address onto a user port
leaves it on the CPU port in 5 of 5 boots without this change and
removes it in 5 of 5 with it.
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
---
net/dsa/port.c | 36 +++++++--
net/dsa/port.h | 6 ++
net/dsa/user.c | 216 +++++++++++++++++++++++++++++++++++++++++++++----
net/dsa/user.h | 8 ++
4 files changed, 242 insertions(+), 24 deletions(-)
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 1f5536c0dffc2..e270a0c5e6641 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -457,7 +457,13 @@ static void dsa_port_bridge_destroy(struct dsa_port *dp,
{
struct dsa_bridge *bridge = dp->bridge;
- dp->bridge = NULL;
+ WRITE_ONCE(dp->bridge, NULL);
+
+ /* Wait for the FDB events that still see the bridge, then release the
+ * host addresses the port holds, before the drivers are told it left.
+ */
+ dsa_flush_workqueue();
+ dsa_user_bridge_host_fdb_purge(dp, bridge);
if (!refcount_dec_and_test(&bridge->refcount))
return;
@@ -528,7 +534,9 @@ int dsa_port_bridge_join(struct dsa_port *dp, struct net_device *br,
&dsa_user_switchdev_blocking_notifier);
dsa_flush_workqueue();
out_rollback_unbridge:
+ dsa_port_bridge_destroy(dp, br);
dsa_broadcast(DSA_NOTIFIER_BRIDGE_LEAVE, &info);
+ return err;
out_rollback:
dsa_port_bridge_destroy(dp, br);
return err;
@@ -1040,13 +1048,14 @@ int dsa_port_standalone_host_fdb_add(struct dsa_port *dp,
return dsa_port_host_fdb_add(dp, addr, vid, db);
}
-int dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
- const unsigned char *addr, u16 vid)
+int __dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid)
{
struct net_device *conduit = dsa_port_to_conduit(dp);
struct dsa_db db = {
.type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
+ .bridge = *bridge,
};
int err;
@@ -1066,6 +1075,12 @@ int dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
return dsa_port_host_fdb_add(dp, addr, vid, db);
}
+int dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
+ return __dsa_port_bridge_host_fdb_add(dp, dp->bridge, addr, vid);
+}
+
static int dsa_port_host_fdb_del(struct dsa_port *dp,
const unsigned char *addr, u16 vid,
struct dsa_db db)
@@ -1091,13 +1106,14 @@ int dsa_port_standalone_host_fdb_del(struct dsa_port *dp,
return dsa_port_host_fdb_del(dp, addr, vid, db);
}
-int dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
- const unsigned char *addr, u16 vid)
+int __dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid)
{
struct net_device *conduit = dsa_port_to_conduit(dp);
struct dsa_db db = {
.type = DSA_DB_BRIDGE,
- .bridge = *dp->bridge,
+ .bridge = *bridge,
};
int err;
@@ -1113,6 +1129,12 @@ int dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
return dsa_port_host_fdb_del(dp, addr, vid, db);
}
+int dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
+ const unsigned char *addr, u16 vid)
+{
+ return __dsa_port_bridge_host_fdb_del(dp, dp->bridge, addr, vid);
+}
+
int dsa_port_lag_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid)
{
diff --git a/net/dsa/port.h b/net/dsa/port.h
index 6bc3291573c02..6524f67a37a20 100644
--- a/net/dsa/port.h
+++ b/net/dsa/port.h
@@ -54,8 +54,14 @@ int dsa_port_standalone_host_fdb_add(struct dsa_port *dp,
const unsigned char *addr, u16 vid);
int dsa_port_standalone_host_fdb_del(struct dsa_port *dp,
const unsigned char *addr, u16 vid);
+int __dsa_port_bridge_host_fdb_add(struct dsa_port *dp,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid);
int dsa_port_bridge_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
+int __dsa_port_bridge_host_fdb_del(struct dsa_port *dp,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid);
int dsa_port_bridge_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_lag_fdb_add(struct dsa_port *dp, const unsigned char *addr,
diff --git a/net/dsa/user.c b/net/dsa/user.c
index 041f9060c8ef0..7f18c7885b9c5 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -43,6 +43,8 @@ struct dsa_switchdev_event_work {
unsigned char addr[ETH_ALEN];
u16 vid;
bool host_addr;
+ bool sync;
+ bool replay;
};
enum dsa_standalone_event {
@@ -2813,6 +2815,7 @@ int dsa_user_create(struct dsa_port *port)
p->dp = port;
INIT_LIST_HEAD(&p->mall_tc_list);
+ INIT_LIST_HEAD(&p->bridge_host_fdbs);
port->user = user_dev;
dsa_user_setup_tagger(user_dev);
@@ -3627,6 +3630,22 @@ static int dsa_user_netdevice_event(struct notifier_block *nb,
return NOTIFY_DONE;
}
+static bool dsa_foreign_dev_check(const struct net_device *dev,
+ const struct net_device *foreign_dev)
+{
+ const struct dsa_port *dp = dsa_user_to_port(dev);
+ struct dsa_switch_tree *dst = dp->ds->dst;
+
+ if (netif_is_bridge_master(foreign_dev))
+ return !dsa_tree_offloads_bridge_dev(dst, foreign_dev);
+
+ if (netif_is_bridge_port(foreign_dev))
+ return !dsa_tree_offloads_bridge_port(dst, foreign_dev);
+
+ /* Everything else is foreign */
+ return true;
+}
+
static void
dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
{
@@ -3639,6 +3658,171 @@ dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
switchdev_work->orig_dev, &info.info, NULL);
}
+/* The bridge notifies a roaming entry without a lock, so with two CPUs the
+ * deletion from the old port and the addition on the new one can arrive
+ * twice or out of order. Hold the host address while the bridge has the
+ * entry on a foreign port. A deletion that still finds the entry on its own
+ * port was sent before the bridge changed the port. An entry on a user port,
+ * also one of another tree, is notified only to that port.
+ */
+static bool dsa_user_bridge_host_fdb_wanted(struct net_device *dev,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid,
+ const struct net_device *leaving)
+{
+ struct net_device *brport;
+ bool want;
+
+ rcu_read_lock();
+ brport = br_switchdev_fdb_find_port(bridge->dev, addr, vid);
+ want = brport && brport != leaving && !dsa_user_dev_check(brport) &&
+ dsa_foreign_dev_check(dev, brport);
+ rcu_read_unlock();
+
+ return want;
+}
+
+static void dsa_user_bridge_host_fdb_set(struct net_device *dev,
+ const struct dsa_bridge *bridge,
+ const unsigned char *addr, u16 vid,
+ bool want)
+{
+ struct dsa_user_priv *p = netdev_priv(dev);
+ struct dsa_port *dp = p->dp;
+ struct dsa_mac_addr *a;
+ bool held = false;
+ int err;
+
+ list_for_each_entry(a, &p->bridge_host_fdbs, list) {
+ if (ether_addr_equal(a->addr, addr) && a->vid == vid) {
+ held = true;
+ break;
+ }
+ }
+
+ if (want == held)
+ return;
+
+ if (want) {
+ a = kzalloc_obj(*a);
+ if (!a)
+ return;
+
+ err = __dsa_port_bridge_host_fdb_add(dp, bridge, addr, vid);
+ if (err) {
+ dev_err(dp->ds->dev,
+ "port %d failed to add %pM vid %d to fdb: %d\n",
+ dp->index, addr, vid, err);
+ kfree(a);
+ return;
+ }
+
+ ether_addr_copy(a->addr, addr);
+ a->vid = vid;
+ list_add(&a->list, &p->bridge_host_fdbs);
+ } else {
+ err = __dsa_port_bridge_host_fdb_del(dp, bridge, addr, vid);
+ if (err) {
+ dev_err(dp->ds->dev,
+ "port %d failed to delete %pM vid %d from fdb: %d\n",
+ dp->index, addr, vid, err);
+ return;
+ }
+
+ list_del(&a->list);
+ kfree(a);
+ }
+}
+
+static void
+dsa_user_bridge_host_fdb_sync(struct dsa_switchdev_event_work *switchdev_work)
+{
+ bool del = switchdev_work->event == SWITCHDEV_FDB_DEL_TO_DEVICE;
+ struct net_device *dev = switchdev_work->dev;
+ const struct dsa_bridge *bridge;
+ bool want = false;
+
+ /* Cleared before the workqueue is flushed and the bridge is freed */
+ bridge = READ_ONCE(dsa_user_to_port(dev)->bridge);
+ if (!bridge)
+ return;
+
+ if (!(del && switchdev_work->replay))
+ want = dsa_user_bridge_host_fdb_wanted(dev, bridge,
+ switchdev_work->addr,
+ switchdev_work->vid,
+ del ? switchdev_work->orig_dev : NULL);
+
+ dsa_user_bridge_host_fdb_set(dev, bridge, switchdev_work->addr,
+ switchdev_work->vid, want);
+ if (want && !del)
+ dsa_fdb_offload_notify(switchdev_work);
+}
+
+/* Only the user port an entry is on gets its events, so when the entry roams
+ * there, release it on the ports of the bridge that still hold it.
+ */
+static void dsa_user_bridge_host_fdb_release_work(struct work_struct *work)
+{
+ struct dsa_switchdev_event_work *switchdev_work =
+ container_of(work, struct dsa_switchdev_event_work, work);
+ struct dsa_port *dp = dsa_user_to_port(switchdev_work->dev);
+ const unsigned char *addr = switchdev_work->addr;
+ const struct dsa_bridge *bridge = READ_ONCE(dp->bridge);
+ u16 vid = switchdev_work->vid;
+ struct dsa_port *other;
+
+ if (bridge) {
+ dsa_tree_for_each_user_port(other, dp->ds->dst) {
+ if (dsa_port_offloads_bridge_dev(other, bridge->dev) &&
+ !dsa_user_bridge_host_fdb_wanted(other->user, bridge,
+ addr, vid, NULL))
+ dsa_user_bridge_host_fdb_set(other->user, bridge,
+ addr, vid, false);
+ }
+ }
+
+ kfree(switchdev_work);
+}
+
+static int dsa_user_bridge_host_fdb_release(struct net_device *dev,
+ const unsigned char *addr, u16 vid)
+{
+ struct dsa_switchdev_event_work *switchdev_work;
+
+ switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
+ if (!switchdev_work)
+ return -ENOMEM;
+
+ INIT_WORK(&switchdev_work->work, dsa_user_bridge_host_fdb_release_work);
+ switchdev_work->dev = dev;
+ ether_addr_copy(switchdev_work->addr, addr);
+ switchdev_work->vid = vid;
+
+ dsa_schedule_work(&switchdev_work->work);
+
+ return 0;
+}
+
+void dsa_user_bridge_host_fdb_purge(struct dsa_port *dp,
+ const struct dsa_bridge *bridge)
+{
+ struct dsa_user_priv *p = netdev_priv(dp->user);
+ struct dsa_mac_addr *a, *tmp;
+ int err;
+
+ list_for_each_entry_safe(a, tmp, &p->bridge_host_fdbs, list) {
+ err = __dsa_port_bridge_host_fdb_del(dp, bridge, a->addr,
+ a->vid);
+ if (err)
+ dev_err(dp->ds->dev,
+ "port %d failed to delete %pM vid %d from fdb: %d\n",
+ dp->index, a->addr, a->vid, err);
+ list_del(&a->list);
+ kfree(a);
+ }
+}
+
static void dsa_user_switchdev_event_work(struct work_struct *work)
{
struct dsa_switchdev_event_work *switchdev_work =
@@ -3653,6 +3837,12 @@ static void dsa_user_switchdev_event_work(struct work_struct *work)
dp = dsa_user_to_port(dev);
ds = dp->ds;
+ if (switchdev_work->sync) {
+ dsa_user_bridge_host_fdb_sync(switchdev_work);
+ kfree(switchdev_work);
+ return;
+ }
+
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (switchdev_work->host_addr)
@@ -3689,22 +3879,6 @@ static void dsa_user_switchdev_event_work(struct work_struct *work)
kfree(switchdev_work);
}
-static bool dsa_foreign_dev_check(const struct net_device *dev,
- const struct net_device *foreign_dev)
-{
- const struct dsa_port *dp = dsa_user_to_port(dev);
- struct dsa_switch_tree *dst = dp->ds->dst;
-
- if (netif_is_bridge_master(foreign_dev))
- return !dsa_tree_offloads_bridge_dev(dst, foreign_dev);
-
- if (netif_is_bridge_port(foreign_dev))
- return !dsa_tree_offloads_bridge_port(dst, foreign_dev);
-
- /* Everything else is foreign */
- return true;
-}
-
static int dsa_user_fdb_event(struct net_device *dev,
struct net_device *orig_dev,
unsigned long event, const void *ctx,
@@ -3723,7 +3897,9 @@ static int dsa_user_fdb_event(struct net_device *dev,
if (switchdev_fdb_is_dynamically_learned(fdb_info)) {
if (dsa_port_offloads_bridge_port(dp, orig_dev))
- return 0;
+ return event == SWITCHDEV_FDB_ADD_TO_DEVICE ?
+ dsa_user_bridge_host_fdb_release(dev, fdb_info->addr,
+ fdb_info->vid) : 0;
/* FDB entries learned by the software bridge or by foreign
* bridge ports should be installed as host addresses only if
@@ -3768,9 +3944,15 @@ static int dsa_user_fdb_event(struct net_device *dev,
ether_addr_copy(switchdev_work->addr, fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
switchdev_work->host_addr = host_addr;
+ switchdev_work->sync = host_addr && !fdb_info->is_local;
+ switchdev_work->replay = !!ctx;
dsa_schedule_work(&switchdev_work->work);
+ if (!host_addr && event == SWITCHDEV_FDB_ADD_TO_DEVICE)
+ return dsa_user_bridge_host_fdb_release(dev, fdb_info->addr,
+ fdb_info->vid);
+
return 0;
}
diff --git a/net/dsa/user.h b/net/dsa/user.h
index 016884bead3cd..dd38104162da4 100644
--- a/net/dsa/user.h
+++ b/net/dsa/user.h
@@ -33,6 +33,12 @@ struct dsa_user_priv {
/* TC context */
struct list_head mall_tc_list;
+
+ /* Host addresses of bridge FDB entries held through this port,
+ * changed from the DSA workqueue, or with the port out of the bridge
+ * and the workqueue flushed
+ */
+ struct list_head bridge_host_fdbs;
};
void dsa_user_mii_bus_init(struct dsa_switch *ds);
@@ -47,6 +53,8 @@ void dsa_user_host_uc_uninstall(struct net_device *dev);
void dsa_user_sync_ha(struct net_device *dev);
void dsa_user_unsync_ha(struct net_device *dev);
void dsa_user_setup_tagger(struct net_device *user);
+void dsa_user_bridge_host_fdb_purge(struct dsa_port *dp,
+ const struct dsa_bridge *bridge);
int dsa_user_change_mtu(struct net_device *dev, int new_mtu);
int dsa_user_change_conduit(struct net_device *dev, struct net_device *conduit,
struct netlink_ext_ack *extack);
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread