diff mbox series

[v2] kernel: DSA roaming fix for Marvell mv88e6xxx

Message ID 20210222053811.32047-1-dqfext@gmail.com
State Accepted
Delegated to: Hauke Mehrtens
Headers show
Series [v2] kernel: DSA roaming fix for Marvell mv88e6xxx | expand

Commit Message

DENG Qingfang Feb. 22, 2021, 5:38 a.m. UTC
Marvell mv88e6xxx switch series cannot perform MAC learning from
CPU-injected (FROM_CPU) DSA frames, which results in 2 issues.
- excessive flooding, due to the fact that DSA treats those addresses
as unknown
- the risk of stale routes, which can lead to temporary packet loss

Backport those patch series from netdev mailing list, which solve these
issues by adding and clearing static entries to the switch's FDB.

Add a hack patch to set default VID to 1 in port_fdb_{add,del}. Otherwise
the static entries will be added to the switch's private FDB if VLAN
filtering disabled, which will not work.

The switch may generate an "ATU violation" warning when a client moves
from the CPU port to a switch port because the static ATU entry added by
DSA core still points to the CPU port. DSA core will then clear the static
entry so it is not fatal. Disable the warning so it will not confuse users.

Link: https://lore.kernel.org/netdev/20210106095136.224739-1-olteanv@gmail.com/
Link: https://lore.kernel.org/netdev/20210116012515.3152-1-tobias@waldekranz.com/
Ref: https://gitlab.nic.cz/turris/turris-build/-/issues/165
Signed-off-by: DENG Qingfang <dqfext@gmail.com>
---
v1 -> v2:
	Dropped upstreamed patch
	Also backported to 5.10

 ...y-switchdev-of-disappearance-of-old-.patch | 126 +++++++++
 ...r-when-a-non-legacy-FDB-operation-fa.patch |  52 ++++
 ...e-switchdev_notifier_fdb_info-in-dsa.patch | 226 +++++++++++++++
 ...tchdev-event-implementation-under-th.patch |  85 ++++++
 ...ly-in-dsa_slave_switchdev_event-if-w.patch |  42 +++
 ...or-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch | 264 ++++++++++++++++++
 ...y-switchdev-of-disappearance-of-old-.patch | 126 +++++++++
 ...r-when-a-non-legacy-FDB-operation-fa.patch |  52 ++++
 ...e-switchdev_notifier_fdb_info-in-dsa.patch | 226 +++++++++++++++
 ...tchdev-event-implementation-under-th.patch |  85 ++++++
 ...ly-in-dsa_slave_switchdev_event-if-w.patch |  42 +++
 ...or-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch | 263 +++++++++++++++++
 .../710-net-dsa-mv88e6xxx-default-VID-1.patch |  18 ++
 ...-dsa-mv88e6xxx-disable-ATU-violation.patch |  12 +
 .../710-net-dsa-mv88e6xxx-default-VID-1.patch |  18 ++
 ...-dsa-mv88e6xxx-disable-ATU-violation.patch |  12 +
 ...hdev-Refactor-br_switchdev_fdb_notif.patch |  75 +++++
 ...hdev-Include-local-flag-in-FDB-notif.patch |  42 +++
 ...hdev-Send-FDB-notifications-for-host.patch |  94 +++++++
 ...local-addresses-in-assisted-CPU-port.patch |  36 +++
 ...bridge-addresses-in-assisted-CPU-por.patch |  30 ++
 ...tic-FDB-entries-on-foreign-interface.patch |  56 ++++
 ...equest-assisted-learning-on-CPU-port.patch |  27 ++
 ...hdev-Refactor-br_switchdev_fdb_notif.patch |  71 +++++
 ...hdev-Include-local-flag-in-FDB-notif.patch |  42 +++
 ...hdev-Send-FDB-notifications-for-host.patch |  94 +++++++
 ...local-addresses-in-assisted-CPU-port.patch |  36 +++
 ...bridge-addresses-in-assisted-CPU-por.patch |  30 ++
 ...tic-FDB-entries-on-foreign-interface.patch |  56 ++++
 ...equest-assisted-learning-on-CPU-port.patch |  27 ++
 30 files changed, 2365 insertions(+)
 create mode 100644 target/linux/generic/backport-5.10/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
 create mode 100644 target/linux/generic/backport-5.10/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
 create mode 100644 target/linux/generic/backport-5.10/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
 create mode 100644 target/linux/generic/backport-5.10/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
 create mode 100644 target/linux/generic/backport-5.10/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
 create mode 100644 target/linux/generic/backport-5.10/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
 create mode 100644 target/linux/generic/backport-5.4/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
 create mode 100644 target/linux/generic/backport-5.4/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
 create mode 100644 target/linux/generic/backport-5.4/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
 create mode 100644 target/linux/generic/backport-5.4/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
 create mode 100644 target/linux/generic/backport-5.4/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
 create mode 100644 target/linux/generic/backport-5.4/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
 create mode 100644 target/linux/generic/hack-5.10/710-net-dsa-mv88e6xxx-default-VID-1.patch
 create mode 100644 target/linux/generic/hack-5.10/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
 create mode 100644 target/linux/generic/hack-5.4/710-net-dsa-mv88e6xxx-default-VID-1.patch
 create mode 100644 target/linux/generic/hack-5.4/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
 create mode 100644 target/linux/generic/pending-5.10/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
 create mode 100644 target/linux/generic/pending-5.10/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
 create mode 100644 target/linux/generic/pending-5.10/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
 create mode 100644 target/linux/generic/pending-5.10/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
 create mode 100644 target/linux/generic/pending-5.10/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
 create mode 100644 target/linux/generic/pending-5.10/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
 create mode 100644 target/linux/generic/pending-5.10/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch
 create mode 100644 target/linux/generic/pending-5.4/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
 create mode 100644 target/linux/generic/pending-5.4/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
 create mode 100644 target/linux/generic/pending-5.4/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
 create mode 100644 target/linux/generic/pending-5.4/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
 create mode 100644 target/linux/generic/pending-5.4/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
 create mode 100644 target/linux/generic/pending-5.4/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
 create mode 100644 target/linux/generic/pending-5.4/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch

Comments

Tad Feb. 23, 2021, 1:23 p.m. UTC | #1
Thank you again for this!

I've had it running on mamba and caiman under 5.10.16 for over 24 hours now.
No issues encountered.

Regards,
Tad.
diff mbox series

Patch

