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

Merge branch 'DSA-with-VLAN-filtering-and-offloading-masters'

Vladimir Oltean says:

====================
DSA with VLAN filtering and offloading masters

This series attempts to make DSA VLANs work in the presence of a master
interface that is:
- filtering, so it drops VLANs that aren't explicitly added to its
  filter list
- offloading, so the old assumptions in the tagging code about there
  being a VLAN tag in the skb are not necessarily true anymore.

For more context:
https://lore.kernel.org/netdev/20200910150738.mwhh2i6j2qgacqev@skbuf/



This probably marks the beginning of a series of patches in which DSA
starts paying much more attention to its upper interfaces, not only for
VLAN purposes but also for address filtering and for management of the
CPU flooding domain. There was a comment from Florian on whether we
could factor some of the mlxsw logic into some common functionality, but
it doesn't look so. This seems bound to be open-coded, but frankly there
isn't a lot to it.

Changes in v2:
Applied Florian's cosmetic suggestion in patch 4/9.
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 098c2fc6 88525fc0
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -3038,7 +3038,11 @@ static int sja1105_setup(struct dsa_switch *ds)
	 * default, and that means vlan_filtering is 0 since they're not under
	 * a bridge, so it's safe to set up switch tagging at this time.
	 */
	return sja1105_setup_8021q_tagging(ds, true);
	rtnl_lock();
	rc = sja1105_setup_8021q_tagging(ds, true);
	rtnl_unlock();

	return rc;
}

static void sja1105_teardown(struct dsa_switch *ds)
@@ -3532,6 +3536,7 @@ static int sja1105_probe(struct spi_device *spi)
		return -ENOMEM;

	priv->dsa_8021q_ctx->ops = &sja1105_dsa_8021q_ops;
	priv->dsa_8021q_ctx->proto = htons(ETH_P_8021Q);
	priv->dsa_8021q_ctx->ds = ds;

	INIT_LIST_HEAD(&priv->dsa_8021q_ctx->crosschip_links);
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ struct dsa_8021q_context {
	const struct dsa_8021q_ops *ops;
	struct dsa_switch *ds;
	struct list_head crosschip_links;
	/* EtherType of RX VID, used for filtering on master interface */
	__be16 proto;
};

#define DSA_8021Q_N_SUBVLAN			8
+50 −8
Original line number Diff line number Diff line
@@ -193,11 +193,44 @@ void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br)
	dsa_port_set_state_now(dp, BR_STATE_FORWARDING);
}

/* Must be called under rcu_read_lock() */
static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
					      bool vlan_filtering)
{
	struct dsa_switch *ds = dp->ds;
	int i;
	int err, i;

	/* VLAN awareness was off, so the question is "can we turn it on".
	 * We may have had 8021q uppers, those need to go. Make sure we don't
	 * enter an inconsistent state: deny changing the VLAN awareness state
	 * as long as we have 8021q uppers.
	 */
	if (vlan_filtering && dsa_is_user_port(ds, dp->index)) {
		struct net_device *upper_dev, *slave = dp->slave;
		struct net_device *br = dp->bridge_dev;
		struct list_head *iter;

		netdev_for_each_upper_dev_rcu(slave, upper_dev, iter) {
			struct bridge_vlan_info br_info;
			u16 vid;

			if (!is_vlan_dev(upper_dev))
				continue;

			vid = vlan_dev_vlan_id(upper_dev);

			/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
			 * device, respectively the VID is not found, returning
			 * 0 means success, which is a failure for us here.
			 */
			err = br_vlan_get_info(br, vid, &br_info);
			if (err == 0) {
				dev_err(ds->dev, "Must remove upper %s first\n",
					upper_dev->name);
				return false;
			}
		}
	}

	if (!ds->vlan_filtering_is_global)
		return true;
@@ -232,16 +265,25 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
	struct dsa_switch *ds = dp->ds;
	int err;

	/* bridge skips -EOPNOTSUPP, so skip the prepare phase */
	if (switchdev_trans_ph_prepare(trans))
		return 0;
	if (switchdev_trans_ph_prepare(trans)) {
		bool apply;

		if (!ds->ops->port_vlan_filtering)
		return 0;
			return -EOPNOTSUPP;

	if (!dsa_port_can_apply_vlan_filtering(dp, vlan_filtering))
		/* We are called from dsa_slave_switchdev_blocking_event(),
		 * which is not under rcu_read_lock(), unlike
		 * dsa_slave_switchdev_event().
		 */
		rcu_read_lock();
		apply = dsa_port_can_apply_vlan_filtering(dp, vlan_filtering);
		rcu_read_unlock();
		if (!apply)
			return -EINVAL;

		return 0;
	}

	if (dsa_port_is_vlan_filtering(dp) == vlan_filtering)
		return 0;

+112 −44
Original line number Diff line number Diff line
@@ -303,13 +303,36 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
	return ret;
}

