From: Wei Fang 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 --- drivers/net/dsa/netc/netc_main.c | 67 ++++++++++++++++++++++++++++++ drivers/net/dsa/netc/netc_switch.h | 7 ++++ 2 files changed, 74 insertions(+) diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index b61152c16e7b..a8e036a7046c 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -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); @@ -1961,6 +1985,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; @@ -1988,6 +2013,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: @@ -2014,6 +2043,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; @@ -2021,6 +2051,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); @@ -2041,6 +2074,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); @@ -2332,6 +2396,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, @@ -2381,6 +2446,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; diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h index 982c8d3a3fbf..305f2a92e2f9 100644 --- a/drivers/net/dsa/netc/netc_switch.h +++ b/drivers/net/dsa/netc/netc_switch.h @@ -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 */ -- 2.34.1