From: Wei Fang <[email protected]> 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 aging time is: aging_time = fdbt_acteu_interval * fdbt_ageing_act_cnt Default values are 3s interval and 100 cycles (300s total), matching the IEEE 802.1Q default aging 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. Dynamic entries for a departing port are also flushed immediately in port_bridge_leave() and netc_mac_link_down(), without waiting for the next aging cycle. Implement set_ageing_time and port_fast_age DSA operations to allow the bridge layer to reconfigure aging parameters and trigger per-port flushes on demand. Signed-off-by: Wei Fang <[email protected]> --- drivers/net/dsa/netc/netc_main.c | 80 ++++++++++++++++++++++++++++++ drivers/net/dsa/netc/netc_switch.h | 8 +++ 2 files changed, 88 insertions(+) diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index 1fe3b43e0459..cffa479e19b3 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -447,6 +447,26 @@ 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_clean); + + /* Update the activity element in FDB table */ + mutex_lock(&priv->fdbt_lock); + ntmp_fdbt_update_activity_element(&priv->ntmp); + /* Delete the aging entries after the activity element is updated */ + ntmp_fdbt_delete_aging_entries(&priv->ntmp, + READ_ONCE(priv->fdbt_ageing_act_cnt)); + mutex_unlock(&priv->fdbt_lock); + + if (atomic_read(&priv->br_cnt)) + schedule_delayed_work(&priv->fdbt_clean, + READ_ONCE(priv->fdbt_acteu_interval)); +} + static void netc_switch_dos_default_config(struct netc_switch *priv) { struct netc_switch_regs *regs = &priv->regs; @@ -860,6 +880,11 @@ static int netc_setup(struct dsa_switch *ds) INIT_HLIST_HEAD(&priv->fdb_list); mutex_init(&priv->fdbt_lock); + priv->fdbt_acteu_interval = NETC_FDBT_CLEAN_INTERVAL; + priv->fdbt_ageing_act_cnt = NETC_FDBT_AGEING_ACT_CNT; + atomic_set(&priv->br_cnt, 0); + INIT_DELAYED_WORK(&priv->fdbt_clean, + netc_clean_fdbt_ageing_entries); INIT_HLIST_HEAD(&priv->vlan_list); mutex_init(&priv->vft_lock); @@ -924,6 +949,7 @@ static void netc_teardown(struct dsa_switch *ds) { struct netc_switch *priv = ds->priv; + disable_delayed_work_sync(&priv->fdbt_clean); netc_destroy_all_lists(priv); netc_free_host_flood_rules(priv); netc_free_ntmp_user(priv); @@ -1918,6 +1944,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; @@ -1944,20 +1971,42 @@ static int netc_port_bridge_join(struct dsa_switch *ds, int port, np->host_flood = NULL; netc_port_wr(np, NETC_PIPFCR, 0); + if (atomic_inc_return(&priv->br_cnt) == 1) + schedule_delayed_work(&priv->fdbt_clean, + READ_ONCE(priv->fdbt_acteu_interval)); + return 0; } +static void netc_port_remove_dynamic_entries(struct netc_port *np) +{ + struct netc_switch *priv = np->switch_priv; + + /* Return if the port is not available */ + if (!np->dp) + return; + + mutex_lock(&priv->fdbt_lock); + ntmp_fdbt_delete_port_dynamic_entries(&priv->ntmp, np->dp->index); + mutex_unlock(&priv->fdbt_lock); +} + static void netc_port_bridge_leave(struct dsa_switch *ds, int port, struct dsa_bridge bridge) { 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; netc_port_set_mlo(np, MLO_DISABLE); netc_port_set_pvid(np, NETC_STANDALONE_PVID); + if (atomic_dec_and_test(&priv->br_cnt)) + cancel_delayed_work_sync(&priv->fdbt_clean); + + netc_port_remove_dynamic_entries(np); uc = ndev->flags & IFF_PROMISC; mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI); @@ -1977,6 +2026,34 @@ 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; + u32 secs = msecs / 1000; + u32 act_cnt, interval; + + if (!secs) + secs = 1; + + for (interval = 1; interval <= secs; interval++) { + act_cnt = secs / interval; + if (act_cnt <= FDBT_ACT_CNT) + break; + } + + WRITE_ONCE(priv->fdbt_acteu_interval, (unsigned long)interval * HZ); + WRITE_ONCE(priv->fdbt_ageing_act_cnt, act_cnt); + + return 0; +} + +static void netc_port_fast_age(struct dsa_switch *ds, int port) +{ + struct netc_port *np = NETC_PORT(ds, port); + + netc_port_remove_dynamic_entries(np); +} + static void netc_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { @@ -2231,6 +2308,7 @@ static void netc_mac_link_down(struct phylink_config *config, np = NETC_PORT(dp->ds, dp->index); netc_port_mac_rx_graceful_stop(np); netc_port_mac_tx_graceful_stop(np); + netc_port_remove_dynamic_entries(np); } static const struct phylink_mac_ops netc_phylink_mac_ops = { @@ -2260,6 +2338,8 @@ 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, .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats, diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h index 982c8d3a3fbf..8ad828f7d28c 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_CLEAN_INTERVAL (3 * HZ) +#define NETC_FDBT_AGEING_ACT_CNT 100 + struct netc_switch; struct netc_switch_info { @@ -124,6 +127,11 @@ struct netc_switch { struct ntmp_user ntmp; struct hlist_head fdb_list; struct mutex fdbt_lock; /* FDB table lock */ + struct delayed_work fdbt_clean; + /* (interval * act_cnt) is ageing time */ + unsigned long fdbt_acteu_interval; + u8 fdbt_ageing_act_cnt; /* maximum is 127 */ + atomic_t br_cnt; struct hlist_head vlan_list; struct mutex vft_lock; /* VLAN filter table lock */ -- 2.34.1
