Commit c6514f36 authored by David S. Miller's avatar David S. Miller
Browse files

Merge branch 'mlxsw-enslavement'



Petr Machata says:

====================
mlxsw: Permit enslavement to netdevices with uppers

The mlxsw driver currently makes the assumption that the user applies
configuration in a bottom-up manner. Thus netdevices need to be added to
the bridge before IP addresses are configured on that bridge or SVI added
on top of it. Enslaving a netdevice to another netdevice that already has
uppers is in fact forbidden by mlxsw for this reason. Despite this safety,
it is rather easy to get into situations where the offloaded configuration
is just plain wrong.

As an example, take a front panel port, configure an IP address: it gets a
RIF. Now enslave the port to the bridge, and the RIF is gone. Remove the
port from the bridge again, but the RIF never comes back. There is a number
of similar situations, where changing the configuration there and back
utterly breaks the offload.

Similarly, detaching a front panel port from a configured topology means
unoffloading of this whole topology -- VLAN uppers, next hops, etc.
Attaching the port back is then not permitted at all. If it were, it would
not result in a working configuration, because much of mlxsw is written to
react to changes in immediate configuration. There is nothing that would go
visit netdevices in the attached-to topology and offload existing routes
and VLAN memberships, for example.

In this patchset, introduce a number of replays to be invoked so that this
sort of post-hoc offload is supported. Then remove the vetoes that
disallowed enslavement of front panel ports to other netdevices with
uppers.

The patchset progresses as follows:

- In patch #1, fix an issue in the bridge driver. To my knowledge, the
  issue could not have resulted in a buggy behavior previously, and thus is
  packaged with this patchset instead of being sent separately to net.

- In patch #2, add a new helper to the switchdev code.

- In patch #3, drop mlxsw selftests that will not be relevant after this
  patchset anymore.

- Patches #4, #5, #6, #7 and #8 prepare the codebase for smoother
  introduction of the rest of the code.

- Patches #9, #10, #11, #12, #13 and #14 replay various aspects of upper
  configuration when a front panel port is introduced into a topology.
  Individual patches take care of bridge and LAG RIF memberships, switchdev
  replay, nexthop and neighbors replay, and MACVLAN offload.

- Patches #15 and #16 introduce RIFs for newly-relevant netdevices when a
  front panel port is enslaved (in which case all uppers are newly
  relevant), or, respectively, deslaved (in which case the newly-relevant
  netdevice is the one being deslaved).

- Up until this point, the introduced scaffolding was not really used,
  because mlxsw still forbids enslavement of mlxsw netdevices to uppers
  with uppers. In patch #17, this condition is finally relaxed.

A sizable selftest suite is available to test all this new code. That will
be sent in a separate patchset.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents a5dc694e 2c5ffe8d
Loading
Loading
Loading
Loading
+251 −61
Original line number Diff line number Diff line
@@ -1132,7 +1132,7 @@ static int mlxsw_sp_port_add_vid(struct net_device *dev,
	return PTR_ERR_OR_ZERO(mlxsw_sp_port_vlan_create(mlxsw_sp_port, vid));
}

