Merge branch 'dsa-bridge-tx-forwarding-offload-fixes-part-1'
authorJakub Kicinski <kuba@kernel.org>
Fri, 8 Oct 2021 22:47:48 +0000 (15:47 -0700)
committerJakub Kicinski <kuba@kernel.org>
Fri, 8 Oct 2021 22:47:49 +0000 (15:47 -0700)
Vladimir Oltean says:

====================
DSA bridge TX forwarding offload fixes - part 1

This is part 1 of a series of fixes to the bridge TX forwarding offload
feature introduced for v5.15. Sadly, the other fixes are so intrusive
that they cannot be reasonably be sent to the "net" tree, as they also
include API changes. So they are left as part 2 for net-next.
====================

Link: https://lore.kernel.org/r/20211007164711.2897238-1-vladimir.oltean@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
MAINTAINERS
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h
drivers/net/dsa/mv88e6xxx/port.c
drivers/net/dsa/mv88e6xxx/port.h
include/linux/dsa/mv88e6xxx.h [new file with mode: 0644]
net/dsa/dsa2.c
net/dsa/tag_dsa.c

index a4a0c2b..17f652b 100644 (file)
@@ -11153,6 +11153,7 @@ S:      Maintained
 F:     Documentation/devicetree/bindings/net/dsa/marvell.txt
 F:     Documentation/networking/devlink/mv88e6xxx.rst
 F:     drivers/net/dsa/mv88e6xxx/
+F:     include/linux/dsa/mv88e6xxx.h
 F:     include/linux/platform_data/mv88e6xxx.h
 
 MARVELL ARMADA 3700 PHY DRIVERS
index 03744d1..d7b2979 100644 (file)
@@ -12,6 +12,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/delay.h>
+#include <linux/dsa/mv88e6xxx.h>
 #include <linux/etherdevice.h>
 #include <linux/ethtool.h>
 #include <linux/if_bridge.h>
@@ -1677,6 +1678,30 @@ 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];
+       u16 pvid = MV88E6XXX_VID_STANDALONE;
+       bool drop_untagged = false;
+       int err;
+
+       if (dp->bridge_dev) {
+               if (br_vlan_enabled(dp->bridge_dev)) {
+                       pvid = p->bridge_pvid.vid;
+                       drop_untagged = !p->bridge_pvid.valid;
+               } else {
+                       pvid = MV88E6XXX_VID_BRIDGED;
+               }
+       }
+
+       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 +1715,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;
@@ -1725,11 +1759,15 @@ static int mv88e6xxx_port_db_load_purge(struct mv88e6xxx_chip *chip, int port,
        u16 fid;
        int err;
 
-       /* Null VLAN ID corresponds to the port private database */
+       /* Ports have two private address databases: one for when the port is
+        * standalone and one for when the port is under a bridge and the
+        * 802.1Q mode is disabled. When the port is standalone, DSA wants its
+        * address database to remain 100% empty, so we never load an ATU entry
+        * into a standalone port's database. Therefore, translate the null
+        * VLAN ID into the port's database used for VLAN-unaware bridging.
+        */
        if (vid == 0) {
-               err = mv88e6xxx_port_get_fid(chip, port, &fid);
-               if (err)
-                       return err;
+               fid = MV88E6XXX_FID_BRIDGED;
        } else {
                err = mv88e6xxx_vtu_get(chip, vid, &vlan);
                if (err)
@@ -2123,6 +2161,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 +2195,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 +2259,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 +2277,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;
        }
@@ -2393,7 +2443,16 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
        int err;
 
        mv88e6xxx_reg_lock(chip);
+
        err = mv88e6xxx_bridge_map(chip, br);
+       if (err)
+               goto unlock;
+
+       err = mv88e6xxx_port_commit_pvid(chip, port);
+       if (err)
+               goto unlock;
+
+unlock:
        mv88e6xxx_reg_unlock(chip);
 
        return err;
@@ -2403,11 +2462,20 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port,
                                        struct net_device *br)
 {
        struct mv88e6xxx_chip *chip = ds->priv;
+       int err;
 
        mv88e6xxx_reg_lock(chip);
+
        if (mv88e6xxx_bridge_map(chip, br) ||
            mv88e6xxx_port_vlan_map(chip, port))
                dev_err(ds->dev, "failed to remap in-chip Port VLAN\n");
+
+       err = mv88e6xxx_port_commit_pvid(chip, port);
+       if (err)
+               dev_err(ds->dev,
+                       "port %d failed to restore standalone pvid: %pe\n",
+                       port, ERR_PTR(err));
+
        mv88e6xxx_reg_unlock(chip);
 }
 
