summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2021-08-24 09:30:58 +0100
committerDavid S. Miller <davem@davemloft.net>2021-08-24 09:30:58 +0100
commit669f047ec12624e1fa07e0df88a84104b1d4dcd9 (patch)
treee190c85cd851e06cc6b74ef4a12de3ea66866043
parent0384dd9d2d807b7d1470ce0abd549b8855037f99 (diff)
parent58adf9dcb15b99f047e80e10c85fb51ed3c88215 (diff)
downloadlinux-669f047ec12624e1fa07e0df88a84104b1d4dcd9.tar.bz2
Merge branch 'dsa-sw-bridging'
Vladimir Oltean says: ==================== Plug holes in DSA's software bridging support Changes in v2: - Make sure that leaving an unoffloaded bridge works well too - Remove a set but unused variable - Tweak a commit message This series addresses some oddities reported by Alvin while he was working on the new rtl8365mb driver (a driver which does not implement bridge offloading for now, and relies on software bridging). First is that DSA behaves, in the lack of a .port_bridge_join method, as if the operation succeeds, and does not kick off its internal procedures for software bridging (the same procedures that were written for indirect software bridging, meaning bridging with an unoffloaded software LAG). Second is that even after being patched to treat ports with software bridging as standalone, we still don't get rid of bridge VLANs, even though we have code to ignore them, that code manages to get bypassed. This is in fact a recurring issue which was brought up by Tobias Waldekranz a while ago, but the solution never made it to the git tree. After debugging with Florian the last time: https://patchwork.kernel.org/project/netdevbpf/patch/20210320225928.2481575-3-olteanv@gmail.com/ I became very concerned about sending these patches to stable kernels. They are relatively large reworks, and they are only tested properly on net-next. A few commands on my test vehicle which has ds->vlan_filtering_is_global set to true: | Nothing is committed to hardware when we add VLAN 100 on a standalone | port $ ip link add link sw0p2 name sw0p2.100 type vlan id 100 | When a neighbor port joins a VLAN-aware bridge, VLAN filtering gets | enabled globally on the switch. This replays the VLAN 100 from | sw0p2.100 and also installs VLAN 1 from the bridge on sw0p0. $ ip link add br0 type bridge vlan_filtering 1 && ip link set sw0p0 master br0 [ 97.948087] sja1105 spi2.0: Reset switch and programmed static config. Reason: VLAN filtering [ 97.957989] sja1105 spi2.0: sja1105_bridge_vlan_add: port 2 vlan 100 [ 97.964442] sja1105 spi2.0: sja1105_bridge_vlan_add: port 4 vlan 100 [ 97.971202] device sw0p0 entered promiscuous mode [ 97.976129] sja1105 spi2.0: sja1105_bridge_vlan_add: port 0 vlan 1 [ 97.982640] sja1105 spi2.0: sja1105_bridge_vlan_add: port 4 vlan 1 | We can see that sw0p2, the standalone port, is now filtering because | of the bridge $ ethtool -k sw0p2 | grep vlan rx-vlan-filter: on [fixed] | When we make the bridge VLAN-unaware, the 8021q upper sw0p2.100 is | uncomitted from hardware. The VLANs managed by the bridge still remain | committed to hardware, because they are managed by the bridge. $ ip link set br0 type bridge vlan_filtering 0 [ 134.218869] sja1105 spi2.0: Reset switch and programmed static config. Reason: VLAN filtering [ 134.228913] sja1105 spi2.0: sja1105_bridge_vlan_del: port 2 vlan 100 | And now the standalone port is not filtering anymore. ethtool -k sw0p2 | grep vlan rx-vlan-filter: off [fixed] The same test with .port_bridge_join and .port_bridge_leave commented out from this driver: | Not a flinch $ ip link add link sw0p2 name sw0p2.100 type vlan id 100 $ ip link add br0 type bridge vlan_filtering 1 && ip link set sw0p0 master br0 Warning: dsa_core: Offloading not supported. $ ethtool -k sw0p2 | grep vlan rx-vlan-filter: off [fixed] $ ip link set br0 type bridge vlan_filtering 0 $ ethtool -k sw0p2 | grep vlan rx-vlan-filter: off [fixed] ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/dsa/hirschmann/hellcreek.c1
-rw-r--r--include/net/dsa.h3
-rw-r--r--net/dsa/dsa_priv.h2
-rw-r--r--net/dsa/port.c46
-rw-r--r--net/dsa/slave.c79
-rw-r--r--net/dsa/switch.c27
6 files changed, 147 insertions, 11 deletions
diff --git a/drivers/net/dsa/hirschmann/hellcreek.c b/drivers/net/dsa/hirschmann/hellcreek.c
index 5c54ae1be62c..3faff95fd49f 100644
--- a/drivers/net/dsa/hirschmann/hellcreek.c
+++ b/drivers/net/dsa/hirschmann/hellcreek.c
@@ -1345,6 +1345,7 @@ static int hellcreek_setup(struct dsa_switch *ds)
* filtering setups are not supported.
*/
ds->vlan_filtering_is_global = true;
+ ds->needs_standalone_vlan_filtering = true;
/* Intercept _all_ PTP multicast traffic */
ret = hellcreek_setup_fdb(hellcreek);
diff --git a/include/net/dsa.h b/include/net/dsa.h
index c7ea0f61056f..f9a17145255a 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -363,6 +363,9 @@ struct dsa_switch {
*/
bool vlan_filtering_is_global;
+ /* Keep VLAN filtering enabled on ports not offloading any upper. */
+ bool needs_standalone_vlan_filtering;
+
/* Pass .port_vlan_add and .port_vlan_del to drivers even for bridges
* that have vlan_filtering=0. All drivers should ideally set this (and
* then the option would get removed), but it is unknown whether this
diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
index 88aaf43b2da4..33ab7d7af9eb 100644
--- a/net/dsa/dsa_priv.h
+++ b/net/dsa/dsa_priv.h
@@ -320,6 +320,8 @@ int dsa_slave_register_notifier(void);
void dsa_slave_unregister_notifier(void);
void dsa_slave_setup_tagger(struct net_device *slave);
int dsa_slave_change_mtu(struct net_device *dev, int new_mtu);
+int dsa_slave_manage_vlan_filtering(struct net_device *dev,
+ bool vlan_filtering);
static inline struct dsa_port *dsa_slave_to_port(const struct net_device *dev)
{
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 4fbe81ffb1ce..616330a16d31 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -373,6 +373,10 @@ void dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br)
{
struct net_device *brport_dev = dsa_port_to_bridge_port(dp);
+ /* Don't try to unoffload something that is not offloaded */
+ if (!brport_dev)
+ return;
+
switchdev_bridge_port_unoffload(brport_dev, dp,
&dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier);
@@ -576,6 +580,7 @@ static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp,
int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
struct netlink_ext_ack *extack)
{
+ bool old_vlan_filtering = dsa_port_is_vlan_filtering(dp);
struct dsa_switch *ds = dp->ds;
bool apply;
int err;
@@ -601,12 +606,49 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
if (err)
return err;
- if (ds->vlan_filtering_is_global)
+ if (ds->vlan_filtering_is_global) {
+ int port;
+
ds->vlan_filtering = vlan_filtering;
- else
+
+ for (port = 0; port < ds->num_ports; port++) {
+ struct net_device *slave;
+
+ if (!dsa_is_user_port(ds, port))
+ continue;
+
+ /* We might be called in the unbind path, so not
+ * all slave devices might still be registered.
+ */
+ slave = dsa_to_port(ds, port)->slave;
+ if (!slave)
+ continue;
+
+ err = dsa_slave_manage_vlan_filtering(slave,
+ vlan_filtering);
+ if (err)
+ goto restore;
+ }
+ } else {
dp->vlan_filtering = vlan_filtering;
+ err = dsa_slave_manage_vlan_filtering(dp->slave,
+ vlan_filtering);
+ if (err)
+ goto restore;
+ }
+
return 0;
+
+restore:
+ ds->ops->port_vlan_filtering(ds, dp->index, old_vlan_filtering, NULL);
+
+ if (ds->vlan_filtering_is_global)
+ ds->vlan_filtering = old_vlan_filtering;
+ else
+ dp->vlan_filtering = old_vlan_filtering;
+
+ return err;
}
/* This enforces legacy behavior for switch drivers which assume they can't
diff --git a/net/dsa/slave.c b/net/dsa/slave.c
index eb9d9e53c536..662ff531d4e2 100644
--- a/net/dsa/slave.c
+++ b/net/dsa/slave.c
@@ -1409,6 +1409,76 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
return 0;
}
+static int dsa_slave_restore_vlan(struct net_device *vdev, int vid, void *arg)
+{
+ __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+ return dsa_slave_vlan_rx_add_vid(arg, proto, vid);
+}
+
+static int dsa_slave_clear_vlan(struct net_device *vdev, int vid, void *arg)
+{
+ __be16 proto = vdev ? vlan_dev_vlan_proto(vdev) : htons(ETH_P_8021Q);
+
+ return dsa_slave_vlan_rx_kill_vid(arg, proto, vid);
+}
+
+/* Keep the VLAN RX filtering list in sync with the hardware only if VLAN
+ * filtering is enabled. The baseline is that only ports that offload a
+ * VLAN-aware bridge are VLAN-aware, and standalone ports are VLAN-unaware,
+ * but there are exceptions for quirky hardware.
+ *
+ * If ds->vlan_filtering_is_global = true, then standalone ports which share
+ * the same switch with other ports that offload a VLAN-aware bridge are also
+ * inevitably VLAN-aware.
+ *
+ * To summarize, a DSA switch port offloads:
+ *
+ * - If standalone (this includes software bridge, software LAG):
+ * - if ds->needs_standalone_vlan_filtering = true, OR if
+ * (ds->vlan_filtering_is_global = true AND there are bridges spanning
+ * this switch chip which have vlan_filtering=1)
+ * - the 8021q upper VLANs
+ * - else (standalone VLAN filtering is not needed, VLAN filtering is not
+ * global, or it is, but no port is under a VLAN-aware bridge):
+ * - no VLAN (any 8021q upper is a software VLAN)
+ *
+ * - If under a vlan_filtering=0 bridge which it offload:
+ * - if ds->configure_vlan_while_not_filtering = true (default):
+ * - the bridge VLANs. These VLANs are committed to hardware but inactive.
+ * - else (deprecated):
+ * - no VLAN. The bridge VLANs are not restored when VLAN awareness is
+ * enabled, so this behavior is broken and discouraged.
+ *
+ * - If under a vlan_filtering=1 bridge which it offload:
+ * - the bridge VLANs
+ * - the 8021q upper VLANs
+ */
+int dsa_slave_manage_vlan_filtering(struct net_device *slave,
+ bool vlan_filtering)
+{
+ int err;
+
+ if (vlan_filtering) {
+ slave->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
+
+ err = vlan_for_each(slave, dsa_slave_restore_vlan, slave);
+ if (err) {
+ vlan_for_each(slave, dsa_slave_clear_vlan, slave);
+ slave->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+ return err;
+ }
+ } else {
+ err = vlan_for_each(slave, dsa_slave_clear_vlan, slave);
+ if (err)
+ return err;
+
+ slave->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER;
+ }
+
+ return 0;
+}
+
struct dsa_hw_port {
struct list_head list;
struct net_device *dev;
@@ -1816,12 +1886,12 @@ void dsa_slave_setup_tagger(struct net_device *slave)
p->xmit = cpu_dp->tag_ops->xmit;
slave->features = master->vlan_features | NETIF_F_HW_TC;
- if (ds->ops->port_vlan_add && ds->ops->port_vlan_del)
- slave->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
slave->hw_features |= NETIF_F_HW_TC;
slave->features |= NETIF_F_LLTX;
if (slave->needed_tailroom)
slave->features &= ~(NETIF_F_SG | NETIF_F_FRAGLIST);
+ if (ds->needs_standalone_vlan_filtering)
+ slave->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
}
static struct lock_class_key dsa_slave_netdev_xmit_lock_key;
@@ -2009,6 +2079,11 @@ static int dsa_slave_changeupper(struct net_device *dev,
err = dsa_port_bridge_join(dp, info->upper_dev, extack);
if (!err)
dsa_bridge_mtu_normalization(dp);
+ if (err == -EOPNOTSUPP) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Offloading not supported");
+ err = 0;
+ }
err = notifier_from_errno(err);
} else {
dsa_port_bridge_leave(dp, info->upper_dev);
diff --git a/net/dsa/switch.c b/net/dsa/switch.c
index fd1a1c6bf9cf..1c797ec8e2c2 100644
--- a/net/dsa/switch.c
+++ b/net/dsa/switch.c
@@ -92,8 +92,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
struct dsa_switch_tree *dst = ds->dst;
int err;
- if (dst->index == info->tree_index && ds->index == info->sw_index &&
- ds->ops->port_bridge_join) {
+ if (dst->index == info->tree_index && ds->index == info->sw_index) {
+ if (!ds->ops->port_bridge_join)
+ return -EOPNOTSUPP;
+
err = ds->ops->port_bridge_join(ds, info->port, info->br);
if (err)
return err;
@@ -114,9 +116,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
static int dsa_switch_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info)
{
- bool unset_vlan_filtering = br_vlan_enabled(info->br);
struct dsa_switch_tree *dst = ds->dst;
struct netlink_ext_ack extack = {0};
+ bool change_vlan_filtering = false;
+ bool vlan_filtering;
int err, port;
if (dst->index == info->tree_index && ds->index == info->sw_index &&
@@ -129,6 +132,15 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
info->sw_index, info->port,
info->br);
+ if (ds->needs_standalone_vlan_filtering && !br_vlan_enabled(info->br)) {
+ change_vlan_filtering = true;
+ vlan_filtering = true;
+ } else if (!ds->needs_standalone_vlan_filtering &&
+ br_vlan_enabled(info->br)) {
+ change_vlan_filtering = true;
+ vlan_filtering = false;
+ }
+
/* If the bridge was vlan_filtering, the bridge core doesn't trigger an
* event for changing vlan_filtering setting upon slave ports leaving
* it. That is a good thing, because that lets us handle it and also
@@ -137,21 +149,22 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
* vlan_filtering callback is only when the last port leaves the last
* VLAN-aware bridge.
*/
- if (unset_vlan_filtering && ds->vlan_filtering_is_global) {
+ if (change_vlan_filtering && ds->vlan_filtering_is_global) {
for (port = 0; port < ds->num_ports; port++) {
struct net_device *bridge_dev;
bridge_dev = dsa_to_port(ds, port)->bridge_dev;
if (bridge_dev && br_vlan_enabled(bridge_dev)) {
- unset_vlan_filtering = false;
+ change_vlan_filtering = false;
break;
}
}
}
- if (unset_vlan_filtering) {
+
+ if (change_vlan_filtering) {
err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
- false, &extack);
+ vlan_filtering, &extack);
if (extack._msg)
dev_err(ds->dev, "port %d: %s\n", info->port,
extack._msg);