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 <[email protected]>
---
 drivers/net/dsa/mv88e6xxx/chip.c | 169 +++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/chip.h |  13 +++
 2 files changed, 182 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index 39cf1bae161e..7d9ef041252d 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -1817,6 +1817,128 @@ static int mv88e6xxx_stu_setup(struct mv88e6xxx_chip 
*chip)
        return mv88e6xxx_stu_loadpurge(chip, &stu);
 }
 
+static int mv88e6xxx_sid_new(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_sid_put(struct mv88e6xxx_chip *chip, u8 sid)
+{
+       struct mv88e6xxx_mst *mst, *tmp;
+       int err = 0;
+
+       list_for_each_entry_safe(mst, tmp, &chip->msts, node) {
+               if (mst->stu.sid == sid) {
+                       if (refcount_dec_and_test(&mst->refcnt)) {
+                               mst->stu.valid = false;
+                               err = mv88e6xxx_stu_loadpurge(chip, &mst->stu);
+                               list_del(&mst->node);
+                               kfree(mst);
+                       }
+
+                       return err;
+               }
+       }
+
+       return -ENOENT;
+}
+
+static int mv88e6xxx_sid_get(struct mv88e6xxx_chip *chip, struct net_device 
*br,
+                            u16 mstid, u8 *sid)
+{
+       struct mv88e6xxx_mst *mst;
+       int err;
+
+       if (!br)
+               return 0;
+
+       if (!mv88e6xxx_has_stu(chip))
+               return -EOPNOTSUPP;
+
+       list_for_each_entry(mst, &chip->msts, node) {
+               if (mst->br == br && mst->mstid == mstid) {
+                       refcount_inc(&mst->refcnt);
+                       *sid = mst->stu.sid;
+                       return 0;
+               }
+       }
+
+       err = mv88e6xxx_sid_new(chip, sid);
+       if (err)
+               return err;
+
+       mst = kzalloc(sizeof(*mst), GFP_KERNEL);
+       if (!mst)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&mst->node);
+       refcount_set(&mst->refcnt, 1);
+       mst->br = br;
+       mst->mstid = mstid;
+       mst->stu.valid = true;
+       mst->stu.sid = *sid;
+       list_add_tail(&mst->node, &chip->msts);
+       return mv88e6xxx_stu_loadpurge(chip, &mst->stu);
+}
+
+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:
+               state = MV88E6XXX_PORT_CTL0_STATE_DISABLED;
+               break;
+       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->mstid == st->mstid) {
+                       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)
 {
@@ -2436,6 +2558,12 @@ static int mv88e6xxx_port_vlan_leave(struct 
mv88e6xxx_chip *chip,
        if (err)
                return err;
 
+       if (!vlan.valid) {
+               err = mv88e6xxx_sid_put(chip, vlan.sid);
+               if (err)
+                       return err;
+       }
+
        return mv88e6xxx_g1_atu_remove(chip, vlan.fid, port, false);
 }
 
@@ -2474,6 +2602,44 @@ static int mv88e6xxx_port_vlan_del(struct dsa_switch 
*ds, int port,
        return err;
 }
 
+static int mv88e6xxx_vlan_mstid_set(struct dsa_switch *ds,
+                                   const struct switchdev_attr *attr)
+{
+       const struct switchdev_vlan_attr *vattr = &attr->u.vlan_attr;
+       struct mv88e6xxx_chip *chip = ds->priv;
+       struct mv88e6xxx_vtu_entry vlan;
+       u8 new_sid;
+       int err;
+
+       mv88e6xxx_reg_lock(chip);
+
+       err = mv88e6xxx_vtu_get(chip, vattr->vid, &vlan);
+       if (err)
+               goto unlock;
+
+       if (!vlan.valid) {
+               err = -EINVAL;
+               goto unlock;
+       }
+
+       err = mv88e6xxx_sid_get(chip, attr->orig_dev, vattr->mstid, &new_sid);
+       if (err)
+               goto unlock;
+
+       if (vlan.sid) {
+               err = mv88e6xxx_sid_put(chip, vlan.sid);
+               if (err)
+                       goto unlock;
+       }
+
+       vlan.sid = new_sid;
+       err = mv88e6xxx_vtu_loadpurge(chip, &vlan);
+
+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)
 {
@@ -5996,6 +6162,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;
 }
@@ -6518,10 +6685,12 @@ 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_filtering    = mv88e6xxx_port_vlan_filtering,
        .port_vlan_add          = mv88e6xxx_port_vlan_add,
        .port_vlan_del          = mv88e6xxx_port_vlan_del,
+       .vlan_mstid_set         = mv88e6xxx_vlan_mstid_set,
        .port_fdb_add           = mv88e6xxx_port_fdb_add,
        .port_fdb_del           = mv88e6xxx_port_fdb_del,
        .port_fdb_dump          = mv88e6xxx_port_fdb_dump,
diff --git a/drivers/net/dsa/mv88e6xxx/chip.h b/drivers/net/dsa/mv88e6xxx/chip.h
index 6d4daa24d3e5..af0f53b65689 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.h
+++ b/drivers/net/dsa/mv88e6xxx/chip.h
@@ -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 mstid;
+
+       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 {
-- 
2.25.1

Reply via email to