| From 54a0ed0df49609f4e3f098f8943e38e389dc2e15 Mon Sep 17 00:00:00 2001 |
| From: Russell King <rmk+kernel@armlinux.org.uk> |
| Date: Tue, 12 May 2020 20:20:25 +0300 |
| Subject: net: dsa: provide an option for drivers to always receive bridge |
| VLANs |
| |
| DSA assumes that a bridge which has vlan filtering disabled is not |
| vlan aware, and ignores all vlan configuration. However, the kernel |
| software bridge code allows configuration in this state. |
| |
| This causes the kernel's idea of the bridge vlan state and the |
| hardware state to disagree, so "bridge vlan show" indicates a correct |
| configuration but the hardware lacks all configuration. Even worse, |
| enabling vlan filtering on a DSA bridge immediately blocks all traffic |
| which, given the output of "bridge vlan show", is very confusing. |
| |
| Provide an option that drivers can set to indicate they want to receive |
| vlan configuration even when vlan filtering is disabled. At the very |
| least, this is safe for Marvell DSA bridges, which do not look up |
| ingress traffic in the VTU if the port is in 8021Q disabled state. It is |
| also safe for the Ocelot switch family. Whether this change is suitable |
| for all DSA bridges is not known. |
| |
| Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk> |
| Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> |
| Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| --- |
| include/net/dsa.h | 7 +++++++ |
| net/dsa/dsa_priv.h | 1 + |
| net/dsa/port.c | 14 ++++++++++++++ |
| net/dsa/slave.c | 8 ++++---- |
| 4 files changed, 26 insertions(+), 4 deletions(-) |
| |
| --- a/include/net/dsa.h |
| +++ b/include/net/dsa.h |
| @@ -270,6 +270,13 @@ struct dsa_switch { |
| */ |
| bool vlan_filtering_is_global; |
| |
| + /* 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 |
| + * would break things or not. |
| + */ |
| + bool configure_vlan_while_not_filtering; |
| + |
| /* In case vlan_filtering_is_global is set, the VLAN awareness state |
| * should be retrieved from here and not from the per-port settings. |
| */ |
| --- a/net/dsa/dsa_priv.h |
| +++ b/net/dsa/dsa_priv.h |
| @@ -139,6 +139,7 @@ int dsa_port_bridge_join(struct dsa_port |
| void dsa_port_bridge_leave(struct dsa_port *dp, struct net_device *br); |
| int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering, |
| struct switchdev_trans *trans); |
| +bool dsa_port_skip_vlan_configuration(struct dsa_port *dp); |
| int dsa_port_ageing_time(struct dsa_port *dp, clock_t ageing_clock, |
| struct switchdev_trans *trans); |
| int dsa_port_fdb_add(struct dsa_port *dp, const unsigned char *addr, |
| --- a/net/dsa/port.c |
| +++ b/net/dsa/port.c |
| @@ -238,6 +238,20 @@ int dsa_port_vlan_filtering(struct dsa_p |
| return 0; |
| } |
| |
| +/* This enforces legacy behavior for switch drivers which assume they can't |
| + * receive VLAN configuration when enslaved to a bridge with vlan_filtering=0 |
| + */ |
| +bool dsa_port_skip_vlan_configuration(struct dsa_port *dp) |
| +{ |
| + struct dsa_switch *ds = dp->ds; |
| + |
| + if (!dp->bridge_dev) |
| + return false; |
| + |
| + return (!ds->configure_vlan_while_not_filtering && |
| + !br_vlan_enabled(dp->bridge_dev)); |
| +} |
| + |
| int dsa_port_ageing_time(struct dsa_port *dp, clock_t ageing_clock, |
| struct switchdev_trans *trans) |
| { |
| --- a/net/dsa/slave.c |
| +++ b/net/dsa/slave.c |
| @@ -317,7 +317,7 @@ static int dsa_slave_vlan_add(struct net |
| if (obj->orig_dev != dev) |
| return -EOPNOTSUPP; |
| |
| - if (dp->bridge_dev && !br_vlan_enabled(dp->bridge_dev)) |
| + if (dsa_port_skip_vlan_configuration(dp)) |
| return 0; |
| |
| vlan = *SWITCHDEV_OBJ_PORT_VLAN(obj); |
| @@ -384,7 +384,7 @@ static int dsa_slave_vlan_del(struct net |
| if (obj->orig_dev != dev) |
| return -EOPNOTSUPP; |
| |
| - if (dp->bridge_dev && !br_vlan_enabled(dp->bridge_dev)) |
| + if (dsa_port_skip_vlan_configuration(dp)) |
| return 0; |
| |
| /* Do not deprogram the CPU port as it may be shared with other user |
| @@ -1118,7 +1118,7 @@ static int dsa_slave_vlan_rx_add_vid(str |
| * need to emulate the switchdev prepare + commit phase. |
| */ |
| if (dp->bridge_dev) { |
| - if (!br_vlan_enabled(dp->bridge_dev)) |
| + if (dsa_port_skip_vlan_configuration(dp)) |
| return 0; |
| |
| /* br_vlan_get_info() returns -EINVAL or -ENOENT if the |
| @@ -1152,7 +1152,7 @@ static int dsa_slave_vlan_rx_kill_vid(st |
| * need to emulate the switchdev prepare + commit phase. |
| */ |
| if (dp->bridge_dev) { |
| - if (!br_vlan_enabled(dp->bridge_dev)) |
| + if (dsa_port_skip_vlan_configuration(dp)) |
| return 0; |
| |
| /* br_vlan_get_info() returns -EINVAL or -ENOENT if the |