static int mlxsw_sp_port_kill_vid(struct net_device *dev,
int mlxsw_sp_port_kill_vid(struct net_device *dev,
			   __be16 __always_unused proto, u16 vid)
{
	struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
@@ -4337,6 +4337,88 @@ static int mlxsw_sp_port_lag_index_get(struct mlxsw_sp *mlxsw_sp,
	return -EBUSY;
}

static int mlxsw_sp_lag_uppers_bridge_join(struct mlxsw_sp_port *mlxsw_sp_port,
					   struct net_device *lag_dev,
					   struct netlink_ext_ack *extack)
{
	struct net_device *upper_dev;
	struct net_device *master;
	struct list_head *iter;
	int done = 0;
	int err;

	master = netdev_master_upper_dev_get(lag_dev);
	if (master && netif_is_bridge_master(master)) {
		err = mlxsw_sp_port_bridge_join(mlxsw_sp_port, lag_dev, master,
						extack);
		if (err)
			return err;
	}

	netdev_for_each_upper_dev_rcu(lag_dev, upper_dev, iter) {
		if (!is_vlan_dev(upper_dev))
			continue;

		master = netdev_master_upper_dev_get(upper_dev);
		if (master && netif_is_bridge_master(master)) {
			err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
							upper_dev, master,
							extack);
			if (err)
				goto err_port_bridge_join;
		}

		++done;
	}

	return 0;

err_port_bridge_join:
	netdev_for_each_upper_dev_rcu(lag_dev, upper_dev, iter) {
		if (!is_vlan_dev(upper_dev))
			continue;

		master = netdev_master_upper_dev_get(upper_dev);
		if (!master || !netif_is_bridge_master(master))
			continue;

		if (!done--)
			break;

		mlxsw_sp_port_bridge_leave(mlxsw_sp_port, upper_dev, master);
	}

	master = netdev_master_upper_dev_get(lag_dev);
	if (master && netif_is_bridge_master(master))
		mlxsw_sp_port_bridge_leave(mlxsw_sp_port, lag_dev, master);

	return err;
}

static void
mlxsw_sp_lag_uppers_bridge_leave(struct mlxsw_sp_port *mlxsw_sp_port,
				 struct net_device *lag_dev)
{
	struct net_device *upper_dev;
	struct net_device *master;
	struct list_head *iter;

	netdev_for_each_upper_dev_rcu(lag_dev, upper_dev, iter) {
		if (!is_vlan_dev(upper_dev))
			continue;

		master = netdev_master_upper_dev_get(upper_dev);
		if (!master)
			continue;

		mlxsw_sp_port_bridge_leave(mlxsw_sp_port, upper_dev, master);
	}

	master = netdev_master_upper_dev_get(lag_dev);
	if (master)
		mlxsw_sp_port_bridge_leave(mlxsw_sp_port, lag_dev, master);
}

static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
				  struct net_device *lag_dev,
				  struct netlink_ext_ack *extack)
@@ -4361,6 +4443,12 @@ static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
	err = mlxsw_sp_port_lag_index_get(mlxsw_sp, lag_id, &port_index);
	if (err)
		return err;

	err = mlxsw_sp_lag_uppers_bridge_join(mlxsw_sp_port, lag_dev,
					      extack);
	if (err)
		goto err_lag_uppers_bridge_join;

	err = mlxsw_sp_lag_col_port_add(mlxsw_sp_port, lag_id, port_index);
	if (err)
		goto err_col_port_add;
@@ -4381,8 +4469,14 @@ static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
	if (err)
		goto err_router_join;

	err = mlxsw_sp_netdevice_enslavement_replay(mlxsw_sp, lag_dev, extack);
	if (err)
		goto err_replay;

	return 0;

err_replay:
	mlxsw_sp_router_port_leave_lag(mlxsw_sp_port, lag_dev);
err_router_join:
	lag->ref_count--;
	mlxsw_sp_port->lagged = 0;
@@ -4390,6 +4484,8 @@ static int mlxsw_sp_port_lag_join(struct mlxsw_sp_port *mlxsw_sp_port,
				     mlxsw_sp_port->local_port);
	mlxsw_sp_lag_col_port_remove(mlxsw_sp_port, lag_id);
err_col_port_add:
	mlxsw_sp_lag_uppers_bridge_leave(mlxsw_sp_port, lag_dev);
err_lag_uppers_bridge_join:
	if (!lag->ref_count)
		mlxsw_sp_lag_destroy(mlxsw_sp, lag_id);
	return err;
@@ -4639,9 +4735,62 @@ static bool mlxsw_sp_bridge_vxlan_is_valid(struct net_device *br_dev,
	return true;
}

static bool mlxsw_sp_netdev_is_master(struct net_device *upper_dev,
				      struct net_device *dev)
{
	return upper_dev == netdev_master_upper_dev_get(dev);
}

static int __mlxsw_sp_netdevice_event(struct mlxsw_sp *mlxsw_sp,
				      unsigned long event, void *ptr,
				      bool process_foreign);

static int mlxsw_sp_netdevice_validate_uppers(struct mlxsw_sp *mlxsw_sp,
					      struct net_device *dev,
					      struct netlink_ext_ack *extack)
{
	struct net_device *upper_dev;
	struct list_head *iter;
	int err;

	netdev_for_each_upper_dev_rcu(dev, upper_dev, iter) {
		struct netdev_notifier_changeupper_info info = {
			.info = {
				.dev = dev,
				.extack = extack,
			},
			.master = mlxsw_sp_netdev_is_master(upper_dev, dev),
			.upper_dev = upper_dev,
			.linking = true,

			/* upper_info is relevant for LAG devices. But we would
			 * only need this if LAG were a valid upper above
			 * another upper (e.g. a bridge that is a member of a
			 * LAG), and that is never a valid configuration. So we
			 * can keep this as NULL.
			 */
			.upper_info = NULL,
		};

		err = __mlxsw_sp_netdevice_event(mlxsw_sp,
						 NETDEV_PRECHANGEUPPER,
						 &info, true);
		if (err)
			return err;

		err = mlxsw_sp_netdevice_validate_uppers(mlxsw_sp, upper_dev,
							 extack);
		if (err)
			return err;
	}

	return 0;
}

static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
					       struct net_device *dev,
					       unsigned long event, void *ptr)
					       unsigned long event, void *ptr,
					       bool replay_deslavement)
{
	struct netdev_notifier_changeupper_info *info;
	struct mlxsw_sp_port *mlxsw_sp_port;
@@ -4679,8 +4828,11 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
		    (!netif_is_bridge_master(upper_dev) ||
		     !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp,
							  upper_dev))) {
			NL_SET_ERR_MSG_MOD(extack, "Enslaving a port to a device that already has an upper device is not supported");
			return -EINVAL;
			err = mlxsw_sp_netdevice_validate_uppers(mlxsw_sp,
								 upper_dev,
								 extack);
			if (err)
				return err;
		}
		if (netif_is_lag_master(upper_dev) &&
		    !mlxsw_sp_master_lag_check(mlxsw_sp, upper_dev,
@@ -4695,11 +4847,6 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
			NL_SET_ERR_MSG_MOD(extack, "Can not put a VLAN on a LAG port");
			return -EINVAL;
		}
		if (netif_is_macvlan(upper_dev) &&
		    !mlxsw_sp_rif_exists(mlxsw_sp, lower_dev)) {
			NL_SET_ERR_MSG_MOD(extack, "macvlan is only supported on top of router interfaces");
			return -EOPNOTSUPP;
		}
		if (netif_is_ovs_master(upper_dev) && vlan_uses_dev(dev)) {
			NL_SET_ERR_MSG_MOD(extack, "Master device is an OVS master and this device has a VLAN");
			return -EINVAL;
@@ -4746,15 +4893,20 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
	case NETDEV_CHANGEUPPER:
		upper_dev = info->upper_dev;
		if (netif_is_bridge_master(upper_dev)) {
			if (info->linking)
			if (info->linking) {
				err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
								lower_dev,
								upper_dev,
								extack);
			else
			} else {
				mlxsw_sp_port_bridge_leave(mlxsw_sp_port,
							   lower_dev,
							   upper_dev);
				if (!replay_deslavement)
					break;
				mlxsw_sp_netdevice_deslavement_replay(mlxsw_sp,
								      lower_dev);
			}
		} else if (netif_is_lag_master(upper_dev)) {
			if (info->linking) {
				err = mlxsw_sp_port_lag_join(mlxsw_sp_port,
@@ -4763,6 +4915,8 @@ static int mlxsw_sp_netdevice_port_upper_event(struct net_device *lower_dev,
				mlxsw_sp_port_lag_col_dist_disable(mlxsw_sp_port);
				mlxsw_sp_port_lag_leave(mlxsw_sp_port,
							upper_dev);
				mlxsw_sp_netdevice_deslavement_replay(mlxsw_sp,
								      dev);
			}
		} else if (netif_is_ovs_master(upper_dev)) {
			if (info->linking)
@@ -4815,13 +4969,15 @@ static int mlxsw_sp_netdevice_port_lower_event(struct net_device *dev,

static int mlxsw_sp_netdevice_port_event(struct net_device *lower_dev,
					 struct net_device *port_dev,
					 unsigned long event, void *ptr)
					 unsigned long event, void *ptr,
					 bool replay_deslavement)
{
	switch (event) {
	case NETDEV_PRECHANGEUPPER:
	case NETDEV_CHANGEUPPER:
		return mlxsw_sp_netdevice_port_upper_event(lower_dev, port_dev,
							   event, ptr);
							   event, ptr,
							   replay_deslavement);
	case NETDEV_CHANGELOWERSTATE:
		return mlxsw_sp_netdevice_port_lower_event(port_dev, event,
							   ptr);
@@ -4830,6 +4986,30 @@ static int mlxsw_sp_netdevice_port_event(struct net_device *lower_dev,
	return 0;
}

/* Called for LAG or its upper VLAN after the per-LAG-lower processing was done,
 * to do any per-LAG / per-LAG-upper processing.
 */
static int mlxsw_sp_netdevice_post_lag_event(struct net_device *dev,
					     unsigned long event,
					     void *ptr)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(dev);
	struct netdev_notifier_changeupper_info *info = ptr;

	if (!mlxsw_sp)
		return 0;

	switch (event) {
	case NETDEV_CHANGEUPPER:
		if (info->linking)
			break;
		if (netif_is_bridge_master(info->upper_dev))
			mlxsw_sp_netdevice_deslavement_replay(mlxsw_sp, dev);
		break;
	}
	return 0;
}

static int mlxsw_sp_netdevice_lag_event(struct net_device *lag_dev,
					unsigned long event, void *ptr)
{
@@ -4840,19 +5020,19 @@ static int mlxsw_sp_netdevice_lag_event(struct net_device *lag_dev,
	netdev_for_each_lower_dev(lag_dev, dev, iter) {
		if (mlxsw_sp_port_dev_check(dev)) {
			ret = mlxsw_sp_netdevice_port_event(lag_dev, dev, event,
							    ptr);
							    ptr, false);
			if (ret)
				return ret;
		}
	}

	return 0;
	return mlxsw_sp_netdevice_post_lag_event(lag_dev, event, ptr);
}

static int mlxsw_sp_netdevice_port_vlan_event(struct net_device *vlan_dev,
					      struct net_device *dev,
					      unsigned long event, void *ptr,
					      u16 vid)
					      u16 vid, bool replay_deslavement)
{
	struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
@@ -4883,27 +5063,30 @@ static int mlxsw_sp_netdevice_port_vlan_event(struct net_device *vlan_dev,
		    (!netif_is_bridge_master(upper_dev) ||
		     !mlxsw_sp_bridge_device_is_offloaded(mlxsw_sp,
							  upper_dev))) {
			NL_SET_ERR_MSG_MOD(extack, "Enslaving a port to a device that already has an upper device is not supported");
			return -EINVAL;
		}
		if (netif_is_macvlan(upper_dev) &&
		    !mlxsw_sp_rif_exists(mlxsw_sp, vlan_dev)) {
			NL_SET_ERR_MSG_MOD(extack, "macvlan is only supported on top of router interfaces");
			return -EOPNOTSUPP;
			err = mlxsw_sp_netdevice_validate_uppers(mlxsw_sp,
								 upper_dev,
								 extack);
			if (err)
				return err;
		}
		break;
	case NETDEV_CHANGEUPPER:
		upper_dev = info->upper_dev;
		if (netif_is_bridge_master(upper_dev)) {
			if (info->linking)
			if (info->linking) {
				err = mlxsw_sp_port_bridge_join(mlxsw_sp_port,
								vlan_dev,
								upper_dev,
								extack);
			else
			} else {
				mlxsw_sp_port_bridge_leave(mlxsw_sp_port,
							   vlan_dev,
							   upper_dev);
				if (!replay_deslavement)
					break;
				mlxsw_sp_netdevice_deslavement_replay(mlxsw_sp,
								      vlan_dev);
			}
		} else if (netif_is_macvlan(upper_dev)) {
			if (!info->linking)
				mlxsw_sp_rif_macvlan_del(mlxsw_sp, upper_dev);
@@ -4927,26 +5110,26 @@ static int mlxsw_sp_netdevice_lag_port_vlan_event(struct net_device *vlan_dev,
		if (mlxsw_sp_port_dev_check(dev)) {
			ret = mlxsw_sp_netdevice_port_vlan_event(vlan_dev, dev,
								 event, ptr,
								 vid);
								 vid, false);
			if (ret)
				return ret;
		}
	}

	return 0;
	return mlxsw_sp_netdevice_post_lag_event(vlan_dev, event, ptr);
}

static int mlxsw_sp_netdevice_bridge_vlan_event(struct net_device *vlan_dev,
static int mlxsw_sp_netdevice_bridge_vlan_event(struct mlxsw_sp *mlxsw_sp,
						struct net_device *vlan_dev,
						struct net_device *br_dev,
						unsigned long event, void *ptr,
						u16 vid)
						u16 vid, bool process_foreign)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(vlan_dev);
	struct netdev_notifier_changeupper_info *info = ptr;
	struct netlink_ext_ack *extack;
	struct net_device *upper_dev;

	if (!mlxsw_sp)
	if (!process_foreign && !mlxsw_sp_lower_get(vlan_dev))
		return 0;

	extack = netdev_notifier_info_to_extack(&info->info);
@@ -4959,13 +5142,6 @@ static int mlxsw_sp_netdevice_bridge_vlan_event(struct net_device *vlan_dev,
			NL_SET_ERR_MSG_MOD(extack, "Unknown upper device type");
			return -EOPNOTSUPP;
		}
		if (!info->linking)
			break;
		if (netif_is_macvlan(upper_dev) &&
		    !mlxsw_sp_rif_exists(mlxsw_sp, vlan_dev)) {
			NL_SET_ERR_MSG_MOD(extack, "macvlan is only supported on top of router interfaces");
			return -EOPNOTSUPP;
		}
		break;
	case NETDEV_CHANGEUPPER:
		upper_dev = info->upper_dev;
@@ -4979,36 +5155,42 @@ static int mlxsw_sp_netdevice_bridge_vlan_event(struct net_device *vlan_dev,
	return 0;
}

static int mlxsw_sp_netdevice_vlan_event(struct net_device *vlan_dev,
					 unsigned long event, void *ptr)
static int mlxsw_sp_netdevice_vlan_event(struct mlxsw_sp *mlxsw_sp,
					 struct net_device *vlan_dev,
					 unsigned long event, void *ptr,
					 bool process_foreign)
{
	struct net_device *real_dev = vlan_dev_real_dev(vlan_dev);
	u16 vid = vlan_dev_vlan_id(vlan_dev);

	if (mlxsw_sp_port_dev_check(real_dev))
		return mlxsw_sp_netdevice_port_vlan_event(vlan_dev, real_dev,
							  event, ptr, vid);
							  event, ptr, vid,
							  true);
	else if (netif_is_lag_master(real_dev))
		return mlxsw_sp_netdevice_lag_port_vlan_event(vlan_dev,
							      real_dev, event,
							      ptr, vid);
	else if (netif_is_bridge_master(real_dev))
		return mlxsw_sp_netdevice_bridge_vlan_event(vlan_dev, real_dev,
							    event, ptr, vid);
		return mlxsw_sp_netdevice_bridge_vlan_event(mlxsw_sp, vlan_dev,
							    real_dev, event,
							    ptr, vid,
							    process_foreign);

	return 0;
}

static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
					   unsigned long event, void *ptr)
static int mlxsw_sp_netdevice_bridge_event(struct mlxsw_sp *mlxsw_sp,
					   struct net_device *br_dev,
					   unsigned long event, void *ptr,
					   bool process_foreign)
{
	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_lower_get(br_dev);
	struct netdev_notifier_changeupper_info *info = ptr;
	struct netlink_ext_ack *extack;
	struct net_device *upper_dev;
	u16 proto;

	if (!mlxsw_sp)
	if (!process_foreign && !mlxsw_sp_lower_get(br_dev))
		return 0;

	extack = netdev_notifier_info_to_extack(&info->info);
@@ -5036,11 +5218,6 @@ static int mlxsw_sp_netdevice_bridge_event(struct net_device *br_dev,
			NL_SET_ERR_MSG_MOD(extack, "VLAN uppers are only supported with 802.1q VLAN protocol");
			return -EOPNOTSUPP;
		}
		if (netif_is_macvlan(upper_dev) &&
		    !mlxsw_sp_rif_exists(mlxsw_sp, br_dev)) {
			NL_SET_ERR_MSG_MOD(extack, "macvlan is only supported on top of router interfaces");
			return -EOPNOTSUPP;
		}
		break;
	case NETDEV_CHANGEUPPER:
		upper_dev = info->upper_dev;
@@ -5146,35 +5323,48 @@ static int mlxsw_sp_netdevice_vxlan_event(struct mlxsw_sp *mlxsw_sp,
	return 0;
}

static int mlxsw_sp_netdevice_event(struct notifier_block *nb,
				    unsigned long event, void *ptr)
static int __mlxsw_sp_netdevice_event(struct mlxsw_sp *mlxsw_sp,
				      unsigned long event, void *ptr,
				      bool process_foreign)
{
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
	struct mlxsw_sp_span_entry *span_entry;
	struct mlxsw_sp *mlxsw_sp;
	int err = 0;

	mlxsw_sp = container_of(nb, struct mlxsw_sp, netdevice_nb);
	if (event == NETDEV_UNREGISTER) {
		span_entry = mlxsw_sp_span_entry_find_by_port(mlxsw_sp, dev);
		if (span_entry)
			mlxsw_sp_span_entry_invalidate(mlxsw_sp, span_entry);
	}
	mlxsw_sp_span_respin(mlxsw_sp);

	if (netif_is_vxlan(dev))
		err = mlxsw_sp_netdevice_vxlan_event(mlxsw_sp, dev, event, ptr);
	else if (mlxsw_sp_port_dev_check(dev))
		err = mlxsw_sp_netdevice_port_event(dev, dev, event, ptr);
		err = mlxsw_sp_netdevice_port_event(dev, dev, event, ptr, true);
	else if (netif_is_lag_master(dev))
		err = mlxsw_sp_netdevice_lag_event(dev, event, ptr);
	else if (is_vlan_dev(dev))
		err = mlxsw_sp_netdevice_vlan_event(dev, event, ptr);
		err = mlxsw_sp_netdevice_vlan_event(mlxsw_sp, dev, event, ptr,
						    process_foreign);
	else if (netif_is_bridge_master(dev))
		err = mlxsw_sp_netdevice_bridge_event(dev, event, ptr);
		err = mlxsw_sp_netdevice_bridge_event(mlxsw_sp, dev, event, ptr,
						      process_foreign);
	else if (netif_is_macvlan(dev))
		err = mlxsw_sp_netdevice_macvlan_event(dev, event, ptr);

	return err;
}

static int mlxsw_sp_netdevice_event(struct notifier_block *nb,
				    unsigned long event, void *ptr)
{
	struct mlxsw_sp *mlxsw_sp;
	int err;

	mlxsw_sp = container_of(nb, struct mlxsw_sp, netdevice_nb);
	mlxsw_sp_span_respin(mlxsw_sp);
	err = __mlxsw_sp_netdevice_event(mlxsw_sp, event, ptr, false);

	return notifier_from_errno(err);
}

+2 −0
Original line number Diff line number Diff line
@@ -700,6 +700,8 @@ int mlxsw_sp_port_pvid_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid,
struct mlxsw_sp_port_vlan *
mlxsw_sp_port_vlan_create(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid);
void mlxsw_sp_port_vlan_destroy(struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan);
int mlxsw_sp_port_kill_vid(struct net_device *dev,
			   __be16 __always_unused proto, u16 vid);
int mlxsw_sp_port_vlan_set(struct mlxsw_sp_port *mlxsw_sp_port, u16 vid_begin,
			   u16 vid_end, bool is_member, bool untagged);
int mlxsw_sp_flow_counter_get(struct mlxsw_sp *mlxsw_sp,
+398 −34

File changed.

Preview size limit exceeded, changes collapsed.

+7 −0
Original line number Diff line number Diff line
@@ -178,5 +178,12 @@ int mlxsw_sp_router_bridge_vlan_add(struct mlxsw_sp *mlxsw_sp,
int mlxsw_sp_router_port_join_lag(struct mlxsw_sp_port *mlxsw_sp_port,
				  struct net_device *lag_dev,
				  struct netlink_ext_ack *extack);
void mlxsw_sp_router_port_leave_lag(struct mlxsw_sp_port *mlxsw_sp_port,
				    struct net_device *lag_dev);
int mlxsw_sp_netdevice_enslavement_replay(struct mlxsw_sp *mlxsw_sp,
					  struct net_device *upper_dev,
					  struct netlink_ext_ack *extack);
void mlxsw_sp_netdevice_deslavement_replay(struct mlxsw_sp *mlxsw_sp,
					   struct net_device *dev);

#endif /* _MLXSW_ROUTER_H_*/
+135 −3
Original line number Diff line number Diff line
@@ -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,
@@ -2350,6 +2435,33 @@ static struct mlxsw_sp_port *mlxsw_sp_lag_rep_port(struct mlxsw_sp *mlxsw_sp,
	return NULL;
}

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,
@@ -2364,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
@@ -2536,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);
@@ -2551,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,
	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
@@ -2769,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;
Loading