diff --git a/target/linux/generic/backport-5.10/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch b/target/linux/generic/backport-5.10/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
new file mode 100644
index 0000000000..c43cb4d1f2
--- /dev/null
+++ b/target/linux/generic/backport-5.10/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
@@ -0,0 +1,126 @@ 
+From 90dc8fd36078a536671adae884d0b929cce6480a Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:30 +0200
+Subject: [PATCH] net: bridge: notify switchdev of disappearance of old FDB
+ entry upon migration
+
+Currently the bridge emits atomic switchdev notifications for
+dynamically learnt FDB entries. Monitoring these notifications works
+wonders for switchdev drivers that want to keep their hardware FDB in
+sync with the bridge's FDB.
+
+For example station A wants to talk to station B in the diagram below,
+and we are concerned with the behavior of the bridge on the DUT device:
+
+                   DUT
+ +-------------------------------------+
+ |                 br0                 |
+ | +------+ +------+ +------+ +------+ |
+ | |      | |      | |      | |      | |
+ | | swp0 | | swp1 | | swp2 | | eth0 | |
+ +-------------------------------------+
+      |        |                  |
+  Station A    |                  |
+               |                  |
+         +--+------+--+    +--+------+--+
+         |  |      |  |    |  |      |  |
+         |  | swp0 |  |    |  | swp0 |  |
+ Another |  +------+  |    |  +------+  | Another
+  switch |     br0    |    |     br0    | switch
+         |  +------+  |    |  +------+  |
+         |  |      |  |    |  |      |  |
+         |  | swp1 |  |    |  | swp1 |  |
+         +--+------+--+    +--+------+--+
+                                  |
+                              Station B
+
+Interfaces swp0, swp1, swp2 are handled by a switchdev driver that has
+the following property: frames injected from its control interface bypass
+the internal address analyzer logic, and therefore, this hardware does
+not learn from the source address of packets transmitted by the network
+stack through it. So, since bridging between eth0 (where Station B is
+attached) and swp0 (where Station A is attached) is done in software,
+the switchdev hardware will never learn the source address of Station B.
+So the traffic towards that destination will be treated as unknown, i.e.
+flooded.
+
+This is where the bridge notifications come in handy. When br0 on the
+DUT sees frames with Station B's MAC address on eth0, the switchdev
+driver gets these notifications and can install a rule to send frames
+towards Station B's address that are incoming from swp0, swp1, swp2,
+only towards the control interface. This is all switchdev driver private
+business, which the notification makes possible.
+
+All is fine until someone unplugs Station B's cable and moves it to the
+other switch:
+
+                   DUT
+ +-------------------------------------+
+ |                 br0                 |
+ | +------+ +------+ +------+ +------+ |
+ | |      | |      | |      | |      | |
+ | | swp0 | | swp1 | | swp2 | | eth0 | |
+ +-------------------------------------+
+      |        |                  |
+  Station A    |                  |
+               |                  |
+         +--+------+--+    +--+------+--+
+         |  |      |  |    |  |      |  |
+         |  | swp0 |  |    |  | swp0 |  |
+ Another |  +------+  |    |  +------+  | Another
+  switch |     br0    |    |     br0    | switch
+         |  +------+  |    |  +------+  |
+         |  |      |  |    |  |      |  |
+         |  | swp1 |  |    |  | swp1 |  |
+         +--+------+--+    +--+------+--+
+               |
+           Station B
+
+Luckily for the use cases we care about, Station B is noisy enough that
+the DUT hears it (on swp1 this time). swp1 receives the frames and
+delivers them to the bridge, who enters the unlikely path in br_fdb_update
+of updating an existing entry. It moves the entry in the software bridge
+to swp1 and emits an addition notification towards that.
+
+As far as the switchdev driver is concerned, all that it needs to ensure
+is that traffic between Station A and Station B is not forever broken.
+If it does nothing, then the stale rule to send frames for Station B
+towards the control interface remains in place. But Station B is no
+longer reachable via the control interface, but via a port that can
+offload the bridge port learning attribute. It's just that the port is
+prevented from learning this address, since the rule overrides FDB
+updates. So the rule needs to go. The question is via what mechanism.
+
+It sure would be possible for this switchdev driver to keep track of all
+addresses which are sent to the control interface, and then also listen
+for bridge notifier events on its own ports, searching for the ones that
+have a MAC address which was previously sent to the control interface.
+But this is cumbersome and inefficient. Instead, with one small change,
+the bridge could notify of the address deletion from the old port, in a
+symmetrical manner with how it did for the insertion. Then the switchdev
+driver would not be required to monitor learn/forget events for its own
+ports. It could just delete the rule towards the control interface upon
+bridge entry migration. This would make hardware address learning be
+possible again. Then it would take a few more packets until the hardware
+and software FDB would be in sync again.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Acked-by: Nikolay Aleksandrov <nikolay@nvidia.com>
+Reviewed-by: Ido Schimmel <idosch@nvidia.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/bridge/br_fdb.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/net/bridge/br_fdb.c
++++ b/net/bridge/br_fdb.c
+@@ -602,6 +602,7 @@ void br_fdb_update(struct net_bridge *br
+ 			/* fastpath: update of existing entry */
+ 			if (unlikely(source != fdb->dst &&
+ 				     !test_bit(BR_FDB_STICKY, &fdb->flags))) {
++				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
+ 				fdb->dst = source;
+ 				fdb_modified = true;
+ 				/* Take over HW learned entry */
diff --git a/target/linux/generic/backport-5.10/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch b/target/linux/generic/backport-5.10/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
new file mode 100644
index 0000000000..e4fd9eb2e4
--- /dev/null
+++ b/target/linux/generic/backport-5.10/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
@@ -0,0 +1,52 @@ 
+From 2fd186501b1cff155cc4a755c210793cfc0dffb5 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:31 +0200
+Subject: [PATCH] net: dsa: be louder when a non-legacy FDB operation fails
+
+The dev_close() call was added in commit c9eb3e0f8701 ("net: dsa: Add
+support for learning FDB through notification") "to indicate inconsistent
+situation" when we could not delete an FDB entry from the port.
+
+bridge fdb del d8:58:d7:00:ca:6d dev swp0 self master
+
+It is a bit drastic and at the same time not helpful if the above fails
+to only print with netdev_dbg log level, but on the other hand to bring
+the interface down.
+
+So increase the verbosity of the error message, and drop dev_close().
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2030,7 +2030,9 @@ static void dsa_slave_switchdev_event_wo
+ 
+ 		err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
+ 		if (err) {
+-			netdev_dbg(dev, "fdb add failed err=%d\n", err);
++			netdev_err(dev,
++				   "failed to add %pM vid %d to fdb: %d\n",
++				   fdb_info->addr, fdb_info->vid, err);
+ 			break;
+ 		}
+ 		fdb_info->offloaded = true;
+@@ -2045,9 +2047,11 @@ static void dsa_slave_switchdev_event_wo
+ 
+ 		err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
+ 		if (err) {
+-			netdev_dbg(dev, "fdb del failed err=%d\n", err);
+-			dev_close(dev);
++			netdev_err(dev,
++				   "failed to delete %pM vid %d from fdb: %d\n",
++				   fdb_info->addr, fdb_info->vid, err);
+ 		}
++
+ 		break;
+ 	}
+ 	rtnl_unlock();
diff --git a/target/linux/generic/backport-5.10/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch b/target/linux/generic/backport-5.10/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
new file mode 100644
index 0000000000..31502f5b23
--- /dev/null
+++ b/target/linux/generic/backport-5.10/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
@@ -0,0 +1,226 @@ 
+From c4bb76a9a0ef87c4cc1f636defed5f12deb9f5a7 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:32 +0200
+Subject: [PATCH] net: dsa: don't use switchdev_notifier_fdb_info in
+ dsa_switchdev_event_work
+
+Currently DSA doesn't add FDB entries on the CPU port, because it only
+does so through switchdev, which is associated with a net_device, and
+there are none of those for the CPU port.
+
+But actually FDB addresses on the CPU port have some use cases of their
+own, if the switchdev operations are initiated from within the DSA
+layer. There is just one problem with the existing code: it passes a
+structure in dsa_switchdev_event_work which was retrieved directly from
+switchdev, so it contains a net_device. We need to generalize the
+contents to something that covers the CPU port as well: the "ds, port"
+tuple is fine for that.
+
+Note that the new procedure for notifying the successful FDB offload is
+inspired from the rocker model.
+
+Also, nothing was being done if added_by_user was false. Let's check for
+that a lot earlier, and don't actually bother to schedule the worker
+for nothing.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/dsa_priv.h |  12 +++++
+ net/dsa/slave.c    | 106 ++++++++++++++++++++++-----------------------
+ 2 files changed, 65 insertions(+), 53 deletions(-)
+
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -73,6 +73,18 @@ struct dsa_notifier_mtu_info {
+ 	int mtu;
+ };
+ 
++struct dsa_switchdev_event_work {
++	struct dsa_switch *ds;
++	int port;
++	struct work_struct work;
++	unsigned long event;
++	/* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
++	 * SWITCHDEV_FDB_DEL_TO_DEVICE
++	 */
++	unsigned char addr[ETH_ALEN];
++	u16 vid;
++};
++
+ struct dsa_slave_priv {
+ 	/* Copy of CPU port xmit for faster access in slave transmit hot path */
+ 	struct sk_buff *	(*xmit)(struct sk_buff *skb,
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2005,76 +2005,66 @@ static int dsa_slave_netdevice_event(str
+ 	return NOTIFY_DONE;
+ }
+ 
+-struct dsa_switchdev_event_work {
+-	struct work_struct work;
+-	struct switchdev_notifier_fdb_info fdb_info;
+-	struct net_device *dev;
+-	unsigned long event;
+-};
++static void
++dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
++{
++	struct dsa_switch *ds = switchdev_work->ds;
++	struct switchdev_notifier_fdb_info info;
++	struct dsa_port *dp;
++
++	if (!dsa_is_user_port(ds, switchdev_work->port))
++		return;
++
++	info.addr = switchdev_work->addr;
++	info.vid = switchdev_work->vid;
++	info.offloaded = true;
++	dp = dsa_to_port(ds, switchdev_work->port);
++	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
++				 dp->slave, &info.info, NULL);
++}
+ 
+ static void dsa_slave_switchdev_event_work(struct work_struct *work)
+ {
+ 	struct dsa_switchdev_event_work *switchdev_work =
+ 		container_of(work, struct dsa_switchdev_event_work, work);
+-	struct net_device *dev = switchdev_work->dev;
+-	struct switchdev_notifier_fdb_info *fdb_info;
+-	struct dsa_port *dp = dsa_slave_to_port(dev);
++	struct dsa_switch *ds = switchdev_work->ds;
++	struct dsa_port *dp;
+ 	int err;
+ 
++	dp = dsa_to_port(ds, switchdev_work->port);
++
+ 	rtnl_lock();
+ 	switch (switchdev_work->event) {
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+-		fdb_info = &switchdev_work->fdb_info;
+-		if (!fdb_info->added_by_user)
+-			break;
+-
+-		err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
++		err = dsa_port_fdb_add(dp, switchdev_work->addr,
++				       switchdev_work->vid);
+ 		if (err) {
+-			netdev_err(dev,
+-				   "failed to add %pM vid %d to fdb: %d\n",
+-				   fdb_info->addr, fdb_info->vid, err);
++			dev_err(ds->dev,
++				"port %d failed to add %pM vid %d to fdb: %d\n",
++				dp->index, switchdev_work->addr,
++				switchdev_work->vid, err);
+ 			break;
+ 		}
+-		fdb_info->offloaded = true;
+-		call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
+-					 &fdb_info->info, NULL);
++		dsa_fdb_offload_notify(switchdev_work);
+ 		break;
+ 
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		fdb_info = &switchdev_work->fdb_info;
+-		if (!fdb_info->added_by_user)
+-			break;
+-
+-		err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
++		err = dsa_port_fdb_del(dp, switchdev_work->addr,
++				       switchdev_work->vid);
+ 		if (err) {
+-			netdev_err(dev,
+-				   "failed to delete %pM vid %d from fdb: %d\n",
+-				   fdb_info->addr, fdb_info->vid, err);
++			dev_err(ds->dev,
++				"port %d failed to delete %pM vid %d from fdb: %d\n",
++				dp->index, switchdev_work->addr,
++				switchdev_work->vid, err);
+ 		}
+ 
+ 		break;
+ 	}
+ 	rtnl_unlock();
+ 
+-	kfree(switchdev_work->fdb_info.addr);
+ 	kfree(switchdev_work);
+-	dev_put(dev);
+-}
+-
+-static int
+-dsa_slave_switchdev_fdb_work_init(struct dsa_switchdev_event_work *
+-				  switchdev_work,
+-				  const struct switchdev_notifier_fdb_info *
+-				  fdb_info)
+-{
+-	memcpy(&switchdev_work->fdb_info, fdb_info,
+-	       sizeof(switchdev_work->fdb_info));
+-	switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
+-	if (!switchdev_work->fdb_info.addr)
+-		return -ENOMEM;
+-	ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
+-			fdb_info->addr);
+-	return 0;
++	if (dsa_is_user_port(ds, dp->index))
++		dev_put(dp->slave);
+ }
+ 
+ /* Called under rcu_read_lock() */
+@@ -2082,7 +2072,9 @@ static int dsa_slave_switchdev_event(str
+ 				     unsigned long event, void *ptr)
+ {
+ 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
++	const struct switchdev_notifier_fdb_info *fdb_info;
+ 	struct dsa_switchdev_event_work *switchdev_work;
++	struct dsa_port *dp;
+ 	int err;
+ 
+ 	if (event == SWITCHDEV_PORT_ATTR_SET) {
+@@ -2095,20 +2087,32 @@ static int dsa_slave_switchdev_event(str
+ 	if (!dsa_slave_dev_check(dev))
+ 		return NOTIFY_DONE;
+ 
++	dp = dsa_slave_to_port(dev);
++
+ 	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ 	if (!switchdev_work)
+ 		return NOTIFY_BAD;
+ 
+ 	INIT_WORK(&switchdev_work->work,
+ 		  dsa_slave_switchdev_event_work);
+-	switchdev_work->dev = dev;
++	switchdev_work->ds = dp->ds;
++	switchdev_work->port = dp->index;
+ 	switchdev_work->event = event;
+ 
+ 	switch (event) {
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		if (dsa_slave_switchdev_fdb_work_init(switchdev_work, ptr))
+-			goto err_fdb_work_init;
++		fdb_info = ptr;
++
++		if (!fdb_info->added_by_user) {
++			kfree(switchdev_work);
++			return NOTIFY_OK;
++		}
++
++		ether_addr_copy(switchdev_work->addr,
++				fdb_info->addr);
++		switchdev_work->vid = fdb_info->vid;
++
+ 		dev_hold(dev);
+ 		break;
+ 	default:
+@@ -2118,10 +2122,6 @@ static int dsa_slave_switchdev_event(str
+ 
+ 	dsa_schedule_work(&switchdev_work->work);
+ 	return NOTIFY_OK;
+-
+-err_fdb_work_init:
+-	kfree(switchdev_work);
+-	return NOTIFY_BAD;
+ }
+ 
+ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
diff --git a/target/linux/generic/backport-5.10/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch b/target/linux/generic/backport-5.10/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
new file mode 100644
index 0000000000..49e095a587
--- /dev/null
+++ b/target/linux/generic/backport-5.10/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
@@ -0,0 +1,85 @@ 
+From 447d290a58bd335d68f665713842365d3d6447df Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:33 +0200
+Subject: [PATCH] net: dsa: move switchdev event implementation under the same
+ switch/case statement
+
+We'll need to start listening to SWITCHDEV_FDB_{ADD,DEL}_TO_DEVICE
+events even for interfaces where dsa_slave_dev_check returns false, so
+we need that check inside the switch-case statement for SWITCHDEV_FDB_*.
+
+This movement also avoids a useless allocation / free of switchdev_work
+on the untreated "default event" case.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 35 ++++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 19 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2077,31 +2077,29 @@ static int dsa_slave_switchdev_event(str
+ 	struct dsa_port *dp;
+ 	int err;
+ 
+-	if (event == SWITCHDEV_PORT_ATTR_SET) {
++	switch (event) {
++	case SWITCHDEV_PORT_ATTR_SET:
+ 		err = switchdev_handle_port_attr_set(dev, ptr,
+ 						     dsa_slave_dev_check,
+ 						     dsa_slave_port_attr_set);
+ 		return notifier_from_errno(err);
+-	}
+-
+-	if (!dsa_slave_dev_check(dev))
+-		return NOTIFY_DONE;
++	case SWITCHDEV_FDB_ADD_TO_DEVICE:
++	case SWITCHDEV_FDB_DEL_TO_DEVICE:
++		if (!dsa_slave_dev_check(dev))
++			return NOTIFY_DONE;
+ 
+-	dp = dsa_slave_to_port(dev);
++		dp = dsa_slave_to_port(dev);
+ 
+-	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+-	if (!switchdev_work)
+-		return NOTIFY_BAD;
+-
+-	INIT_WORK(&switchdev_work->work,
+-		  dsa_slave_switchdev_event_work);
+-	switchdev_work->ds = dp->ds;
+-	switchdev_work->port = dp->index;
+-	switchdev_work->event = event;
++		switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
++		if (!switchdev_work)
++			return NOTIFY_BAD;
++
++		INIT_WORK(&switchdev_work->work,
++			  dsa_slave_switchdev_event_work);
++		switchdev_work->ds = dp->ds;
++		switchdev_work->port = dp->index;
++		switchdev_work->event = event;
+ 
+-	switch (event) {
+-	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+-	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ 		fdb_info = ptr;
+ 
+ 		if (!fdb_info->added_by_user) {
+@@ -2114,13 +2112,12 @@ static int dsa_slave_switchdev_event(str
+ 		switchdev_work->vid = fdb_info->vid;
+ 
+ 		dev_hold(dev);
++		dsa_schedule_work(&switchdev_work->work);
+ 		break;
+ 	default:
+-		kfree(switchdev_work);
+ 		return NOTIFY_DONE;
+ 	}
+ 
+-	dsa_schedule_work(&switchdev_work->work);
+ 	return NOTIFY_OK;
+ }
+ 
diff --git a/target/linux/generic/backport-5.10/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch b/target/linux/generic/backport-5.10/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
new file mode 100644
index 0000000000..cbf2739469
--- /dev/null
+++ b/target/linux/generic/backport-5.10/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
@@ -0,0 +1,42 @@ 
+From 5fb4a451a87d8ed3363d28b63a3295399373d6c4 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:34 +0200
+Subject: [PATCH] net: dsa: exit early in dsa_slave_switchdev_event if we can't
+ program the FDB
+
+Right now, the following would happen for a switch driver that does not
+implement .port_fdb_add or .port_fdb_del.
+
+dsa_slave_switchdev_event returns NOTIFY_OK and schedules:
+-> dsa_slave_switchdev_event_work
+   -> dsa_port_fdb_add
+      -> dsa_port_notify(DSA_NOTIFIER_FDB_ADD)
+         -> dsa_switch_fdb_add
+            -> if (!ds->ops->port_fdb_add) return -EOPNOTSUPP;
+   -> an error is printed with dev_dbg, and
+      dsa_fdb_offload_notify(switchdev_work) is not called.
+
+We can avoid scheduling the worker for nothing and say NOTIFY_DONE.
+Because we don't call dsa_fdb_offload_notify, the static FDB entry will
+remain just in the software bridge.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2090,6 +2090,9 @@ static int dsa_slave_switchdev_event(str
+ 
+ 		dp = dsa_slave_to_port(dev);
+ 
++		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
++			return NOTIFY_DONE;
++
+ 		switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ 		if (!switchdev_work)
+ 			return NOTIFY_BAD;
diff --git a/target/linux/generic/backport-5.10/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch b/target/linux/generic/backport-5.10/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
new file mode 100644
index 0000000000..978ece93b3
--- /dev/null
+++ b/target/linux/generic/backport-5.10/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
@@ -0,0 +1,264 @@ 
+From d5f19486cee79d04c054427577ac96ed123706db Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:35 +0200
+Subject: [PATCH] net: dsa: listen for SWITCHDEV_{FDB,DEL}_ADD_TO_DEVICE on
+ foreign bridge neighbors
+
+Some DSA switches (and not only) cannot learn source MAC addresses from
+packets injected from the CPU. They only perform hardware address
+learning from inbound traffic.
+
+This can be problematic when we have a bridge spanning some DSA switch
+ports and some non-DSA ports (which we'll call "foreign interfaces" from
+DSA's perspective).
+
+There are 2 classes of problems created by the lack of learning on
+CPU-injected traffic:
+- excessive flooding, due to the fact that DSA treats those addresses as
+  unknown
+- the risk of stale routes, which can lead to temporary packet loss
+
+To illustrate the second class, consider the following situation, which
+is common in production equipment (wireless access points, where there
+is a WLAN interface and an Ethernet switch, and these form a single
+bridging domain).
+
+ AP 1:
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+       |                                                       ^        ^
+       |                                                       |        |
+       |                                                       |        |
+       |                                                    Client A  Client B
+       |
+       |
+       |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ AP 2
+
+- br0 of AP 1 will know that Clients A and B are reachable via wlan0
+- the hardware fdb of a DSA switch driver today is not kept in sync with
+  the software entries on other bridge ports, so it will not know that
+  clients A and B are reachable via the CPU port UNLESS the hardware
+  switch itself performs SA learning from traffic injected from the CPU.
+  Nonetheless, a substantial number of switches don't.
+- the hardware fdb of the DSA switch on AP 2 may autonomously learn that
+  Client A and B are reachable through swp0. Therefore, the software br0
+  of AP 2 also may or may not learn this. In the example we're
+  illustrating, some Ethernet traffic has been going on, and br0 from AP
+  2 has indeed learnt that it can reach Client B through swp0.
+
+One of the wireless clients, say Client B, disconnects from AP 1 and
+roams to AP 2. The topology now looks like this:
+
+ AP 1:
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+       |                                                            ^
+       |                                                            |
+       |                                                         Client A
+       |
+       |
+       |                                                         Client B
+       |                                                            |
+       |                                                            v
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ AP 2
+
+- br0 of AP 1 still knows that Client A is reachable via wlan0 (no change)
+- br0 of AP 1 will (possibly) know that Client B has left wlan0. There
+  are cases where it might never find out though. Either way, DSA today
+  does not process that notification in any way.
+- the hardware FDB of the DSA switch on AP 1 may learn autonomously that
+  Client B can be reached via swp0, if it receives any packet with
+  Client 1's source MAC address over Ethernet.
+- the hardware FDB of the DSA switch on AP 2 still thinks that Client B
+  can be reached via swp0. It does not know that it has roamed to wlan0,
+  because it doesn't perform SA learning from the CPU port.
+
+Now Client A contacts Client B.
+AP 1 routes the packet fine towards swp0 and delivers it on the Ethernet
+segment.
+AP 2 sees a frame on swp0 and its fdb says that the destination is swp0.
+Hairpinning is disabled => drop.
+
+This problem comes from the fact that these switches have a 'blind spot'
+for addresses coming from software bridging. The generic solution is not
+to assume that hardware learning can be enabled somehow, but to listen
+to more bridge learning events. It turns out that the bridge driver does
+learn in software from all inbound frames, in __br_handle_local_finish.
+A proper SWITCHDEV_FDB_ADD_TO_DEVICE notification is emitted for the
+addresses serviced by the bridge on 'foreign' interfaces. The software
+bridge also does the right thing on migration, by notifying that the old
+entry is deleted, so that does not need to be special-cased in DSA. When
+it is deleted, we just need to delete our static FDB entry towards the
+CPU too, and wait.
+
+The problem is that DSA currently only cares about SWITCHDEV_FDB_ADD_TO_DEVICE
+events received on its own interfaces, such as static FDB entries.
+
+Luckily we can change that, and DSA can listen to all switchdev FDB
+add/del events in the system and figure out if those events were emitted
+by a bridge that spans at least one of DSA's own ports. In case that is
+true, DSA will also offload that address towards its own CPU port, in
+the eventuality that there might be bridge clients attached to the DSA
+switch who want to talk to the station connected to the foreign
+interface.
+
+In terms of implementation, we need to keep the fdb_info->added_by_user
+check for the case where the switchdev event was targeted directly at a
+DSA switch port. But we don't need to look at that flag for snooped
+events. So the check is currently too late, we need to move it earlier.
+This also simplifies the code a bit, since we avoid uselessly allocating
+and freeing switchdev_work.
+
+We could probably do some improvements in the future. For example,
+multi-bridge support is rudimentary at the moment. If there are two
+bridges spanning a DSA switch's ports, and both of them need to service
+the same MAC address, then what will happen is that the migration of one
+of those stations will trigger the deletion of the FDB entry from the
+CPU port while it is still used by other bridge. That could be improved
+with reference counting but is left for another time.
+
+This behavior needs to be enabled at driver level by setting
+ds->assisted_learning_on_cpu_port = true. This is because we don't want
+to inflict a potential performance penalty (accesses through
+MDIO/I2C/SPI are expensive) to hardware that really doesn't need it
+because address learning on the CPU port works there.
+
+Reported-by: DENG Qingfang <dqfext@gmail.com>
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ include/net/dsa.h |  5 +++++
+ net/dsa/slave.c   | 66 +++++++++++++++++++++++++++++++++++++++++++++----------
+ 2 files changed, 60 insertions(+), 11 deletions(-)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -317,6 +317,11 @@ struct dsa_switch {
+ 	 */
+ 	bool			untag_bridge_pvid;
+ 
++	/* Let DSA manage the FDB entries towards the CPU, based on the
++	 * software bridge database.
++	 */
++	bool			assisted_learning_on_cpu_port;
++
+ 	/* In case vlan_filtering_is_global is set, the VLAN awareness state
+ 	 * should be retrieved from here and not from the per-port settings.
+ 	 */
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2067,6 +2067,28 @@ static void dsa_slave_switchdev_event_wo
+ 		dev_put(dp->slave);
+ }
+ 
++static int dsa_lower_dev_walk(struct net_device *lower_dev,
++			      struct netdev_nested_priv *priv)
++{
++	if (dsa_slave_dev_check(lower_dev)) {
++		priv->data = (void *)netdev_priv(lower_dev);
++		return 1;
++	}
++
++	return 0;
++}
++
++static struct dsa_slave_priv *dsa_slave_dev_lower_find(struct net_device *dev)
++{
++	struct netdev_nested_priv priv = {
++		.data = NULL,
++	};
++
++	netdev_walk_all_lower_dev_rcu(dev, dsa_lower_dev_walk, &priv);
++
++	return (struct dsa_slave_priv *)priv.data;
++}
++
+ /* Called under rcu_read_lock() */
+ static int dsa_slave_switchdev_event(struct notifier_block *unused,
+ 				     unsigned long event, void *ptr)
+@@ -2085,10 +2107,37 @@ static int dsa_slave_switchdev_event(str
+ 		return notifier_from_errno(err);
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		if (!dsa_slave_dev_check(dev))
+-			return NOTIFY_DONE;
++		fdb_info = ptr;
++
++		if (dsa_slave_dev_check(dev)) {
++			if (!fdb_info->added_by_user)
++				return NOTIFY_OK;
++
++			dp = dsa_slave_to_port(dev);
++		} else {
++			/* Snoop addresses learnt on foreign interfaces
++			 * bridged with us, for switches that don't
++			 * automatically learn SA from CPU-injected traffic
++			 */
++			struct net_device *br_dev;
++			struct dsa_slave_priv *p;
++
++			br_dev = netdev_master_upper_dev_get_rcu(dev);
++			if (!br_dev)
++				return NOTIFY_DONE;
++
++			if (!netif_is_bridge_master(br_dev))
++				return NOTIFY_DONE;
++
++			p = dsa_slave_dev_lower_find(br_dev);
++			if (!p)
++				return NOTIFY_DONE;
+ 
+-		dp = dsa_slave_to_port(dev);
++			dp = p->dp->cpu_dp;
++
++			if (!dp->ds->assisted_learning_on_cpu_port)
++				return NOTIFY_DONE;
++		}
+ 
+ 		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
+ 			return NOTIFY_DONE;
+@@ -2103,18 +2152,13 @@ static int dsa_slave_switchdev_event(str
+ 		switchdev_work->port = dp->index;
+ 		switchdev_work->event = event;
+ 
+-		fdb_info = ptr;
+-
+-		if (!fdb_info->added_by_user) {
+-			kfree(switchdev_work);
+-			return NOTIFY_OK;
+-		}
+-
+ 		ether_addr_copy(switchdev_work->addr,
+ 				fdb_info->addr);
+ 		switchdev_work->vid = fdb_info->vid;
+ 
+-		dev_hold(dev);
++		/* Hold a reference on the slave for dsa_fdb_offload_notify */
++		if (dsa_is_user_port(dp->ds, dp->index))
++			dev_hold(dev);
+ 		dsa_schedule_work(&switchdev_work->work);
+ 		break;
+ 	default:
diff --git a/target/linux/generic/backport-5.4/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch b/target/linux/generic/backport-5.4/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
new file mode 100644
index 0000000000..df4e74cd96
--- /dev/null
+++ b/target/linux/generic/backport-5.4/770-v5.12-net-bridge-notify-switchdev-of-disappearance-of-old-.patch
@@ -0,0 +1,126 @@ 
+From 90dc8fd36078a536671adae884d0b929cce6480a Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:30 +0200
+Subject: [PATCH] net: bridge: notify switchdev of disappearance of old FDB
+ entry upon migration
+
+Currently the bridge emits atomic switchdev notifications for
+dynamically learnt FDB entries. Monitoring these notifications works
+wonders for switchdev drivers that want to keep their hardware FDB in
+sync with the bridge's FDB.
+
+For example station A wants to talk to station B in the diagram below,
+and we are concerned with the behavior of the bridge on the DUT device:
+
+                   DUT
+ +-------------------------------------+
+ |                 br0                 |
+ | +------+ +------+ +------+ +------+ |
+ | |      | |      | |      | |      | |
+ | | swp0 | | swp1 | | swp2 | | eth0 | |
+ +-------------------------------------+
+      |        |                  |
+  Station A    |                  |
+               |                  |
+         +--+------+--+    +--+------+--+
+         |  |      |  |    |  |      |  |
+         |  | swp0 |  |    |  | swp0 |  |
+ Another |  +------+  |    |  +------+  | Another
+  switch |     br0    |    |     br0    | switch
+         |  +------+  |    |  +------+  |
+         |  |      |  |    |  |      |  |
+         |  | swp1 |  |    |  | swp1 |  |
+         +--+------+--+    +--+------+--+
+                                  |
+                              Station B
+
+Interfaces swp0, swp1, swp2 are handled by a switchdev driver that has
+the following property: frames injected from its control interface bypass
+the internal address analyzer logic, and therefore, this hardware does
+not learn from the source address of packets transmitted by the network
+stack through it. So, since bridging between eth0 (where Station B is
+attached) and swp0 (where Station A is attached) is done in software,
+the switchdev hardware will never learn the source address of Station B.
+So the traffic towards that destination will be treated as unknown, i.e.
+flooded.
+
+This is where the bridge notifications come in handy. When br0 on the
+DUT sees frames with Station B's MAC address on eth0, the switchdev
+driver gets these notifications and can install a rule to send frames
+towards Station B's address that are incoming from swp0, swp1, swp2,
+only towards the control interface. This is all switchdev driver private
+business, which the notification makes possible.
+
+All is fine until someone unplugs Station B's cable and moves it to the
+other switch:
+
+                   DUT
+ +-------------------------------------+
+ |                 br0                 |
+ | +------+ +------+ +------+ +------+ |
+ | |      | |      | |      | |      | |
+ | | swp0 | | swp1 | | swp2 | | eth0 | |
+ +-------------------------------------+
+      |        |                  |
+  Station A    |                  |
+               |                  |
+         +--+------+--+    +--+------+--+
+         |  |      |  |    |  |      |  |
+         |  | swp0 |  |    |  | swp0 |  |
+ Another |  +------+  |    |  +------+  | Another
+  switch |     br0    |    |     br0    | switch
+         |  +------+  |    |  +------+  |
+         |  |      |  |    |  |      |  |
+         |  | swp1 |  |    |  | swp1 |  |
+         +--+------+--+    +--+------+--+
+               |
+           Station B
+
+Luckily for the use cases we care about, Station B is noisy enough that
+the DUT hears it (on swp1 this time). swp1 receives the frames and
+delivers them to the bridge, who enters the unlikely path in br_fdb_update
+of updating an existing entry. It moves the entry in the software bridge
+to swp1 and emits an addition notification towards that.
+
+As far as the switchdev driver is concerned, all that it needs to ensure
+is that traffic between Station A and Station B is not forever broken.
+If it does nothing, then the stale rule to send frames for Station B
+towards the control interface remains in place. But Station B is no
+longer reachable via the control interface, but via a port that can
+offload the bridge port learning attribute. It's just that the port is
+prevented from learning this address, since the rule overrides FDB
+updates. So the rule needs to go. The question is via what mechanism.
+
+It sure would be possible for this switchdev driver to keep track of all
+addresses which are sent to the control interface, and then also listen
+for bridge notifier events on its own ports, searching for the ones that
+have a MAC address which was previously sent to the control interface.
+But this is cumbersome and inefficient. Instead, with one small change,
+the bridge could notify of the address deletion from the old port, in a
+symmetrical manner with how it did for the insertion. Then the switchdev
+driver would not be required to monitor learn/forget events for its own
+ports. It could just delete the rule towards the control interface upon
+bridge entry migration. This would make hardware address learning be
+possible again. Then it would take a few more packets until the hardware
+and software FDB would be in sync again.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Acked-by: Nikolay Aleksandrov <nikolay@nvidia.com>
+Reviewed-by: Ido Schimmel <idosch@nvidia.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/bridge/br_fdb.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/net/bridge/br_fdb.c
++++ b/net/bridge/br_fdb.c
+@@ -581,6 +581,7 @@ void br_fdb_update(struct net_bridge *br
+ 
+ 			/* fastpath: update of existing entry */
+ 			if (unlikely(source != fdb->dst && !fdb->is_sticky)) {
++				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
+ 				fdb->dst = source;
+ 				fdb_modified = true;
+ 				/* Take over HW learned entry */
diff --git a/target/linux/generic/backport-5.4/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch b/target/linux/generic/backport-5.4/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
new file mode 100644
index 0000000000..967502f022
--- /dev/null
+++ b/target/linux/generic/backport-5.4/771-v5.12-net-dsa-be-louder-when-a-non-legacy-FDB-operation-fa.patch
@@ -0,0 +1,52 @@ 
+From 2fd186501b1cff155cc4a755c210793cfc0dffb5 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:31 +0200
+Subject: [PATCH] net: dsa: be louder when a non-legacy FDB operation fails
+
+The dev_close() call was added in commit c9eb3e0f8701 ("net: dsa: Add
+support for learning FDB through notification") "to indicate inconsistent
+situation" when we could not delete an FDB entry from the port.
+
+bridge fdb del d8:58:d7:00:ca:6d dev swp0 self master
+
+It is a bit drastic and at the same time not helpful if the above fails
+to only print with netdev_dbg log level, but on the other hand to bring
+the interface down.
+
+So increase the verbosity of the error message, and drop dev_close().
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1590,7 +1590,9 @@ static void dsa_slave_switchdev_event_wo
+ 
+ 		err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
+ 		if (err) {
+-			netdev_dbg(dev, "fdb add failed err=%d\n", err);
++			netdev_err(dev,
++				   "failed to add %pM vid %d to fdb: %d\n",
++				   fdb_info->addr, fdb_info->vid, err);
+ 			break;
+ 		}
+ 		fdb_info->offloaded = true;
+@@ -1605,9 +1607,11 @@ static void dsa_slave_switchdev_event_wo
+ 
+ 		err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
+ 		if (err) {
+-			netdev_dbg(dev, "fdb del failed err=%d\n", err);
+-			dev_close(dev);
++			netdev_err(dev,
++				   "failed to delete %pM vid %d from fdb: %d\n",
++				   fdb_info->addr, fdb_info->vid, err);
+ 		}
++
+ 		break;
+ 	}
+ 	rtnl_unlock();
diff --git a/target/linux/generic/backport-5.4/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch b/target/linux/generic/backport-5.4/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
new file mode 100644
index 0000000000..ad7c242280
--- /dev/null
+++ b/target/linux/generic/backport-5.4/772-v5.12-net-dsa-don-t-use-switchdev_notifier_fdb_info-in-dsa.patch
@@ -0,0 +1,226 @@ 
+From c4bb76a9a0ef87c4cc1f636defed5f12deb9f5a7 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:32 +0200
+Subject: [PATCH] net: dsa: don't use switchdev_notifier_fdb_info in
+ dsa_switchdev_event_work
+
+Currently DSA doesn't add FDB entries on the CPU port, because it only
+does so through switchdev, which is associated with a net_device, and
+there are none of those for the CPU port.
+
+But actually FDB addresses on the CPU port have some use cases of their
+own, if the switchdev operations are initiated from within the DSA
+layer. There is just one problem with the existing code: it passes a
+structure in dsa_switchdev_event_work which was retrieved directly from
+switchdev, so it contains a net_device. We need to generalize the
+contents to something that covers the CPU port as well: the "ds, port"
+tuple is fine for that.
+
+Note that the new procedure for notifying the successful FDB offload is
+inspired from the rocker model.
+
+Also, nothing was being done if added_by_user was false. Let's check for
+that a lot earlier, and don't actually bother to schedule the worker
+for nothing.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/dsa_priv.h |  12 +++++
+ net/dsa/slave.c    | 106 ++++++++++++++++++++++-----------------------
+ 2 files changed, 65 insertions(+), 53 deletions(-)
+
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -62,6 +62,18 @@ struct dsa_notifier_vlan_info {
+ 	int port;
+ };
+ 
++struct dsa_switchdev_event_work {
++	struct dsa_switch *ds;
++	int port;
++	struct work_struct work;
++	unsigned long event;
++	/* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
++	 * SWITCHDEV_FDB_DEL_TO_DEVICE
++	 */
++	unsigned char addr[ETH_ALEN];
++	u16 vid;
++};
++
+ struct dsa_slave_priv {
+ 	/* Copy of CPU port xmit for faster access in slave transmit hot path */
+ 	struct sk_buff *	(*xmit)(struct sk_buff *skb,
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1565,76 +1565,66 @@ static int dsa_slave_netdevice_event(str
+ 	return NOTIFY_DONE;
+ }
+ 
+-struct dsa_switchdev_event_work {
+-	struct work_struct work;
+-	struct switchdev_notifier_fdb_info fdb_info;
+-	struct net_device *dev;
+-	unsigned long event;
+-};
++static void
++dsa_fdb_offload_notify(struct dsa_switchdev_event_work *switchdev_work)
++{
++	struct dsa_switch *ds = switchdev_work->ds;
++	struct switchdev_notifier_fdb_info info;
++	struct dsa_port *dp;
++
++	if (!dsa_is_user_port(ds, switchdev_work->port))
++		return;
++
++	info.addr = switchdev_work->addr;
++	info.vid = switchdev_work->vid;
++	info.offloaded = true;
++	dp = dsa_to_port(ds, switchdev_work->port);
++	call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED,
++				 dp->slave, &info.info, NULL);
++}
+ 
+ static void dsa_slave_switchdev_event_work(struct work_struct *work)
+ {
+ 	struct dsa_switchdev_event_work *switchdev_work =
+ 		container_of(work, struct dsa_switchdev_event_work, work);
+-	struct net_device *dev = switchdev_work->dev;
+-	struct switchdev_notifier_fdb_info *fdb_info;
+-	struct dsa_port *dp = dsa_slave_to_port(dev);
++	struct dsa_switch *ds = switchdev_work->ds;
++	struct dsa_port *dp;
+ 	int err;
+ 
++	dp = dsa_to_port(ds, switchdev_work->port);
++
+ 	rtnl_lock();
+ 	switch (switchdev_work->event) {
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+-		fdb_info = &switchdev_work->fdb_info;
+-		if (!fdb_info->added_by_user)
+-			break;
+-
+-		err = dsa_port_fdb_add(dp, fdb_info->addr, fdb_info->vid);
++		err = dsa_port_fdb_add(dp, switchdev_work->addr,
++				       switchdev_work->vid);
+ 		if (err) {
+-			netdev_err(dev,
+-				   "failed to add %pM vid %d to fdb: %d\n",
+-				   fdb_info->addr, fdb_info->vid, err);
++			dev_err(ds->dev,
++				"port %d failed to add %pM vid %d to fdb: %d\n",
++				dp->index, switchdev_work->addr,
++				switchdev_work->vid, err);
+ 			break;
+ 		}
+-		fdb_info->offloaded = true;
+-		call_switchdev_notifiers(SWITCHDEV_FDB_OFFLOADED, dev,
+-					 &fdb_info->info, NULL);
++		dsa_fdb_offload_notify(switchdev_work);
+ 		break;
+ 
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		fdb_info = &switchdev_work->fdb_info;
+-		if (!fdb_info->added_by_user)
+-			break;
+-
+-		err = dsa_port_fdb_del(dp, fdb_info->addr, fdb_info->vid);
++		err = dsa_port_fdb_del(dp, switchdev_work->addr,
++				       switchdev_work->vid);
+ 		if (err) {
+-			netdev_err(dev,
+-				   "failed to delete %pM vid %d from fdb: %d\n",
+-				   fdb_info->addr, fdb_info->vid, err);
++			dev_err(ds->dev,
++				"port %d failed to delete %pM vid %d from fdb: %d\n",
++				dp->index, switchdev_work->addr,
++				switchdev_work->vid, err);
+ 		}
+ 
+ 		break;
+ 	}
+ 	rtnl_unlock();
+ 
+-	kfree(switchdev_work->fdb_info.addr);
+ 	kfree(switchdev_work);
+-	dev_put(dev);
+-}
+-
+-static int
+-dsa_slave_switchdev_fdb_work_init(struct dsa_switchdev_event_work *
+-				  switchdev_work,
+-				  const struct switchdev_notifier_fdb_info *
+-				  fdb_info)
+-{
+-	memcpy(&switchdev_work->fdb_info, fdb_info,
+-	       sizeof(switchdev_work->fdb_info));
+-	switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
+-	if (!switchdev_work->fdb_info.addr)
+-		return -ENOMEM;
+-	ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
+-			fdb_info->addr);
+-	return 0;
++	if (dsa_is_user_port(ds, dp->index))
++		dev_put(dp->slave);
+ }
+ 
+ /* Called under rcu_read_lock() */
+@@ -1642,7 +1632,9 @@ static int dsa_slave_switchdev_event(str
+ 				     unsigned long event, void *ptr)
+ {
+ 	struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
++	const struct switchdev_notifier_fdb_info *fdb_info;
+ 	struct dsa_switchdev_event_work *switchdev_work;
++	struct dsa_port *dp;
+ 	int err;
+ 
+ 	if (event == SWITCHDEV_PORT_ATTR_SET) {
+@@ -1655,20 +1647,32 @@ static int dsa_slave_switchdev_event(str
+ 	if (!dsa_slave_dev_check(dev))
+ 		return NOTIFY_DONE;
+ 
++	dp = dsa_slave_to_port(dev);
++
+ 	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ 	if (!switchdev_work)
+ 		return NOTIFY_BAD;
+ 
+ 	INIT_WORK(&switchdev_work->work,
+ 		  dsa_slave_switchdev_event_work);
+-	switchdev_work->dev = dev;
++	switchdev_work->ds = dp->ds;
++	switchdev_work->port = dp->index;
+ 	switchdev_work->event = event;
+ 
+ 	switch (event) {
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE: /* fall through */
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		if (dsa_slave_switchdev_fdb_work_init(switchdev_work, ptr))
+-			goto err_fdb_work_init;
++		fdb_info = ptr;
++
++		if (!fdb_info->added_by_user) {
++			kfree(switchdev_work);
++			return NOTIFY_OK;
++		}
++
++		ether_addr_copy(switchdev_work->addr,
++				fdb_info->addr);
++		switchdev_work->vid = fdb_info->vid;
++
+ 		dev_hold(dev);
+ 		break;
+ 	default:
+@@ -1678,10 +1682,6 @@ static int dsa_slave_switchdev_event(str
+ 
+ 	dsa_schedule_work(&switchdev_work->work);
+ 	return NOTIFY_OK;
+-
+-err_fdb_work_init:
+-	kfree(switchdev_work);
+-	return NOTIFY_BAD;
+ }
+ 
+ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
diff --git a/target/linux/generic/backport-5.4/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch b/target/linux/generic/backport-5.4/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
new file mode 100644
index 0000000000..619c71c7bb
--- /dev/null
+++ b/target/linux/generic/backport-5.4/773-v5.12-net-dsa-move-switchdev-event-implementation-under-th.patch
@@ -0,0 +1,85 @@ 
+From 447d290a58bd335d68f665713842365d3d6447df Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:33 +0200
+Subject: [PATCH] net: dsa: move switchdev event implementation under the same
+ switch/case statement
+
+We'll need to start listening to SWITCHDEV_FDB_{ADD,DEL}_TO_DEVICE
+events even for interfaces where dsa_slave_dev_check returns false, so
+we need that check inside the switch-case statement for SWITCHDEV_FDB_*.
+
+This movement also avoids a useless allocation / free of switchdev_work
+on the untreated "default event" case.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 35 ++++++++++++++++-------------------
+ 1 file changed, 16 insertions(+), 19 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1637,31 +1637,29 @@ static int dsa_slave_switchdev_event(str
+ 	struct dsa_port *dp;
+ 	int err;
+ 
+-	if (event == SWITCHDEV_PORT_ATTR_SET) {
++	switch (event) {
++	case SWITCHDEV_PORT_ATTR_SET:
+ 		err = switchdev_handle_port_attr_set(dev, ptr,
+ 						     dsa_slave_dev_check,
+ 						     dsa_slave_port_attr_set);
+ 		return notifier_from_errno(err);
+-	}
+-
+-	if (!dsa_slave_dev_check(dev))
+-		return NOTIFY_DONE;
++	case SWITCHDEV_FDB_ADD_TO_DEVICE:
++	case SWITCHDEV_FDB_DEL_TO_DEVICE:
++		if (!dsa_slave_dev_check(dev))
++			return NOTIFY_DONE;
+ 
+-	dp = dsa_slave_to_port(dev);
++		dp = dsa_slave_to_port(dev);
+ 
+-	switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+-	if (!switchdev_work)
+-		return NOTIFY_BAD;
+-
+-	INIT_WORK(&switchdev_work->work,
+-		  dsa_slave_switchdev_event_work);
+-	switchdev_work->ds = dp->ds;
+-	switchdev_work->port = dp->index;
+-	switchdev_work->event = event;
++		switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
++		if (!switchdev_work)
++			return NOTIFY_BAD;
++
++		INIT_WORK(&switchdev_work->work,
++			  dsa_slave_switchdev_event_work);
++		switchdev_work->ds = dp->ds;
++		switchdev_work->port = dp->index;
++		switchdev_work->event = event;
+ 
+-	switch (event) {
+-	case SWITCHDEV_FDB_ADD_TO_DEVICE: /* fall through */
+-	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+ 		fdb_info = ptr;
+ 
+ 		if (!fdb_info->added_by_user) {
+@@ -1674,13 +1672,12 @@ static int dsa_slave_switchdev_event(str
+ 		switchdev_work->vid = fdb_info->vid;
+ 
+ 		dev_hold(dev);
++		dsa_schedule_work(&switchdev_work->work);
+ 		break;
+ 	default:
+-		kfree(switchdev_work);
+ 		return NOTIFY_DONE;
+ 	}
+ 
+-	dsa_schedule_work(&switchdev_work->work);
+ 	return NOTIFY_OK;
+ }
+ 
diff --git a/target/linux/generic/backport-5.4/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch b/target/linux/generic/backport-5.4/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
new file mode 100644
index 0000000000..25e0d30e1a
--- /dev/null
+++ b/target/linux/generic/backport-5.4/774-v5.12-net-dsa-exit-early-in-dsa_slave_switchdev_event-if-w.patch
@@ -0,0 +1,42 @@ 
+From 5fb4a451a87d8ed3363d28b63a3295399373d6c4 Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:34 +0200
+Subject: [PATCH] net: dsa: exit early in dsa_slave_switchdev_event if we can't
+ program the FDB
+
+Right now, the following would happen for a switch driver that does not
+implement .port_fdb_add or .port_fdb_del.
+
+dsa_slave_switchdev_event returns NOTIFY_OK and schedules:
+-> dsa_slave_switchdev_event_work
+   -> dsa_port_fdb_add
+      -> dsa_port_notify(DSA_NOTIFIER_FDB_ADD)
+         -> dsa_switch_fdb_add
+            -> if (!ds->ops->port_fdb_add) return -EOPNOTSUPP;
+   -> an error is printed with dev_dbg, and
+      dsa_fdb_offload_notify(switchdev_work) is not called.
+
+We can avoid scheduling the worker for nothing and say NOTIFY_DONE.
+Because we don't call dsa_fdb_offload_notify, the static FDB entry will
+remain just in the software bridge.
+
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ net/dsa/slave.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1650,6 +1650,9 @@ static int dsa_slave_switchdev_event(str
+ 
+ 		dp = dsa_slave_to_port(dev);
+ 
++		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
++			return NOTIFY_DONE;
++
+ 		switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
+ 		if (!switchdev_work)
+ 			return NOTIFY_BAD;
diff --git a/target/linux/generic/backport-5.4/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch b/target/linux/generic/backport-5.4/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
new file mode 100644
index 0000000000..ecc74ea050
--- /dev/null
+++ b/target/linux/generic/backport-5.4/775-v5.12-net-dsa-listen-for-SWITCHDEV_-FDB-DEL-_ADD_TO_DEVICE.patch
@@ -0,0 +1,263 @@ 
+From d5f19486cee79d04c054427577ac96ed123706db Mon Sep 17 00:00:00 2001
+From: Vladimir Oltean <vladimir.oltean@nxp.com>
+Date: Wed, 6 Jan 2021 11:51:35 +0200
+Subject: [PATCH] net: dsa: listen for SWITCHDEV_{FDB,DEL}_ADD_TO_DEVICE on
+ foreign bridge neighbors
+
+Some DSA switches (and not only) cannot learn source MAC addresses from
+packets injected from the CPU. They only perform hardware address
+learning from inbound traffic.
+
+This can be problematic when we have a bridge spanning some DSA switch
+ports and some non-DSA ports (which we'll call "foreign interfaces" from
+DSA's perspective).
+
+There are 2 classes of problems created by the lack of learning on
+CPU-injected traffic:
+- excessive flooding, due to the fact that DSA treats those addresses as
+  unknown
+- the risk of stale routes, which can lead to temporary packet loss
+
+To illustrate the second class, consider the following situation, which
+is common in production equipment (wireless access points, where there
+is a WLAN interface and an Ethernet switch, and these form a single
+bridging domain).
+
+ AP 1:
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+       |                                                       ^        ^
+       |                                                       |        |
+       |                                                       |        |
+       |                                                    Client A  Client B
+       |
+       |
+       |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ AP 2
+
+- br0 of AP 1 will know that Clients A and B are reachable via wlan0
+- the hardware fdb of a DSA switch driver today is not kept in sync with
+  the software entries on other bridge ports, so it will not know that
+  clients A and B are reachable via the CPU port UNLESS the hardware
+  switch itself performs SA learning from traffic injected from the CPU.
+  Nonetheless, a substantial number of switches don't.
+- the hardware fdb of the DSA switch on AP 2 may autonomously learn that
+  Client A and B are reachable through swp0. Therefore, the software br0
+  of AP 2 also may or may not learn this. In the example we're
+  illustrating, some Ethernet traffic has been going on, and br0 from AP
+  2 has indeed learnt that it can reach Client B through swp0.
+
+One of the wireless clients, say Client B, disconnects from AP 1 and
+roams to AP 2. The topology now looks like this:
+
+ AP 1:
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+       |                                                            ^
+       |                                                            |
+       |                                                         Client A
+       |
+       |
+       |                                                         Client B
+       |                                                            |
+       |                                                            v
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ |    swp0    | |    swp1    | |    swp2    | |    swp3    | |    wlan0   |
+ +------------+ +------------+ +------------+ +------------+ +------------+
+ +------------------------------------------------------------------------+
+ |                                          br0                           |
+ +------------------------------------------------------------------------+
+ AP 2
+
+- br0 of AP 1 still knows that Client A is reachable via wlan0 (no change)
+- br0 of AP 1 will (possibly) know that Client B has left wlan0. There
+  are cases where it might never find out though. Either way, DSA today
+  does not process that notification in any way.
+- the hardware FDB of the DSA switch on AP 1 may learn autonomously that
+  Client B can be reached via swp0, if it receives any packet with
+  Client 1's source MAC address over Ethernet.
+- the hardware FDB of the DSA switch on AP 2 still thinks that Client B
+  can be reached via swp0. It does not know that it has roamed to wlan0,
+  because it doesn't perform SA learning from the CPU port.
+
+Now Client A contacts Client B.
+AP 1 routes the packet fine towards swp0 and delivers it on the Ethernet
+segment.
+AP 2 sees a frame on swp0 and its fdb says that the destination is swp0.
+Hairpinning is disabled => drop.
+
+This problem comes from the fact that these switches have a 'blind spot'
+for addresses coming from software bridging. The generic solution is not
+to assume that hardware learning can be enabled somehow, but to listen
+to more bridge learning events. It turns out that the bridge driver does
+learn in software from all inbound frames, in __br_handle_local_finish.
+A proper SWITCHDEV_FDB_ADD_TO_DEVICE notification is emitted for the
+addresses serviced by the bridge on 'foreign' interfaces. The software
+bridge also does the right thing on migration, by notifying that the old
+entry is deleted, so that does not need to be special-cased in DSA. When
+it is deleted, we just need to delete our static FDB entry towards the
+CPU too, and wait.
+
+The problem is that DSA currently only cares about SWITCHDEV_FDB_ADD_TO_DEVICE
+events received on its own interfaces, such as static FDB entries.
+
+Luckily we can change that, and DSA can listen to all switchdev FDB
+add/del events in the system and figure out if those events were emitted
+by a bridge that spans at least one of DSA's own ports. In case that is
+true, DSA will also offload that address towards its own CPU port, in
+the eventuality that there might be bridge clients attached to the DSA
+switch who want to talk to the station connected to the foreign
+interface.
+
+In terms of implementation, we need to keep the fdb_info->added_by_user
+check for the case where the switchdev event was targeted directly at a
+DSA switch port. But we don't need to look at that flag for snooped
+events. So the check is currently too late, we need to move it earlier.
+This also simplifies the code a bit, since we avoid uselessly allocating
+and freeing switchdev_work.
+
+We could probably do some improvements in the future. For example,
+multi-bridge support is rudimentary at the moment. If there are two
+bridges spanning a DSA switch's ports, and both of them need to service
+the same MAC address, then what will happen is that the migration of one
+of those stations will trigger the deletion of the FDB entry from the
+CPU port while it is still used by other bridge. That could be improved
+with reference counting but is left for another time.
+
+This behavior needs to be enabled at driver level by setting
+ds->assisted_learning_on_cpu_port = true. This is because we don't want
+to inflict a potential performance penalty (accesses through
+MDIO/I2C/SPI are expensive) to hardware that really doesn't need it
+because address learning on the CPU port works there.
+
+Reported-by: DENG Qingfang <dqfext@gmail.com>
+Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[Backported to linux-5.4.y]
+Signed-off-by: DENG Qingfang <dqfext@gmail.com>
+---
+ include/net/dsa.h |  5 ++++
+ net/dsa/slave.c   | 63 ++++++++++++++++++++++++++++++++++++++---------
+ 2 files changed, 57 insertions(+), 11 deletions(-)
+
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -279,6 +279,11 @@ struct dsa_switch {
+ 	 */
+ 	bool			configure_vlan_while_not_filtering;
+ 
++	/* Let DSA manage the FDB entries towards the CPU, based on the
++	 * software bridge database.
++	 */
++	bool			assisted_learning_on_cpu_port;
++
+ 	/* In case vlan_filtering_is_global is set, the VLAN awareness state
+ 	 * should be retrieved from here and not from the per-port settings.
+ 	 */
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1627,6 +1627,25 @@ static void dsa_slave_switchdev_event_wo
+ 		dev_put(dp->slave);
+ }
+ 
++static int dsa_lower_dev_walk(struct net_device *lower_dev, void *data)
++{
++	if (dsa_slave_dev_check(lower_dev)) {
++		*((void **)data) = (void *)netdev_priv(lower_dev);
++		return 1;
++	}
++
++	return 0;
++}
++
++static struct dsa_slave_priv *dsa_slave_dev_lower_find(struct net_device *dev)
++{
++	struct dsa_slave_priv *data = NULL;
++
++	netdev_walk_all_lower_dev_rcu(dev, dsa_lower_dev_walk, (void **) &data);
++
++	return data;
++}
++
+ /* Called under rcu_read_lock() */
+ static int dsa_slave_switchdev_event(struct notifier_block *unused,
+ 				     unsigned long event, void *ptr)
+@@ -1645,10 +1664,37 @@ static int dsa_slave_switchdev_event(str
+ 		return notifier_from_errno(err);
+ 	case SWITCHDEV_FDB_ADD_TO_DEVICE:
+ 	case SWITCHDEV_FDB_DEL_TO_DEVICE:
+-		if (!dsa_slave_dev_check(dev))
+-			return NOTIFY_DONE;
++		fdb_info = ptr;
++
++		if (dsa_slave_dev_check(dev)) {
++			if (!fdb_info->added_by_user)
++				return NOTIFY_OK;
++
++			dp = dsa_slave_to_port(dev);
++		} else {
++			/* Snoop addresses learnt on foreign interfaces
++			 * bridged with us, for switches that don't
++			 * automatically learn SA from CPU-injected traffic
++			 */
++			struct net_device *br_dev;
++			struct dsa_slave_priv *p;
++
++			br_dev = netdev_master_upper_dev_get_rcu(dev);
++			if (!br_dev)
++				return NOTIFY_DONE;
++
++			if (!netif_is_bridge_master(br_dev))
++				return NOTIFY_DONE;
++
++			p = dsa_slave_dev_lower_find(br_dev);
++			if (!p)
++				return NOTIFY_DONE;
+ 
+-		dp = dsa_slave_to_port(dev);
++			dp = p->dp->cpu_dp;
++
++			if (!dp->ds->assisted_learning_on_cpu_port)
++				return NOTIFY_DONE;
++		}
+ 
+ 		if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
+ 			return NOTIFY_DONE;
+@@ -1663,18 +1709,13 @@ static int dsa_slave_switchdev_event(str
+ 		switchdev_work->port = dp->index;
+ 		switchdev_work->event = event;
+ 
+-		fdb_info = ptr;
+-
+-		if (!fdb_info->added_by_user) {
+-			kfree(switchdev_work);
+-			return NOTIFY_OK;
+-		}
+-
+ 		ether_addr_copy(switchdev_work->addr,
+ 				fdb_info->addr);
+ 		switchdev_work->vid = fdb_info->vid;
+ 
+-		dev_hold(dev);
++		/* Hold a reference on the slave for dsa_fdb_offload_notify */
++		if (dsa_is_user_port(dp->ds, dp->index))
++			dev_hold(dev);
+ 		dsa_schedule_work(&switchdev_work->work);
+ 		break;
+ 	default:
diff --git a/target/linux/generic/hack-5.10/710-net-dsa-mv88e6xxx-default-VID-1.patch b/target/linux/generic/hack-5.10/710-net-dsa-mv88e6xxx-default-VID-1.patch
new file mode 100644
index 0000000000..f301cc1e2d
--- /dev/null
+++ b/target/linux/generic/hack-5.10/710-net-dsa-mv88e6xxx-default-VID-1.patch
@@ -0,0 +1,18 @@ 
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -2088,6 +2088,7 @@ static int mv88e6xxx_port_fdb_add(struct
+ 	struct mv88e6xxx_chip *chip = ds->priv;
+ 	int err;
+ 
++	vid = vid ? : 1;
+ 	mv88e6xxx_reg_lock(chip);
+ 	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
+ 					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
+@@ -2102,6 +2103,7 @@ static int mv88e6xxx_port_fdb_del(struct
+ 	struct mv88e6xxx_chip *chip = ds->priv;
+ 	int err;
+ 
++	vid = vid ? : 1;
+ 	mv88e6xxx_reg_lock(chip);
+ 	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, 0);
+ 	mv88e6xxx_reg_unlock(chip);
diff --git a/target/linux/generic/hack-5.10/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch b/target/linux/generic/hack-5.10/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
new file mode 100644
index 0000000000..46a1ba1d96
--- /dev/null
+++ b/target/linux/generic/hack-5.10/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
@@ -0,0 +1,12 @@ 
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -2650,6 +2650,9 @@ static int mv88e6xxx_setup_port(struct m
+ 	if (dsa_is_cpu_port(ds, port))
+ 		reg = 0;
+ 
++	/* Disable ATU member violation interrupt */
++	reg |= MV88E6XXX_PORT_ASSOC_VECTOR_IGNORE_WRONG;
++
+ 	err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
+ 				   reg);
+ 	if (err)
diff --git a/target/linux/generic/hack-5.4/710-net-dsa-mv88e6xxx-default-VID-1.patch b/target/linux/generic/hack-5.4/710-net-dsa-mv88e6xxx-default-VID-1.patch
new file mode 100644
index 0000000000..5dc5ac6825
--- /dev/null
+++ b/target/linux/generic/hack-5.4/710-net-dsa-mv88e6xxx-default-VID-1.patch
@@ -0,0 +1,18 @@ 
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -1930,6 +1930,7 @@ static int mv88e6xxx_port_fdb_add(struct
+ 	struct mv88e6xxx_chip *chip = ds->priv;
+ 	int err;
+ 
++	vid = vid ? : 1;
+ 	mv88e6xxx_reg_lock(chip);
+ 	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid,
+ 					   MV88E6XXX_G1_ATU_DATA_STATE_UC_STATIC);
+@@ -1944,6 +1945,7 @@ static int mv88e6xxx_port_fdb_del(struct
+ 	struct mv88e6xxx_chip *chip = ds->priv;
+ 	int err;
+ 
++	vid = vid ? : 1;
+ 	mv88e6xxx_reg_lock(chip);
+ 	err = mv88e6xxx_port_db_load_purge(chip, port, addr, vid, 0);
+ 	mv88e6xxx_reg_unlock(chip);
diff --git a/target/linux/generic/hack-5.4/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch b/target/linux/generic/hack-5.4/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
new file mode 100644
index 0000000000..1da388c862
--- /dev/null
+++ b/target/linux/generic/hack-5.4/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch
@@ -0,0 +1,12 @@ 
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -2492,6 +2492,9 @@ static int mv88e6xxx_setup_port(struct m
+ 	if (dsa_is_cpu_port(ds, port))
+ 		reg = 0;
+ 
++	/* Disable ATU member violation interrupt */
++	reg |= MV88E6XXX_PORT_ASSOC_VECTOR_IGNORE_WRONG;
++
+ 	err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ASSOC_VECTOR,
+ 				   reg);
+ 	if (err)
diff --git a/target/linux/generic/pending-5.10/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch b/target/linux/generic/pending-5.10/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
new file mode 100644
index 0000000000..081d002c7e
--- /dev/null
+++ b/target/linux/generic/pending-5.10/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
@@ -0,0 +1,75 @@ 
+From 46fe6cecb296d850c1ee2b333e57093ac4b733f3 Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:09 +0100
+Subject: [PATCH] net: bridge: switchdev: Refactor br_switchdev_fdb_notify
+
+Instead of having to add more and more arguments to
+br_switchdev_fdb_call_notifiers, get rid of it and build the info
+struct directly in br_switchdev_fdb_notify.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+---
+ net/bridge/br_switchdev.c | 41 +++++++++++----------------------------
+ 1 file changed, 11 insertions(+), 30 deletions(-)
+
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -102,46 +102,27 @@ int br_switchdev_set_port_flag(struct ne
+ 	return 0;
+ }
+ 
+-static void
+-br_switchdev_fdb_call_notifiers(bool adding, const unsigned char *mac,
+-				u16 vid, struct net_device *dev,
+-				bool added_by_user, bool offloaded)
+-{
+-	struct switchdev_notifier_fdb_info info;
+-	unsigned long notifier_type;
+-
+-	info.addr = mac;
+-	info.vid = vid;
+-	info.added_by_user = added_by_user;
+-	info.offloaded = offloaded;
+-	notifier_type = adding ? SWITCHDEV_FDB_ADD_TO_DEVICE : SWITCHDEV_FDB_DEL_TO_DEVICE;
+-	call_switchdev_notifiers(notifier_type, dev, &info.info, NULL);
+-}
+-
+ void
+ br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
+ {
++	struct switchdev_notifier_fdb_info info = {
++		.addr = fdb->key.addr.addr,
++		.vid = fdb->key.vlan_id,
++		.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags),
++		.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
++	};
++
+ 	if (!fdb->dst)
+ 		return;
+ 
+ 	switch (type) {
+ 	case RTM_DELNEIGH:
+-		br_switchdev_fdb_call_notifiers(false, fdb->key.addr.addr,
+-						fdb->key.vlan_id,
+-						fdb->dst->dev,
+-						test_bit(BR_FDB_ADDED_BY_USER,
+-							 &fdb->flags),
+-						test_bit(BR_FDB_OFFLOADED,
+-							 &fdb->flags));
++		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
++					 fdb->dst->dev, &info.info, NULL);
+ 		break;
+ 	case RTM_NEWNEIGH:
+-		br_switchdev_fdb_call_notifiers(true, fdb->key.addr.addr,
+-						fdb->key.vlan_id,
+-						fdb->dst->dev,
+-						test_bit(BR_FDB_ADDED_BY_USER,
+-							 &fdb->flags),
+-						test_bit(BR_FDB_OFFLOADED,
+-							 &fdb->flags));
++		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
++					 fdb->dst->dev, &info.info, NULL);
+ 		break;
+ 	}
+ }
diff --git a/target/linux/generic/pending-5.10/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch b/target/linux/generic/pending-5.10/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
new file mode 100644
index 0000000000..41374c88df
--- /dev/null
+++ b/target/linux/generic/pending-5.10/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
@@ -0,0 +1,42 @@ 
+From ec5be4f79026282925ae383caa431a8d41e3456a Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:10 +0100
+Subject: [PATCH] net: bridge: switchdev: Include local flag in FDB
+ notifications
+
+Some switchdev drivers, notably DSA, ignore all dynamically learned
+address notifications (!added_by_user) as these are autonomously added
+by the switch. Previously, such a notification was indistinguishable
+from a local address notification. Include a local bit in the
+notification so that the two classes can be discriminated.
+
+This allows DSA-like devices to add local addresses to the hardware
+FDB (with the CPU as the destination), thereby avoiding flows towards
+the CPU being flooded by the switch as unknown unicast.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ include/net/switchdev.h   | 1 +
+ net/bridge/br_switchdev.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/include/net/switchdev.h
++++ b/include/net/switchdev.h
+@@ -224,6 +224,7 @@ struct switchdev_notifier_fdb_info {
+ 	const unsigned char *addr;
+ 	u16 vid;
+ 	u8 added_by_user:1,
++	   local:1,
+ 	   offloaded:1;
+ };
+ 
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -109,6 +109,7 @@ br_switchdev_fdb_notify(const struct net
+ 		.addr = fdb->key.addr.addr,
+ 		.vid = fdb->key.vlan_id,
+ 		.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags),
++		.local = test_bit(BR_FDB_LOCAL, &fdb->flags),
+ 		.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
+ 	};
+ 
diff --git a/target/linux/generic/pending-5.10/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch b/target/linux/generic/pending-5.10/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
new file mode 100644
index 0000000000..2317989fa5
--- /dev/null
+++ b/target/linux/generic/pending-5.10/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
@@ -0,0 +1,94 @@ 
+From 2e50fd9322047253c327550b4485cf8761035a8c Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:11 +0100
+Subject: [PATCH] net: bridge: switchdev: Send FDB notifications for host
+ addresses
+
+Treat addresses added to the bridge itself in the same way as regular
+ports and send out a notification so that drivers may sync it down to
+the hardware FDB.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/bridge/br_fdb.c       |  4 ++--
+ net/bridge/br_private.h   |  7 ++++---
+ net/bridge/br_switchdev.c | 11 +++++------
+ 3 files changed, 11 insertions(+), 11 deletions(-)
+
+--- a/net/bridge/br_fdb.c
++++ b/net/bridge/br_fdb.c
+@@ -602,7 +602,7 @@ void br_fdb_update(struct net_bridge *br
+ 			/* fastpath: update of existing entry */
+ 			if (unlikely(source != fdb->dst &&
+ 				     !test_bit(BR_FDB_STICKY, &fdb->flags))) {
+-				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
++				br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH);
+ 				fdb->dst = source;
+ 				fdb_modified = true;
+ 				/* Take over HW learned entry */
+@@ -735,7 +735,7 @@ static void fdb_notify(struct net_bridge
+ 	int err = -ENOBUFS;
+ 
+ 	if (swdev_notify)
+-		br_switchdev_fdb_notify(fdb, type);
++		br_switchdev_fdb_notify(br, fdb, type);
+ 
+ 	skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC);
+ 	if (skb == NULL)
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -1527,8 +1527,8 @@ bool nbp_switchdev_allowed_egress(const
+ int br_switchdev_set_port_flag(struct net_bridge_port *p,
+ 			       unsigned long flags,
+ 			       unsigned long mask);
+-void br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb,
+-			     int type);
++void br_switchdev_fdb_notify(struct net_bridge *br,
++			     const struct net_bridge_fdb_entry *fdb, int type);
+ int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
+ 			       struct netlink_ext_ack *extack);
+ int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid);
+@@ -1574,7 +1574,8 @@ static inline int br_switchdev_port_vlan
+ }
+ 
+ static inline void
+-br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
++br_switchdev_fdb_notify(struct net_bridge *br,
++			const struct net_bridge_fdb_entry *fdb, int type)
+ {
+ }
+ 
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -103,7 +103,8 @@ int br_switchdev_set_port_flag(struct ne
+ }
+ 
+ void
+-br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
++br_switchdev_fdb_notify(struct net_bridge *br,
++			const struct net_bridge_fdb_entry *fdb, int type)
+ {
+ 	struct switchdev_notifier_fdb_info info = {
+ 		.addr = fdb->key.addr.addr,
+@@ -112,18 +113,16 @@ br_switchdev_fdb_notify(const struct net
+ 		.local = test_bit(BR_FDB_LOCAL, &fdb->flags),
+ 		.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
+ 	};
+-
+-	if (!fdb->dst)
+-		return;
++	struct net_device *dev = fdb->dst ? fdb->dst->dev : br->dev;
+ 
+ 	switch (type) {
+ 	case RTM_DELNEIGH:
+ 		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
+-					 fdb->dst->dev, &info.info, NULL);
++					 dev, &info.info, NULL);
+ 		break;
+ 	case RTM_NEWNEIGH:
+ 		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
+-					 fdb->dst->dev, &info.info, NULL);
++					 dev, &info.info, NULL);
+ 		break;
+ 	}
+ }
diff --git a/target/linux/generic/pending-5.10/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch b/target/linux/generic/pending-5.10/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
new file mode 100644
index 0000000000..9246d1fa40
--- /dev/null
+++ b/target/linux/generic/pending-5.10/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
@@ -0,0 +1,36 @@ 
+From dd082716b43a3684b2f473ae5d1e76d1c076d86d Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:12 +0100
+Subject: [PATCH] net: dsa: Include local addresses in assisted CPU port
+ learning
+
+Add local addresses (i.e. the ports' MAC addresses) to the hardware
+FDB when assisted CPU port learning is enabled.
+
+NOTE: The bridge's own MAC address is also "local". If that address is
+not shared with any port, the bridge's MAC is not be added by this
+functionality - but the following commit takes care of that case.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2144,10 +2144,12 @@ static int dsa_slave_switchdev_event(str
+ 		fdb_info = ptr;
+ 
+ 		if (dsa_slave_dev_check(dev)) {
+-			if (!fdb_info->added_by_user)
+-				return NOTIFY_OK;
+-
+ 			dp = dsa_slave_to_port(dev);
++
++			if (fdb_info->local && dp->ds->assisted_learning_on_cpu_port)
++				dp = dp->cpu_dp;
++			else if (!fdb_info->added_by_user)
++				return NOTIFY_OK;
+ 		} else {
+ 			/* Snoop addresses learnt on foreign interfaces
+ 			 * bridged with us, for switches that don't
diff --git a/target/linux/generic/pending-5.10/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch b/target/linux/generic/pending-5.10/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
new file mode 100644
index 0000000000..77f0dca252
--- /dev/null
+++ b/target/linux/generic/pending-5.10/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
@@ -0,0 +1,30 @@ 
+From 0663ebde114a6fb2c28c622ba5212b302d4d2581 Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:13 +0100
+Subject: [PATCH] net: dsa: Include bridge addresses in assisted CPU port
+ learning
+
+Now that notifications are sent out for addresses added to the bridge
+itself, extend DSA to include those addresses in the hardware FDB when
+assisted CPU port learning is enabled.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2158,7 +2158,11 @@ static int dsa_slave_switchdev_event(str
+ 			struct net_device *br_dev;
+ 			struct dsa_slave_priv *p;
+ 
+-			br_dev = netdev_master_upper_dev_get_rcu(dev);
++			if (netif_is_bridge_master(dev))
++				br_dev = dev;
++			else
++				br_dev = netdev_master_upper_dev_get_rcu(dev);
++
+ 			if (!br_dev)
+ 				return NOTIFY_DONE;
+ 
diff --git a/target/linux/generic/pending-5.10/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch b/target/linux/generic/pending-5.10/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
new file mode 100644
index 0000000000..39271108ec
--- /dev/null
+++ b/target/linux/generic/pending-5.10/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
@@ -0,0 +1,56 @@ 
+From 81e39fd78db82fb51b05fff309b9c521f1a0bc5a Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:14 +0100
+Subject: [PATCH] net: dsa: Sync static FDB entries on foreign interfaces to
+ hardware
+
+Reuse the "assisted_learning_on_cpu_port" functionality to always add
+entries for user-configured entries on foreign interfaces, even if
+assisted_learning_on_cpu_port is not enabled. E.g. in this situation:
+
+   br0
+   / \
+swp0 dummy0
+
+$ bridge fdb add 02:00:de:ad:00:01 dev dummy0 vlan 1 master
+
+Results in DSA adding an entry in the hardware FDB, pointing this
+address towards the CPU port.
+
+The same is true for entries added to the bridge itself, e.g:
+
+$ bridge fdb add 02:00:de:ad:00:01 dev br0 vlan 1 self
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -2151,9 +2151,12 @@ static int dsa_slave_switchdev_event(str
+ 			else if (!fdb_info->added_by_user)
+ 				return NOTIFY_OK;
+ 		} else {
+-			/* Snoop addresses learnt on foreign interfaces
+-			 * bridged with us, for switches that don't
+-			 * automatically learn SA from CPU-injected traffic
++			/* Snoop addresses added to foreign interfaces
++			 * bridged with us, or the bridge
++			 * itself. Dynamically learned addresses can
++			 * also be added for switches that don't
++			 * automatically learn SA from CPU-injected
++			 * traffic.
+ 			 */
+ 			struct net_device *br_dev;
+ 			struct dsa_slave_priv *p;
+@@ -2175,7 +2178,8 @@ static int dsa_slave_switchdev_event(str
+ 
+ 			dp = p->dp->cpu_dp;
+ 
+-			if (!dp->ds->assisted_learning_on_cpu_port)
++			if (!fdb_info->added_by_user &&
++			    !dp->ds->assisted_learning_on_cpu_port)
+ 				return NOTIFY_DONE;
+ 		}
+ 
diff --git a/target/linux/generic/pending-5.10/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch b/target/linux/generic/pending-5.10/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch
new file mode 100644
index 0000000000..5b8d058247
--- /dev/null
+++ b/target/linux/generic/pending-5.10/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch
@@ -0,0 +1,27 @@ 
+From:   Tobias Waldekranz <tobias@waldekranz.com>
+Subject: [RFC net-next 7/7] net: dsa: mv88e6xxx: Request assisted learning on CPU port
+Date:   Sat, 16 Jan 2021 02:25:15 +0100
+Archived-At: <https://lore.kernel.org/netdev/20210116012515.3152-8-tobias@waldekranz.com/>
+
+While the hardware is capable of performing learning on the CPU port,
+it requires alot of additions to the bridge's forwarding path in order
+to handle multi-destination traffic correctly.
+
+Until that is in place, opt for the next best thing and let DSA sync
+the relevant addresses down to the hardware FDB.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -5405,6 +5405,7 @@ static int mv88e6xxx_register_switch(str
+ 	ds->ops = &mv88e6xxx_switch_ops;
+ 	ds->ageing_time_min = chip->info->age_time_coeff;
+ 	ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;
++	ds->assisted_learning_on_cpu_port = true;
+ 
+ 	dev_set_drvdata(dev, ds);
+ 
diff --git a/target/linux/generic/pending-5.4/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch b/target/linux/generic/pending-5.4/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
new file mode 100644
index 0000000000..bfa2d375e1
--- /dev/null
+++ b/target/linux/generic/pending-5.4/762-net-bridge-switchdev-Refactor-br_switchdev_fdb_notif.patch
@@ -0,0 +1,71 @@ 
+From 46fe6cecb296d850c1ee2b333e57093ac4b733f3 Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:09 +0100
+Subject: [PATCH] net: bridge: switchdev: Refactor br_switchdev_fdb_notify
+
+Instead of having to add more and more arguments to
+br_switchdev_fdb_call_notifiers, get rid of it and build the info
+struct directly in br_switchdev_fdb_notify.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+---
+ net/bridge/br_switchdev.c | 37 +++++++++++--------------------------
+ 1 file changed, 11 insertions(+), 26 deletions(-)
+
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -102,42 +102,27 @@ int br_switchdev_set_port_flag(struct ne
+ 	return 0;
+ }
+ 
+-static void
+-br_switchdev_fdb_call_notifiers(bool adding, const unsigned char *mac,
+-				u16 vid, struct net_device *dev,
+-				bool added_by_user, bool offloaded)
+-{
+-	struct switchdev_notifier_fdb_info info;
+-	unsigned long notifier_type;
+-
+-	info.addr = mac;
+-	info.vid = vid;
+-	info.added_by_user = added_by_user;
+-	info.offloaded = offloaded;
+-	notifier_type = adding ? SWITCHDEV_FDB_ADD_TO_DEVICE : SWITCHDEV_FDB_DEL_TO_DEVICE;
+-	call_switchdev_notifiers(notifier_type, dev, &info.info, NULL);
+-}
+-
+ void
+ br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
+ {
++	struct switchdev_notifier_fdb_info info = {
++		.addr = fdb->key.addr.addr,
++		.vid = fdb->key.vlan_id,
++		.added_by_user = fdb->added_by_user,
++		.offloaded = fdb->offloaded,
++	};
++
+ 	if (!fdb->dst)
+ 		return;
+ 
+ 	switch (type) {
+ 	case RTM_DELNEIGH:
+-		br_switchdev_fdb_call_notifiers(false, fdb->key.addr.addr,
+-						fdb->key.vlan_id,
+-						fdb->dst->dev,
+-						fdb->added_by_user,
+-						fdb->offloaded);
++		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
++					 fdb->dst->dev, &info.info, NULL);
+ 		break;
+ 	case RTM_NEWNEIGH:
+-		br_switchdev_fdb_call_notifiers(true, fdb->key.addr.addr,
+-						fdb->key.vlan_id,
+-						fdb->dst->dev,
+-						fdb->added_by_user,
+-						fdb->offloaded);
++		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
++					 fdb->dst->dev, &info.info, NULL);
+ 		break;
+ 	}
+ }
diff --git a/target/linux/generic/pending-5.4/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch b/target/linux/generic/pending-5.4/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
new file mode 100644
index 0000000000..49d6f079ba
--- /dev/null
+++ b/target/linux/generic/pending-5.4/763-net-bridge-switchdev-Include-local-flag-in-FDB-notif.patch
@@ -0,0 +1,42 @@ 
+From ec5be4f79026282925ae383caa431a8d41e3456a Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:10 +0100
+Subject: [PATCH] net: bridge: switchdev: Include local flag in FDB
+ notifications
+
+Some switchdev drivers, notably DSA, ignore all dynamically learned
+address notifications (!added_by_user) as these are autonomously added
+by the switch. Previously, such a notification was indistinguishable
+from a local address notification. Include a local bit in the
+notification so that the two classes can be discriminated.
+
+This allows DSA-like devices to add local addresses to the hardware
+FDB (with the CPU as the destination), thereby avoiding flows towards
+the CPU being flooded by the switch as unknown unicast.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ include/net/switchdev.h   | 1 +
+ net/bridge/br_switchdev.c | 1 +
+ 2 files changed, 2 insertions(+)
+
+--- a/include/net/switchdev.h
++++ b/include/net/switchdev.h
+@@ -124,6 +124,7 @@ struct switchdev_notifier_fdb_info {
+ 	const unsigned char *addr;
+ 	u16 vid;
+ 	u8 added_by_user:1,
++	   local:1,
+ 	   offloaded:1;
+ };
+ 
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -109,6 +109,7 @@ br_switchdev_fdb_notify(const struct net
+ 		.addr = fdb->key.addr.addr,
+ 		.vid = fdb->key.vlan_id,
+ 		.added_by_user = fdb->added_by_user,
++		.local = fdb->is_local,
+ 		.offloaded = fdb->offloaded,
+ 	};
+ 
diff --git a/target/linux/generic/pending-5.4/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch b/target/linux/generic/pending-5.4/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
new file mode 100644
index 0000000000..8b869dd8f3
--- /dev/null
+++ b/target/linux/generic/pending-5.4/764-net-bridge-switchdev-Send-FDB-notifications-for-host.patch
@@ -0,0 +1,94 @@ 
+From 2e50fd9322047253c327550b4485cf8761035a8c Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:11 +0100
+Subject: [PATCH] net: bridge: switchdev: Send FDB notifications for host
+ addresses
+
+Treat addresses added to the bridge itself in the same way as regular
+ports and send out a notification so that drivers may sync it down to
+the hardware FDB.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/bridge/br_fdb.c       |  4 ++--
+ net/bridge/br_private.h   |  7 ++++---
+ net/bridge/br_switchdev.c | 11 +++++------
+ 3 files changed, 11 insertions(+), 11 deletions(-)
+
+--- a/net/bridge/br_fdb.c
++++ b/net/bridge/br_fdb.c
+@@ -581,7 +581,7 @@ void br_fdb_update(struct net_bridge *br
+ 
+ 			/* fastpath: update of existing entry */
+ 			if (unlikely(source != fdb->dst && !fdb->is_sticky)) {
+-				br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
++				br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH);
+ 				fdb->dst = source;
+ 				fdb_modified = true;
+ 				/* Take over HW learned entry */
+@@ -697,7 +697,7 @@ static void fdb_notify(struct net_bridge
+ 	int err = -ENOBUFS;
+ 
+ 	if (swdev_notify)
+-		br_switchdev_fdb_notify(fdb, type);
++		br_switchdev_fdb_notify(br, fdb, type);
+ 
+ 	skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC);
+ 	if (skb == NULL)
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -1203,8 +1203,8 @@ bool nbp_switchdev_allowed_egress(const
+ int br_switchdev_set_port_flag(struct net_bridge_port *p,
+ 			       unsigned long flags,
+ 			       unsigned long mask);
+-void br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb,
+-			     int type);
++void br_switchdev_fdb_notify(struct net_bridge *br,
++			     const struct net_bridge_fdb_entry *fdb, int type);
+ int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
+ 			       struct netlink_ext_ack *extack);
+ int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid);
+@@ -1250,7 +1250,8 @@ static inline int br_switchdev_port_vlan
+ }
+ 
+ static inline void
+-br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
++br_switchdev_fdb_notify(struct net_bridge *br,
++			const struct net_bridge_fdb_entry *fdb, int type)
+ {
+ }
+ 
+--- a/net/bridge/br_switchdev.c
++++ b/net/bridge/br_switchdev.c
+@@ -103,7 +103,8 @@ int br_switchdev_set_port_flag(struct ne
+ }
+ 
+ void
+-br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
++br_switchdev_fdb_notify(struct net_bridge *br,
++			const struct net_bridge_fdb_entry *fdb, int type)
+ {
+ 	struct switchdev_notifier_fdb_info info = {
+ 		.addr = fdb->key.addr.addr,
+@@ -112,18 +113,16 @@ br_switchdev_fdb_notify(const struct net
+ 		.local = fdb->is_local,
+ 		.offloaded = fdb->offloaded,
+ 	};
+-
+-	if (!fdb->dst)
+-		return;
++	struct net_device *dev = fdb->dst ? fdb->dst->dev : br->dev;
+ 
+ 	switch (type) {
+ 	case RTM_DELNEIGH:
+ 		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
+-					 fdb->dst->dev, &info.info, NULL);
++					 dev, &info.info, NULL);
+ 		break;
+ 	case RTM_NEWNEIGH:
+ 		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
+-					 fdb->dst->dev, &info.info, NULL);
++					 dev, &info.info, NULL);
+ 		break;
+ 	}
+ }
diff --git a/target/linux/generic/pending-5.4/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch b/target/linux/generic/pending-5.4/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
new file mode 100644
index 0000000000..618ccf0982
--- /dev/null
+++ b/target/linux/generic/pending-5.4/765-net-dsa-Include-local-addresses-in-assisted-CPU-port.patch
@@ -0,0 +1,36 @@ 
+From dd082716b43a3684b2f473ae5d1e76d1c076d86d Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:12 +0100
+Subject: [PATCH] net: dsa: Include local addresses in assisted CPU port
+ learning
+
+Add local addresses (i.e. the ports' MAC addresses) to the hardware
+FDB when assisted CPU port learning is enabled.
+
+NOTE: The bridge's own MAC address is also "local". If that address is
+not shared with any port, the bridge's MAC is not be added by this
+functionality - but the following commit takes care of that case.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 8 +++++---
+ 1 file changed, 5 insertions(+), 3 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1695,10 +1695,12 @@ static int dsa_slave_switchdev_event(str
+ 		fdb_info = ptr;
+ 
+ 		if (dsa_slave_dev_check(dev)) {
+-			if (!fdb_info->added_by_user)
+-				return NOTIFY_OK;
+-
+ 			dp = dsa_slave_to_port(dev);
++
++			if (fdb_info->local && dp->ds->assisted_learning_on_cpu_port)
++				dp = dp->cpu_dp;
++			else if (!fdb_info->added_by_user)
++				return NOTIFY_OK;
+ 		} else {
+ 			/* Snoop addresses learnt on foreign interfaces
+ 			 * bridged with us, for switches that don't
diff --git a/target/linux/generic/pending-5.4/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch b/target/linux/generic/pending-5.4/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
new file mode 100644
index 0000000000..ed34c7ad24
--- /dev/null
+++ b/target/linux/generic/pending-5.4/766-net-dsa-Include-bridge-addresses-in-assisted-CPU-por.patch
@@ -0,0 +1,30 @@ 
+From 0663ebde114a6fb2c28c622ba5212b302d4d2581 Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:13 +0100
+Subject: [PATCH] net: dsa: Include bridge addresses in assisted CPU port
+ learning
+
+Now that notifications are sent out for addresses added to the bridge
+itself, extend DSA to include those addresses in the hardware FDB when
+assisted CPU port learning is enabled.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1709,7 +1709,11 @@ static int dsa_slave_switchdev_event(str
+ 			struct net_device *br_dev;
+ 			struct dsa_slave_priv *p;
+ 
+-			br_dev = netdev_master_upper_dev_get_rcu(dev);
++			if (netif_is_bridge_master(dev))
++				br_dev = dev;
++			else
++				br_dev = netdev_master_upper_dev_get_rcu(dev);
++
+ 			if (!br_dev)
+ 				return NOTIFY_DONE;
+ 
diff --git a/target/linux/generic/pending-5.4/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch b/target/linux/generic/pending-5.4/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
new file mode 100644
index 0000000000..94f45c6be4
--- /dev/null
+++ b/target/linux/generic/pending-5.4/767-net-dsa-Sync-static-FDB-entries-on-foreign-interface.patch
@@ -0,0 +1,56 @@ 
+From 81e39fd78db82fb51b05fff309b9c521f1a0bc5a Mon Sep 17 00:00:00 2001
+From: Tobias Waldekranz <tobias@waldekranz.com>
+Date: Sat, 16 Jan 2021 02:25:14 +0100
+Subject: [PATCH] net: dsa: Sync static FDB entries on foreign interfaces to
+ hardware
+
+Reuse the "assisted_learning_on_cpu_port" functionality to always add
+entries for user-configured entries on foreign interfaces, even if
+assisted_learning_on_cpu_port is not enabled. E.g. in this situation:
+
+   br0
+   / \
+swp0 dummy0
+
+$ bridge fdb add 02:00:de:ad:00:01 dev dummy0 vlan 1 master
+
+Results in DSA adding an entry in the hardware FDB, pointing this
+address towards the CPU port.
+
+The same is true for entries added to the bridge itself, e.g:
+
+$ bridge fdb add 02:00:de:ad:00:01 dev br0 vlan 1 self
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ net/dsa/slave.c | 12 ++++++++----
+ 1 file changed, 8 insertions(+), 4 deletions(-)
+
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1702,9 +1702,12 @@ static int dsa_slave_switchdev_event(str
+ 			else if (!fdb_info->added_by_user)
+ 				return NOTIFY_OK;
+ 		} else {
+-			/* Snoop addresses learnt on foreign interfaces
+-			 * bridged with us, for switches that don't
+-			 * automatically learn SA from CPU-injected traffic
++			/* Snoop addresses added to foreign interfaces
++			 * bridged with us, or the bridge
++			 * itself. Dynamically learned addresses can
++			 * also be added for switches that don't
++			 * automatically learn SA from CPU-injected
++			 * traffic.
+ 			 */
+ 			struct net_device *br_dev;
+ 			struct dsa_slave_priv *p;
+@@ -1726,7 +1729,8 @@ static int dsa_slave_switchdev_event(str
+ 
+ 			dp = p->dp->cpu_dp;
+ 
+-			if (!dp->ds->assisted_learning_on_cpu_port)
++			if (!fdb_info->added_by_user &&
++			    !dp->ds->assisted_learning_on_cpu_port)
+ 				return NOTIFY_DONE;
+ 		}
+ 
diff --git a/target/linux/generic/pending-5.4/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch b/target/linux/generic/pending-5.4/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch
new file mode 100644
index 0000000000..cb421f164b
--- /dev/null
+++ b/target/linux/generic/pending-5.4/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch
@@ -0,0 +1,27 @@ 
+From:   Tobias Waldekranz <tobias@waldekranz.com>
+Subject: [RFC net-next 7/7] net: dsa: mv88e6xxx: Request assisted learning on CPU port
+Date:   Sat, 16 Jan 2021 02:25:15 +0100
+Archived-At: <https://lore.kernel.org/netdev/20210116012515.3152-8-tobias@waldekranz.com/>
+
+While the hardware is capable of performing learning on the CPU port,
+it requires alot of additions to the bridge's forwarding path in order
+to handle multi-destination traffic correctly.
+
+Until that is in place, opt for the next best thing and let DSA sync
+the relevant addresses down to the hardware FDB.
+
+Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
+---
+ drivers/net/dsa/mv88e6xxx/chip.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/drivers/net/dsa/mv88e6xxx/chip.c
++++ b/drivers/net/dsa/mv88e6xxx/chip.c
+@@ -5080,6 +5080,7 @@ static int mv88e6xxx_register_switch(str
+ 	ds->ops = &mv88e6xxx_switch_ops;
+ 	ds->ageing_time_min = chip->info->age_time_coeff;
+ 	ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;
++	ds->assisted_learning_on_cpu_port = true;
+ 
+ 	dev_set_drvdata(dev, ds);
+