net: dsa: mv88e6xxx: MST Offloading
authorTobias Waldekranz <tobias@waldekranz.com>
Wed, 16 Mar 2022 15:08:57 +0000 (16:08 +0100)
committerJakub Kicinski <kuba@kernel.org>
Thu, 17 Mar 2022 23:50:00 +0000 (16:50 -0700)
Allocate a SID in the STU for each MSTID in use by a bridge and handle
the mapping of MSTIDs to VLANs using the SID field of each VTU entry.

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/dsa/mv88e6xxx/chip.c
drivers/net/dsa/mv88e6xxx/chip.h

index c14a62aa6a6c338580a8ba3ee4ec4c23cfa4a8c7..bed1a5658eac0db259ac5e28ff1666867897badf 100644 (file)
@@ -1667,24 +1667,31 @@ static int mv88e6xxx_pvt_setup(struct mv88e6xxx_chip *chip)
        return 0;
 }
 
-static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port)
+static int mv88e6xxx_port_fast_age_fid(struct mv88e6xxx_chip *chip, int port,
+                                      u16 fid)
 {
-       struct mv88e6xxx_chip *chip = ds->priv;
-       int err;
-
-       if (dsa_to_port(ds, port)->lag)
+       if (dsa_to_port(chip->ds, port)->lag)
                /* Hardware is incapable of fast-aging a LAG through a
                 * regular ATU move operation. Until we have something
                 * more fancy in place this is a no-op.
                 */
-               return;
+               return -EOPNOTSUPP;
+
+       return mv88e6xxx_g1_atu_remove(chip, fid, port, false);
+}
+
+static void mv88e6xxx_port_fast_age(struct dsa_switch *ds, int port)
+{
+       struct mv88e6xxx_chip *chip = ds->priv;
+       int err;
 
        mv88e6xxx_reg_lock(chip);
-       err = mv88e6xxx_g1_atu_remove(chip, 0, port, false);
+       err = mv88e6xxx_port_fast_age_fid(chip, port, 0);
        mv88e6xxx_reg_unlock(chip);
 
        if (err)
-               dev_err(ds->dev, "p%d: failed to flush ATU\n", port);
+               dev_err(chip->ds->dev, "p%d: failed to flush ATU: %d\n",
+                       port, err);
 }
 
 static int mv88e6xxx_vtu_setup(struct mv88e6xxx_chip *chip)
@@ -1818,6 +1825,160 @@ static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip *chip)
        return mv88e6xxx_stu_loadpurge(chip, &stu);
 }
 
