From: wei.fang@oss•nxp.com
To: claudiu.manoil@nxp•com, vladimir.oltean@nxp•com,
xiaoning.wang@nxp•com, andrew+netdev@lunn•ch,
davem@davemloft•net, edumazet@google•com, kuba@kernel•org,
pabeni@redhat•com, chleroy@kernel•org, andrew@lunn•ch,
olteanv@gmail•com
Cc: wei.fang@nxp•com, imx@lists•linux.dev, netdev@vger•kernel.org,
linux-kernel@vger•kernel.org, linuxppc-dev@lists•ozlabs.org,
linux-arm-kernel@lists•infradead.org
Subject: [PATCH net-next 9/9] net: dsa: netc: implement dynamic FDB entry aging
Date: Wed, 27 May 2026 18:02:17 +0800 [thread overview]
Message-ID: <20260527100217.794987-10-wei.fang@oss.nxp.com> (raw)
In-Reply-To: <20260527100217.794987-1-wei.fang@oss.nxp.com>
From: Wei Fang <wei.fang@nxp•com>
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 <wei.fang@nxp•com>
---
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
next prev parent reply other threads:[~2026-05-27 10:00 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-27 10:02 [PATCH net-next 0/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` [PATCH net-next 1/9] net: enetc: add interfaces to manage FDB entries wei.fang
2026-05-27 10:02 ` [PATCH net-next 2/9] net: enetc: add "Update" and "Delete" operations to VLAN filter table wei.fang
2026-05-27 10:02 ` [PATCH net-next 3/9] net: enetc: add interfaces to manage egress treatment table wei.fang
2026-05-27 10:02 ` [PATCH net-next 4/9] net: enetc: add "Update" operation to the egress count table wei.fang
2026-05-27 10:02 ` [PATCH net-next 5/9] net: dsa: netc: initialize the group bitmap of ETT and ECT wei.fang
2026-05-27 10:02 ` [PATCH net-next 6/9] net: enetc: add helpers to set/clear table bitmap wei.fang
2026-05-27 10:02 ` [PATCH net-next 7/9] net: dsa: netc: add VLAN filter table and egress treatment management wei.fang
2026-05-27 10:02 ` [PATCH net-next 8/9] net: dsa: netc: add bridge mode support wei.fang
2026-05-27 10:02 ` wei.fang [this message]
2026-05-29 1:53 ` [PATCH net-next 0/9] " Wei Fang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260527100217.794987-10-wei.fang@oss.nxp.com \
--to=wei.fang@oss$(echo .)nxp.com \
--cc=andrew+netdev@lunn$(echo .)ch \
--cc=andrew@lunn$(echo .)ch \
--cc=chleroy@kernel$(echo .)org \
--cc=claudiu.manoil@nxp$(echo .)com \
--cc=davem@davemloft$(echo .)net \
--cc=edumazet@google$(echo .)com \
--cc=imx@lists$(echo .)linux.dev \
--cc=kuba@kernel$(echo .)org \
--cc=linux-arm-kernel@lists$(echo .)infradead.org \
--cc=linux-kernel@vger$(echo .)kernel.org \
--cc=linuxppc-dev@lists$(echo .)ozlabs.org \
--cc=netdev@vger$(echo .)kernel.org \
--cc=olteanv@gmail$(echo .)com \
--cc=pabeni@redhat$(echo .)com \
--cc=vladimir.oltean@nxp$(echo .)com \
--cc=wei.fang@nxp$(echo .)com \
--cc=xiaoning.wang@nxp$(echo .)com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox