From: Alexander Sverdlin <alexander.sverd...@siemens.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: sta...@vger.kernel.org
Signed-off-by: Alexander Sverdlin <alexander.sverd...@siemens.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


Reply via email to