Commit 669f047e authored by David S. Miller's avatar David S. Miller

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: default avatarDavid S. Miller <davem@davemloft.net>
parents 0384dd9d 58adf9dc
...@@ -1345,6 +1345,7 @@ static int hellcreek_setup(struct dsa_switch *ds) ...@@ -1345,6 +1345,7 @@ static int hellcreek_setup(struct dsa_switch *ds)
* filtering setups are not supported. * filtering setups are not supported.
*/ */
ds->vlan_filtering_is_global = true; ds->vlan_filtering_is_global = true;
ds->needs_standalone_vlan_filtering = true;
/* Intercept _all_ PTP multicast traffic */ /* Intercept _all_ PTP multicast traffic */
ret = hellcreek_setup_fdb(hellcreek); ret = hellcreek_setup_fdb(hellcreek);
......
...@@ -363,6 +363,9 @@ struct dsa_switch { ...@@ -363,6 +363,9 @@ struct dsa_switch {
*/ */
bool vlan_filtering_is_global; 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 /* 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 * that have vlan_filtering=0. All drivers should ideally set this (and
* then the option would get removed), but it is unknown whether this * then the option would get removed), but it is unknown whether this
......
...@@ -320,6 +320,8 @@ int dsa_slave_register_notifier(void); ...@@ -320,6 +320,8 @@ int dsa_slave_register_notifier(void);
void dsa_slave_unregister_notifier(void); void dsa_slave_unregister_notifier(void);
void dsa_slave_setup_tagger(struct net_device *slave); void dsa_slave_setup_tagger(struct net_device *slave);
int dsa_slave_change_mtu(struct net_device *dev, int new_mtu); 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) static inline struct dsa_port *dsa_slave_to_port(const struct net_device *dev)
{ {
......
...@@ -373,6 +373,10 @@ void dsa_port_pre_bridge_leave(struct dsa_port *dp, struct net_device *br) ...@@ -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); 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, switchdev_bridge_port_unoffload(brport_dev, dp,
&dsa_slave_switchdev_notifier, &dsa_slave_switchdev_notifier,
&dsa_slave_switchdev_blocking_notifier); &dsa_slave_switchdev_blocking_notifier);
...@@ -576,6 +580,7 @@ static bool dsa_port_can_apply_vlan_filtering(struct dsa_port *dp, ...@@ -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, int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
bool old_vlan_filtering = dsa_port_is_vlan_filtering(dp);
struct dsa_switch *ds = dp->ds; struct dsa_switch *ds = dp->ds;
bool apply; bool apply;
int err; int err;
...@@ -601,12 +606,49 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering, ...@@ -601,12 +606,49 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
if (err) if (err)
return err; return err;
if (ds->vlan_filtering_is_global) if (ds->vlan_filtering_is_global) {
int port;
ds->vlan_filtering = vlan_filtering; 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; dp->vlan_filtering = vlan_filtering;
err = dsa_slave_manage_vlan_filtering(dp->slave,
vlan_filtering);
if (err)
goto restore;
}
return 0; 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 /* This enforces legacy behavior for switch drivers which assume they can't
......
...@@ -1409,6 +1409,76 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, ...@@ -1409,6 +1409,76 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
return 0; 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 dsa_hw_port {
struct list_head list; struct list_head list;
struct net_device *dev; struct net_device *dev;
...@@ -1816,12 +1886,12 @@ void dsa_slave_setup_tagger(struct net_device *slave) ...@@ -1816,12 +1886,12 @@ void dsa_slave_setup_tagger(struct net_device *slave)
p->xmit = cpu_dp->tag_ops->xmit; p->xmit = cpu_dp->tag_ops->xmit;
slave->features = master->vlan_features | NETIF_F_HW_TC; 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->hw_features |= NETIF_F_HW_TC;
slave->features |= NETIF_F_LLTX; slave->features |= NETIF_F_LLTX;
if (slave->needed_tailroom) if (slave->needed_tailroom)
slave->features &= ~(NETIF_F_SG | NETIF_F_FRAGLIST); 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; static struct lock_class_key dsa_slave_netdev_xmit_lock_key;
...@@ -2009,6 +2079,11 @@ static int dsa_slave_changeupper(struct net_device *dev, ...@@ -2009,6 +2079,11 @@ static int dsa_slave_changeupper(struct net_device *dev,
err = dsa_port_bridge_join(dp, info->upper_dev, extack); err = dsa_port_bridge_join(dp, info->upper_dev, extack);
if (!err) if (!err)
dsa_bridge_mtu_normalization(dp); dsa_bridge_mtu_normalization(dp);
if (err == -EOPNOTSUPP) {
NL_SET_ERR_MSG_MOD(extack,
"Offloading not supported");
err = 0;
}
err = notifier_from_errno(err); err = notifier_from_errno(err);
} else { } else {
dsa_port_bridge_leave(dp, info->upper_dev); dsa_port_bridge_leave(dp, info->upper_dev);
......
...@@ -92,8 +92,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds, ...@@ -92,8 +92,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
struct dsa_switch_tree *dst = ds->dst; struct dsa_switch_tree *dst = ds->dst;
int err; int err;
if (dst->index == info->tree_index && ds->index == info->sw_index && if (dst->index == info->tree_index && ds->index == info->sw_index) {
ds->ops->port_bridge_join) { if (!ds->ops->port_bridge_join)
return -EOPNOTSUPP;
err = ds->ops->port_bridge_join(ds, info->port, info->br); err = ds->ops->port_bridge_join(ds, info->port, info->br);
if (err) if (err)
return err; return err;
...@@ -114,9 +116,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds, ...@@ -114,9 +116,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
static int dsa_switch_bridge_leave(struct dsa_switch *ds, static int dsa_switch_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info) struct dsa_notifier_bridge_info *info)
{ {
bool unset_vlan_filtering = br_vlan_enabled(info->br);
struct dsa_switch_tree *dst = ds->dst; struct dsa_switch_tree *dst = ds->dst;
struct netlink_ext_ack extack = {0}; struct netlink_ext_ack extack = {0};
bool change_vlan_filtering = false;
bool vlan_filtering;
int err, port; int err, port;
if (dst->index == info->tree_index && ds->index == info->sw_index && 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, ...@@ -129,6 +132,15 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
info->sw_index, info->port, info->sw_index, info->port,
info->br); 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 /* If the bridge was vlan_filtering, the bridge core doesn't trigger an
* event for changing vlan_filtering setting upon slave ports leaving * event for changing vlan_filtering setting upon slave ports leaving
* it. That is a good thing, because that lets us handle it and also * 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, ...@@ -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_filtering callback is only when the last port leaves the last
* VLAN-aware bridge. * 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++) { for (port = 0; port < ds->num_ports; port++) {
struct net_device *bridge_dev; struct net_device *bridge_dev;
bridge_dev = dsa_to_port(ds, port)->bridge_dev; bridge_dev = dsa_to_port(ds, port)->bridge_dev;
if (bridge_dev && br_vlan_enabled(bridge_dev)) { if (bridge_dev && br_vlan_enabled(bridge_dev)) {
unset_vlan_filtering = false; change_vlan_filtering = false;
break; break;
} }
} }
} }
if (unset_vlan_filtering) {
if (change_vlan_filtering) {
err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port), err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
false, &extack); vlan_filtering, &extack);
if (extack._msg) if (extack._msg)
dev_err(ds->dev, "port %d: %s\n", info->port, dev_err(ds->dev, "port %d: %s\n", info->port,
extack._msg); extack._msg);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment