[PATCH 1/2] net: dsa: RCU-protect dsa_ptr in struct net_device
A. Sverdlin
alexander.sverdlin at siemens.com
Tue Sep 10 06:03:15 PDT 2024
From: Alexander Sverdlin <alexander.sverdlin at 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: stable at vger.kernel.org
Signed-off-by: Alexander Sverdlin <alexander.sverdlin at 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
More information about the Linux-mediatek
mailing list