@@ -2853,6 +2921,20 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
        if (err)
                return err;
 
+       /* Associate MV88E6XXX_VID_BRIDGED with MV88E6XXX_FID_BRIDGED in the
+        * ATU by virtue of the fact that mv88e6xxx_atu_new() will pick it as
+        * the first free FID after MV88E6XXX_FID_STANDALONE. This will be used
+        * as the private PVID on ports under a VLAN-unaware bridge.
+        * Shared (DSA and CPU) ports must also be members of it, to translate
+        * the VID from the DSA tag into MV88E6XXX_FID_BRIDGED, instead of
+        * relying on their port default FID.
+        */
+       err = mv88e6xxx_port_vlan_join(chip, port, MV88E6XXX_VID_BRIDGED,
+                                      MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED,
+                                      false);
+       if (err)
+               return err;
+
        if (chip->info->ops->port_set_jumbo_size) {
                err = chip->info->ops->port_set_jumbo_size(chip, port, 10218);
                if (err)
@@ -2925,7 +3007,7 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
         * database, and allow bidirectional communication between the
         * CPU and DSA port(s), and the other ports.
         */
-       err = mv88e6xxx_port_set_fid(chip, port, 0);
+       err = mv88e6xxx_port_set_fid(chip, port, MV88E6XXX_FID_STANDALONE);
        if (err)
                return err;
 
@@ -3115,6 +3197,10 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
                }
        }
 
+       err = mv88e6xxx_vtu_setup(chip);
+       if (err)
+               goto unlock;
+
        /* Setup Switch Port Registers */
        for (i = 0; i < mv88e6xxx_num_ports(chip); i++) {
                if (dsa_is_unused_port(ds, i))
@@ -3144,10 +3230,6 @@ static int mv88e6xxx_setup(struct dsa_switch *ds)
        if (err)
                goto unlock;
 
-       err = mv88e6xxx_vtu_setup(chip);
-       if (err)
-               goto unlock;
-
        err = mv88e6xxx_pvt_setup(chip);
        if (err)
                goto unlock;
index 59f316c..8271b8a 100644 (file)
@@ -21,6 +21,9 @@
 #define EDSA_HLEN              8
 #define MV88E6XXX_N_FID                4096
 
+#define MV88E6XXX_FID_STANDALONE       0
+#define MV88E6XXX_FID_BRIDGED          1
+
 /* PVT limits for 4-bit port and 5-bit switch */
 #define MV88E6XXX_MAX_PVT_SWITCHES     32
 #define MV88E6XXX_MAX_PVT_PORTS                16
@@ -246,9 +249,15 @@ struct mv88e6xxx_policy {
        u16 vid;
 };
 
+struct mv88e6xxx_vlan {
+       u16     vid;
+       bool    valid;
+};
+
 struct mv88e6xxx_port {
        struct mv88e6xxx_chip *chip;
        int port;
+       struct mv88e6xxx_vlan bridge_pvid;
        u64 serdes_stats[2];
        u64 atu_member_violation;
        u64 atu_miss_violation;
index 451028c..d9817b2 100644 (file)
@@ -1257,6 +1257,27 @@ int mv88e6xxx_port_set_8021q_mode(struct mv88e6xxx_chip *chip, int port,
        return 0;
 }
 
+int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
+                                bool drop_untagged)
+{
+       u16 old, new;
+       int err;
+
+       err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_CTL2, &old);
+       if (err)
+               return err;
+
+       if (drop_untagged)
+               new = old | MV88E6XXX_PORT_CTL2_DISCARD_UNTAGGED;
+       else
+               new = old & ~MV88E6XXX_PORT_CTL2_DISCARD_UNTAGGED;
+
+       if (new == old)
+               return 0;
+
+       return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL2, new);
+}
+
 int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port)
 {
        u16 reg;
index b10e5ae..03382b6 100644 (file)
@@ -423,6 +423,8 @@ int mv88e6393x_port_set_cmode(struct mv88e6xxx_chip *chip, int port,
                              phy_interface_t mode);
 int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
 int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
+int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
+                                bool drop_untagged);
 int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port);
 int mv88e6095_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port,
                                     int upstream_port);
