@@ -443,6 +443,8 @@ static int prestera_port_bridge_join(struct prestera_port *port,
goto err_brport_create;
}
+ switchdev_bridge_port_offload_notify(port->dev);
+
if (bridge->vlan_enabled)
return 0;
@@ -2326,6 +2326,8 @@ int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
if (err)
goto err_port_join;
+ switchdev_bridge_port_offload_notify(brport_dev);
+
return 0;
err_port_join:
@@ -1111,10 +1111,14 @@ static int ocelot_port_obj_del(struct net_device *dev,
return ret;
}
-static int ocelot_netdevice_bridge_join(struct ocelot *ocelot, int port,
+static int ocelot_netdevice_bridge_join(struct net_device *dev,
struct net_device *bridge)
{
+ struct ocelot_port_private *priv = netdev_priv(dev);
+ struct ocelot_port *ocelot_port = &priv->port;
+ struct ocelot *ocelot = ocelot_port->ocelot;
struct switchdev_brport_flags flags;
+ int port = priv->chip_port;
int err;
flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
@@ -1124,15 +1128,20 @@ static int ocelot_netdevice_bridge_join(struct ocelot *ocelot, int port,
if (err)
return err;
+ switchdev_bridge_port_offload_notify(dev);
ocelot_port_bridge_flags(ocelot, port, flags);
return 0;
}
-static int ocelot_netdevice_bridge_leave(struct ocelot *ocelot, int port,
+static int ocelot_netdevice_bridge_leave(struct net_device *dev,
struct net_device *bridge)
{
+ struct ocelot_port_private *priv = netdev_priv(dev);
+ struct ocelot_port *ocelot_port = &priv->port;
+ struct ocelot *ocelot = ocelot_port->ocelot;
struct switchdev_brport_flags flags;
+ int port = priv->chip_port;
int err;
flags.mask = BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
@@ -1146,7 +1155,8 @@ static int ocelot_netdevice_bridge_leave(struct ocelot *ocelot, int port,
}
static int ocelot_netdevice_changeupper(struct net_device *dev,
- struct netdev_notifier_changeupper_info *info)
+ struct netdev_notifier_changeupper_info *info,
+ bool notify)
{
struct ocelot_port_private *priv = netdev_priv(dev);
struct ocelot_port *ocelot_port = &priv->port;
@@ -1156,11 +1166,11 @@ static int ocelot_netdevice_changeupper(struct net_device *dev,
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking) {
- err = ocelot_netdevice_bridge_join(ocelot, port,
- info->upper_dev);
+ err = ocelot_netdevice_bridge_join(dev, info->upper_dev);
+ if (!err && notify)
+ switchdev_bridge_port_offload_notify(dev);
} else {
- err = ocelot_netdevice_bridge_leave(ocelot, port,
- info->upper_dev);
+ err = ocelot_netdevice_bridge_leave(dev, info->upper_dev);
}
}
if (netif_is_lag_master(info->upper_dev)) {
@@ -1182,6 +1192,12 @@ static int ocelot_netdevice_changeupper(struct net_device *dev,
return notifier_from_errno(err);
}
+/* Treat CHANGEUPPER events on an offloaded LAG as individual CHANGEUPPER
+ * events for the lower physical ports of the LAG.
+ * If the LAG upper isn't offloaded, ignore its CHANGEUPPER events.
+ * In case the LAG joined a bridge, notify that we are offloading it and can do
+ * forwarding in hardware towards it.
+ */
static int
ocelot_netdevice_lag_changeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
@@ -1191,11 +1207,20 @@ ocelot_netdevice_lag_changeupper(struct net_device *dev,
int err = NOTIFY_DONE;
netdev_for_each_lower_dev(dev, lower, iter) {
- err = ocelot_netdevice_changeupper(lower, info);
+ struct ocelot_port_private *priv = netdev_priv(lower);
+ struct ocelot_port *ocelot_port = &priv->port;
+
+ if (ocelot_port->bond != dev)
+ return NOTIFY_OK;
+
+ err = ocelot_netdevice_changeupper(lower, info, false);
if (err)
return notifier_from_errno(err);
}
+ if (info->linking && netif_is_bridge_master(info->upper_dev))
+ switchdev_bridge_port_offload_notify(dev);
+
return NOTIFY_DONE;
}
@@ -1230,7 +1255,7 @@ static int ocelot_netdevice_event(struct notifier_block *unused,
struct netdev_notifier_changeupper_info *info = ptr;
if (ocelot_netdevice_dev_check(dev))
- return ocelot_netdevice_changeupper(dev, info);
+ return ocelot_netdevice_changeupper(dev, info, true);
if (netif_is_lag_master(dev))
return ocelot_netdevice_lag_changeupper(dev, info);
@@ -2592,6 +2592,8 @@ static int ofdpa_port_bridge_join(struct ofdpa_port *ofdpa_port,
ofdpa_port->bridge_dev = bridge;
+ switchdev_bridge_port_offload_notify(ofdpa_port->dev);
+
return ofdpa_port_vlan_add(ofdpa_port, OFDPA_UNTAGGED_VID, 0);
}
@@ -27,6 +27,7 @@
#include <linux/sys_soc.h>
#include <linux/dma/ti-cppi5.h>
#include <linux/dma/k3-udma-glue.h>
+#include <net/switchdev.h>
#include "cpsw_ale.h"
#include "cpsw_sl.h"
@@ -2096,6 +2097,7 @@ static int am65_cpsw_netdevice_port_link(struct net_device *ndev, struct net_dev
common->br_members |= BIT(priv->port->port_id);
am65_cpsw_port_offload_fwd_mark_update(common);
+ switchdev_bridge_port_offload_notify(ndev);
return NOTIFY_DONE;
}
@@ -1522,6 +1522,7 @@ static int cpsw_netdevice_port_link(struct net_device *ndev,
cpsw->br_members |= BIT(priv->emac_port);
cpsw_port_offload_fwd_mark_update(cpsw);
+ switchdev_bridge_port_offload_notify(ndev);
return NOTIFY_DONE;
}
@@ -1237,6 +1237,8 @@ static int dpaa2_switch_port_bridge_join(struct net_device *netdev,
if (!err)
port_priv->bridge_dev = upper_dev;
+ switchdev_bridge_port_offload_notify(netdev);
+
return err;
}
@@ -197,6 +197,8 @@ enum switchdev_notifier_type {
SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE,
SWITCHDEV_VXLAN_FDB_DEL_TO_DEVICE,
SWITCHDEV_VXLAN_FDB_OFFLOADED,
+
+ SWITCHDEV_BRPORT_OFFLOADED,
};
struct switchdev_notifier_info {
@@ -204,6 +206,11 @@ struct switchdev_notifier_info {
struct netlink_ext_ack *extack;
};
+struct switchdev_notifier_brport_info {
+ struct switchdev_notifier_info info; /* must be first */
+ struct netdev_phys_item_id ppid;
+};
+
struct switchdev_notifier_fdb_info {
struct switchdev_notifier_info info; /* must be first */
struct list_head list;
@@ -284,6 +291,9 @@ int switchdev_handle_port_attr_set(struct net_device *dev,
int (*set_cb)(struct net_device *dev,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack));
+
+int switchdev_bridge_port_offload_notify(struct net_device *dev);
+
#else
static inline void switchdev_deferred_process(void)
@@ -380,6 +390,12 @@ switchdev_handle_port_attr_set(struct net_device *dev,
{
return 0;
}
+
+static inline int switchdev_bridge_port_offload_notify(struct net_device *dev)
+{
+ return 0;
+}
+
#endif
#endif /* _LINUX_SWITCHDEV_H_ */
@@ -151,9 +151,10 @@ static int br_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_brport_info *brport_info;
+ struct switchdev_notifier_fdb_info *fdb_info;
struct net_bridge_port *p;
struct net_bridge *br;
- struct switchdev_notifier_fdb_info *fdb_info;
int err = NOTIFY_DONE;
p = br_port_get_rtnl_rcu(dev);
@@ -191,6 +192,11 @@ static int br_switchdev_event(struct notifier_block *unused,
/* Don't delete static entries */
br_fdb_delete_by_port(br, p, fdb_info->vid, 0);
break;
+ case SWITCHDEV_BRPORT_OFFLOADED:
+ brport_info = ptr;
+ p->ppid = brport_info->ppid;
+ p->offloaded = true;
+ break;
}
out:
@@ -643,9 +643,7 @@ 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;
+ nbp_switchdev_mark_set(p);
dev_disable_lro(dev);
@@ -671,13 +669,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 +698,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;
@@ -328,6 +328,8 @@ struct net_bridge_port {
#endif
#ifdef CONFIG_NET_SWITCHDEV
int offload_fwd_mark;
+ bool offloaded;
+ struct netdev_phys_item_id ppid;
#endif
u16 group_fwd_mask;
u16 backup_redirected_cnt;
@@ -1572,7 +1574,7 @@ 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_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,9 +1594,8 @@ 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)
+static inline void nbp_switchdev_mark_set(struct net_bridge_port *p)
{
- return 0;
}
static inline void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
@@ -8,36 +8,29 @@
#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->offloaded)
+ 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)
+void nbp_switchdev_mark_set(struct net_bridge_port *p)
{
- struct netdev_phys_item_id ppid = { };
- int err;
-
- ASSERT_RTNL();
+ if (!p->offloaded)
+ return;
- err = dev_get_port_parent_id(p->dev, &ppid, true);
- if (err) {
- if (err == -EOPNOTSUPP)
- return 0;
- return err;
- }
-
- p->offload_fwd_mark = br_switchdev_mark_get(p->br, p->dev);
-
- return 0;
+ p->offload_fwd_mark = br_switchdev_mark_get(p->br, p);
}
void nbp_switchdev_frame_mark(const struct net_bridge_port *p,
@@ -2295,7 +2295,8 @@ static struct notifier_block dsa_slave_switchdev_notifier;
static struct notifier_block dsa_slave_switchdev_blocking_notifier;
static int dsa_slave_changeupper(struct net_device *dev,
- struct netdev_notifier_changeupper_info *info)
+ struct netdev_notifier_changeupper_info *info,
+ bool notify)
{
struct dsa_port *dp = dsa_slave_to_port(dev);
int err = NOTIFY_DONE;
@@ -2305,6 +2306,8 @@ static int dsa_slave_changeupper(struct net_device *dev,
if (info->linking) {
err = dsa_port_bridge_join(dp, bridge_dev);
+ if (!err && notify)
+ switchdev_bridge_port_offload_notify(dev);
if (!err) {
dsa_bridge_mtu_normalization(dp);
br_fdb_replay(bridge_dev, dev,
@@ -2364,22 +2367,23 @@ dsa_slave_lag_changeupper(struct net_device *dev,
dp = dsa_slave_to_port(lower);
if (!dp->lag_dev)
- /* Software LAG */
- continue;
+ /* Software LAG, ignore all its CHANGEUPPER events */
+ return NOTIFY_DONE;
- err = dsa_slave_changeupper(lower, info);
+ err = dsa_slave_changeupper(lower, info, false);
if (notifier_to_errno(err))
- break;
+ return err;
}
- if (netif_is_bridge_master(info->upper_dev) && !err) {
+ if (info->linking && netif_is_bridge_master(info->upper_dev)) {
+ switchdev_bridge_port_offload_notify(dev);
br_fdb_replay(info->upper_dev, dev,
&dsa_slave_switchdev_notifier);
br_mdb_replay(info->upper_dev, dev,
&dsa_slave_switchdev_blocking_notifier);
}
- return err;
+ return 0;
}
static int
@@ -2475,7 +2479,7 @@ static int dsa_slave_netdevice_event(struct notifier_block *nb,
}
case NETDEV_CHANGEUPPER:
if (dsa_slave_dev_check(dev))
- return dsa_slave_changeupper(dev, ptr);
+ return dsa_slave_changeupper(dev, ptr, true);
if (netif_is_lag_master(dev))
return dsa_slave_lag_changeupper(dev, ptr);
@@ -546,3 +546,21 @@ int switchdev_handle_port_attr_set(struct net_device *dev,
return err;
}
EXPORT_SYMBOL_GPL(switchdev_handle_port_attr_set);
+
+/* 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_notify(struct net_device *dev)
+{
+ struct switchdev_notifier_brport_info info;
+ int err;
+
+ err = dev_get_port_parent_id(dev, &info.ppid, true);
+ if (err)
+ return err;
+
+ return call_switchdev_notifiers(SWITCHDEV_BRPORT_OFFLOADED, dev,
+ &info.info, NULL);
+}
+EXPORT_SYMBOL(switchdev_bridge_port_offload_notify);