From: Sven Eckelmann Date: Mon, 27 Oct 2025 16:19:24 +0000 (+0100) Subject: realtek: dsa: Add MSTI to HW MST ID mapping X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=44976dcac4687bfec84a5317c53d9b14ffc78735;p=openwrt%2Fstaging%2Fxback.git realtek: dsa: Add MSTI to HW MST ID mapping The MSTI range is 0..4095 but the HW range is only supporting a lower range - for example 0..63 for RTL930x. But the HW doesn't really need to know the actual MSTI. It is therefore possible to use a mapping from MSTI to HW slot to allow a larger range of MSTIs. Since the CIST (MSTI 0) is always needed, the mapping data structure is skipping this entry and is always keeping the HW slot 0 for CIST. This doesn't increase the total number of MSTIs a HW supports. Suggested-by: Jonas Gorski Signed-off-by: Sven Eckelmann Link: https://github.com/openwrt/openwrt/pull/20421 Signed-off-by: Hauke Mehrtens --- diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/common.c b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/common.c index dfb4c2f24c..98559c7c36 100644 --- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/common.c +++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/common.c @@ -1542,6 +1542,12 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev) return err; } + priv->msts = devm_kcalloc(priv->dev, + priv->n_mst - 1, sizeof(struct rtldsa_mst), + GFP_KERNEL); + if (!priv->msts) + return -ENOMEM; + priv->wq = create_singlethread_workqueue("rtl83xx"); if (!priv->wq) { dev_err(dev, "Error creating workqueue: %d\n", err); diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c index cc29fd1ba6..160e29af80 100644 --- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c +++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/dsa.c @@ -1453,6 +1453,217 @@ static int rtl83xx_set_ageing_time(struct dsa_switch *ds, unsigned int msec) 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) @@ -1681,9 +1892,17 @@ static int rtldsa_port_mst_state_set(struct dsa_switch *ds, int port, 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; @@ -1860,9 +2079,13 @@ static int rtl83xx_vlan_del(struct dsa_switch *ds, int port, 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); @@ -1894,12 +2117,20 @@ static int rtldsa_vlan_msti_set(struct dsa_switch *ds, struct dsa_bridge bridge, { 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); diff --git a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h index c2c166d545..52c0819dc0 100644 --- a/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h +++ b/target/linux/realtek/files-6.12/drivers/net/dsa/rtl83xx/rtl838x.h @@ -781,6 +781,14 @@ struct rtl838x_vlan_info { int l2_tunnel_list_id; }; +struct rtldsa_mst { + /** @msti: MSTI mapped to this slot. 0 == unused */ + u16 msti; + + /** @refcount: number of vlans currently using this msti, undefined when unused */ + struct kref refcount; +}; + enum l2_entry_type { L2_INVALID = 0, L2_UNICAST = 1, @@ -1251,6 +1259,12 @@ struct rtl838x_switch_priv { struct rtl838x_l3_intf *interfaces[MAX_INTERFACES]; u16 intf_mtus[MAX_INTF_MTUS]; int intf_mtu_count[MAX_INTF_MTUS]; + + /** + * @msts: MSTI to HW MST slot allocations. index 0 is for HW slot 1 because CIST is + * not stored in @msts + */ + struct rtldsa_mst *msts; struct delayed_work counters_work; };