net: dsa: netc: implement dynamic FDB entry ageing

The NETC switch does not age out dynamic FDB entries automatically.
Without software management, stale entries persist after topology
changes and cause incorrect forwarding.

Add a delayed work that periodically removes entries that have not been
refreshed within the specified cycles. The effective ageing time is:

  ageing_time = fdbt_ageing_delay * 100

Default values are 3s interval and 100 cycles (300s total), matching
the IEEE 802.1Q default ageing time. The work starts when the first
port joins a bridge (tracked via br_cnt) and is cancelled when the
last port leaves. All FDB operations are serialized under fdbt_lock.

Implement .set_ageing_time() to allow the bridge layer to reconfigure
ageing parameters on demand.

Signed-off-by: Wei Fang <wei.fang@nxp.com>
Link: https://patch.msgid.link/20260611021458.2629145-10-wei.fang@oss.nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Wei Fang 2026-06-11 10:14:58 +08:00 committed by Jakub Kicinski
parent 751aa5a5d5
commit 05b5ee610f
2 changed files with 74 additions and 0 deletions

View File

@ -447,6 +447,25 @@ static void netc_free_ntmp_user(struct netc_switch *priv)
netc_free_ntmp_bitmaps(priv);
}
static void netc_clean_fdbt_ageing_entries(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct netc_switch *priv;
priv = container_of(dwork, struct netc_switch, fdbt_ageing_work);
/* Update the activity element in FDB table */
mutex_lock(&priv->fdbt_lock);
ntmp_fdbt_update_activity_element(&priv->ntmp);
/* Delete the ageing entries after the activity element is updated */
ntmp_fdbt_delete_ageing_entries(&priv->ntmp, NETC_FDBT_AGEING_THRESH);
mutex_unlock(&priv->fdbt_lock);
if (atomic_read(&priv->br_cnt))
schedule_delayed_work(&priv->fdbt_ageing_work,
READ_ONCE(priv->fdbt_ageing_delay));
}
static void netc_switch_dos_default_config(struct netc_switch *priv)
{
struct netc_switch_regs *regs = &priv->regs;
@ -872,6 +891,10 @@ static int netc_setup(struct dsa_switch *ds)
INIT_HLIST_HEAD(&priv->fdb_list);
mutex_init(&priv->fdbt_lock);
priv->fdbt_ageing_delay = NETC_FDBT_AGEING_DELAY;
atomic_set(&priv->br_cnt, 0);
INIT_DELAYED_WORK(&priv->fdbt_ageing_work,
netc_clean_fdbt_ageing_entries);
INIT_HLIST_HEAD(&priv->vlan_list);
mutex_init(&priv->vft_lock);
@ -936,6 +959,7 @@ static void netc_teardown(struct dsa_switch *ds)
{
struct netc_switch *priv = ds->priv;
disable_delayed_work_sync(&priv->fdbt_ageing_work);
netc_destroy_all_lists(priv);
netc_free_host_flood_rules(priv);
netc_free_ntmp_user(priv);
@ -1970,6 +1994,7 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
struct netlink_ext_ack *extack)
{
struct netc_port *np = NETC_PORT(ds, port);
struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
int err;
@ -1997,6 +2022,10 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port,
out:
netc_port_remove_host_flood(np, np->host_flood);
if (atomic_inc_return(&priv->br_cnt) == 1)
schedule_delayed_work(&priv->fdbt_ageing_work,
READ_ONCE(priv->fdbt_ageing_delay));
return 0;
disable_mlo:
@ -2023,6 +2052,7 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
{
struct netc_port *np = NETC_PORT(ds, port);
struct net_device *ndev = np->dp->user;
struct netc_switch *priv = ds->priv;
u16 vlan_unaware_pvid;
bool mc, uc;
@ -2030,6 +2060,9 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
netc_port_set_pvid(np, NETC_STANDALONE_PVID);
np->pvid = NETC_STANDALONE_PVID;
if (atomic_dec_and_test(&priv->br_cnt))
cancel_delayed_work_sync(&priv->fdbt_ageing_work);
netc_port_remove_dynamic_entries(np);
uc = ndev->flags & IFF_PROMISC;
mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
@ -2066,6 +2099,37 @@ static void netc_port_bridge_leave(struct dsa_switch *ds, int port,
netc_port_del_vlan_entry(np, vlan_unaware_pvid);
}
static int netc_set_ageing_time(struct dsa_switch *ds, unsigned int msecs)
{
struct netc_switch *priv = ds->priv;
unsigned long delay_jiffies;
/* The dynamic FDB entry is deleted when its activity counter reaches
* NETC_FDBT_AGEING_THRESH (100). Each delayed_work tick increments
* the counter by 1 if the entry is inactive.
*
* Therefore:
* msecs (ms) = NETC_FDBT_AGEING_THRESH * delay_ms (ms)
* delay_ms = msecs / NETC_FDBT_AGEING_THRESH
* delay_jiffies = (delay_ms / 1000) * HZ
* = (msecs * HZ) / (1000 * NETC_FDBT_AGEING_THRESH)
*
* Use DIV_ROUND_CLOSEST_ULL to perform a single nearest-jiffy
* rounding, avoiding the two-step rounding error of the intermediate
* delay_ms approach.
* Maximum error = +/-0.5 jiffy * 100 = +/-50000/HZ ms.
*/
delay_jiffies = DIV_ROUND_CLOSEST_ULL((u64)msecs * HZ,
1000 * NETC_FDBT_AGEING_THRESH);
WRITE_ONCE(priv->fdbt_ageing_delay, delay_jiffies);
if (atomic_read(&priv->br_cnt))
mod_delayed_work(system_percpu_wq, &priv->fdbt_ageing_work,
READ_ONCE(priv->fdbt_ageing_delay));
return 0;
}
static void netc_port_fast_age(struct dsa_switch *ds, int port)
{
struct netc_port *np = NETC_PORT(ds, port);
@ -2357,6 +2421,7 @@ static const struct dsa_switch_ops netc_switch_ops = {
.port_vlan_del = netc_port_vlan_del,
.port_bridge_join = netc_port_bridge_join,
.port_bridge_leave = netc_port_bridge_leave,
.set_ageing_time = netc_set_ageing_time,
.port_fast_age = netc_port_fast_age,
.get_pause_stats = netc_port_get_pause_stats,
.get_rmon_stats = netc_port_get_rmon_stats,
@ -2406,6 +2471,8 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->fdb_isolation = true;
ds->max_num_bridges = priv->info->num_ports - 1;
ds->ageing_time_min = 1000;
ds->ageing_time_max = U32_MAX;
ds->priv = priv;
priv->ds = ds;

View File

@ -50,6 +50,9 @@
/* PAUSE refresh threshold: send refresh when timer reaches this value */
#define NETC_PAUSE_THRESH 0x7FFF
#define NETC_FDBT_AGEING_DELAY (3 * HZ)
#define NETC_FDBT_AGEING_THRESH 100
struct netc_switch;
struct netc_switch_info {
@ -124,6 +127,10 @@ struct netc_switch {
struct ntmp_user ntmp;
struct hlist_head fdb_list;
struct mutex fdbt_lock; /* FDB table lock */
struct delayed_work fdbt_ageing_work;
/* (fdbt_ageing_delay * NETC_FDBT_AGEING_THRESH) is ageing time */
unsigned long fdbt_ageing_delay;
atomic_t br_cnt;
struct hlist_head vlan_list;
struct mutex vft_lock; /* VLAN filter table lock */