+static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, u8 *sid)
+{
+       DECLARE_BITMAP(busy, MV88E6XXX_N_SID) = { 0 };
+       struct mv88e6xxx_mst *mst;
+
+       __set_bit(0, busy);
+
+       list_for_each_entry(mst, &chip->msts, node)
+               __set_bit(mst->stu.sid, busy);
+
+       *sid = find_first_zero_bit(busy, MV88E6XXX_N_SID);
+
+       return (*sid >= mv88e6xxx_max_sid(chip)) ? -ENOSPC : 0;
+}
+
+static int mv88e6xxx_mst_put(struct mv88e6xxx_chip *chip, u8 sid)
+{
+       struct mv88e6xxx_mst *mst, *tmp;
+       int err;
+
+       if (!sid)
+               return 0;
+
+       list_for_each_entry_safe(mst, tmp, &chip->msts, node) {
+               if (mst->stu.sid != sid)
+                       continue;
+
+               if (!refcount_dec_and_test(&mst->refcnt))
+                       return 0;
+
+               mst->stu.valid = false;
+               err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
+               if (err) {
+                       refcount_set(&mst->refcnt, 1);
+                       return err;
+               }
+
+               list_del(&mst->node);
+               kfree(mst);
+               return 0;
+       }
+
+       return -ENOENT;
+}
+
+static int mv88e6xxx_mst_get(struct mv88e6xxx_chip *chip, struct net_device *br,
+                            u16 msti, u8 *sid)
+{
+       struct mv88e6xxx_mst *mst;
+       int err, i;
+
+       if (!mv88e6xxx_has_stu(chip)) {
+               err = -EOPNOTSUPP;
+               goto err;
+       }
+
+       if (!msti) {
+               *sid = 0;
+               return 0;
+       }
+
+       list_for_each_entry(mst, &chip->msts, node) {
+               if (mst->br == br && mst->msti == msti) {
+                       refcount_inc(&mst->refcnt);
+                       *sid = mst->stu.sid;
+                       return 0;
+               }
+       }
+
+       err = mv88e6xxx_sid_get(chip, sid);
+       if (err)
+               goto err;
+
+       mst = kzalloc(sizeof(*mst), GFP_KERNEL);
+       if (!mst) {
+               err = -ENOMEM;
+               goto err;
+       }
+
+       INIT_LIST_HEAD(&mst->node);
+       refcount_set(&mst->refcnt, 1);
+       mst->br = br;
+       mst->msti = msti;
+       mst->stu.valid = true;
+       mst->stu.sid = *sid;
+
+       /* The bridge starts out all ports in the disabled state. But
+        * a STU state of disabled means to go by the port-global
+        * state. So we set all user port's initial state to blocking,
+        * to match the bridge's behavior.
+        */
+       for (i = 0; i < mv88e6xxx_num_ports(chip); i++)
+               mst->stu.state[i] = dsa_is_user_port(chip->ds, i) ?
+                       MV88E6XXX_PORT_CTL0_STATE_BLOCKING :
+                       MV88E6XXX_PORT_CTL0_STATE_DISABLED;
+
+       err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
+       if (err)
+               goto err_free;
+
+       list_add_tail(&mst->node, &chip->msts);
+       return 0;
+
+err_free:
+       kfree(mst);
+err:
+       return err;
+}
+
+static int mv88e6xxx_port_mst_state_set(struct dsa_switch *ds, int port,
+                                       const struct switchdev_mst_state *st)
+{
+       struct dsa_port *dp = dsa_to_port(ds, port);
+       struct mv88e6xxx_chip *chip = ds->priv;
+       struct mv88e6xxx_mst *mst;
+       u8 state;
+       int err;
+
+       if (!mv88e6xxx_has_stu(chip))
+               return -EOPNOTSUPP;
+
+       switch (st->state) {
+       case BR_STATE_DISABLED:
+       case BR_STATE_BLOCKING:
+       case BR_STATE_LISTENING:
+               state = MV88E6XXX_PORT_CTL0_STATE_BLOCKING;
+               break;
+       case BR_STATE_LEARNING:
+               state = MV88E6XXX_PORT_CTL0_STATE_LEARNING;
+               break;
+       case BR_STATE_FORWARDING:
+               state = MV88E6XXX_PORT_CTL0_STATE_FORWARDING;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       list_for_each_entry(mst, &chip->msts, node) {
+               if (mst->br == dsa_port_bridge_dev_get(dp) &&
+                   mst->msti == st->msti) {
+                       if (mst->stu.state[port] == state)
+                               return 0;
+
+                       mst->stu.state[port] = state;
+                       mv88e6xxx_reg_lock(chip);
+                       err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
+                       mv88e6xxx_reg_unlock(chip);
+                       return err;
+               }
+       }
+
+       return -ENOENT;
+}
+
 static int mv88e6xxx_port_check_hw_vlan(struct dsa_switch *ds, int port,
                                        u16 vid)
 {
@@ -2437,6 +2598,12 @@ static int mv88e6xxx_port_vlan_leave(struct mv88e6xxx_chip *chip,
        if (err)
                return err;
 
+       if (!vlan.valid) {
+               err = mv88e6xxx_mst_put(chip, vlan.sid);
+               if (err)
+                       return err;
+       }
+
        return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false);
 }
 
@@ -2482,6 +2649,69 @@ unlock:
        return err;
 }
 
