realtek: dsa: consolidate L2 helpers in l2.c

Move the hash index, hash/CAM lookup, unicast reference lookup and
ageing-time helpers into l2.c. This removes the dependency of L2 code
on lookup helpers implemented in dsa.c.

Rename the moved functions with the otto_l2_ prefix, place their
public declarations in l2.h and update includes and callers.

Leave DSA callbacks and port/bridge handling in place.
No functional changes intended.

Assisted-by: ChatGPT (OpenAI GPT-6 Astra)
Link: https://github.com/openwrt/openwrt/pull/25802
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
This commit is contained in:
Markus Stockhausen committed 2026-10-11 13:41:18 +02:00
1 parent 174f1180a4
commit 74521f894e
5 files changed
+143 -136

No files matched your search

@@ -201,28 +201,6 @@ static int rtldsa_ports_probe(struct rtl838x_switch_priv *priv)
return 0;
}
static int rtl83xx_get_l2aging(struct rtl838x_switch_priv *priv)
{
int t = sw_r32(priv->r->l2_ctrl_1);
/* RTL838x uses a high-resolution 23-bit AGE_UNIT where one unit is
* 204.8 ms. RTL839x and RTL93xx use a 21-bit AGE_UNIT where one unit
* is 600 ms. An AGE_UNIT value of 0 disables dynamic address aging.
*/
if (priv->r->high_res_l2_age) {
t &= GENMASK(22, 0);
t = t * 128 / 625;
} else {
t &= GENMASK(20, 0);
t = t * 3 / 5;
}
pr_debug("L2 AGING time: %d sec\n", t);
pr_debug("Dynamic aging for ports: %x\n", sw_r32(priv->r->l2_port_aging_out));
return t;
}
// Currently Unused
// /* Allocate a 64 bit octet counter located in the LOG HW table */
// static int rtl83xx_octet_cntr_alloc(struct rtl838x_switch_priv *priv)
@@ -519,7 +497,7 @@ static int rtl83xx_sw_probe(struct platform_device *pdev)
if (soc_info.family != RTL9310_FAMILY_ID)
sw_w32(0x1, priv->r->imr_glb);
rtl83xx_get_l2aging(priv);
otto_l2_get_ageing_time(priv);
if (priv->r->qos_init)
priv->r->qos_init(priv);
@@ -797,87 +797,6 @@ static void rtldsa_setup_l2_mc_entry(struct rtl838x_l2_entry *e, int vid, u64 ma
u64_to_ether_addr(mac, e->mac);
}
static int rtldsa_l2_hash_index(u32 key, int slot)
{
return slot > 3 ? ((key >> 14) & 0xffff) | (slot & 3) : ((key << 2) | slot) & 0xffff;
}
/* Uses the seed to identify a hash bucket in the L2 using the derived hash key and then loops
* over the entries in the bucket until either a matching entry is found or an empty slot
* Returns the filled in rtl838x_l2_entry and the index in the bucket when an entry was found
* when an empty slot was found and must exist is false, the index of the slot is returned
* when no slots are available returns -1
*/
int rtldsa_find_l2_hash_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e)
{
u32 key = priv->r->l2_hash_key(priv, seed);
int free_slot = -1;
u64 entry;
pr_debug("%s: using key %x, for seed %016llx\n", __func__, key, seed);
/* Loop over all entries in the hash-bucket and over the second block on 93xx SoCs */
for (int i = 0; i < priv->r->l2_bucket_size; i++) {
entry = priv->r->read_l2_entry_using_hash(key, i, e);
pr_debug("valid %d, mac %016llx\n", e->valid, ether_addr_to_u64(&e->mac[0]));
/* Remember the first free slot, but keep looking: the address
* may be sitting further down the bucket.
*/
if (!e->valid) {
if (free_slot < 0)
free_slot = i;
continue;
}
if ((entry & 0x0fffffffffffffffULL) == seed)
return rtldsa_l2_hash_index(key, i);
}
if (must_exist || free_slot < 0)
return -1;
/* The loop leaves the last entry it read here, valid bit included.
* Reading the free slot back does not undo that on RTL930x and
* RTL931x, whose readers return as soon as the valid bit is clear.
*/
memset(e, 0, sizeof(*e));
return rtldsa_l2_hash_index(key, free_slot);
}
/* Uses the seed to identify an entry in the CAM by looping over all its entries
* Returns the filled in rtl838x_l2_entry and the index in the CAM when an entry was found
* when an empty slot was found the index of the slot is returned
* when no slots are available returns -1
*/
static int rtldsa_find_l2_cam_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e)
{
int idx = -1;
int cam_rows;
u64 entry;
cam_rows = otto_table_rows(priv->r->l2_cam_tbl);
if (cam_rows < 0)
return -1;
for (int i = 0; i < cam_rows; i++) {
entry = priv->r->read_cam(i, e);
if (!must_exist && !e->valid) {
if (idx < 0) /* First empty entry? */
idx = i;
break;
} else if ((entry & 0x0fffffffffffffffULL) == seed) {
pr_debug("Found entry in CAM\n");
idx = i;
break;
}
}
return idx;
}
static int rtldsa_port_fdb_add(struct dsa_switch *ds, int port,
const unsigned char *addr, u16 vid,
const struct dsa_db db)
@@ -901,11 +820,11 @@ static int rtldsa_port_fdb_add(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
idx = rtldsa_find_l2_hash_entry(priv, seed, false, &e);
idx = otto_l2_find_hash_entry(priv, seed, false, &e);
/* Found an existing or empty entry */
if (idx >= 0) {
struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx);
struct rtldsa_l2_uc *m = otto_l2_uc_lookup(priv, idx);
if (m) {
/* A slot nobody had claimed carries whatever its last
@@ -922,7 +841,7 @@ static int rtldsa_port_fdb_add(struct dsa_switch *ds, int port,
}
/* Hash buckets full, try CAM */
idx = rtldsa_find_l2_cam_entry(priv, seed, false, &e);
idx = otto_l2_find_cam_entry(priv, seed, false, &e);
if (idx >= 0) {
rtldsa_setup_l2_uc_entry(&e, port, vid, mac);
@@ -956,10 +875,10 @@ static int rtldsa_port_fdb_del(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
idx = rtldsa_find_l2_hash_entry(priv, seed, true, &e);
idx = otto_l2_find_hash_entry(priv, seed, true, &e);
if (idx >= 0) {
struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx);
struct rtldsa_l2_uc *m = otto_l2_uc_lookup(priv, idx);
pr_debug("Found entry index %d, key %d and bucket %d\n", idx, idx >> 2, idx & 3);
@@ -982,7 +901,7 @@ static int rtldsa_port_fdb_del(struct dsa_switch *ds, int port,
}
/* Check CAM for spillover from hash buckets */
idx = rtldsa_find_l2_cam_entry(priv, seed, true, &e);
idx = otto_l2_find_cam_entry(priv, seed, true, &e);
if (idx >= 0) {
e.valid = false;
@@ -1091,7 +1010,7 @@ static int rtldsa_83xx_port_mdb_add(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
idx = rtldsa_find_l2_hash_entry(priv, seed, false, &e);
idx = otto_l2_find_hash_entry(priv, seed, false, &e);
/* Found an existing or empty entry */
if (idx >= 0) {
@@ -1113,7 +1032,7 @@ static int rtldsa_83xx_port_mdb_add(struct dsa_switch *ds, int port,
}
/* Hash buckets full, try CAM */
idx = rtldsa_find_l2_cam_entry(priv, seed, false, &e);
idx = otto_l2_find_cam_entry(priv, seed, false, &e);
if (idx >= 0) {
if (e.valid) {
@@ -1177,7 +1096,7 @@ static int rtldsa_port_mdb_del(struct dsa_switch *ds, int port,
mutex_lock(&priv->reg_mutex);
idx = rtldsa_find_l2_hash_entry(priv, seed, true, &e);
idx = otto_l2_find_hash_entry(priv, seed, true, &e);
if (idx >= 0) {
pr_debug("Found entry index %d, key %d and bucket %d\n", idx, idx >> 2, idx & 3);
@@ -1190,7 +1109,7 @@ static int rtldsa_port_mdb_del(struct dsa_switch *ds, int port,
}
/* Check CAM for spillover from hash buckets */
idx = rtldsa_find_l2_cam_entry(priv, seed, true, &e);
idx = otto_l2_find_cam_entry(priv, seed, true, &e);
if (idx >= 0) {
portmask = rtldsa_mc_group_del_port(priv, e.mc_portmask_index, port);
@@ -5,6 +5,8 @@
#include <linux/debugfs.h>
#include <linux/etherdevice.h>
#include <linux/seq_file.h>
#include <linux/soc/realtek/otto_table.h>
#include <linux/string.h>
#include "l2.h"
#include "l3.h"
@@ -56,13 +58,127 @@
#define RTL839X_SPCL_TRAP_ARP_CTRL (0x1060)
int otto_l2_get_ageing_time(struct rtl838x_switch_priv *priv)
{
int t = sw_r32(priv->r->l2_ctrl_1);
/* RTL838x uses a high-resolution 23-bit AGE_UNIT where one unit is
* 204.8 ms. RTL839x and RTL93xx use a 21-bit AGE_UNIT where one unit
* is 600 ms. An AGE_UNIT value of 0 disables dynamic address aging.
*/
if (priv->r->high_res_l2_age) {
t &= GENMASK(22, 0);
t = t * 128 / 625;
} else {
t &= GENMASK(20, 0);
t = t * 3 / 5;
}
pr_debug("L2 AGING time: %d sec\n", t);
pr_debug("Dynamic aging for ports: %x\n", sw_r32(priv->r->l2_port_aging_out));
return t;
}
static int otto_l2_hash_index(u32 key, int slot)
{
return slot > 3 ? ((key >> 14) & 0xffff) | (slot & 3) : ((key << 2) | slot) & 0xffff;
}
/* Uses the seed to identify a hash bucket in the L2 using the derived hash key and then loops
* over the entries in the bucket until either a matching entry is found or an empty slot
* Returns the filled in rtl838x_l2_entry and the index in the bucket when an entry was found
* when an empty slot was found and must exist is false, the index of the slot is returned
* when no slots are available returns -1
*/
int otto_l2_find_hash_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e)
{
u32 key = priv->r->l2_hash_key(priv, seed);
int free_slot = -1;
u64 entry;
pr_debug("%s: using key %x, for seed %016llx\n", __func__, key, seed);
/* Loop over all entries in the hash-bucket and over the second block on 93xx SoCs */
for (int i = 0; i < priv->r->l2_bucket_size; i++) {
entry = priv->r->read_l2_entry_using_hash(key, i, e);
pr_debug("valid %d, mac %016llx\n", e->valid, ether_addr_to_u64(&e->mac[0]));
/* Remember the first free slot, but keep looking: the address
* may be sitting further down the bucket.
*/
if (!e->valid) {
if (free_slot < 0)
free_slot = i;
continue;
}
if ((entry & 0x0fffffffffffffffULL) == seed)
return otto_l2_hash_index(key, i);
}
if (must_exist || free_slot < 0)
return -1;
/* The loop leaves the last entry it read here, valid bit included.
* Reading the free slot back does not undo that on RTL930x and
* RTL931x, whose readers return as soon as the valid bit is clear.
*/
memset(e, 0, sizeof(*e));
return otto_l2_hash_index(key, free_slot);
}
/* Uses the seed to identify an entry in the CAM by looping over all its entries
* Returns the filled in rtl838x_l2_entry and the index in the CAM when an entry was found
* when an empty slot was found the index of the slot is returned
* when no slots are available returns -1
*/
int otto_l2_find_cam_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e)
{
int idx = -1;
int cam_rows;
u64 entry;
cam_rows = otto_table_rows(priv->r->l2_cam_tbl);
if (cam_rows < 0)
return -1;
for (int i = 0; i < cam_rows; i++) {
entry = priv->r->read_cam(i, e);
if (!must_exist && !e->valid) {
if (idx < 0) /* First empty entry? */
idx = i;
break;
} else if ((entry & 0x0fffffffffffffffULL) == seed) {
pr_debug("Found entry in CAM\n");
idx = i;
break;
}
}
return idx;
}
/* RTL931x hashes its second block into rows the fib_entries count does not
* reach, so an index can fall outside the map and is simply not tracked.
*/
struct rtldsa_l2_uc *otto_l2_uc_lookup(struct rtl838x_switch_priv *priv, int idx)
{
if (idx < 0 || idx >= priv->r->fib_entries)
return NULL;
return &priv->l2_uc_map[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 otto_l2_uc_get(struct rtl838x_switch_priv *priv, int idx)
{
struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx);
struct rtldsa_l2_uc *m = otto_l2_uc_lookup(priv, idx);
if (!m)
return;
@@ -77,7 +193,7 @@ static void otto_l2_uc_get(struct rtl838x_switch_priv *priv, int idx)
static void otto_l2_uc_put(struct rtl838x_switch_priv *priv, int idx)
{
struct rtldsa_l2_uc *m = rtldsa_l2_uc_lookup(priv, idx);
struct rtldsa_l2_uc *m = otto_l2_uc_lookup(priv, idx);
if (!m || !m->l3_refcount || m->l3_refcount == RTLDSA_L2_L3_REFCOUNT_MAX)
return;
@@ -92,7 +208,7 @@ static void otto_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);
struct rtldsa_l2_uc *m = otto_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.
@@ -126,7 +242,7 @@ static void otto_l2_uc_put_row(struct rtl838x_switch_priv *priv,
{
struct rtl838x_l2_entry e = {};
if (rtldsa_find_l2_hash_entry(priv, nh->l2_seed, true, &e) != nh->l2_id)
if (otto_l2_find_hash_entry(priv, nh->l2_seed, true, &e) != nh->l2_id)
return;
if (!e.next_hop)
@@ -157,7 +273,7 @@ int otto_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop
*/
guard(mutex)(&priv->reg_mutex);
idx = rtldsa_find_l2_hash_entry(priv, seed, false, &e);
idx = otto_l2_find_hash_entry(priv, seed, false, &e);
if (idx < 0) {
pr_err("%s: No more L2 forwarding entries available\n", __func__);
return -1;
@@ -183,7 +299,7 @@ int otto_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop
otto_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);
struct rtldsa_l2_uc *m = otto_l2_uc_lookup(priv, idx);
/* An entry nobody had claimed carries whatever its last owner
* left behind, including a count for a route long gone.
@@ -252,7 +368,7 @@ int otto_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop
* the address that was installed, and it is still the same row. A
* negative index means the address has left the bucket altogether.
*/
idx = rtldsa_find_l2_hash_entry(priv, nh->l2_seed, true, &e);
idx = otto_l2_find_hash_entry(priv, nh->l2_seed, true, &e);
if (idx != nh->l2_id) {
dev_err(priv->dev, "next hop %d is at %d now, not removing it\n",
nh->l2_id, idx);
@@ -7,6 +7,7 @@
struct rtl838x_switch_priv;
struct rtl838x_l2_entry;
struct rtldsa_l2_uc;
struct otto_l3_nexthop;
enum rtldsa_flood_type {
@@ -58,6 +59,14 @@ enum rtldsa_flood_type {
#define RTLDSA_L2_L3_REFCOUNT_MAX 0x7f
/* Returns the L2 ageing time in seconds. */
int otto_l2_get_ageing_time(struct rtl838x_switch_priv *priv);
int otto_l2_find_hash_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e);
int otto_l2_find_cam_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e);
struct rtldsa_l2_uc *otto_l2_uc_lookup(struct rtl838x_switch_priv *priv, int idx);
int otto_l2_nexthop_add(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh,
bool require_existing);
int otto_l2_nexthop_del(struct rtl838x_switch_priv *priv, struct otto_l3_nexthop *nh);
@@ -611,19 +611,4 @@ extern struct rtl83xx_soc_info soc_info;
void rtl838x_dbgfs_init(struct rtl838x_switch_priv *priv);
void rtl930x_dbgfs_init(struct rtl838x_switch_priv *priv);
int rtldsa_find_l2_hash_entry(struct rtl838x_switch_priv *priv, u64 seed,
bool must_exist, struct rtl838x_l2_entry *e);
/* RTL931x hashes its second block into rows the fib_entries count does not
* reach, so an index can fall outside the map and is simply not tracked.
*/
static inline struct rtldsa_l2_uc *rtldsa_l2_uc_lookup(struct rtl838x_switch_priv *priv,
int idx)
{
if (idx < 0 || idx >= priv->r->fib_entries)
return NULL;
return &priv->l2_uc_map[idx];
}
#endif /* _RTL838X_H */