[RFC PATCH net-next v2 2/6] net: dsa: qca8k: skip host FDB entries on CPU conduits
From: Brandon Mahdavi
Date: Sat Aug 01 2026 - 23:50:54 EST
DSA installs host FDB entries on the CPU path for addresses behind
software interfaces. QCA8K has one ATU entry per {MAC, VID}, so
successive adds for independent CPU conduits overwrite the destination.
Combining those ports in one destination mask can instead duplicate
host-bound traffic.
These entries are not required. Unknown unicast flooding sends host
traffic to the CPU path, while ingress lookup membership restricts
which conduit receives traffic from each user port. Skip host entries
on CPU ports, including the lag_fdb callbacks used by a CPU conduit LAG.
A station which roams from a user port to an interface behind the
CPU can leave a dynamically learned entry pointing to the old user
port. Such an entry suppresses unknown unicast flooding and blackholes
host-bound traffic. Purge it when handling the skipped host add, but
preserve deliberately configured static entries. Skip the matching
delete so it cannot remove a newly learned entry.
The non-LAG host FDB purge path was tested on IPQ4019 with assisted
learning enabled. Stale entries were removed when a wireless station
associated and downstream traffic recovered immediately.
Link: https://lore.kernel.org/netdev/20260725032159.421627-1-brandon.mahdavi@xxxxxxxxxx/
Link: https://github.com/openwrt/openwrt/issues/21317
Link: https://github.com/openwrt/openwrt/pull/24162
Suggested-by: Olaf Marzocchi <lists@xxxxxxxxxxxxx>
Signed-off-by: Brandon Mahdavi <brandon.mahdavi@xxxxxxxxxx>
---
drivers/net/dsa/qca/qca8k-common.c | 70 ++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/drivers/net/dsa/qca/qca8k-common.c b/drivers/net/dsa/qca/qca8k-common.c
index 5f0a875e5c87..c0a0d44e7c70 100644
--- a/drivers/net/dsa/qca/qca8k-common.c
+++ b/drivers/net/dsa/qca/qca8k-common.c
@@ -322,6 +322,36 @@ static int qca8k_fdb_search_and_del(struct qca8k_priv *priv, u8 port_mask,
return ret;
}
+static int qca8k_fdb_purge_dynamic(struct qca8k_priv *priv, const u8 *mac,
+ u16 vid)
+{
+ struct qca8k_fdb fdb = { 0 };
+ int ret;
+
+ mutex_lock(&priv->reg_mutex);
+
+ qca8k_fdb_write(priv, vid, 0, mac, 0);
+ ret = qca8k_fdb_access(priv, QCA8K_FDB_SEARCH, -1);
+ if (ret < 0)
+ goto out;
+
+ ret = qca8k_fdb_read(priv, &fdb);
+ if (ret < 0)
+ goto out;
+
+ /* Keep deliberately configured static entries. */
+ if (!fdb.aging || fdb.aging == QCA8K_ATU_STATUS_STATIC) {
+ ret = 0;
+ goto out;
+ }
+
+ ret = qca8k_fdb_access(priv, QCA8K_FDB_PURGE, -1);
+
+out:
+ mutex_unlock(&priv->reg_mutex);
+ return ret;
+}
+
static int qca8k_vlan_access(struct qca8k_priv *priv,
enum qca8k_vlan_cmd cmd, u16 vid)
{
@@ -829,6 +859,14 @@ int qca8k_port_fdb_add(struct dsa_switch *ds, int port,
struct qca8k_priv *priv = ds->priv;
u16 port_mask = BIT(port);
+ /* Unknown unicast flooding delivers host traffic to the conduit. */
+ if (dsa_is_cpu_port(ds, port)) {
+ if (!vid)
+ vid = QCA8K_PORT_VID_DEF;
+
+ return qca8k_fdb_purge_dynamic(priv, addr, vid);
+ }
+
return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
}
@@ -839,6 +877,9 @@ int qca8k_port_fdb_del(struct dsa_switch *ds, int port,
struct qca8k_priv *priv = ds->priv;
u16 port_mask = BIT(port);
+ if (dsa_is_cpu_port(ds, port))
+ return 0;
+
if (!vid)
vid = QCA8K_PORT_VID_DEF;
@@ -881,6 +922,25 @@ static u8 qca8k_lag_port_mask(struct dsa_switch *ds,
return port_mask;
}
+static bool qca8k_lag_is_cpu(struct dsa_switch *ds,
+ const struct dsa_lag *lag)
+{
+ struct dsa_port *dp;
+ bool has_cpu = false;
+
+ dsa_lag_foreach_port(dp, ds->dst, lag) {
+ if (dp->ds != ds)
+ continue;
+
+ if (!dsa_port_is_cpu(dp))
+ return false;
+
+ has_cpu = true;
+ }
+
+ return has_cpu;
+}
+
int qca8k_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
const unsigned char *addr, u16 vid,
struct dsa_db db)
@@ -890,6 +950,13 @@ int qca8k_lag_fdb_add(struct dsa_switch *ds, struct dsa_lag lag,
port_mask = qca8k_lag_port_mask(ds, &lag);
+ if (qca8k_lag_is_cpu(ds, &lag)) {
+ if (!vid)
+ vid = QCA8K_PORT_VID_DEF;
+
+ return qca8k_fdb_purge_dynamic(priv, addr, vid);
+ }
+
return qca8k_port_fdb_insert(priv, addr, port_mask, vid);
}
@@ -900,6 +967,9 @@ int qca8k_lag_fdb_del(struct dsa_switch *ds, struct dsa_lag lag,
struct qca8k_priv *priv = ds->priv;
u8 port_mask;
+ if (qca8k_lag_is_cpu(ds, &lag))
+ return 0;
+
if (!vid)
vid = QCA8K_PORT_VID_DEF;
--
2.43.0