mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
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:
parent
751aa5a5d5
commit
05b5ee610f
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user