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:

  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.

When a port leaves the bridge, its dynamic FDB entries are flushed
right away in port_bridge_leave(), without waiting for the ageing
cycle. Additionally, when a link down event is detected on a port,
netc_mac_link_down() will also clear the port's dynamic FDB entries
via netc_port_remove_dynamic_entries(). Non-bridge ports have no
dynamic FDB entries, so this call is always safe.

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   | 90 ++++++++++++++++++++++++++++++
 drivers/net/dsa/netc/netc_switch.h |  7 +++
 2 files changed, 97 insertions(+)

diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 5b58ce06beb8..3737a852cafc 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;
@@ -861,6 +880,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);
 
@@ -925,6 +948,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);
@@ -1948,6 +1972,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;
 
@@ -1974,20 +1999,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_ageing_work,
+                                     READ_ONCE(priv->fdbt_ageing_delay));
+
        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_ageing_work);
+
+       netc_port_remove_dynamic_entries(np);
        uc = ndev->flags & IFF_PROMISC;
        mc = ndev->flags & (IFF_PROMISC | IFF_ALLMULTI);
 
@@ -2007,6 +2054,44 @@ 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);
+
+       netc_port_remove_dynamic_entries(np);
+}
+
 static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
                                  struct phylink_config *config)
 {
@@ -2261,6 +2346,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 = {
@@ -2290,6 +2376,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,
@@ -2338,6 +2426,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..4abef8b383bd 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 * ageing_act_cnt) 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


Reply via email to