+static int mv88e6xxx_port_vlan_fast_age(struct dsa_switch *ds, int port, u16 vid)
+{
+       struct mv88e6xxx_chip *chip = ds->priv;
+       struct mv88e6xxx_vtu_entry vlan;
+       int err;
+
+       mv88e6xxx_reg_lock(chip);
+
+       err = mv88e6xxx_vtu_get(chip, vid, &vlan);
+       if (err)
+               goto unlock;
+
+       err = mv88e6xxx_port_fast_age_fid(chip, port, vlan.fid);
+
+unlock:
+       mv88e6xxx_reg_unlock(chip);
+
+       return err;
+}
+
+static int mv88e6xxx_vlan_msti_set(struct dsa_switch *ds,
+                                  struct dsa_bridge bridge,
+                                  const struct switchdev_vlan_msti *msti)
+{
+       struct mv88e6xxx_chip *chip = ds->priv;
+       struct mv88e6xxx_vtu_entry vlan;
+       u8 old_sid, new_sid;
+       int err;
+
+       mv88e6xxx_reg_lock(chip);
+
+       err = mv88e6xxx_vtu_get(chip, msti->vid, &vlan);
+       if (err)
+               goto unlock;
+
+       if (!vlan.valid) {
+               err = -EINVAL;
+               goto unlock;
+       }
+
+       old_sid = vlan.sid;
+
+       err = mv88e6xxx_mst_get(chip, bridge.dev, msti->msti, &new_sid);
+       if (err)
+               goto unlock;
+
+       if (new_sid != old_sid) {
+               vlan.sid = new_sid;
+
+               err = mv88e6xxx_vtu_loadpurge(chip, &vlan);
+               if (err) {
+                       mv88e6xxx_mst_put(chip, new_sid);
+                       goto unlock;
+               }
+       }
+
+       err = mv88e6xxx_mst_put(chip, old_sid);
+
+unlock:
+       mv88e6xxx_reg_unlock(chip);
+       return err;
+}
+
 static int mv88e6xxx_port_fdb_add(struct dsa_switch *ds, int port,
                                  const unsigned char *addr, u16 vid,
                                  struct dsa_db db)
@@ -6008,6 +6238,7 @@ static struct mv88e6xxx_chip *mv88e6xxx_alloc_chip(struct device *dev)
        mutex_init(&chip->reg_lock);
        INIT_LIST_HEAD(&chip->mdios);
        idr_init(&chip->policies);
+       INIT_LIST_HEAD(&chip->msts);
 
        return chip;
 }
@@ -6540,10 +6771,13 @@ static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
        .port_pre_bridge_flags  = mv88e6xxx_port_pre_bridge_flags,
        .port_bridge_flags      = mv88e6xxx_port_bridge_flags,
        .port_stp_state_set     = mv88e6xxx_port_stp_state_set,
+       .port_mst_state_set     = mv88e6xxx_port_mst_state_set,
        .port_fast_age          = mv88e6xxx_port_fast_age,
+       .port_vlan_fast_age     = mv88e6xxx_port_vlan_fast_age,
        .port_vlan_filtering    = mv88e6xxx_port_vlan_filtering,
        .port_vlan_add          = mv88e6xxx_port_vlan_add,
        .port_vlan_del          = mv88e6xxx_port_vlan_del,
+       .vlan_msti_set          = mv88e6xxx_vlan_msti_set,
        .port_fdb_add           = mv88e6xxx_port_fdb_add,
        .port_fdb_del           = mv88e6xxx_port_fdb_del,
        .port_fdb_dump          = mv88e6xxx_port_fdb_dump,
index 6d4daa24d3e596fbfe82dad1e3bf9283e24ecb40..6a0b66354e1da808946b3efb0cc985c89643eb6a 100644 (file)
@@ -297,6 +297,16 @@ struct mv88e6xxx_region_priv {
        enum mv88e6xxx_region_id id;
 };
 
+struct mv88e6xxx_mst {
+       struct list_head node;
+
+       refcount_t refcnt;
+       struct net_device *br;
+       u16 msti;
+
+       struct mv88e6xxx_stu_entry stu;
+};
+
 struct mv88e6xxx_chip {
        const struct mv88e6xxx_info *info;
 
@@ -397,6 +407,9 @@ struct mv88e6xxx_chip {
 
        /* devlink regions */
        struct devlink_region *regions[_MV88E6XXX_REGION_MAX];
+
+       /* Bridge MST to SID mappings */
+       struct list_head msts;
 };
 
 struct mv88e6xxx_bus_ops {