diff --git a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c index 5d5e282dc2a..bb4e4f1a27d 100644 --- a/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c +++ b/target/linux/realtek/files-6.18/drivers/net/dsa/rtl83xx/common.c @@ -634,6 +634,85 @@ void rtldsa_packet_cntr_free(struct rtl838x_switch_priv *priv, int idx) } } +/* The count pins at its ceiling rather than wrapping: an entry that reached it + * is never freed by the driver again, which leaks a row but never hands a live + * one to somebody else. + */ +static void rtldsa_l2_uc_get(struct rtl838x_switch_priv *priv, int idx) +{ + struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx); + + if (!m) + return; + + if (m->l3_refcount == RTLDSA_L2_L3_REFCOUNT_MAX) { + dev_warn_once(priv->dev, "L2 entry %d has too many routes to count\n", idx); + return; + } + + m->l3_refcount++; +} + +static void rtldsa_l2_uc_put(struct rtl838x_switch_priv *priv, int idx) +{ + struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx); + + if (!m || !m->l3_refcount || m->l3_refcount == RTLDSA_L2_L3_REFCOUNT_MAX) + return; + + m->l3_refcount--; +} + +/* Give the row back once nothing forwards through it any more. @e has to be a + * fresh read of the row at nh->l2_id. + */ +static void rtldsa_l2_uc_release_row(struct rtl838x_switch_priv *priv, + struct otto_l3_nexthop *nh, + struct rtl838x_l2_entry *e) +{ + struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, nh->l2_id); + + /* Another route is still forwarding through this entry: it has to stay + * exactly as it is, next hop and route id included. + */ + if (m && m->l3_refcount) + return; + + /* The bridge put this address here as well, so the entry stays; it + * just stops being a next hop. + */ + if (e->is_static && (!m || !m->fdb_ref)) + e->valid = false; + e->next_hop = false; + /* A route id takes that field on the families that keep one, so what + * goes back is the relay VID, which the row still carries either way. + */ + e->vid = e->rvid; + + priv->r->write_l2_entry_using_hash(nh->l2_id >> 2, nh->l2_id & 0x3, e); +} + +/* Release a reference taken on a remembered index. The switch drops rows on + * its own, by ageing and by the per-port flush the bridge asks for, and + * whoever claims one next counts itself from zero: that count is not ours to + * spend. Search on the seed the reference was taken on, because the caller has + * already overwritten the address. + */ +static void rtldsa_l2_uc_put_row(struct rtl838x_switch_priv *priv, + struct otto_l3_nexthop *nh) +{ + struct rtl838x_l2_entry e = {}; + + if (rtldsa_find_l2_hash_entry(priv, nh->l2_seed, true, &e) != nh->l2_id) + return; + + if (!e.next_hop) + return; + + rtldsa_l2_uc_put(priv, nh->l2_id); + rtldsa_l2_uc_release_row(priv, nh, &e); +} + /* Add an L2 nexthop entry for the L3 routing system / PIE forwarding in the SoC * Use VID and MAC in rtl838x_l2_entry to identify either a free slot in the L2 hash table * or mark an existing entry as a nexthop by setting it's nexthop bit @@ -649,6 +728,11 @@ int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexth pr_debug("%s searching for %08llx vid %d, seed: %016llx\n", __func__, nh->mac, nh->rvid, seed); + /* The search, the count and the write are one step: anything else may + * claim the entry we settled on in between. + */ + guard(mutex)(&priv->reg_mutex); + idx = rtldsa_find_l2_hash_entry(priv, seed, false, &e); if (idx < 0) { pr_err("%s: No more L2 forwarding entries available\n", __func__); @@ -656,14 +740,19 @@ int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexth } /* Found an existing (e->valid is true) or empty entry, make it a nexthop entry */ + if (nh->l2_installed && nh->l2_id != idx) + rtldsa_l2_uc_put_row(priv, nh); + if (!nh->l2_installed || nh->l2_id != idx) { struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx); - /* An entry nobody had claimed carries whatever its last - * owner left behind. + /* An entry nobody had claimed carries whatever its last owner + * left behind, including a count for a route long gone. */ if (m && !e.valid) *m = (struct rtldsa_l2_uc){}; + + rtldsa_l2_uc_get(priv, idx); } nh->l2_id = idx; @@ -700,11 +789,12 @@ int rtldsa_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexth int rtldsa_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh) { struct rtl838x_l2_entry e = {}; - struct rtldsa_l2_uc *m; u32 key = nh->l2_id >> 2; int i = nh->l2_id & 0x3; int idx; + guard(mutex)(&priv->reg_mutex); + dev_dbg(priv->dev, "next hop %d sits at key %d, index %d\n", nh->l2_id, key, i); /* Search on the seed the installer claimed the row on, because the @@ -712,8 +802,6 @@ int rtldsa_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexth * recorded index answers both questions at once: the row still holds * the address that was installed, and it is still the same row. A * negative index means the address has left the bucket altogether. - * Nothing counts the routes sharing a next hop yet, so a sibling taken - * down first can get here. */ idx = rtldsa_find_l2_hash_entry(priv, nh->l2_seed, true, &e); if (idx != nh->l2_id) { @@ -728,20 +816,8 @@ int rtldsa_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexth return -ESTALE; } - m = rtldsa_l2_uc_lookup(priv, nh->l2_id); - - /* The bridge put this address here as well, so the entry stays; it - * just stops being a next hop. - */ - if (e.is_static && (!m || !m->fdb_ref)) - e.valid = false; - e.next_hop = false; - /* A route id takes that field on the families that keep one, so what - * goes back is the relay VID, which the row still carries either way. - */ - e.vid = e.rvid; - - priv->r->write_l2_entry_using_hash(key, i, &e); + rtldsa_l2_uc_put(priv, nh->l2_id); + rtldsa_l2_uc_release_row(priv, nh, &e); return 0; }