lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20240910130321.337154-2-alexander.sverdlin@siemens.com>
Date: Tue, 10 Sep 2024 15:03:15 +0200
From: "A. Sverdlin" <alexander.sverdlin@...mens.com>
To: netdev@...r.kernel.org
Cc: Alexander Sverdlin <alexander.sverdlin@...mens.com>,
 Ar1nç ÜNAL <arinc.unal@...nc9.com>, Daniel Golle
 <daniel@...rotopia.org>, DENG Qingfang <dqfext@...il.com>, Sean Wang
 <sean.wang@...iatek.com>, Andrew Lunn <andrew@...n.ch>, Florian Fainelli
 <f.fainelli@...il.com>, Vladimir Oltean <olteanv@...il.com>, "David S.
 Miller" <davem@...emloft.net>, Eric Dumazet <edumazet@...gle.com>, Jakub
 Kicinski <kuba@...nel.org>, Paolo Abeni <pabeni@...hat.com>, Matthias
 Brugger <matthias.bgg@...il.com>, AngeloGioacchino Del Regno
 <angelogioacchino.delregno@...labora.com>, Claudiu Manoil
 <claudiu.manoil@....com>, Alexandre Belloni
 <alexandre.belloni@...tlin.com>, UNGLinuxDriver@...rochip.com, Broadcom
 internal kernel review list <bcm-kernel-feedback-list@...adcom.com>,
 Lorenzo Bianconi <lorenzo@...nel.org>, Felix Fietkau <nbd@....name>, Mark
 Lee <Mark-MC.Lee@...iatek.com>, Roopa Prabhu <roopa@...dia.com>, Nikolay
 Aleksandrov <razor@...ckwall.org>, linux-mediatek@...ts.infradead.org,
 bridge@...ts.linux.dev, stable@...r.kernel.org
Subject: [PATCH 1/2] net: dsa: RCU-protect dsa_ptr in struct net_device

From: Alexander Sverdlin <alexander.sverdlin@...mens.com>

There are multiple races of zeroing dsa_ptr in struct net_device (on
shutdown/remove) against asynchronous dereferences all over the net
code. Widespread pattern is as follows:

CPU0					CPU1
if (netdev_uses_dsa())
					dev->dsa_ptr = NULL;
        dev->dsa_ptr->...

One of the possible crashes:

Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010
CPU: 0 PID: 12 Comm: ksoftirqd/0 Tainted: G O 6.1.99+ #1
pc : lan9303_rcv
lr : lan9303_rcv
Call trace:
 lan9303_rcv
 dsa_switch_rcv
 __netif_receive_skb_list_core
 netif_receive_skb_list_internal
 napi_gro_receive
 fec_enet_rx_napi
 __napi_poll
 net_rx_action
...

RCU-protect dsa_ptr and use rcu_dereference() or rtnl_dereference()
depending on the calling context.

Rename netdev_uses_dsa() into __netdev_uses_dsa_currently()
(assumes ether RCU or RTNL lock held) and netdev_uses_dsa_currently()
variants which better reflect the uselessness of the function's
return value, which becomes outdated right after the call.

Fixes: ee534378f005 ("net: dsa: fix panic when DSA master device unbinds on shutdown")
Cc: stable@...r.kernel.org
Signed-off-by: Alexander Sverdlin <alexander.sverdlin@...mens.com>
---
 drivers/net/dsa/mt7530.c                    |   3 +-
 drivers/net/dsa/ocelot/felix.c              |   3 +-
 drivers/net/dsa/qca/qca8k-8xxx.c            |   3 +-
 drivers/net/ethernet/broadcom/bcmsysport.c  |   8 +-
 drivers/net/ethernet/mediatek/airoha_eth.c  |   2 +-
 drivers/net/ethernet/mediatek/mtk_eth_soc.c |  22 +++--
 drivers/net/ethernet/mediatek/mtk_ppe.c     |  15 ++-
 include/linux/netdevice.h                   |   2 +-
 include/net/dsa.h                           |  36 +++++--
 include/net/dsa_stubs.h                     |   6 +-
 net/bridge/br_input.c                       |   2 +-
 net/core/dev.c                              |   3 +-
 net/core/flow_dissector.c                   |  19 ++--
 net/dsa/conduit.c                           |  66 ++++++++-----
 net/dsa/dsa.c                               |  19 ++--
 net/dsa/port.c                              |   3 +-
 net/dsa/tag.c                               |   3 +-
 net/dsa/tag.h                               |  19 ++--
 net/dsa/tag_8021q.c                         |  10 +-
 net/dsa/tag_brcm.c                          |   2 +-
 net/dsa/tag_dsa.c                           |   8 +-
 net/dsa/tag_qca.c                           |  10 +-
 net/dsa/tag_sja1105.c                       |  22 +++--
 net/dsa/user.c                              | 104 +++++++++++---------
 net/ethernet/eth.c                          |   2 +-
 25 files changed, 240 insertions(+), 152 deletions(-)

diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
index ec18e68bf3a8..82d3f1786156 100644
--- a/drivers/net/dsa/mt7530.c
+++ b/drivers/net/dsa/mt7530.c
@@ -20,6 +20,7 @@
 #include <linux/reset.h>
 #include <linux/gpio/consumer.h>
 #include <linux/gpio/driver.h>
+#include <linux/rtnetlink.h>
 #include <net/dsa.h>
 
 #include "mt7530.h"
@@ -3092,7 +3093,7 @@ mt753x_conduit_state_change(struct dsa_switch *ds,
 			    const struct net_device *conduit,
 			    bool operational)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	struct mt7530_priv *priv = ds->priv;
 	int val = 0;
 	u8 mask;
diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c
index 4a705f7333f4..f6bc0ff0c116 100644
--- a/drivers/net/dsa/ocelot/felix.c
+++ b/drivers/net/dsa/ocelot/felix.c
@@ -21,6 +21,7 @@
 #include <linux/of_net.h>
 #include <linux/pci.h>
 #include <linux/of.h>
+#include <linux/rtnetlink.h>
 #include <net/pkt_sched.h>
 #include <net/dsa.h>
 #include "felix.h"
@@ -57,7 +58,7 @@ static int felix_cpu_port_for_conduit(struct dsa_switch *ds,
 		return lag;
 	}
 
-	cpu_dp = conduit->dsa_ptr;
+	cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	return cpu_dp->index;
 }
 
diff --git a/drivers/net/dsa/qca/qca8k-8xxx.c b/drivers/net/dsa/qca/qca8k-8xxx.c
index f8d8c70642c4..10b4d7e9be2f 100644
--- a/drivers/net/dsa/qca/qca8k-8xxx.c
+++ b/drivers/net/dsa/qca/qca8k-8xxx.c
@@ -20,6 +20,7 @@
 #include <linux/gpio/consumer.h>
 #include <linux/etherdevice.h>
 #include <linux/dsa/tag_qca.h>
+#include <linux/rtnetlink.h>
 
 #include "qca8k.h"
 #include "qca8k_leds.h"
