@@ -1518,7 +1518,7 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
if (err)
goto err_egress_flood;
- return 0;
+ return switchdev_bridge_port_offload(netdev, NULL);
err_egress_flood:
dpaa2_switch_port_set_fdb(port_priv, NULL);
@@ -1552,6 +1552,8 @@ static int dpaa2_switch_port_bridge_leave(struct net_device *netdev)
struct ethsw_core *ethsw = port_priv->ethsw_data;
int err;
+ switchdev_bridge_port_unoffload(netdev);
+
/* First of all, fast age any learn FDB addresses on this switch port */
dpaa2_switch_port_fast_age(port_priv);
@@ -443,6 +443,10 @@ static int prestera_port_bridge_join(struct prestera_port *port,
goto err_brport_create;
}
+ err = switchdev_bridge_port_offload(port->dev, NULL);
+ if (err)
+ goto err_brport_offload;
+
if (bridge->vlan_enabled)
return 0;
@@ -453,6 +457,7 @@ static int prestera_port_bridge_join(struct prestera_port *port,
return 0;
err_port_join:
+err_brport_offload:
prestera_bridge_port_put(br_port);
err_brport_create:
prestera_bridge_put(bridge);
@@ -520,6 +525,8 @@ static void prestera_port_bridge_leave(struct prestera_port *port,
if (!br_port)
return;
+ switchdev_bridge_port_unoffload(port->dev);
+
bridge = br_port->bridge;
if (bridge->vlan_enabled)
@@ -2326,7 +2326,7 @@ int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
if (err)
goto err_port_join;
- return 0;
+ return switchdev_bridge_port_offload(brport_dev, extack);
err_port_join:
mlxsw_sp_bridge_port_put(mlxsw_sp->bridge, bridge_port);
@@ -2348,6 +2348,8 @@ void mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port,
if (!bridge_port)
return;
+ switchdev_bridge_port_unoffload(brport_dev);
+
bridge_device->ops->port_leave(bridge_device, bridge_port,
mlxsw_sp_port);
mlxsw_sp_bridge_port_put(mlxsw_sp->bridge, bridge_port);
@@ -1213,7 +1213,7 @@ static int ocelot_netdevice_bridge_join(struct net_device *dev,
if (err)
goto err_switchdev_sync;
- return 0;
+ return switchdev_bridge_port_offload(brport_dev, extack);
err_switchdev_sync:
ocelot_port_bridge_leave(ocelot, port, bridge);
@@ -1234,6 +1234,8 @@ static int ocelot_netdevice_bridge_leave(struct net_device *dev,
if (err)
return err;
+ switchdev_bridge_port_unoffload(brport_dev);
+
ocelot_port_bridge_leave(ocelot, port, bridge);
return 0;
@@ -2592,13 +2592,19 @@ static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port,
ofdpa_port->bridge_dev = bridge;
- return ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
+ err = ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
+ if (err)
+ return err;
+
+ return switchdev_bridge_port_offload(ofdpa_port->dev, NULL);
}
static int ofdpa_port_bridge_leave(struct ofdpa_port *ofdpa_port)
{
int err;
+ switchdev_bridge_port_unoffload(ofdpa_port->dev);
+
err = ofdpa_port_vlan_del(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
if (err)
return err;
@@ -7,6 +7,7 @@
#include <linux/clk.h>
#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -2082,6 +2083,7 @@ static int am65_cpsw_netdevice_port_link(struct net_device *ndev, struct net_dev
{
struct am65_cpsw_common *common = am65_ndev_to_common(ndev);
struct am65_cpsw_ndev_priv *priv = am65_ndev_to_priv(ndev);
+ int err;
if (!common->br_members) {
common->hw_bridge_dev = br_ndev;
@@ -2097,7 +2099,8 @@ static int am65_cpsw_netdevice_port_link(struct net_device *ndev, struct net_dev
am65_cpsw_port_offload_fwd_mark_update(common);
- return NOTIFY_DONE;
+ err = switchdev_bridge_port_offload(ndev, NULL);
+ return notifier_to_errno(err);
}
static void am65_cpsw_netdevice_port_unlink(struct net_device *ndev)
@@ -2105,6 +2108,8 @@ static void am65_cpsw_netdevice_port_unlink(struct net_device *ndev)
struct am65_cpsw_common *common = am65_ndev_to_common(ndev);
struct am65_cpsw_ndev_priv *priv = am65_ndev_to_priv(ndev);
+ switchdev_bridge_port_unoffload(ndev);
+
common->br_members &= ~BIT(priv->port->port_id);
am65_cpsw_port_offload_fwd_mark_update(common);
@@ -1508,6 +1508,7 @@ static int cpsw_netdevice_port_link(struct net_device *ndev,
{
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
+ int err;
if (!cpsw->br_members) {
cpsw->hw_bridge_dev = br_ndev;
@@ -1523,7 +1524,8 @@ static int cpsw_netdevice_port_link(struct net_device *ndev,
cpsw_port_offload_fwd_mark_update(cpsw);
- return NOTIFY_DONE;
+ err = switchdev_bridge_port_offload(ndev, NULL);
+ return notifier_to_errno(err);
}
static void cpsw_netdevice_port_unlink(struct net_device *ndev)
@@ -1531,6 +1533,8 @@ static void cpsw_netdevice_port_unlink(struct net_device *ndev)
struct cpsw_priv *priv = netdev_priv(ndev);
struct cpsw_common *cpsw = priv->cpsw;
+ switchdev_bridge_port_unoffload(ndev);
+
cpsw->br_members &= ~BIT(priv->emac_port);
cpsw_port_offload_fwd_mark_update(cpsw);
@@ -196,4 +196,20 @@ static inline int br_fdb_replay(struct net_device *br_dev,
}
#endif
+#if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_NET_SWITCHDEV)
+int switchdev_bridge_port_offload(struct net_device *dev,
+ struct netlink_ext_ack *extack);
+int switchdev_bridge_port_unoffload(struct net_device *dev);
+#else
+int switchdev_bridge_port_offload(struct net_device *dev,
+ struct netlink_ext_ack *extack)
+{
+ return 0;
+}
+
+int switchdev_bridge_port_unoffload(struct net_device *dev)
+{
+}
+#endif
+
#endif
@@ -643,10 +643,6 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
if (err)
goto err5;
- err = nbp_switchdev_mark_set(p);
- if (err)
- goto err6;
-
dev_disable_lro(dev);
list_add_rcu(&p->list, &br->port_list);
@@ -671,13 +667,13 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
*/
err = dev_pre_changeaddr_notify(br->dev, dev->dev_addr, extack);
if (err)
- goto err7;
+ goto err6;
}
err = nbp_vlan_init(p, extack);
if (err) {
netdev_err(dev, "failed to initialize vlan filtering on this port\n");
- goto err7;
+ goto err6;
}
spin_lock_bh(&br->lock);
@@ -700,11 +696,10 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
return 0;
-err7:
+err6:
list_del_rcu(&p->list);
br_fdb_delete_by_port(br, p, 0, 1);
nbp_update_port_count(br);
-err6:
netdev_upper_dev_unlink(dev, br->dev);
err5:
dev->priv_flags &= ~IFF_BRIDGE_PORT;
@@ -326,8 +326,10 @@ struct net_bridge_port {
#ifdef CONFIG_NET_POLL_CONTROLLER
struct netpoll *np;
#endif
+ int offload_count;
#ifdef CONFIG_NET_SWITCHDEV
int offload_fwd_mark;
+ struct netdev_phys_item_id ppid;
#endif
u16 group_fwd_mask;
u16 backup_redirected_cnt;
@@ -1572,7 +1574,6 @@ static inline void br_sysfs_delbr(struct net_device *dev) { return; }
/* br_switchdev.c */
#ifdef CONFIG_NET_SWITCHDEV
-int nbp_switchdev_mark_set(struct net_bridge_port *p);
void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb);
bool nbp_switchdev_allowed_egress(const struct net_bridge_port *p,
@@ -1592,11 +1593,6 @@ static inline void br_switchdev_frame_unmark(struct sk_buff *skb)
skb->offload_fwd_mark = 0;
}
#else
-static inline int nbp_switchdev_mark_set(struct net_bridge_port *p)
-{
- return 0;
-}
-
static inline void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb)
{
@@ -8,37 +8,109 @@
#include "br_private.h"
-static int br_switchdev_mark_get(struct net_bridge *br, struct net_device *dev)
+static int br_switchdev_mark_get(struct net_bridge *br,
+ struct net_bridge_port *new_nbp)
{
struct net_bridge_port *p;
/* dev is yet to be added to the port list. */
list_for_each_entry(p, &br->port_list, list) {
- if (netdev_port_same_parent_id(dev, p->dev))
+ if (!p->offload_count)
+ continue;
+
+ if (netdev_phys_item_id_same(&p->ppid, &new_nbp->ppid))
return p->offload_fwd_mark;
}
return ++br->offload_fwd_mark;
}
-int nbp_switchdev_mark_set(struct net_bridge_port *p)
+static int nbp_switchdev_mark_set(struct net_bridge_port *p,
+ struct netdev_phys_item_id ppid,
+ struct netlink_ext_ack *extack)
+{
+ if (p->offload_count) {
+ /* Prevent unsupported configurations such as a bridge port
+ * which is a bonding interface, and the member ports are from
+ * different hardware switches.
+ */
+ if (!netdev_phys_item_id_same(&p->ppid, &ppid)) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Same bridge port cannot be offloaded by two physical switches");
+ return -EBUSY;
+ }
+ /* Be tolerant with drivers that call SWITCHDEV_BRPORT_OFFLOADED
+ * more than once for the same bridge port, such as when the
+ * bridge port is an offloaded bonding/team interface.
+ */
+ p->offload_count++;
+ return 0;
+ }
+
+ p->ppid = ppid;
+ p->offload_count = 1;
+ p->offload_fwd_mark = br_switchdev_mark_get(p->br, p);
+
+ return 0;
+}
+
+static void nbp_switchdev_mark_clear(struct net_bridge_port *p,
+ struct netdev_phys_item_id ppid)
+{
+ if (WARN_ON(!netdev_phys_item_id_same(&p->ppid, &ppid)))
+ return;
+ if (WARN_ON(!p->offload_count))
+ return;
+
+ p->offload_count--;
+ if (p->offload_count)
+ return;
+
+ p->offload_fwd_mark = 0;
+}
+
+/* Let the bridge know that this port is offloaded, so that it can use
+ * the port parent id obtained by recursion to determine the bridge
+ * port's switchdev mark.
+ */
+int switchdev_bridge_port_offload(struct net_device *dev,
+ struct netlink_ext_ack *extack)
{
- struct netdev_phys_item_id ppid = { };
+ struct netdev_phys_item_id ppid;
+ struct net_bridge_port *p;
int err;
- ASSERT_RTNL();
+ p = br_port_get_rtnl(dev);
+ if (!p)
+ return -ENODEV;
- err = dev_get_port_parent_id(p->dev, &ppid, true);
- if (err) {
- if (err == -EOPNOTSUPP)
- return 0;
+ err = dev_get_port_parent_id(dev, &ppid, true);
+ if (err)
+ return err;
+
+ return nbp_switchdev_mark_set(p, ppid, extack);
+}
+EXPORT_SYMBOL_GPL(switchdev_bridge_port_offload);
+
+int switchdev_bridge_port_unoffload(struct net_device *dev)
+{
+ struct netdev_phys_item_id ppid;
+ struct net_bridge_port *p;
+ int err;
+
+ p = br_port_get_rtnl(dev);
+ if (!p)
+ return -ENODEV;
+
+ err = dev_get_port_parent_id(dev, &ppid, true);
+ if (err)
return err;
- }
- p->offload_fwd_mark = br_switchdev_mark_get(p->br, p->dev);
+ nbp_switchdev_mark_clear(p, ppid);
return 0;
}
+EXPORT_SYMBOL_GPL(switchdev_bridge_port_unoffload);
void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
struct sk_buff *skb)