summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c')
-rw-r--r--drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c267
1 files changed, 202 insertions, 65 deletions
diff --git a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
index d88e62bc759f..a48bf342084d 100644
--- a/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
+++ b/drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
@@ -61,7 +61,7 @@ struct mlxsw_sp_bridge_port {
struct mlxsw_sp_bridge_device *bridge_device;
struct list_head list;
struct list_head vlans_list;
- unsigned int ref_count;
+ refcount_t ref_count;
u8 stp_state;
unsigned long flags;
bool mrouter;
@@ -384,6 +384,91 @@ mlxsw_sp_bridge_port_find(struct mlxsw_sp_bridge *bridge,
return __mlxsw_sp_bridge_port_find(bridge_device, brport_dev);
}
+static int mlxsw_sp_port_obj_add(struct net_device *dev, const void *ctx,
+ const struct switchdev_obj *obj,
+ struct netlink_ext_ack *extack);
+static int mlxsw_sp_port_obj_del(struct net_device *dev, const void *ctx,
+ const struct switchdev_obj *obj);
+
+struct mlxsw_sp_bridge_port_replay_switchdev_objs {
+ struct net_device *brport_dev;
+ struct mlxsw_sp_port *mlxsw_sp_port;
+ int done;
+};
+
+static int
+mlxsw_sp_bridge_port_replay_switchdev_objs(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_port_obj_info *port_obj_info = ptr;
+ struct netlink_ext_ack *extack = port_obj_info->info.extack;
+ struct mlxsw_sp_bridge_port_replay_switchdev_objs *rso;
+ int err = 0;
+
+ rso = (void *)port_obj_info->info.ctx;
+
+ if (event != SWITCHDEV_PORT_OBJ_ADD ||
+ dev != rso->brport_dev)
+ goto out;
+
+ /* When a port is joining the bridge through a LAG, there likely are
+ * VLANs configured on that LAG already. The replay will thus attempt to
+ * have the given port-vlans join the corresponding FIDs. But the LAG
+ * netdevice has already called the ndo_vlan_rx_add_vid NDO for its VLAN
+ * memberships, back before CHANGEUPPER was distributed and netdevice
+ * master set. So now before propagating the VLAN events further, we
+ * first need to kill the corresponding VID at the mlxsw_sp_port.
+ *
+ * Note that this doesn't need to be rolled back on failure -- if the
+ * replay fails, the enslavement is off, and the VIDs would be killed by
+ * LAG anyway as part of its rollback.
+ */
+ if (port_obj_info->obj->id == SWITCHDEV_OBJ_ID_PORT_VLAN) {
+ u16 vid = SWITCHDEV_OBJ_PORT_VLAN(port_obj_info->obj)->vid;
+
+ err = mlxsw_sp_port_kill_vid(rso->mlxsw_sp_port->dev, 0, vid);
+ if (err)
+ goto out;
+ }
+
+ ++rso->done;
+ err = mlxsw_sp_port_obj_add(rso->mlxsw_sp_port->dev, NULL,
+ port_obj_info->obj, extack);
+
+out:
+ return notifier_from_errno(err);
+}
+
+static struct notifier_block mlxsw_sp_bridge_port_replay_switchdev_objs_nb = {
+ .notifier_call = mlxsw_sp_bridge_port_replay_switchdev_objs,
+};
+
+static int
+mlxsw_sp_bridge_port_unreplay_switchdev_objs(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
+ struct switchdev_notifier_port_obj_info *port_obj_info = ptr;
+ struct mlxsw_sp_bridge_port_replay_switchdev_objs *rso;
+
+ rso = (void *)port_obj_info->info.ctx;
+
+ if (event != SWITCHDEV_PORT_OBJ_ADD ||
+ dev != rso->brport_dev)
+ return NOTIFY_DONE;
+ if (!rso->done--)
+ return NOTIFY_STOP;
+
+ mlxsw_sp_port_obj_del(rso->mlxsw_sp_port->dev, NULL,
+ port_obj_info->obj);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block mlxsw_sp_bridge_port_unreplay_switchdev_objs_nb = {
+ .notifier_call = mlxsw_sp_bridge_port_unreplay_switchdev_objs,
+};
+
static struct mlxsw_sp_bridge_port *
mlxsw_sp_bridge_port_create(struct mlxsw_sp_bridge_device *bridge_device,
struct net_device *brport_dev,
@@ -405,12 +490,12 @@ mlxsw_sp_bridge_port_create(struct mlxsw_sp_bridge_device *bridge_device,
bridge_port->system_port = mlxsw_sp_port->local_port;
bridge_port->dev = brport_dev;
bridge_port->bridge_device = bridge_device;
- bridge_port->stp_state = BR_STATE_DISABLED;
+ bridge_port->stp_state = br_port_get_stp_state(brport_dev);
bridge_port->flags = BR_LEARNING | BR_FLOOD | BR_LEARNING_SYNC |
BR_MCAST_FLOOD;
INIT_LIST_HEAD(&bridge_port->vlans_list);
list_add(&bridge_port->list, &bridge_device->ports_list);
- bridge_port->ref_count = 1;
+ refcount_set(&bridge_port->ref_count, 1);
err = switchdev_bridge_port_offload(brport_dev, mlxsw_sp_port->dev,
NULL, NULL, NULL, false, extack);
@@ -446,7 +531,7 @@ mlxsw_sp_bridge_port_get(struct mlxsw_sp_bridge *bridge,
bridge_port = mlxsw_sp_bridge_port_find(bridge, brport_dev);
if (bridge_port) {
- bridge_port->ref_count++;
+ refcount_inc(&bridge_port->ref_count);
return bridge_port;
}
@@ -473,7 +558,7 @@ static void mlxsw_sp_bridge_port_put(struct mlxsw_sp_bridge *bridge,
{
struct mlxsw_sp_bridge_device *bridge_device;
- if (--bridge_port->ref_count != 0)
+ if (!refcount_dec_and_test(&bridge_port->ref_count))
return;
bridge_device = bridge_port->bridge_device;
mlxsw_sp_bridge_port_destroy(bridge_port);
@@ -1479,29 +1564,15 @@ err_port_vlan_set:
}
static int
-mlxsw_sp_br_ban_rif_pvid_change(struct mlxsw_sp *mlxsw_sp,
- const struct net_device *br_dev,
- const struct switchdev_obj_port_vlan *vlan)
+mlxsw_sp_br_rif_pvid_change(struct mlxsw_sp *mlxsw_sp,
+ struct net_device *br_dev,
+ const struct switchdev_obj_port_vlan *vlan,
+ struct netlink_ext_ack *extack)
{
- u16 pvid;
-
- pvid = mlxsw_sp_rif_vid(mlxsw_sp, br_dev);
- if (!pvid)
- return 0;
-
- if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
- if (vlan->vid != pvid) {
- netdev_err(br_dev, "Can't change PVID, it's used by router interface\n");
- return -EBUSY;
- }
- } else {
- if (vlan->vid == pvid) {
- netdev_err(br_dev, "Can't remove PVID, it's used by router interface\n");
- return -EBUSY;
- }
- }
+ bool flag_pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
- return 0;
+ return mlxsw_sp_router_bridge_vlan_add(mlxsw_sp, br_dev, vlan->vid,
+ flag_pvid, extack);
}
static int mlxsw_sp_port_vlans_add(struct mlxsw_sp_port *mlxsw_sp_port,
@@ -1518,8 +1589,8 @@ static int mlxsw_sp_port_vlans_add(struct mlxsw_sp_port *mlxsw_sp_port,
int err = 0;
if (br_vlan_enabled(orig_dev))
- err = mlxsw_sp_br_ban_rif_pvid_change(mlxsw_sp,
- orig_dev, vlan);
+ err = mlxsw_sp_br_rif_pvid_change(mlxsw_sp, orig_dev,
+ vlan, extack);
if (!err)
err = -EOPNOTSUPP;
return err;
@@ -2365,6 +2436,33 @@ static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp,
}
static int
+mlxsw_sp_bridge_port_replay(struct mlxsw_sp_bridge_port *bridge_port,
+ struct mlxsw_sp_port *mlxsw_sp_port,
+ struct netlink_ext_ack *extack)
+{
+ struct mlxsw_sp_bridge_port_replay_switchdev_objs rso = {
+ .brport_dev = bridge_port->dev,
+ .mlxsw_sp_port = mlxsw_sp_port,
+ };
+ struct notifier_block *nb;
+ int err;
+
+ nb = &mlxsw_sp_bridge_port_replay_switchdev_objs_nb;
+ err = switchdev_bridge_port_replay(bridge_port->dev, mlxsw_sp_port->dev,
+ &rso, NULL, nb, extack);
+ if (err)
+ goto err_replay;
+
+ return 0;
+
+err_replay:
+ nb = &mlxsw_sp_bridge_port_unreplay_switchdev_objs_nb;
+ switchdev_bridge_port_replay(bridge_port->dev, mlxsw_sp_port->dev,
+ &rso, NULL, nb, extack);
+ return err;
+}
+
+static int
mlxsw_sp_bridge_vlan_aware_port_join(struct mlxsw_sp_bridge_port *bridge_port,
struct mlxsw_sp_port *mlxsw_sp_port,
struct netlink_ext_ack *extack)
@@ -2378,7 +2476,7 @@ mlxsw_sp_bridge_vlan_aware_port_join(struct mlxsw_sp_bridge_port *bridge_port,
if (mlxsw_sp_port->default_vlan->fid)
mlxsw_sp_port_vlan_router_leave(mlxsw_sp_port->default_vlan);
- return 0;
+ return mlxsw_sp_bridge_port_replay(bridge_port, mlxsw_sp_port, extack);
}
static int
@@ -2550,6 +2648,7 @@ mlxsw_sp_bridge_8021d_port_join(struct mlxsw_sp_bridge_device *bridge_device,
struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan;
struct net_device *dev = bridge_port->dev;
u16 vid;
+ int err;
vid = is_vlan_dev(dev) ? vlan_dev_vlan_id(dev) : MLXSW_SP_DEFAULT_VID;
mlxsw_sp_port_vlan = mlxsw_sp_port_vlan_find_by_vid(mlxsw_sp_port, vid);
@@ -2565,8 +2664,20 @@ mlxsw_sp_bridge_8021d_port_join(struct mlxsw_sp_bridge_device *bridge_device,
if (mlxsw_sp_port_vlan->fid)
mlxsw_sp_port_vlan_router_leave(mlxsw_sp_port_vlan);
- return mlxsw_sp_port_vlan_bridge_join(mlxsw_sp_port_vlan, bridge_port,
- extack);
+ err = mlxsw_sp_port_vlan_bridge_join(mlxsw_sp_port_vlan, bridge_port,
+ extack);
+ if (err)
+ return err;
+
+ err = mlxsw_sp_bridge_port_replay(bridge_port, mlxsw_sp_port, extack);
+ if (err)
+ goto err_replay;
+
+ return 0;
+
+err_replay:
+ mlxsw_sp_port_vlan_bridge_leave(mlxsw_sp_port_vlan);
+ return err;
}
static void
@@ -2783,8 +2894,15 @@ int mlxsw_sp_port_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
if (err)
goto err_port_join;
+ err = mlxsw_sp_netdevice_enslavement_replay(mlxsw_sp, br_dev, extack);
+ if (err)
+ goto err_replay;
+
return 0;
+err_replay:
+ bridge_device->ops->port_leave(bridge_device, bridge_port,
+ mlxsw_sp_port);
err_port_join:
mlxsw_sp_bridge_port_put(mlxsw_sp->bridge, bridge_port);
return err;
@@ -2811,23 +2929,8 @@ void mlxsw_sp_port_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port,
mlxsw_sp_bridge_port_put(mlxsw_sp->bridge, bridge_port);
}
-int mlxsw_sp_bridge_vxlan_join(struct mlxsw_sp *mlxsw_sp,
- const struct net_device *br_dev,
- const struct net_device *vxlan_dev, u16 vid,
- struct netlink_ext_ack *extack)
-{
- struct mlxsw_sp_bridge_device *bridge_device;
-
- bridge_device = mlxsw_sp_bridge_device_find(mlxsw_sp->bridge, br_dev);
- if (WARN_ON(!bridge_device))
- return -EINVAL;
-
- return bridge_device->ops->vxlan_join(bridge_device, vxlan_dev, vid,
- extack);
-}
-
-void mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
- const struct net_device *vxlan_dev)
+static void __mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
+ const struct net_device *vxlan_dev)
{
struct vxlan_dev *vxlan = netdev_priv(vxlan_dev);
struct mlxsw_sp_fid *fid;
@@ -2845,6 +2948,47 @@ void mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
mlxsw_sp_fid_put(fid);
}
+int mlxsw_sp_bridge_vxlan_join(struct mlxsw_sp *mlxsw_sp,
+ const struct net_device *br_dev,
+ struct net_device *vxlan_dev, u16 vid,
+ struct netlink_ext_ack *extack)
+{
+ struct mlxsw_sp_bridge_device *bridge_device;
+ struct mlxsw_sp_port *mlxsw_sp_port;
+ int err;
+
+ bridge_device = mlxsw_sp_bridge_device_find(mlxsw_sp->bridge, br_dev);
+ if (WARN_ON(!bridge_device))
+ return -EINVAL;
+
+ mlxsw_sp_port = mlxsw_sp_port_dev_lower_find(bridge_device->dev);
+ if (!mlxsw_sp_port)
+ return -EINVAL;
+
+ err = bridge_device->ops->vxlan_join(bridge_device, vxlan_dev, vid,
+ extack);
+ if (err)
+ return err;
+
+ err = switchdev_bridge_port_offload(vxlan_dev, mlxsw_sp_port->dev,
+ NULL, NULL, NULL, false, extack);
+ if (err)
+ goto err_bridge_port_offload;
+
+ return 0;
+
+err_bridge_port_offload:
+ __mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
+ return err;
+}
+
+void mlxsw_sp_bridge_vxlan_leave(struct mlxsw_sp *mlxsw_sp,
+ struct net_device *vxlan_dev)
+{
+ switchdev_bridge_port_unoffload(vxlan_dev, NULL, NULL, NULL);
+ __mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
+}
+
static void
mlxsw_sp_switchdev_vxlan_addr_convert(const union vxlan_addr *vxlan_addr,
enum mlxsw_sp_l3proto *proto,
@@ -2948,9 +3092,6 @@ static void mlxsw_sp_fdb_notify_mac_process(struct mlxsw_sp *mlxsw_sp,
goto just_remove;
}
- if (mlxsw_sp_fid_is_dummy(mlxsw_sp, fid))
- goto just_remove;
-
mlxsw_sp_port_vlan = mlxsw_sp_port_vlan_find_by_fid(mlxsw_sp_port, fid);
if (!mlxsw_sp_port_vlan) {
netdev_err(mlxsw_sp_port->dev, "Failed to find a matching {Port, VID} following FDB notification\n");
@@ -3018,9 +3159,6 @@ static void mlxsw_sp_fdb_notify_mac_lag_process(struct mlxsw_sp *mlxsw_sp,
goto just_remove;
}
- if (mlxsw_sp_fid_is_dummy(mlxsw_sp, fid))
- goto just_remove;
-
mlxsw_sp_port_vlan = mlxsw_sp_port_vlan_find_by_fid(mlxsw_sp_port, fid);
if (!mlxsw_sp_port_vlan) {
netdev_err(mlxsw_sp_port->dev, "Failed to find a matching {Port, VID} following FDB notification\n");
@@ -3262,6 +3400,7 @@ out:
struct mlxsw_sp_switchdev_event_work {
struct work_struct work;
+ netdevice_tracker dev_tracker;
union {
struct switchdev_notifier_fdb_info fdb_info;
struct switchdev_notifier_vxlan_fdb_info vxlan_fdb_info;
@@ -3418,8 +3557,8 @@ static void mlxsw_sp_switchdev_bridge_fdb_event_work(struct work_struct *work)
out:
rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
+ netdev_put(dev, &switchdev_work->dev_tracker);
kfree(switchdev_work);
- dev_put(dev);
}
static void
@@ -3430,7 +3569,6 @@ mlxsw_sp_switchdev_vxlan_fdb_add(struct mlxsw_sp *mlxsw_sp,
struct switchdev_notifier_vxlan_fdb_info *vxlan_fdb_info;
struct mlxsw_sp_bridge_device *bridge_device;
struct net_device *dev = switchdev_work->dev;
- u8 all_zeros_mac[ETH_ALEN] = { 0 };
enum mlxsw_sp_l3proto proto;
union mlxsw_sp_l3addr addr;
struct net_device *br_dev;
@@ -3452,7 +3590,7 @@ mlxsw_sp_switchdev_vxlan_fdb_add(struct mlxsw_sp *mlxsw_sp,
mlxsw_sp_switchdev_vxlan_addr_convert(&vxlan_fdb_info->remote_ip,
&proto, &addr);
- if (ether_addr_equal(vxlan_fdb_info->eth_addr, all_zeros_mac)) {
+ if (is_zero_ether_addr(vxlan_fdb_info->eth_addr)) {
err = mlxsw_sp_nve_flood_ip_add(mlxsw_sp, fid, proto, &addr);
if (err) {
mlxsw_sp_fid_put(fid);
@@ -3504,7 +3642,6 @@ mlxsw_sp_switchdev_vxlan_fdb_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_bridge_device *bridge_device;
struct net_device *dev = switchdev_work->dev;
struct net_device *br_dev = netdev_master_upper_dev_get(dev);
- u8 all_zeros_mac[ETH_ALEN] = { 0 };
enum mlxsw_sp_l3proto proto;
union mlxsw_sp_l3addr addr;
struct mlxsw_sp_fid *fid;
@@ -3525,7 +3662,7 @@ mlxsw_sp_switchdev_vxlan_fdb_del(struct mlxsw_sp *mlxsw_sp,
mlxsw_sp_switchdev_vxlan_addr_convert(&vxlan_fdb_info->remote_ip,
&proto, &addr);
- if (ether_addr_equal(vxlan_fdb_info->eth_addr, all_zeros_mac)) {
+ if (is_zero_ether_addr(vxlan_fdb_info->eth_addr)) {
mlxsw_sp_nve_flood_ip_del(mlxsw_sp, fid, proto, &addr);
mlxsw_sp_fid_put(fid);
return;
@@ -3574,8 +3711,8 @@ static void mlxsw_sp_switchdev_vxlan_fdb_event_work(struct work_struct *work)
out:
rtnl_unlock();
+ netdev_put(dev, &switchdev_work->dev_tracker);
kfree(switchdev_work);
- dev_put(dev);
}
static int
@@ -3675,7 +3812,7 @@ static int mlxsw_sp_switchdev_event(struct notifier_block *unused,
* upper device containig mlxsw_sp_port or just a
* mlxsw_sp_port
*/
- dev_hold(dev);
+ netdev_hold(dev, &switchdev_work->dev_tracker, GFP_ATOMIC);
break;
case SWITCHDEV_VXLAN_FDB_ADD_TO_DEVICE:
case SWITCHDEV_VXLAN_FDB_DEL_TO_DEVICE:
@@ -3685,7 +3822,7 @@ static int mlxsw_sp_switchdev_event(struct notifier_block *unused,
info);
if (err)
goto err_vxlan_work_prepare;
- dev_hold(dev);
+ netdev_hold(dev, &switchdev_work->dev_tracker, GFP_ATOMIC);
break;
default:
kfree(switchdev_work);
@@ -3756,7 +3893,7 @@ mlxsw_sp_switchdev_vxlan_vlan_add(struct mlxsw_sp *mlxsw_sp,
mlxsw_sp_fid_put(fid);
return -EINVAL;
}
- mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
+ __mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
mlxsw_sp_fid_put(fid);
return 0;
}
@@ -3772,7 +3909,7 @@ mlxsw_sp_switchdev_vxlan_vlan_add(struct mlxsw_sp *mlxsw_sp,
/* Fourth case: Thew new VLAN is PVID, which means the VLAN currently
* mapped to the VNI should be unmapped
*/
- mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
+ __mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
mlxsw_sp_fid_put(fid);
/* Fifth case: The new VLAN is also egress untagged, which means the
@@ -3812,7 +3949,7 @@ mlxsw_sp_switchdev_vxlan_vlan_del(struct mlxsw_sp *mlxsw_sp,
if (mlxsw_sp_fid_8021q_vid(fid) != vid)
goto out;
- mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
+ __mlxsw_sp_bridge_vxlan_leave(mlxsw_sp, vxlan_dev);
out:
mlxsw_sp_fid_put(fid);