diff --git a/include/linux/dsa/mv88e6xxx.h b/include/linux/dsa/mv88e6xxx.h
new file mode 100644 (file)
index 0000000..8c3d45e
--- /dev/null
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright 2021 NXP
+ */
+
+#ifndef _NET_DSA_TAG_MV88E6XXX_H
+#define _NET_DSA_TAG_MV88E6XXX_H
+
+#include <linux/if_vlan.h>
+
+#define MV88E6XXX_VID_STANDALONE       0
+#define MV88E6XXX_VID_BRIDGED          (VLAN_N_VID - 1)
+
+#endif
index b29262e..6d5cc02 100644 (file)
@@ -170,7 +170,7 @@ void dsa_bridge_num_put(const struct net_device *bridge_dev, int bridge_num)
        /* Check if the bridge is still in use, otherwise it is time
         * to clean it up so we can reuse this bridge_num later.
         */
-       if (!dsa_bridge_num_find(bridge_dev))
+       if (dsa_bridge_num_find(bridge_dev) < 0)
                clear_bit(bridge_num, &dsa_fwd_offloading_bridges);
 }
 
index e5127b7..b3da4b2 100644 (file)
@@ -45,6 +45,7 @@
  *   6    6       2        2      4    2       N
  */
 
+#include <linux/dsa/mv88e6xxx.h>
 #include <linux/etherdevice.h>
 #include <linux/list.h>
 #include <linux/slab.h>
@@ -129,12 +130,9 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
        u8 tag_dev, tag_port;
        enum dsa_cmd cmd;
        u8 *dsa_header;
-       u16 pvid = 0;
-       int err;
 
        if (skb->offload_fwd_mark) {
                struct dsa_switch_tree *dst = dp->ds->dst;
-               struct net_device *br = dp->bridge_dev;
 
                cmd = DSA_CMD_FORWARD;
 
@@ -144,19 +142,6 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
                 */
                tag_dev = dst->last_switch + 1 + dp->bridge_num;
                tag_port = 0;
-
-               /* If we are offloading forwarding for a VLAN-unaware bridge,
-                * inject packets to hardware using the bridge's pvid, since
-                * that's where the packets ingressed from.
-                */
-               if (!br_vlan_enabled(br)) {
-                       /* Safe because __dev_queue_xmit() runs under
-                        * rcu_read_lock_bh()
-                        */
-                       err = br_vlan_get_pvid_rcu(br, &pvid);
-                       if (err)
-                               return NULL;
-               }
        } else {
                cmd = DSA_CMD_FROM_CPU;
                tag_dev = dp->ds->index;
@@ -180,16 +165,21 @@ static struct sk_buff *dsa_xmit_ll(struct sk_buff *skb, struct net_device *dev,
                        dsa_header[2] &= ~0x10;
                }
        } else {
+               struct net_device *br = dp->bridge_dev;
+               u16 vid;
+
+               vid = br ? MV88E6XXX_VID_BRIDGED : MV88E6XXX_VID_STANDALONE;
+
                skb_push(skb, DSA_HLEN + extra);
                dsa_alloc_etype_header(skb, DSA_HLEN + extra);
 
-               /* Construct untagged DSA tag. */
+               /* Construct DSA header from untagged frame. */
                dsa_header = dsa_etype_header_pos_tx(skb) + extra;
 
                dsa_header[0] = (cmd << 6) | tag_dev;
                dsa_header[1] = tag_port << 3;
-               dsa_header[2] = pvid >> 8;
-               dsa_header[3] = pvid & 0xff;
+               dsa_header[2] = vid >> 8;
+               dsa_header[3] = vid & 0xff;
        }
 
        return skb;