/* Must be called under rcu_read_lock() */
static int
dsa_slave_vlan_check_for_8021q_uppers(struct net_device *slave,
				      const struct switchdev_obj_port_vlan *vlan)
{
	struct net_device *upper_dev;
	struct list_head *iter;

	netdev_for_each_upper_dev_rcu(slave, upper_dev, iter) {
		u16 vid;

		if (!is_vlan_dev(upper_dev))
			continue;

		vid = vlan_dev_vlan_id(upper_dev);
		if (vid >= vlan->vid_begin && vid <= vlan->vid_end)
			return -EBUSY;
	}

	return 0;
}

static int dsa_slave_vlan_add(struct net_device *dev,
			      const struct switchdev_obj *obj,
			      struct switchdev_trans *trans)
{
	struct net_device *master = dsa_slave_to_master(dev);
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan vlan;
	int err;
	int vid, err;

	if (obj->orig_dev != dev)
		return -EOPNOTSUPP;
@@ -319,6 +342,17 @@ static int dsa_slave_vlan_add(struct net_device *dev,

	vlan = *SWITCHDEV_OBJ_PORT_VLAN(obj);

	/* Deny adding a bridge VLAN when there is already an 802.1Q upper with
	 * the same VID.
	 */
	if (trans->ph_prepare && br_vlan_enabled(dp->bridge_dev)) {
		rcu_read_lock();
		err = dsa_slave_vlan_check_for_8021q_uppers(dev, &vlan);
		rcu_read_unlock();
		if (err)
			return err;
	}

	err = dsa_port_vlan_add(dp, &vlan, trans);
	if (err)
		return err;
@@ -333,6 +367,12 @@ static int dsa_slave_vlan_add(struct net_device *dev,
	if (err)
		return err;

	for (vid = vlan.vid_begin; vid <= vlan.vid_end; vid++) {
		err = vlan_vid_add(master, htons(ETH_P_8021Q), vid);
		if (err)
			return err;
	}

	return 0;
}

@@ -376,7 +416,10 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
static int dsa_slave_vlan_del(struct net_device *dev,
			      const struct switchdev_obj *obj)
{
	struct net_device *master = dsa_slave_to_master(dev);
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan *vlan;
	int vid, err;

	if (obj->orig_dev != dev)
		return -EOPNOTSUPP;
@@ -384,10 +427,19 @@ static int dsa_slave_vlan_del(struct net_device *dev,
	if (dsa_port_skip_vlan_configuration(dp))
		return 0;

	vlan = SWITCHDEV_OBJ_PORT_VLAN(obj);

	/* Do not deprogram the CPU port as it may be shared with other user
	 * ports which can be members of this VLAN as well.
	 */
	return dsa_port_vlan_del(dp, SWITCHDEV_OBJ_PORT_VLAN(obj));
	err = dsa_port_vlan_del(dp, vlan);
	if (err)
		return err;

	for (vid = vlan->vid_begin; vid <= vlan->vid_end; vid++)
		vlan_vid_del(master, htons(ETH_P_8021Q), vid);

	return 0;
}

static int dsa_slave_port_obj_del(struct net_device *dev,
@@ -1232,6 +1284,7 @@ static int dsa_slave_get_ts_info(struct net_device *dev,
static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
				     u16 vid)
{
	struct net_device *master = dsa_slave_to_master(dev);
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan vlan = {
		.obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
@@ -1240,26 +1293,9 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
		/* This API only allows programming tagged, non-PVID VIDs */
		.flags = 0,
	};
	struct bridge_vlan_info info;
	struct switchdev_trans trans;
	int ret;

	/* Check for a possible bridge VLAN entry now since there is no
	 * need to emulate the switchdev prepare + commit phase.
	 */
	if (dp->bridge_dev) {
		if (dsa_port_skip_vlan_configuration(dp))
			return 0;

		/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
		 * device, respectively the VID is not found, returning
		 * 0 means success, which is a failure for us here.
		 */
		ret = br_vlan_get_info(dp->bridge_dev, vid, &info);
		if (ret == 0)
			return -EBUSY;
	}

	/* User port... */
	trans.ph_prepare = true;
	ret = dsa_port_vlan_add(dp, &vlan, &trans);
@@ -1282,12 +1318,13 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
	if (ret)
		return ret;

	return 0;
	return vlan_vid_add(master, proto, vid);
}

static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
				      u16 vid)
{
	struct net_device *master = dsa_slave_to_master(dev);
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan vlan = {
		.vid_begin = vid,
@@ -1295,29 +1332,18 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
		/* This API only allows programming tagged, non-PVID VIDs */
		.flags = 0,
	};
	struct bridge_vlan_info info;
	int ret;

	/* Check for a possible bridge VLAN entry now since there is no
	 * need to emulate the switchdev prepare + commit phase.
	 */
	if (dp->bridge_dev) {
		if (dsa_port_skip_vlan_configuration(dp))
			return 0;

		/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
		 * device, respectively the VID is not found, returning
		 * 0 means success, which is a failure for us here.
		 */
		ret = br_vlan_get_info(dp->bridge_dev, vid, &info);
		if (ret == 0)
			return -EBUSY;
	}
	int err;

	/* Do not deprogram the CPU port as it may be shared with other user
	 * ports which can be members of this VLAN as well.
	 */
	return dsa_port_vlan_del(dp, &vlan);
	err = dsa_port_vlan_del(dp, &vlan);
	if (err)
		return err;

	vlan_vid_del(master, proto, vid);

	return 0;
}

struct dsa_hw_port {
@@ -1895,9 +1921,9 @@ static int dsa_slave_changeupper(struct net_device *dev,
	return err;
}

static int dsa_slave_upper_vlan_check(struct net_device *dev,
				      struct netdev_notifier_changeupper_info *
				      info)
static int
dsa_prevent_bridging_8021q_upper(struct net_device *dev,
				 struct netdev_notifier_changeupper_info *info)
{
	struct netlink_ext_ack *ext_ack;
	struct net_device *slave;
@@ -1927,14 +1953,56 @@ static int dsa_slave_upper_vlan_check(struct net_device *dev,
	return NOTIFY_DONE;
}

static int
dsa_slave_check_8021q_upper(struct net_device *dev,
			    struct netdev_notifier_changeupper_info *info)
{
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct net_device *br = dp->bridge_dev;
	struct bridge_vlan_info br_info;
	struct netlink_ext_ack *extack;
	int err = NOTIFY_DONE;
	u16 vid;

	if (!br || !br_vlan_enabled(br))
		return NOTIFY_DONE;

	extack = netdev_notifier_info_to_extack(&info->info);
	vid = vlan_dev_vlan_id(info->upper_dev);

	/* br_vlan_get_info() returns -EINVAL or -ENOENT if the
	 * device, respectively the VID is not found, returning
	 * 0 means success, which is a failure for us here.
	 */
	err = br_vlan_get_info(br, vid, &br_info);
	if (err == 0) {
		NL_SET_ERR_MSG_MOD(extack,
				   "This VLAN is already configured by the bridge");
		return notifier_from_errno(-EBUSY);
	}

	return NOTIFY_DONE;
}

static int dsa_slave_netdevice_event(struct notifier_block *nb,
				     unsigned long event, void *ptr)
{
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);

	if (event == NETDEV_CHANGEUPPER) {
	switch (event) {
	case NETDEV_PRECHANGEUPPER: {
		struct netdev_notifier_changeupper_info *info = ptr;

		if (!dsa_slave_dev_check(dev))
			return dsa_slave_upper_vlan_check(dev, ptr);
			return dsa_prevent_bridging_8021q_upper(dev, ptr);

		if (is_vlan_dev(info->upper_dev))
			return dsa_slave_check_8021q_upper(dev, ptr);
		break;
	}
	case NETDEV_CHANGEUPPER:
		if (!dsa_slave_dev_check(dev))
			return NOTIFY_DONE;

		return dsa_slave_changeupper(dev, ptr);
	}
+0 −41
Original line number Diff line number Diff line
@@ -232,43 +232,6 @@ static int dsa_switch_mdb_del(struct dsa_switch *ds,
	return 0;
}

static int dsa_port_vlan_device_check(struct net_device *vlan_dev,
				      int vlan_dev_vid,
				      void *arg)
{
	struct switchdev_obj_port_vlan *vlan = arg;
	u16 vid;

	for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) {
		if (vid == vlan_dev_vid)
			return -EBUSY;
	}

	return 0;
}

static int dsa_port_vlan_check(struct dsa_switch *ds, int port,
			       const struct switchdev_obj_port_vlan *vlan)
{
	const struct dsa_port *dp = dsa_to_port(ds, port);
	int err = 0;

	/* Device is not bridged, let it proceed with the VLAN device
	 * creation.
	 */
	if (!dp->bridge_dev)
		return err;

	/* dsa_slave_vlan_rx_{add,kill}_vid() cannot use the prepare phase and
	 * already checks whether there is an overlapping bridge VLAN entry
	 * with the same VID, so here we only need to check that if we are
	 * adding a bridge VLAN entry there is not an overlapping VLAN device
	 * claiming that VID.
	 */
	return vlan_for_each(dp->slave, dsa_port_vlan_device_check,
			     (void *)vlan);
}

static bool dsa_switch_vlan_match(struct dsa_switch *ds, int port,
				  struct dsa_notifier_vlan_info *info)
{
@@ -291,10 +254,6 @@ static int dsa_switch_vlan_prepare(struct dsa_switch *ds,

	for (port = 0; port < ds->num_ports; port++) {
		if (dsa_switch_vlan_match(ds, port, info)) {
			err = dsa_port_vlan_check(ds, port, info->vlan);
			if (err)
				return err;

			err = ds->ops->port_vlan_prepare(ds, port, info->vlan);
			if (err)
				return err;
Loading