Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

Merge branch 'dsa-vlan'



Florian Fainelli says:

====================
net: dsa: VLAN devices w/ filtering

This patch series supports having VLAN devices on top of DSA/switch
ports while the switch has VLAN filtering globally turned on (as is the
case with Broadcom switches). Whether the switch does global or per-port
VLAN filtering, having VLAN entries for these VLAN devices is
beneficial.

We take care of a few possibly problematic cases:

- adding a VLAN device while there is an existing VLAN entry created by
  a VLAN aware bridge. The entire bridge's VLAN database and not just
  the specific bridge port is being checked to be safe and conserative

- adding a bridge VLAN entry when there is an existing VLAN device
  created is also not possible because that would lead to the bridge
  being able to manipulate the VLAN device's VID/attributes under its feet

- enslaving a VLAN device into a VLAN aware bridge since that duplicates
  functionality already offered by the VLAN aware bridge

Here are the different test cases that were run to exercise this:

ip addr flush dev gphy
ip link add dev br0 type bridge
echo 1 > /sys/class/net/br0/bridge/vlan_filtering
ip link set dev gphy master br0
udhcpc -i br0

vconfig add rgmii_1 100
ifconfig rgmii_1.100 192.168.100.10
ping -c 2 192.168.100.1

vconfig add br0 42
bridge vlan add vid 42 dev gphy
bridge vlan add vid 42 dev br0 self
ifconfig br0.42 192.168.42.2
ping -c 2 192.168.42.1

ip link del rgmii_1.100
vconfig add rgmii_1 100
ifconfig rgmii_1.100 192.168.100.10
ping -c 2 192.168.100.1
echo 0 > /sys/class/net/br0/bridge/vlan_filtering
ping -c 2 192.168.100.1

ip link del rgmii_1.100
echo 1 > /sys/class/net/br0/bridge/vlan_filtering

vconfig add rgmii_1 100
brctl addif br0 rgmii_1
bridge vlan add vid 100 dev rgmii_1

vconfig rem rgmii_1.100
bridge vlan add vid 100 dev rgmii_1
vconfig add rgmii_1 100

bridge vlan del vid 100 dev rgmii_1
vconfig add rgmii_1 100
brctl addif br0 rgmii_1.100
====================

Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 6d20faec 061f6a50
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -291,7 +291,10 @@ int dsa_port_vlan_add(struct dsa_port *dp,
		.vlan = vlan,
	};

	if (br_vlan_enabled(dp->bridge_dev))
	/* Can be called from dsa_slave_port_obj_add() or
	 * dsa_slave_vlan_rx_add_vid()
	 */
	if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev))
		return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_ADD, &info);

	return 0;
@@ -306,10 +309,13 @@ int dsa_port_vlan_del(struct dsa_port *dp,
		.vlan = vlan,
	};

	if (netif_is_bridge_master(vlan->obj.orig_dev))
	if (vlan->obj.orig_dev && netif_is_bridge_master(vlan->obj.orig_dev))
		return -EOPNOTSUPP;

	if (br_vlan_enabled(dp->bridge_dev))
	/* Can be called from dsa_slave_port_obj_del() or
	 * dsa_slave_vlan_rx_kill_vid()
	 */
	if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev))
		return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_DEL, &info);

	return 0;
+106 −4
Original line number Diff line number Diff line
@@ -983,6 +983,72 @@ static int dsa_slave_get_ts_info(struct net_device *dev,
	return ds->ops->get_ts_info(ds, p->dp->index, ts);
}

static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
				     u16 vid)
{
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan vlan = {
		.vid_begin = vid,
		.vid_end = vid,
		/* 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) {
		/* 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;
	}

	ret = dsa_port_vlan_add(dp, &vlan, NULL);
	if (ret == -EOPNOTSUPP)
		ret = 0;

	return ret;
}

static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
				      u16 vid)
{
	struct dsa_port *dp = dsa_slave_to_port(dev);
	struct switchdev_obj_port_vlan vlan = {
		.vid_begin = vid,
		.vid_end = vid,
		/* 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) {
		/* 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;
	}

	ret = dsa_port_vlan_del(dp, &vlan);
	if (ret == -EOPNOTSUPP)
		ret = 0;

	return ret;
}

static const struct ethtool_ops dsa_slave_ethtool_ops = {
	.get_drvinfo		= dsa_slave_get_drvinfo,
	.get_regs_len		= dsa_slave_get_regs_len,
@@ -1048,6 +1114,8 @@ static const struct net_device_ops dsa_slave_netdev_ops = {
	.ndo_setup_tc		= dsa_slave_setup_tc,
	.ndo_get_stats64	= dsa_slave_get_stats64,
	.ndo_get_port_parent_id	= dsa_slave_get_port_parent_id,
	.ndo_vlan_rx_add_vid	= dsa_slave_vlan_rx_add_vid,
	.ndo_vlan_rx_kill_vid	= dsa_slave_vlan_rx_kill_vid,
};

static const struct switchdev_ops dsa_slave_switchdev_ops = {
@@ -1307,7 +1375,8 @@ int dsa_slave_create(struct dsa_port *port)
	if (slave_dev == NULL)
		return -ENOMEM;

	slave_dev->features = master->vlan_features | NETIF_F_HW_TC;
	slave_dev->features = master->vlan_features | NETIF_F_HW_TC |
				NETIF_F_HW_VLAN_CTAG_FILTER;
	slave_dev->hw_features |= NETIF_F_HW_TC;
	slave_dev->ethtool_ops = &dsa_slave_ethtool_ops;
	eth_hw_addr_inherit(slave_dev, master);
@@ -1408,16 +1477,49 @@ 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)
{
	struct netlink_ext_ack *ext_ack;
	struct net_device *slave;
	struct dsa_port *dp;

	ext_ack = netdev_notifier_info_to_extack(&info->info);

	if (!is_vlan_dev(dev))
		return NOTIFY_DONE;

	slave = vlan_dev_real_dev(dev);
	if (!dsa_slave_dev_check(slave))
		return NOTIFY_DONE;

	dp = dsa_slave_to_port(slave);
	if (!dp->bridge_dev)
		return NOTIFY_DONE;

	/* Deny enslaving a VLAN device into a VLAN-aware bridge */
	if (br_vlan_enabled(dp->bridge_dev) &&
	    netif_is_bridge_master(info->upper_dev) && info->linking) {
		NL_SET_ERR_MSG_MOD(ext_ack,
				   "Cannot enslave VLAN device into VLAN aware bridge");
		return notifier_from_errno(-EINVAL);
	}

	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) {
		if (!dsa_slave_dev_check(dev))
		return NOTIFY_DONE;
			return dsa_slave_upper_vlan_check(dev, ptr);

	if (event == NETDEV_CHANGEUPPER)
		return dsa_slave_changeupper(dev, ptr);
	}

	return NOTIFY_DONE;
}
+42 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@

#include <linux/netdevice.h>
#include <linux/notifier.h>
#include <linux/if_vlan.h>
#include <net/switchdev.h>

#include "dsa_priv.h"
@@ -168,6 +169,43 @@ 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 pharse 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 int
dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds,
			       const struct switchdev_obj_port_vlan *vlan,
@@ -179,6 +217,10 @@ dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds,
		return -EOPNOTSUPP;

	for_each_set_bit(port, bitmap, ds->num_ports) {
		err = dsa_port_vlan_check(ds, port, vlan);
		if (err)
			return err;

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