return 0;
}
+/**
+ * rtldsa_mst_init() - Initialize newly "allocated" MST HW slot
+ * @priv: private data of rtldsa switch
+ * @mst_slot: MST slot of MSTI
+ */
+static void rtldsa_mst_init(struct rtl838x_switch_priv *priv, u16 mst_slot)
+ __must_hold(&priv->reg_mutex)
+{
+ struct dsa_port *dp;
+ unsigned int port;
+ u8 state;
+
+ dsa_switch_for_each_user_port(dp, priv->ds) {
+ if (dp->bridge)
+ state = BR_STATE_DISABLED;
+ else
+ state = dp->stp_state;
+
+ port = dp->index;
+
+ rtldsa_port_xstp_state_set(priv, port, state, mst_slot);
+ }
+}
+
+/**
+ * rtldsa_mst_find() - Find HW MST slot for MSTI (without reference counting)
+ * @priv: private data of rtldsa switch
+ * @msti: MSTI to search
+ *
+ * Return: found HW slot (unmodified reference count) or negative encoded error value
+ */
+static int rtldsa_mst_find(struct rtl838x_switch_priv *priv, u16 msti)
+ __must_hold(&priv->reg_mutex)
+{
+ unsigned int i;
+
+ /* CIST is always mapped to 0 */
+ if (msti == 0)
+ return 0;
+
+ if (msti > 4095)
+ return -EINVAL;
+
+ /* search for existing entry */
+ for (i = 0; i < priv->n_mst - 1; i++) {
+ if (priv->msts[i].msti != msti)
+ continue;
+
+ return i + 1;
+ }
+
+ return -ENOENT;
+}
+
+/**
+ * rtldsa_mst_get() - Get (or allocate) HW MST slot for MSTI
+ * @priv: private data of rtldsa switch
+ * @msti: MSTI for which a HW slot is needed
+ *
+ * Return: allocated slot (with increased reference count) or negative encoded error value
+ */
+static int rtldsa_mst_get(struct rtl838x_switch_priv *priv, u16 msti)
+ __must_hold(&priv->reg_mutex)
+{
+ unsigned int i;
+ int ret;
+
+ ret = rtldsa_mst_find(priv, msti);
+
+ /* CIST doesn't need reference counting */
+ if (ret == 0)
+ return ret;
+
+ /* valid HW slot was found - refcount needs to be adjusted */
+ if (ret > 0) {
+ u16 index = ret - 1;
+
+ kref_get(&priv->msts[index].refcount);
+ return ret;
+ }
+
+ /* any error except "no entry found" cannot be handled */
+ if (ret != -ENOENT)
+ return ret;
+
+ /* search for free slot */
+ for (i = 0; i < priv->n_mst - 1; i++) {
+ if (priv->msts[i].msti != 0)
+ continue;
+
+ kref_init(&priv->msts[i].refcount);
+ priv->msts[i].msti = msti;
+
+ rtldsa_mst_init(priv, i + 1);
+ return i + 1;
+ }
+
+ return -ENOSPC;
+}
+
+/**
+ * rtldsa_mst_recycle_slot() - Try to recycle old MST slot in case of -ENOSPC of rtldsa_mst_get()
+ * @priv: private data of rtldsa switch
+ * @msti: MSTI for which a HW slot is needed
+ * @old_mst_slot: old mst slot which will be released "soon"
+ *
+ * If a VLAN should be moved from one MSTI to another one, it is possible that there are currently
+ * not enough slots still available to perform a get+put operation. But if this slot is used
+ * by a single VLAN anyway, it is not needed to really allocate a new slow - reassigning it to
+ * the new MSTI is good enough.
+ *
+ * This is only allowed when holding the reg_mutex over both calls rtldsa_mst_get() and
+ * rtldsa_mst_recycle(). After a rtldsa_mst_recycle() call, rtldsa_mst_put_slot() must no longer
+ * be called for @old_mst_slot.
+ *
+ * Return: allocated slot (with increased reference count) or negative encoded error value
+ */
+static int rtldsa_mst_recycle_slot(struct rtl838x_switch_priv *priv, u16 msti, u16 old_mst_slot)
+ __must_hold(&priv->reg_mutex)
+{
+ u16 index;
+
+ /* CIST is always mapped to 0 */
+ if (msti == 0)
+ return 0;
+
+ if (old_mst_slot == 0)
+ return -ENOSPC;
+
+ if (msti > 4095)
+ return -EINVAL;
+
+ if (old_mst_slot >= priv->n_mst)
+ return -EINVAL;
+
+ index = old_mst_slot - 1;
+
+ /* this slot is unused - should not happen because rtldsa_mst_get() searches for it */
+ if (priv->msts[index].msti == 0)
+ return -EINVAL;
+
+ /* it is only allowed to swap when no other VLAN is using this MST slot */
+ if (kref_read(&priv->msts[index].refcount) != 1)
+ return -ENOSPC;
+
+ priv->msts[index].msti = msti;
+ return old_mst_slot;
+}
+
+static void rtldsa_mst_release_slot(struct kref *ref)
+{
+ struct rtldsa_mst *slot = container_of(ref, struct rtldsa_mst, refcount);
+
+ slot->msti = 0;
+}
+
+/**
+ * rtldsa_mst_put_slot() - Decrement VLAN use counter for MST slot
+ * @priv: private data of rtldsa switch
+ * @mst_slot: MST slot which should be put
+ *
+ * Return: false when MST slot reference counter was only decreased or an invalid @mst_slot was
+ * given, true when @mst_slot is now unused
+ */
+static bool rtldsa_mst_put_slot(struct rtl838x_switch_priv *priv, u16 mst_slot)
+ __must_hold(&priv->reg_mutex)
+{
+ unsigned int index;
+
+ /* CIST is always mapped to 0 and cannot be put */
+ if (mst_slot == 0)
+ return 0;
+
+ if (mst_slot >= priv->n_mst)
+ return 0;
+
+ index = mst_slot - 1;
+
+ /* this slot is unused and must not release a reference */
+ if (priv->msts[index].msti == 0)
+ return 0;
+
+ return kref_put(&priv->msts[index].refcount, rtldsa_mst_release_slot);
+}
+
+/**
+ * rtldsa_mst_replace() - Get HW slot for @msti and drop old HW slot
+ * @priv: private data of rtldsa switch
+ * @msti: MSTI for which a HW slot is needed
+ * @old_mst_slot: old mst slot which will no longer be assigned to VLAN
+ *
+ * Return: allocated slot (with increased reference count) or negative encoded error value
+ */
+static int rtldsa_mst_replace(struct rtl838x_switch_priv *priv, u16 msti, u16 old_mst_slot)
+ __must_hold(&priv->reg_mutex)
+{
+ int mst_slot_new;
+
+ mst_slot_new = rtldsa_mst_get(priv, msti);
+ if (mst_slot_new == -ENOSPC)
+ return rtldsa_mst_recycle_slot(priv, msti, old_mst_slot);
+
+ /* directly return errors and don't free old slot */
+ if (mst_slot_new < 0)
+ return mst_slot_new;
+
+ rtldsa_mst_put_slot(priv, old_mst_slot);
+
+ return mst_slot_new;
+}
+
static void rtldsa_update_port_member(struct rtl838x_switch_priv *priv, int port,
const struct net_device *bridge_dev, bool join)
__must_hold(&priv->reg_mutex)
const struct switchdev_mst_state *st)
{
struct rtl838x_switch_priv *priv = ds->priv;
+ int mst_slot;
mutex_lock(&priv->reg_mutex);
- rtldsa_port_xstp_state_set(priv, port, st->state, st->msti);
+
+ mst_slot = rtldsa_mst_find(priv, st->msti);
+ if (mst_slot < 0) {
+ mutex_unlock(&priv->reg_mutex);
+ return mst_slot;
+ }
+
+ rtldsa_port_xstp_state_set(priv, port, st->state, mst_slot);
mutex_unlock(&priv->reg_mutex);
return 0;
info.member_ports &= (~BIT_ULL(port));
/* VLANs without members are set back (implicitly) to CIST by DSA */
- if (!info.member_ports)
+ if (!info.member_ports) {
+ u16 mst = info.fid;
info.fid = 0;
+ rtldsa_mst_put_slot(priv, mst);
+ }
+
priv->r->vlan_set_untagged(vlan->vid, info.untagged_ports);
pr_debug("Untagged ports, VLAN %d: %llx\n", vlan->vid, info.untagged_ports);
{
struct rtl838x_switch_priv *priv = ds->priv;
struct rtl838x_vlan_info info;
- int mst_slot = msti->msti;
-
- if (mst_slot >= priv->n_mst)
- return -EINVAL;
+ u16 mst_slot_old;
+ int mst_slot;
priv->r->vlan_tables_read(msti->vid, &info);
+ mst_slot_old = info.fid;
+
+ /* find HW slot for MSTI */
+ mutex_lock(&priv->reg_mutex);
+ mst_slot = rtldsa_mst_replace(priv, msti->msti, mst_slot_old);
+ mutex_unlock(&priv->reg_mutex);
+
+ if (mst_slot < 0)
+ return mst_slot;
+
info.fid = mst_slot;
priv->r->vlan_set_tagged(msti->vid, &info);