summaryrefslogtreecommitdiff
path: root/drivers/net/dsa/mv88e6xxx/chip.c
diff options
context:
space:
mode:
authorVladimir Oltean <vladimir.oltean@nxp.com>2021-10-07 19:47:10 +0300
committerJakub Kicinski <kuba@kernel.org>2021-10-08 15:47:46 -0700
commit8b6836d824702cacf68190982181f8ca3aff9c3e (patch)
tree33d2bfe45431c5a5f7be3e3487e7fed42fb48497 /drivers/net/dsa/mv88e6xxx/chip.c
parentc7709a02c18aabebc3b2988d24661763a0449443 (diff)
net: dsa: mv88e6xxx: keep the pvid at 0 when VLAN-unaware
The VLAN support in mv88e6xxx has a loaded history. Commit 2ea7a679ca2a ("net: dsa: Don't add vlans when vlan filtering is disabled") noticed some issues with VLAN and decided the best way to deal with them was to make the DSA core ignore VLANs added by the bridge while VLAN awareness is turned off. Those issues were never explained, just presented as "at least one corner case". That approach had problems of its own, presented by commit 54a0ed0df496 ("net: dsa: provide an option for drivers to always receive bridge VLANs") for the DSA core, followed by commit 1fb74191988f ("net: dsa: mv88e6xxx: fix vlan setup") which applied ds->configure_vlan_while_not_filtering = true for mv88e6xxx in particular. We still don't know what corner case Andrew saw when he wrote commit 2ea7a679ca2a ("net: dsa: Don't add vlans when vlan filtering is disabled"), but Tobias now reports that when we use TX forwarding offload, pinging an external station from the bridge device is broken if the front-facing DSA user port has flooding turned off. The full description is in the link below, but for short, when a mv88e6xxx port is under a VLAN-unaware bridge, it inherits that bridge's pvid. So packets ingressing a user port will be classified to e.g. VID 1 (assuming that value for the bridge_default_pvid), whereas when tag_dsa.c xmits towards a user port, it always sends packets using a VID of 0 if that port is standalone or under a VLAN-unaware bridge - or at least it did so prior to commit d82f8ab0d874 ("net: dsa: tag_dsa: offload the bridge forwarding process"). In any case, when there is a conversation between the CPU and a station connected to a user port, the station's MAC address is learned in VID 1 but the CPU tries to transmit through VID 0. The packets reach the intended station, but via flooding and not by virtue of matching the existing ATU entry. DSA has established (and enforced in other drivers: sja1105, felix, mt7530) that a VLAN-unaware port should use a private pvid, and not inherit the one from the bridge. The bridge's pvid should only be inherited when that bridge is VLAN-aware, so all state transitions need to be handled. On the other hand, all bridge VLANs should sit in the VTU starting with the moment when the bridge offloads them via switchdev, they are just not used. This solves the problem that Tobias sees because packets ingressing on VLAN-unaware user ports now get classified to VID 0, which is also the VID used by tag_dsa.c on xmit. Fixes: d82f8ab0d874 ("net: dsa: tag_dsa: offload the bridge forwarding process") Link: https://patchwork.kernel.org/project/netdevbpf/patch/20211003222312.284175-2-vladimir.oltean@nxp.com/#24491503 Reported-by: Tobias Waldekranz <tobias@waldekranz.com> Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Diffstat (limited to 'drivers/net/dsa/mv88e6xxx/chip.c')
-rw-r--r--drivers/net/dsa/mv88e6xxx/chip.c53
1 files changed, 47 insertions, 6 deletions
diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 03744d1c43fc..d672112afffd 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1677,6 +1677,26 @@ static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
return 0;
}
+static int mv88e6xxx_port_commit_pvid(struct mv88e6xxx_chip *chip, int port)
+{
+ struct dsa_port *dp = dsa_to_port(chip->ds, port);
+ struct mv88e6xxx_port *p = &chip->ports[port];
+ bool drop_untagged = false;
+ u16 pvid = 0;
+ int err;
+
+ if (dp->bridge_dev && br_vlan_enabled(dp->bridge_dev)) {
+ pvid = p->bridge_pvid.vid;
+ drop_untagged = !p->bridge_pvid.valid;
+ }
+
+ err = mv88e6xxx_port_set_pvid(chip, port, pvid);
+ if (err)
+ return err;
+
+ return mv88e6xxx_port_drop_untagged(chip, port, drop_untagged);
+}
+
static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering,
struct netlink_ext_ack *extack)
@@ -1690,7 +1710,16 @@ static int mv88e6xxx_port_vlan_filtering(struct dsa_switch *ds, int port,
return -EOPNOTSUPP;
mv88e6xxx_reg_lock(chip);
+
err = mv88e6xxx_port_set_8021q_mode(chip, port, mode);
+ if (err)
+ goto unlock;
+
+ err = mv88e6xxx_port_commit_pvid(chip, port);
+ if (err)
+ goto unlock;
+
+unlock:
mv88e6xxx_reg_unlock(chip);
return err;
@@ -2123,6 +2152,7 @@ static int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port,
struct mv88e6xxx_chip *chip = ds->priv;
bool untagged = vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED;
bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;
+ struct mv88e6xxx_port *p = &chip->ports[port];
bool warn;
u8 member;
int err;
@@ -2156,13 +2186,21 @@ static int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port,
}
if (pvid) {
- err = mv88e6xxx_port_set_pvid(chip, port, vlan->vid);
- if (err) {
- dev_err(ds->dev, "p%d: failed to set PVID %d\n",
- port, vlan->vid);
+ p->bridge_pvid.vid = vlan->vid;
+ p->bridge_pvid.valid = true;
+
+ err = mv88e6xxx_port_commit_pvid(chip, port);
+ if (err)
+ goto out;
+ } else if (vlan->vid && p->bridge_pvid.vid == vlan->vid) {
+ /* The old pvid was reinstalled as a non-pvid VLAN */
+ p->bridge_pvid.valid = false;
+
+ err = mv88e6xxx_port_commit_pvid(chip, port);
+ if (err)
goto out;
- }
}
+
out:
mv88e6xxx_reg_unlock(chip);
@@ -2212,6 +2250,7 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
struct mv88e6xxx_chip *chip = ds->priv;
+ struct mv88e6xxx_port *p = &chip->ports[port];
int err = 0;
u16 pvid;
@@ -2229,7 +2268,9 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch *ds, int port,
goto unlock;
if (vlan->vid == pvid) {
- err = mv88e6xxx_port_set_pvid(chip, port, 0);
+ p->bridge_pvid.valid = false;
+
+ err = mv88e6xxx_port_commit_pvid(chip, port);
if (err)
goto unlock;
}