@@ -1754,7 +1755,7 @@ static void
 qca8k_conduit_change(struct dsa_switch *ds, const struct net_device *conduit,
 		     bool operational)
 {
-	struct dsa_port *dp = conduit->dsa_ptr;
+	struct dsa_port *dp = rtnl_dereference(conduit->dsa_ptr);
 	struct qca8k_priv *priv = ds->priv;
 
 	/* Ethernet MIB/MDIO is only supported for CPU port 0 */
diff --git a/drivers/net/ethernet/broadcom/bcmsysport.c b/drivers/net/ethernet/broadcom/bcmsysport.c
index c9faa8540859..bd9bc081346d 100644
--- a/drivers/net/ethernet/broadcom/bcmsysport.c
+++ b/drivers/net/ethernet/broadcom/bcmsysport.c
@@ -145,7 +145,7 @@ static void bcm_sysport_set_rx_csum(struct net_device *dev,
 	 * sure we tell the RXCHK hardware to expect a 4-bytes Broadcom
 	 * tag after the Ethernet MAC Source Address.
 	 */
-	if (netdev_uses_dsa(dev))
+	if (__netdev_uses_dsa_currently(dev))
 		reg |= RXCHK_BRCM_TAG_EN;
 	else
 		reg &= ~RXCHK_BRCM_TAG_EN;
@@ -173,7 +173,7 @@ static void bcm_sysport_set_tx_csum(struct net_device *dev,
 	 * checksum to be computed correctly when using VLAN HW acceleration,
 	 * else it has no effect, so it can always be turned on.
 	 */
-	if (netdev_uses_dsa(dev))
+	if (__netdev_uses_dsa_currently(dev))
 		reg |= tdma_control_bit(priv, SW_BRCM_TAG);
 	else
 		reg &= ~tdma_control_bit(priv, SW_BRCM_TAG);
@@ -1950,7 +1950,7 @@ static inline void gib_set_pad_extension(struct bcm_sysport_priv *priv)
 
 	reg = gib_readl(priv, GIB_CONTROL);
 	/* Include Broadcom tag in pad extension and fix up IPG_LENGTH */
-	if (netdev_uses_dsa(priv->netdev)) {
+	if (__netdev_uses_dsa_currently(priv->netdev)) {
 		reg &= ~(GIB_PAD_EXTENSION_MASK << GIB_PAD_EXTENSION_SHIFT);
 		reg |= ENET_BRCM_TAG_LEN << GIB_PAD_EXTENSION_SHIFT;
 	}
@@ -2299,7 +2299,7 @@ static u16 bcm_sysport_select_queue(struct net_device *dev, struct sk_buff *skb,
 	struct bcm_sysport_tx_ring *tx_ring;
 	unsigned int q, port;
 
-	if (!netdev_uses_dsa(dev))
+	if (!__netdev_uses_dsa_currently(dev))
 		return netdev_pick_tx(dev, skb, NULL);
 
 	/* DSA tagging layer will have configured the correct queue */
diff --git a/drivers/net/ethernet/mediatek/airoha_eth.c b/drivers/net/ethernet/mediatek/airoha_eth.c
index 1c5b85a86df1..f7425d393b22 100644
--- a/drivers/net/ethernet/mediatek/airoha_eth.c
+++ b/drivers/net/ethernet/mediatek/airoha_eth.c
@@ -2255,7 +2255,7 @@ static int airoha_dev_open(struct net_device *dev)
 	if (err)
 		return err;
 
-	if (netdev_uses_dsa(dev))
+	if (__netdev_uses_dsa_currently(dev))
 		airoha_fe_set(eth, REG_GDM_INGRESS_CFG(port->id),
 			      GDM_STAG_EN_MASK);
 	else
diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 16ca427cf4c3..82a828349323 100644
--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -24,6 +24,7 @@
 #include <linux/pcs/pcs-mtk-lynxi.h>
 #include <linux/jhash.h>
 #include <linux/bitfield.h>
+#include <linux/rcupdate.h>
 #include <net/dsa.h>
 #include <net/dst_metadata.h>
 #include <net/page_pool/helpers.h>
@@ -1375,7 +1376,8 @@ static void mtk_tx_set_dma_desc_v2(struct net_device *dev, void *txd,
 		/* tx checksum offload */
 		if (info->csum)
 			data |= TX_DMA_CHKSUM_V2;
-		if (mtk_is_netsys_v3_or_greater(eth) && netdev_uses_dsa(dev))
+		if (mtk_is_netsys_v3_or_greater(eth) &&
+		    __netdev_uses_dsa_currently(dev))
 			data |= TX_DMA_SPTAG_V3;
 	}
 	WRITE_ONCE(desc->txd5, data);
@@ -2183,7 +2185,7 @@ static int mtk_poll_rx(struct napi_struct *napi, int budget,
 		 * hardware treats the MTK special tag as a VLAN and untags it.
 		 */
 		if (mtk_is_netsys_v1(eth) && (trxd.rxd2 & RX_DMA_VTAG) &&
-		    netdev_uses_dsa(netdev)) {
+		    __netdev_uses_dsa_currently(netdev)) {
 			unsigned int port = RX_DMA_VPID(trxd.rxd3) & GENMASK(2, 0);
 
 			if (port < ARRAY_SIZE(eth->dsa_meta) &&
@@ -3304,7 +3306,7 @@ static void mtk_gdm_config(struct mtk_eth *eth, u32 id, u32 config)
 
 	val |= config;
 
-	if (eth->netdev[id] && netdev_uses_dsa(eth->netdev[id]))
+	if (eth->netdev[id] && __netdev_uses_dsa_currently(eth->netdev[id]))
 		val |= MTK_GDMA_SPECIAL_TAG;
 
 	mtk_w32(eth, val, MTK_GDMA_FWD_CFG(id));
@@ -3313,12 +3315,16 @@ static void mtk_gdm_config(struct mtk_eth *eth, u32 id, u32 config)
 
 static bool mtk_uses_dsa(struct net_device *dev)
 {
+	bool ret = false;
 #if IS_ENABLED(CONFIG_NET_DSA)
-	return netdev_uses_dsa(dev) &&
-	       dev->dsa_ptr->tag_ops->proto == DSA_TAG_PROTO_MTK;
-#else
-	return false;
+	struct dsa_port *dp;
+
+	rcu_read_lock();
+	dp = rcu_dereference(dev->dsa_ptr);
+	ret = dp && dp->tag_ops->proto == DSA_TAG_PROTO_MTK;
+	rcu_read_unlock();
 #endif
+	return ret;
 }
 
 static int mtk_device_event(struct notifier_block *n, unsigned long event, void *ptr)
@@ -4482,7 +4488,7 @@ static u16 mtk_select_queue(struct net_device *dev, struct sk_buff *skb,
 	struct mtk_mac *mac = netdev_priv(dev);
 	unsigned int queue = 0;
 
-	if (netdev_uses_dsa(dev))
+	if (__netdev_uses_dsa_currently(dev))
 		queue = skb_get_queue_mapping(skb) + 3;
 	else
 		queue = mac->id;
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe.c b/drivers/net/ethernet/mediatek/mtk_ppe.c
index 0acee405a749..0c78ec90d855 100644
--- a/drivers/net/ethernet/mediatek/mtk_ppe.c
+++ b/drivers/net/ethernet/mediatek/mtk_ppe.c
@@ -8,6 +8,7 @@
 #include <linux/platform_device.h>
 #include <linux/if_ether.h>
 #include <linux/if_vlan.h>
+#include <linux/rcupdate.h>
 #include <net/dst_metadata.h>
 #include <net/dsa.h>
 #include "mtk_eth_soc.h"
@@ -785,9 +786,17 @@ void __mtk_ppe_check_skb(struct mtk_ppe *ppe, struct sk_buff *skb, u16 hash)
 	switch (skb->protocol) {
 #if IS_ENABLED(CONFIG_NET_DSA)
 	case htons(ETH_P_XDSA):
-		if (!netdev_uses_dsa(skb->dev) ||
-		    skb->dev->dsa_ptr->tag_ops->proto != DSA_TAG_PROTO_MTK)
-			goto out;
+		{
+			struct dsa_port *dp;
+			bool proto_mtk;
+
+			rcu_read_lock();
+			dp = rcu_dereference(skb->dev->dsa_ptr);
+			proto_mtk = dp && dp->tag_ops->proto == DSA_TAG_PROTO_MTK;
+			rcu_read_unlock();
+			if (!proto_mtk)
+				goto out;
+		}
 
 		if (!skb_metadata_dst(skb))
 			tag += 4;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 607009150b5f..e645c7eabd42 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2229,7 +2229,7 @@ struct net_device {
 	struct vlan_info __rcu	*vlan_info;
 #endif
 #if IS_ENABLED(CONFIG_NET_DSA)
-	struct dsa_port		*dsa_ptr;
+	struct dsa_port	 __rcu	*dsa_ptr;
 #endif
 #if IS_ENABLED(CONFIG_TIPC)
 	struct tipc_bearer __rcu *tipc_ptr;
diff --git a/include/net/dsa.h b/include/net/dsa.h
index d7a6c2930277..ab2777611e71 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -19,6 +19,7 @@
 #include <linux/phy.h>
 #include <linux/platform_data/dsa.h>
 #include <linux/phylink.h>
+#include <linux/rcupdate.h>
 #include <net/devlink.h>
 #include <net/switchdev.h>
 
@@ -92,8 +93,9 @@ struct dsa_switch;
 struct dsa_device_ops {
 	struct sk_buff *(*xmit)(struct sk_buff *skb, struct net_device *dev);
 	struct sk_buff *(*rcv)(struct sk_buff *skb, struct net_device *dev);
-	void (*flow_dissect)(const struct sk_buff *skb, __be16 *proto,
-			     int *offset);
+	void (*flow_dissect)(const struct sk_buff *skb,
+			     const struct dsa_port *dp,
+			     __be16 *proto, int *offset);
 	int (*connect)(struct dsa_switch *ds);
 	void (*disconnect)(struct dsa_switch *ds);
 	unsigned int needed_headroom;
@@ -1334,14 +1336,29 @@ bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
 				 struct dsa_db db);
 
 /* Keep inline for faster access in hot path */
-static inline bool netdev_uses_dsa(const struct net_device *dev)
+
+/* Must be called under RCU or RTNL lock */
+static inline bool __netdev_uses_dsa_currently(const struct net_device *dev)
 {
 #if IS_ENABLED(CONFIG_NET_DSA)
-	return dev->dsa_ptr && dev->dsa_ptr->rcv;
+	struct dsa_port *dp = rcu_dereference_rtnl(dev->dsa_ptr);
+
+	return dp && dp->rcv;
 #endif
 	return false;
 }
 
+static inline bool netdev_uses_dsa_currently(const struct net_device *dev)
+{
+	bool ret = false;
+#if IS_ENABLED(CONFIG_NET_DSA)
+	rcu_read_lock();
+	ret = __netdev_uses_dsa_currently(dev);
+	rcu_read_unlock();
+#endif
+	return ret;
+}
+
 /* All DSA tags that push the EtherType to the right (basically all except tail
  * tags, which don't break dissection) can be treated the same from the
  * perspective of the flow dissector.
@@ -1355,17 +1372,20 @@ static inline bool netdev_uses_dsa(const struct net_device *dev)
  *    that, in __be16 shorts).
  *
  *  - proto: the value of the real EtherType.
+ *
+ * Must be called under RCU read lock (because of dp).
  */
 static inline void dsa_tag_generic_flow_dissect(const struct sk_buff *skb,
+						const struct dsa_port *dp,
 						__be16 *proto, int *offset)
 {
-#if IS_ENABLED(CONFIG_NET_DSA)
-	const struct dsa_device_ops *ops = skb->dev->dsa_ptr->tag_ops;
-	int tag_len = ops->needed_headroom;
+	int tag_len;
 
+	RCU_LOCKDEP_WARN(!rcu_read_lock_any_held(), "no rcu lock held");
+
+	tag_len = dp->tag_ops->needed_headroom;
 	*offset = tag_len;
 	*proto = ((__be16 *)skb->data)[(tag_len / 2) - 1];
-#endif
 }
 
 void dsa_unregister_switch(struct dsa_switch *ds);
diff --git a/include/net/dsa_stubs.h b/include/net/dsa_stubs.h
index 6f384897f287..ca899305ba36 100644
--- a/include/net/dsa_stubs.h
+++ b/include/net/dsa_stubs.h
@@ -22,9 +22,6 @@ static inline int dsa_conduit_hwtstamp_validate(struct net_device *dev,
 						const struct kernel_hwtstamp_config *config,
 						struct netlink_ext_ack *extack)
 {
-	if (!netdev_uses_dsa(dev))
-		return 0;
-
 	/* rtnl_lock() is a sufficient guarantee, because as long as
 	 * netdev_uses_dsa() returns true, the dsa_core module is still
 	 * registered, and so, dsa_unregister_stubs() couldn't have run.
@@ -33,6 +30,9 @@ static inline int dsa_conduit_hwtstamp_validate(struct net_device *dev,
 	 */
 	ASSERT_RTNL();
 
+	if (!__netdev_uses_dsa_currently(dev))
+		return 0;
+
 	return dsa_stubs->conduit_hwtstamp_validate(dev, config, extack);
 }
 
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index ceaa5a89b947..542cfc5678df 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -443,7 +443,7 @@ static rx_handler_result_t br_handle_frame_dummy(struct sk_buff **pskb)
 
 rx_handler_func_t *br_get_rx_handler(const struct net_device *dev)
 {
-	if (netdev_uses_dsa(dev))
+	if (__netdev_uses_dsa_currently(dev))
 		return br_handle_frame_dummy;
 
 	return br_handle_frame;
diff --git a/net/core/dev.c b/net/core/dev.c
index f66e61407883..9ae1c097cbad 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -5568,7 +5568,8 @@ static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
 		}
 	}
 
-	if (unlikely(skb_vlan_tag_present(skb)) && !netdev_uses_dsa(skb->dev)) {
+	if (unlikely(skb_vlan_tag_present(skb)) &&
+	    !__netdev_uses_dsa_currently(skb->dev)) {
 check_vlan_id:
 		if (skb_vlan_tag_get_id(skb)) {
 			/* Vlan id is non 0 and vlan_do_receive() above couldn't
diff --git a/net/core/flow_dissector.c b/net/core/flow_dissector.c
index 0e638a37aa09..e1523c609bb7 100644
--- a/net/core/flow_dissector.c
+++ b/net/core/flow_dissector.c
@@ -1071,25 +1071,30 @@ bool __skb_flow_dissect(const struct net *net,
 		nhoff = skb_network_offset(skb);
 		hlen = skb_headlen(skb);
 #if IS_ENABLED(CONFIG_NET_DSA)
-		if (unlikely(skb->dev && netdev_uses_dsa(skb->dev) &&
-			     proto == htons(ETH_P_XDSA))) {
+		if (unlikely(skb->dev && proto == htons(ETH_P_XDSA))) {
 			struct metadata_dst *md_dst = skb_metadata_dst(skb);
-			const struct dsa_device_ops *ops;
+			struct dsa_port *dp;
 			int offset = 0;
 
-			ops = skb->dev->dsa_ptr->tag_ops;
+			rcu_read_lock();
+
+			dp = rcu_dereference(skb->dev->dsa_ptr);
 			/* Only DSA header taggers break flow dissection */
-			if (ops->needed_headroom &&
+			if (dp && dp->tag_ops->needed_headroom &&
 			    (!md_dst || md_dst->type != METADATA_HW_PORT_MUX)) {
-				if (ops->flow_dissect)
-					ops->flow_dissect(skb, &proto, &offset);
+				if (dp->tag_ops->flow_dissect)
+					dp->tag_ops->flow_dissect(skb, dp,
+								  &proto, &offset);
 				else
 					dsa_tag_generic_flow_dissect(skb,
+								     dp,
 								     &proto,
 								     &offset);
 				hlen -= offset;
 				nhoff += offset;
 			}
+
+			rcu_read_unlock();
 		}
 #endif
 	}
diff --git a/net/dsa/conduit.c b/net/dsa/conduit.c
index 3dfdb3cb47dc..967770cdf88f 100644
--- a/net/dsa/conduit.c
+++ b/net/dsa/conduit.c
@@ -9,6 +9,7 @@
 #include <linux/ethtool.h>
 #include <linux/netdevice.h>
 #include <linux/netlink.h>
+#include <linux/rcupdate.h>
 #include <net/dsa.h>
 
 #include "conduit.h"
@@ -18,7 +19,7 @@
 
 static int dsa_conduit_get_regs_len(struct net_device *dev)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	int port = cpu_dp->index;
@@ -48,7 +49,7 @@ static int dsa_conduit_get_regs_len(struct net_device *dev)
 static void dsa_conduit_get_regs(struct net_device *dev,
 				 struct ethtool_regs *regs, void *data)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	struct ethtool_drvinfo *cpu_info;
@@ -84,7 +85,7 @@ static void dsa_conduit_get_ethtool_stats(struct net_device *dev,
 					  struct ethtool_stats *stats,
 					  uint64_t *data)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	int port = cpu_dp->index;
@@ -103,7 +104,7 @@ static void dsa_conduit_get_ethtool_phy_stats(struct net_device *dev,
 					      struct ethtool_stats *stats,
 					      uint64_t *data)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	int port = cpu_dp->index;
@@ -127,7 +128,7 @@ static void dsa_conduit_get_ethtool_phy_stats(struct net_device *dev,
 
 static int dsa_conduit_get_sset_count(struct net_device *dev, int sset)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	int count = 0;
@@ -150,7 +151,7 @@ static int dsa_conduit_get_sset_count(struct net_device *dev, int sset)
 static void dsa_conduit_get_strings(struct net_device *dev, uint32_t stringset,
 				    uint8_t *data)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops;
 	struct dsa_switch *ds = cpu_dp->ds;
 	int port = cpu_dp->index;
@@ -202,7 +203,7 @@ int __dsa_conduit_hwtstamp_validate(struct net_device *dev,
 				    const struct kernel_hwtstamp_config *config,
 				    struct netlink_ext_ack *extack)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	struct dsa_switch *ds = cpu_dp->ds;
 	struct dsa_switch_tree *dst;
 	struct dsa_port *dp;
@@ -222,7 +223,7 @@ int __dsa_conduit_hwtstamp_validate(struct net_device *dev,
 
 static int dsa_conduit_ethtool_setup(struct net_device *dev)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 	struct dsa_switch *ds = cpu_dp->ds;
 	struct ethtool_ops *ops;
 
@@ -251,7 +252,7 @@ static int dsa_conduit_ethtool_setup(struct net_device *dev)
 
 static void dsa_conduit_ethtool_teardown(struct net_device *dev)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
 
 	if (netif_is_lag_master(dev))
 		return;
@@ -267,13 +268,14 @@ static void dsa_conduit_ethtool_teardown(struct net_device *dev)
  */
 static void dsa_conduit_set_promiscuity(struct net_device *dev, int inc)
 {
-	const struct dsa_device_ops *ops = dev->dsa_ptr->tag_ops;
+	const struct dsa_device_ops *ops;
+
+	ASSERT_RTNL();
+	ops = rtnl_dereference(dev->dsa_ptr)->tag_ops;
 
 	if ((dev->priv_flags & IFF_UNICAST_FLT) && !ops->promisc_on_conduit)
 		return;
 
-	ASSERT_RTNL();
-
 	dev_set_promiscuity(dev, inc);
 }
 
@@ -281,10 +283,17 @@ static ssize_t tagging_show(struct device *d, struct device_attribute *attr,
 			    char *buf)
 {
 	struct net_device *dev = to_net_dev(d);
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp;
+	int ret = 0;
+
+	rcu_read_lock();
+	cpu_dp = rcu_dereference(dev->dsa_ptr);
+	if (cpu_dp)
+		ret = sysfs_emit(buf, "%s\n",
+				 dsa_tag_protocol_to_str(cpu_dp->tag_ops));
+	rcu_read_unlock();
 
-	return sysfs_emit(buf, "%s\n",
-		       dsa_tag_protocol_to_str(cpu_dp->tag_ops));
+	return ret;
 }
 
 static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
@@ -293,7 +302,7 @@ static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
 	const struct dsa_device_ops *new_tag_ops, *old_tag_ops;
 	const char *end = strchrnul(buf, '\n'), *name;
 	struct net_device *dev = to_net_dev(d);
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp;
 	size_t len = end - buf;
 	int err;
 
@@ -305,13 +314,17 @@ static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
 	if (!name)
 		return -ENOMEM;
 
-	old_tag_ops = cpu_dp->tag_ops;
 	new_tag_ops = dsa_tag_driver_get_by_name(name);
 	kfree(name);
 	/* Bad tagger name? */
 	if (IS_ERR(new_tag_ops))
 		return PTR_ERR(new_tag_ops);
 
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	cpu_dp = rtnl_dereference(dev->dsa_ptr);
+	old_tag_ops = cpu_dp->tag_ops;
 	if (new_tag_ops == old_tag_ops)
 		/* Drop the temporarily held duplicate reference, since
 		 * the DSA switch tree uses this tagger.
@@ -321,6 +334,7 @@ static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
 	err = dsa_tree_change_tag_proto(cpu_dp->ds->dst, new_tag_ops,
 					old_tag_ops);
 	if (err) {
+		rtnl_unlock();
 		/* On failure the old tagger is restored, so we don't need the
 		 * driver for the new one.
 		 */
@@ -331,6 +345,7 @@ static ssize_t tagging_store(struct device *d, struct device_attribute *attr,
 	/* On success we no longer need the module for the old tagging protocol
 	 */
 out:
+	rtnl_unlock();
 	dsa_tag_driver_put(old_tag_ops);
 	return count;
 }
@@ -384,13 +399,11 @@ int dsa_conduit_setup(struct net_device *dev, struct dsa_port *cpu_dp)
 		netdev_warn(dev, "error %d setting MTU to %d to include DSA overhead\n",
 			    ret, mtu);
 
-	/* If we use a tagging format that doesn't have an ethertype
-	 * field, make sure that all packets from this point on get
-	 * sent to the tag format's receive function.
+	rcu_assign_pointer(dev->dsa_ptr, cpu_dp);
+	/*
+	 * No need to synchronize_rcu() here because dsa_ptr in not going away
+	 * before it will be zeroed
 	 */
-	wmb();
-
-	dev->dsa_ptr = cpu_dp;
 
 	dsa_conduit_set_promiscuity(dev, 1);
 
@@ -418,13 +431,12 @@ void dsa_conduit_teardown(struct net_device *dev)
 	dsa_conduit_reset_mtu(dev);
 	dsa_conduit_set_promiscuity(dev, -1);
 
-	dev->dsa_ptr = NULL;
-
 	/* If we used a tagging format that doesn't have an ethertype
 	 * field, make sure that all packets from this point get sent
 	 * without the tag and go through the regular receive path.
 	 */
-	wmb();
+	rcu_assign_pointer(dev->dsa_ptr, NULL);
+	synchronize_rcu();
 }
 
 int dsa_conduit_lag_setup(struct net_device *lag_dev, struct dsa_port *cpu_dp,
@@ -434,7 +446,7 @@ int dsa_conduit_lag_setup(struct net_device *lag_dev, struct dsa_port *cpu_dp,
 	bool conduit_setup = false;
 	int err;
 
-	if (!netdev_uses_dsa(lag_dev)) {
+	if (!__netdev_uses_dsa_currently(lag_dev)) {
 		err = dsa_conduit_setup(lag_dev, cpu_dp);
 		if (err)
 			return err;
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
index 668c729946ea..36b9ebddc8b8 100644
--- a/net/dsa/dsa.c
+++ b/net/dsa/dsa.c
@@ -976,6 +976,8 @@ static int dsa_tree_bind_tag_proto(struct dsa_switch_tree *dst,
 /* Since the dsa/tagging sysfs device attribute is per conduit, the assumption
  * is that all DSA switches within a tree share the same tagger, otherwise
  * they would have formed disjoint trees (different "dsa,member" values).
+ *
+ * Must be called with RTNL lock held.
  */
 int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
 			      const struct dsa_device_ops *tag_ops,
@@ -985,8 +987,7 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
 	struct dsa_port *dp;
 	int err = -EBUSY;
 
-	if (!rtnl_trylock())
-		return restart_syscall();
+	ASSERT_RTNL();
 
 	/* At the moment we don't allow changing the tag protocol under
 	 * traffic. The rtnl_mutex also happens to serialize concurrent
@@ -1011,15 +1012,12 @@ int dsa_tree_change_tag_proto(struct dsa_switch_tree *dst,
 	if (err)
 		goto out_unwind_tagger;
 
-	rtnl_unlock();
-
 	return 0;
 
 out_unwind_tagger:
 	info.tag_ops = old_tag_ops;
 	dsa_tree_notify(dst, DSA_NOTIFIER_TAG_PROTO, &info);
 out_unlock:
-	rtnl_unlock();
 	return err;
 }
 
@@ -1027,7 +1025,7 @@ static void dsa_tree_conduit_state_change(struct dsa_switch_tree *dst,
 					  struct net_device *conduit)
 {
 	struct dsa_notifier_conduit_state_info info;
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 
 	info.conduit = conduit;
 	info.operational = dsa_port_conduit_is_operational(cpu_dp);
@@ -1039,7 +1037,7 @@ void dsa_tree_conduit_admin_state_change(struct dsa_switch_tree *dst,
 					 struct net_device *conduit,
 					 bool up)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	bool notify = false;
 
 	/* Don't keep track of admin state on LAG DSA conduits,
@@ -1062,7 +1060,7 @@ void dsa_tree_conduit_oper_state_change(struct dsa_switch_tree *dst,
 					struct net_device *conduit,
 					bool up)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	bool notify = false;
 
 	/* Don't keep track of oper state on LAG DSA conduits,
@@ -1594,10 +1592,11 @@ void dsa_switch_shutdown(struct dsa_switch *ds)
 	}
 
 	/* Disconnect from further netdevice notifiers on the conduit,
-	 * since netdev_uses_dsa() will now return false.
+	 * from now on, netdev_uses_dsa_currently() will return false.
 	 */
 	dsa_switch_for_each_cpu_port(dp, ds)
-		dp->conduit->dsa_ptr = NULL;
+		rcu_assign_pointer(dp->conduit->dsa_ptr, NULL);
+	synchronize_rcu();
 
 	rtnl_unlock();
 out:
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 25258b33e59e..6220c520d776 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -11,6 +11,7 @@
 #include <linux/notifier.h>
 #include <linux/of_mdio.h>
 #include <linux/of_net.h>
+#include <linux/rtnetlink.h>
 
 #include "dsa.h"
 #include "port.h"
@@ -1414,7 +1415,7 @@ static int dsa_port_assign_conduit(struct dsa_port *dp,
 	if (err && fail_on_err)
 		return err;
 
-	dp->cpu_dp = conduit->dsa_ptr;
+	dp->cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	dp->cpu_port_in_lag = netif_is_lag_master(conduit);
 
 	return 0;
diff --git a/net/dsa/tag.c b/net/dsa/tag.c
index 79ad105902d9..ba662adecc14 100644
--- a/net/dsa/tag.c
+++ b/net/dsa/tag.c
@@ -10,6 +10,7 @@
 #include <linux/netdevice.h>
 #include <linux/ptp_classify.h>
 #include <linux/skbuff.h>
+#include <linux/rcupdate.h>
 #include <net/dsa.h>
 #include <net/dst_metadata.h>
 
@@ -55,7 +56,7 @@ static int dsa_switch_rcv(struct sk_buff *skb, struct net_device *dev,
 			  struct packet_type *pt, struct net_device *unused)
 {
 	struct metadata_dst *md_dst = skb_metadata_dst(skb);
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
+	struct dsa_port *cpu_dp = rcu_dereference(dev->dsa_ptr);
 	struct sk_buff *nskb = NULL;
 	struct dsa_user_priv *p;
 
diff --git a/net/dsa/tag.h b/net/dsa/tag.h
index d5707870906b..dbb2217d4344 100644
--- a/net/dsa/tag.h
+++ b/net/dsa/tag.h
@@ -5,6 +5,7 @@
 
 #include <linux/if_vlan.h>
 #include <linux/list.h>
+#include <linux/rcupdate.h>
 #include <linux/types.h>
 #include <net/dsa.h>
 
@@ -32,11 +33,14 @@ static inline int dsa_tag_protocol_overhead(const struct dsa_device_ops *ops)
 static inline struct net_device *dsa_conduit_find_user(struct net_device *dev,
 						       int device, int port)
 {
-	struct dsa_port *cpu_dp = dev->dsa_ptr;
-	struct dsa_switch_tree *dst = cpu_dp->dst;
+	struct dsa_port *cpu_dp;
 	struct dsa_port *dp;
 
-	list_for_each_entry(dp, &dst->ports, list)
+	cpu_dp = rcu_dereference(dev->dsa_ptr);
+	if (!cpu_dp)
+		return NULL;
+
+	list_for_each_entry(dp, &cpu_dp->dst->ports, list)
 		if (dp->ds->index == device && dp->index == port &&
 		    dp->type == DSA_PORT_TYPE_USER)
 			return dp->user;
@@ -184,14 +188,17 @@ static inline struct sk_buff *dsa_software_vlan_untag(struct sk_buff *skb)
 static inline struct net_device *
 dsa_find_designated_bridge_port_by_vid(struct net_device *conduit, u16 vid)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
-	struct dsa_switch_tree *dst = cpu_dp->dst;
+	struct dsa_port *cpu_dp;
 	struct bridge_vlan_info vinfo;
 	struct net_device *user;
 	struct dsa_port *dp;
 	int err;
 
-	list_for_each_entry(dp, &dst->ports, list) {
+	cpu_dp = rcu_dereference(conduit->dsa_ptr);
+	if (!cpu_dp)
+		return NULL;
+
+	list_for_each_entry(dp, &cpu_dp->dst->ports, list) {
 		if (dp->type != DSA_PORT_TYPE_USER)
 			continue;
 
diff --git a/net/dsa/tag_8021q.c b/net/dsa/tag_8021q.c
index 3ee53e28ec2e..c9fb4fd2a4cf 100644
--- a/net/dsa/tag_8021q.c
+++ b/net/dsa/tag_8021q.c
@@ -5,6 +5,7 @@
  * primitives for taggers that rely on 802.1Q VLAN tags to use.
  */
 #include <linux/if_vlan.h>
+#include <linux/rcupdate.h>
 #include <linux/dsa/8021q.h>
 
 #include "port.h"
@@ -474,14 +475,17 @@ EXPORT_SYMBOL_GPL(dsa_8021q_xmit);
 static struct net_device *
 dsa_tag_8021q_find_port_by_vbid(struct net_device *conduit, int vbid)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
-	struct dsa_switch_tree *dst = cpu_dp->dst;
+	struct dsa_port *cpu_dp;
 	struct dsa_port *dp;
 
 	if (WARN_ON(!vbid))
 		return NULL;
 
-	dsa_tree_for_each_user_port(dp, dst) {
+	cpu_dp = rcu_dereference(conduit->dsa_ptr);
+	if (!cpu_dp)
+		return NULL;
+
+	dsa_tree_for_each_user_port(dp, cpu_dp->dst) {
 		if (!dp->bridge)
 			continue;
 
diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c
index 8c3c068728e5..c87f16cd959f 100644
--- a/net/dsa/tag_brcm.c
+++ b/net/dsa/tag_brcm.c
@@ -269,7 +269,7 @@ static struct sk_buff *brcm_leg_tag_rcv(struct sk_buff *skb,
 		return NULL;
 
 	/* VLAN tag is added by BCM63xx internal switch */
-	if (netdev_uses_dsa(skb->dev))
+	if (__netdev_uses_dsa_currently(skb->dev))
 		len += VLAN_HLEN;
 
 	/* Remove Broadcom tag and update checksum */
diff --git a/net/dsa/tag_dsa.c b/net/dsa/tag_dsa.c
index 2a2c4fb61a65..7f5dc8d384c9 100644
--- a/net/dsa/tag_dsa.c
+++ b/net/dsa/tag_dsa.c
@@ -48,6 +48,7 @@
 #include <linux/dsa/mv88e6xxx.h>
 #include <linux/etherdevice.h>
 #include <linux/list.h>
+#include <linux/rcupdate.h>
 #include <linux/slab.h>
 
 #include "tag.h"
@@ -257,14 +258,15 @@ static struct sk_buff *dsa_rcv_ll(struct sk_buff *skb, struct net_device *dev,
 	source_port = (dsa_header[1] >> 3) & 0x1f;
 
 	if (trunk) {
-		struct dsa_port *cpu_dp = dev->dsa_ptr;
-		struct dsa_lag *lag;
+		struct dsa_port *cpu_dp = rcu_dereference(dev->dsa_ptr);
+		struct dsa_lag *lag = NULL;
 
 		/* The exact source port is not available in the tag,
 		 * so we inject the frame directly on the upper
 		 * team/bond.
 		 */
-		lag = dsa_lag_by_id(cpu_dp->dst, source_port + 1);
+		if (cpu_dp)
+			lag = dsa_lag_by_id(cpu_dp->dst, source_port + 1);
 		skb->dev = lag ? lag->dev : NULL;
 	} else {
 		skb->dev = dsa_conduit_find_user(dev, source_device,
diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
index 0cf61286b426..dbc1f659ed07 100644
--- a/net/dsa/tag_qca.c
+++ b/net/dsa/tag_qca.c
@@ -5,6 +5,7 @@
 
 #include <linux/etherdevice.h>
 #include <linux/bitfield.h>
+#include <linux/rcupdate.h>
 #include <net/dsa.h>
 #include <linux/dsa/tag_qca.h>
 
@@ -36,8 +37,8 @@ static struct sk_buff *qca_tag_xmit(struct sk_buff *skb, struct net_device *dev)
 static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
 {
 	struct qca_tagger_data *tagger_data;
-	struct dsa_port *dp = dev->dsa_ptr;
-	struct dsa_switch *ds = dp->ds;
+	struct dsa_port *dp;
+	struct dsa_switch *ds;
 	u8 ver, pk_type;
 	__be16 *phdr;
 	int port;
@@ -45,6 +46,11 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev)
 
 	BUILD_BUG_ON(sizeof(struct qca_mgmt_ethhdr) != QCA_HDR_MGMT_HEADER_LEN + QCA_HDR_LEN);
 
+	dp = rcu_dereference(dev->dsa_ptr);
+	if (!dp)
+		return NULL;
+	ds = dp->ds;
+
 	tagger_data = ds->tagger_data;
 
 	if (unlikely(!pskb_may_pull(skb, QCA_HDR_LEN)))
diff --git a/net/dsa/tag_sja1105.c b/net/dsa/tag_sja1105.c
index 3e902af7eea6..0746a7d34178 100644
--- a/net/dsa/tag_sja1105.c
+++ b/net/dsa/tag_sja1105.c
@@ -5,6 +5,7 @@
 #include <linux/dsa/sja1105.h>
 #include <linux/dsa/8021q.h>
 #include <linux/packing.h>
+#include <linux/rcupdate.h>
 
 #include "tag.h"
 #include "tag_8021q.h"
@@ -530,12 +531,13 @@ static struct sk_buff *sja1110_rcv_meta(struct sk_buff *skb, u16 rx_header)
 	int n_ts = SJA1110_RX_HEADER_N_TS(rx_header);
 	struct sja1105_tagger_data *tagger_data;
 	struct net_device *conduit = skb->dev;
+	struct dsa_switch *ds = NULL;
 	struct dsa_port *cpu_dp;
-	struct dsa_switch *ds;
 	int i;
 
-	cpu_dp = conduit->dsa_ptr;
-	ds = dsa_switch_find(cpu_dp->dst->index, switch_id);
+	cpu_dp = rcu_dereference(conduit->dsa_ptr);
+	if (cpu_dp)
+		ds = dsa_switch_find(cpu_dp->dst->index, switch_id);
 	if (!ds) {
 		net_err_ratelimited("%s: cannot find switch id %d\n",
 				    conduit->name, switch_id);
@@ -662,24 +664,26 @@ static struct sk_buff *sja1110_rcv(struct sk_buff *skb,
 	return skb;
 }
 
-static void sja1105_flow_dissect(const struct sk_buff *skb, __be16 *proto,
-				 int *offset)
+static void sja1105_flow_dissect(const struct sk_buff *skb,
+				 const struct dsa_port *dp,
+				 __be16 *proto, int *offset)
 {
 	/* No tag added for management frames, all ok */
 	if (unlikely(sja1105_is_link_local(skb)))
 		return;
 
-	dsa_tag_generic_flow_dissect(skb, proto, offset);
+	dsa_tag_generic_flow_dissect(skb, dp, proto, offset);
 }
 
-static void sja1110_flow_dissect(const struct sk_buff *skb, __be16 *proto,
-				 int *offset)
+static void sja1110_flow_dissect(const struct sk_buff *skb,
+				 const struct dsa_port *dp,
+				 __be16 *proto, int *offset)
 {
 	/* Management frames have 2 DSA tags on RX, so the needed_headroom we
 	 * declared is fine for the generic dissector adjustment procedure.
 	 */
 	if (unlikely(sja1105_is_link_local(skb)))
-		return dsa_tag_generic_flow_dissect(skb, proto, offset);
+		return dsa_tag_generic_flow_dissect(skb, dp, proto, offset);
 
 	/* For the rest, there is a single DSA tag, the tag_8021q one */
 	*offset = VLAN_HLEN;
diff --git a/net/dsa/user.c b/net/dsa/user.c
index f5adfa1d978a..2afd21e34772 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -21,6 +21,7 @@
 #include <linux/if_hsr.h>
 #include <net/dcbnl.h>
 #include <linux/netpoll.h>
+#include <linux/rcupdate.h>
 #include <linux/string.h>
 
 #include "conduit.h"
@@ -2839,7 +2840,7 @@ int dsa_user_change_conduit(struct net_device *dev, struct net_device *conduit,
 		return -EOPNOTSUPP;
 	}
 
-	if (!netdev_uses_dsa(conduit)) {
+	if (!__netdev_uses_dsa_currently(conduit)) {
 		NL_SET_ERR_MSG_MOD(extack,
 				   "Interface not eligible as DSA conduit");
 		return -EOPNOTSUPP;
@@ -3141,8 +3142,8 @@ static int dsa_lag_conduit_validate(struct net_device *lag_dev,
 
 	netdev_for_each_lower_dev(lag_dev, lower1, iter1) {
 		netdev_for_each_lower_dev(lag_dev, lower2, iter2) {
-			if (!netdev_uses_dsa(lower1) ||
-			    !netdev_uses_dsa(lower2)) {
+			if (!__netdev_uses_dsa_currently(lower1) ||
+			    !__netdev_uses_dsa_currently(lower2)) {
 				NL_SET_ERR_MSG_MOD(extack,
 						   "All LAG ports must be eligible as DSA conduits");
 				return notifier_from_errno(-EINVAL);
@@ -3151,8 +3152,8 @@ static int dsa_lag_conduit_validate(struct net_device *lag_dev,
 			if (lower1 == lower2)
 				continue;
 
-			if (!dsa_port_tree_same(lower1->dsa_ptr,
-						lower2->dsa_ptr)) {
+			if (!dsa_port_tree_same(rtnl_dereference(lower1->dsa_ptr),
+						rtnl_dereference(lower2->dsa_ptr))) {
 				NL_SET_ERR_MSG_MOD(extack,
 						   "LAG contains DSA conduits of disjoint switch trees");
 				return notifier_from_errno(-EINVAL);
@@ -3169,7 +3170,7 @@ dsa_conduit_prechangeupper_sanity_check(struct net_device *conduit,
 {
 	struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(&info->info);
 
-	if (!netdev_uses_dsa(conduit))
+	if (!__netdev_uses_dsa_currently(conduit))
 		return NOTIFY_DONE;
 
 	if (!info->linking)
@@ -3205,20 +3206,22 @@ dsa_lag_conduit_prechangelower_sanity_check(struct net_device *dev,
 	struct net_device *lower;
 	struct list_head *iter;
 
-	if (!netdev_uses_dsa(lag_dev) || !netif_is_lag_master(lag_dev))
+	if (!__netdev_uses_dsa_currently(lag_dev) ||
+	    !netif_is_lag_master(lag_dev))
 		return NOTIFY_DONE;
 
 	if (!info->linking)
 		return NOTIFY_DONE;
 
-	if (!netdev_uses_dsa(dev)) {
+	if (!__netdev_uses_dsa_currently(dev)) {
 		NL_SET_ERR_MSG(extack,
 			       "Only DSA conduits can join a LAG DSA conduit");
 		return notifier_from_errno(-EINVAL);
 	}
 
 	netdev_for_each_lower_dev(lag_dev, lower, iter) {
-		if (!dsa_port_tree_same(dev->dsa_ptr, lower->dsa_ptr)) {
+		if (!dsa_port_tree_same(rtnl_dereference(dev->dsa_ptr),
+					rtnl_dereference(lower->dsa_ptr))) {
 			NL_SET_ERR_MSG(extack,
 				       "Interface is DSA conduit for a different switch tree than this LAG");
 			return notifier_from_errno(-EINVAL);
@@ -3257,7 +3260,8 @@ dsa_bridge_prechangelower_sanity_check(struct net_device *new_lower,
 	extack = netdev_notifier_info_to_extack(&info->info);
 
 	netdev_for_each_lower_dev(br, lower, iter) {
-		if (!netdev_uses_dsa(new_lower) && !netdev_uses_dsa(lower))
+		if (!__netdev_uses_dsa_currently(new_lower) &&
+		    !__netdev_uses_dsa_currently(lower))
 			continue;
 
 		if (!netdev_port_same_parent_id(lower, new_lower)) {
@@ -3295,7 +3299,7 @@ static int dsa_conduit_lag_join(struct net_device *conduit,
 				struct netdev_lag_upper_info *uinfo,
 				struct netlink_ext_ack *extack)
 {
-	struct dsa_port *cpu_dp = conduit->dsa_ptr;
+	struct dsa_port *cpu_dp = rtnl_dereference(conduit->dsa_ptr);
 	struct dsa_switch_tree *dst = cpu_dp->dst;
 	struct dsa_port *dp;
 	int err;
@@ -3328,7 +3332,7 @@ static int dsa_conduit_lag_join(struct net_device *conduit,
 		}
 	}
 
-	dsa_conduit_lag_teardown(lag_dev, conduit->dsa_ptr);
+	dsa_conduit_lag_teardown(lag_dev, rtnl_dereference(conduit->dsa_ptr));
 
 	return err;
 }
@@ -3336,17 +3340,16 @@ static int dsa_conduit_lag_join(struct net_device *conduit,
 static void dsa_conduit_lag_leave(struct net_device *conduit,
 				  struct net_device *lag_dev)
 {
-	struct dsa_port *dp, *cpu_dp = lag_dev->dsa_ptr;
+	struct dsa_port *dp, *cpu_dp = rtnl_dereference(lag_dev->dsa_ptr);
 	struct dsa_switch_tree *dst = cpu_dp->dst;
 	struct dsa_port *new_cpu_dp = NULL;
 	struct net_device *lower;
 	struct list_head *iter;
 
 	netdev_for_each_lower_dev(lag_dev, lower, iter) {
-		if (netdev_uses_dsa(lower)) {
-			new_cpu_dp = lower->dsa_ptr;
+		new_cpu_dp = rtnl_dereference(lower->dsa_ptr);
+		if (new_cpu_dp)
 			break;
-		}
 	}
 
 	if (new_cpu_dp) {
@@ -3360,8 +3363,11 @@ static void dsa_conduit_lag_leave(struct net_device *conduit,
 		/* Update the index of the virtual CPU port to match the lowest
 		 * physical CPU port
 		 */
-		lag_dev->dsa_ptr = new_cpu_dp;
-		wmb();
+		rcu_assign_pointer(lag_dev->dsa_ptr, new_cpu_dp);
+		/*
+		 * No need to synchronize_rcu() here because dsa_ptr in not
+		 * going away before it will be zeroed
+		 */
 	} else {
 		/* If the LAG DSA conduit has no ports left, migrate back all
 		 * user ports to the first physical CPU port
@@ -3372,7 +3378,7 @@ static void dsa_conduit_lag_leave(struct net_device *conduit,
 	/* This DSA conduit has left its LAG in any case, so let
 	 * the CPU port leave the hardware LAG as well
 	 */
-	dsa_conduit_lag_teardown(lag_dev, conduit->dsa_ptr);
+	dsa_conduit_lag_teardown(lag_dev, rtnl_dereference(conduit->dsa_ptr));
 }
 
 static int dsa_conduit_changeupper(struct net_device *dev,
@@ -3381,7 +3387,7 @@ static int dsa_conduit_changeupper(struct net_device *dev,
 	struct netlink_ext_ack *extack;
 	int err = NOTIFY_DONE;
 
-	if (!netdev_uses_dsa(dev))
+	if (!__netdev_uses_dsa_currently(dev))
 		return err;
 
 	extack = netdev_notifier_info_to_extack(&info->info);
@@ -3464,14 +3470,14 @@ static int dsa_user_netdevice_event(struct notifier_block *nb,
 			err = dsa_port_lag_change(dp, info->lower_state_info);
 		}
 
+		dp = rtnl_dereference(dev->dsa_ptr);
+		if (!dp)
+			return NOTIFY_OK;
+
 		/* Mirror LAG port events on DSA conduits that are in
 		 * a LAG towards their respective switch CPU ports
 		 */
-		if (netdev_uses_dsa(dev)) {
-			dp = dev->dsa_ptr;
-
-			err = dsa_port_lag_change(dp, info->lower_state_info);
-		}
+		err = dsa_port_lag_change(dp, info->lower_state_info);
 
 		return notifier_from_errno(err);
 	}
@@ -3481,39 +3487,41 @@ static int dsa_user_netdevice_event(struct notifier_block *nb,
 		 * DSA driver may require the conduit port (and indirectly
 		 * the tagger) to be available for some special operation.
 		 */
-		if (netdev_uses_dsa(dev)) {
-			struct dsa_port *cpu_dp = dev->dsa_ptr;
-			struct dsa_switch_tree *dst = cpu_dp->ds->dst;
-
-			/* Track when the conduit port is UP */
-			dsa_tree_conduit_oper_state_change(dst, dev,
-							   netif_oper_up(dev));
-
-			/* Track when the conduit port is ready and can accept
-			 * packet.
-			 * NETDEV_UP event is not enough to flag a port as ready.
-			 * We also have to wait for linkwatch_do_dev to dev_activate
-			 * and emit a NETDEV_CHANGE event.
-			 * We check if a conduit port is ready by checking if the dev
-			 * have a qdisc assigned and is not noop.
-			 */
-			dsa_tree_conduit_admin_state_change(dst, dev,
-							    !qdisc_tx_is_noop(dev));
+		struct dsa_port *cpu_dp = rtnl_dereference(dev->dsa_ptr);
+		struct dsa_switch_tree *dst;
 
-			return NOTIFY_OK;
-		}
+		if (!cpu_dp)
+			return NOTIFY_DONE;
+
+		dst = cpu_dp->ds->dst;
+
+		/* Track when the conduit port is UP */
+		dsa_tree_conduit_oper_state_change(dst, dev,
+						   netif_oper_up(dev));
+
+		/* Track when the conduit port is ready and can accept
+		 * packet.
+		 * NETDEV_UP event is not enough to flag a port as ready.
+		 * We also have to wait for linkwatch_do_dev to dev_activate
+		 * and emit a NETDEV_CHANGE event.
+		 * We check if a conduit port is ready by checking if the dev
+		 * have a qdisc assigned and is not noop.
+		 */
+		dsa_tree_conduit_admin_state_change(dst, dev,
+						    !qdisc_tx_is_noop(dev));
+
+		return NOTIFY_OK;
 
-		return NOTIFY_DONE;
 	}
 	case NETDEV_GOING_DOWN: {
 		struct dsa_port *dp, *cpu_dp;
 		struct dsa_switch_tree *dst;
 		LIST_HEAD(close_list);
 
-		if (!netdev_uses_dsa(dev))
+		cpu_dp = rtnl_dereference(dev->dsa_ptr);
+		if (!cpu_dp)
 			return NOTIFY_DONE;
 
-		cpu_dp = dev->dsa_ptr;
 		dst = cpu_dp->ds->dst;
 
 		dsa_tree_conduit_admin_state_change(dst, dev, false);
diff --git a/net/ethernet/eth.c b/net/ethernet/eth.c
index 4e3651101b86..a05ff82551c3 100644
--- a/net/ethernet/eth.c
+++ b/net/ethernet/eth.c
@@ -170,7 +170,7 @@ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
 	 * variants has been configured on the receiving interface,
 	 * and if so, set skb->protocol without looking at the packet.
 	 */
-	if (unlikely(netdev_uses_dsa(dev)))
+	if (unlikely(netdev_uses_dsa_currently(dev)))
 		return htons(ETH_P_XDSA);
 
 	if (likely(eth_proto_is_802_3(eth->h_proto)))
-- 
2.46.0


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