drivers/net/ethernet/ti/am65-cpsw-switchdev.c | 12 +++-- drivers/net/ethernet/ti/cpsw_ale.c | 44 ++++++++++++++++++- drivers/net/ethernet/ti/cpsw_ale.h | 2 + 3 files changed, 54 insertions(+), 4 deletions(-)
On an FDB flush, dynamic ALE entries don't get deleted. They stay stale
until the ALE ages them out on its own ~30 seconds later. Two issues in the
DEL_TO_DEVICE handling cause this.
First, the delete was gated on the same "added_by_user" check used for
ADD, so every dynamic delete was dropped before reaching
cpsw_ale_del_ucast() at all. Drop that gate. Dynamic deletes are the only
way to remove a hardware-learned entry early.
Second, dropping the gate alone isn't enough: ALE_VLAN_AWARE is always
on in switch mode, so a dynamic entry is stored under a real, nonzero
vid (possibly several, if the same MAC was learned on more than one
vid on a trunk port). With the bridge's vlan_filtering off, the bridge
core never learns the real vid and reports vid=0 on delete, so
cpsw_ale_del_ucast()'s exact (addr, vid) match never finds the row it
returns -ENOENT and the entry is left in place. To solve this use a new
cpsw_ale_del_ucast_dynamic_by_port() that matches by (addr, port). If vid
is 0, it clears every dynamic row for that MAC on that port regardless of
vid. If vid!=0 it only clears the entry matching both MAC and vid on
that port.
Handling of the user added entries remains unaffected as the changes
made are only for dynamic learned entries.
Fixes: 86e8b070b25e ("net: ti: am65-cpsw-nuss: Add switchdev support")
Signed-off-by: MD Danish Anwar <danishanwar@ti.com>
---
v2 - v1:
Address comments recieved from Sashiko [1]
Sashiko had 1 High and 2 Medium comments on v1. 1 High and 1 medium is addressed
in this patch. 1 Medium is a acceptable behaviour and not an actual issue.
Sashiko also had 1 Low and 1 Medium pre-existing issues. Those two
pre-existing issues are still there and can be planned to fix later but
not as part of this patch.
Addressed comments
1) Dynamic delete with non zero vid was going through cpsw_ale_del_ucast()
doesn't use port based matching and no ucast_type filtering happens, so
the row that gets blanked may belong to a different port or be an
ALE_UCAST_PERSISTANT row. This is fixed by calling
cpsw_ale_del_ucast_dynamic_by_port() for all dynamic entries. vid handling
is taken care by this API.
2) Added EXPORT_SYMBOL_GPL() for cpsw_ale_del_ucast_dynamic_by_port()
3) There was a comment about cost associated with full ALE walk for each
dynamic delete. The ALE size is small (max 512 entries on the largest
supported device), so no change is done here.
[1] https://lore.kernel.org/all/179006497666.2160803.14768308117153644313@kernel.org/
v1 https://lore.kernel.org/all/20260918075926.3616434-1-danishanwar@ti.com/
drivers/net/ethernet/ti/am65-cpsw-switchdev.c | 12 +++--
drivers/net/ethernet/ti/cpsw_ale.c | 44 ++++++++++++++++++-
drivers/net/ethernet/ti/cpsw_ale.h | 2 +
3 files changed, 54 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/ti/am65-cpsw-switchdev.c b/drivers/net/ethernet/ti/am65-cpsw-switchdev.c
index 53cdac272b583..0dc681748b0e3 100644
--- a/drivers/net/ethernet/ti/am65-cpsw-switchdev.c
+++ b/drivers/net/ethernet/ti/am65-cpsw-switchdev.c
@@ -397,13 +397,19 @@ static void am65_cpsw_switchdev_event_work(struct work_struct *work)
fdb->addr, fdb->vid, fdb->added_by_user,
fdb->offloaded, port_id);
- if (!fdb->added_by_user || fdb->is_local)
+ if (fdb->is_local)
break;
if (memcmp(port->slave.mac_addr, (u8 *)fdb->addr, ETH_ALEN) == 0)
port_id = HOST_PORT_NUM;
- cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
- fdb->vid ? ALE_VLAN : 0, fdb->vid);
+ if (!fdb->added_by_user)
+ cpsw_ale_del_ucast_dynamic_by_port(cpsw->ale,
+ (u8 *)fdb->addr,
+ port_id,
+ fdb->vid);
+ else
+ cpsw_ale_del_ucast(cpsw->ale, (u8 *)fdb->addr, port_id,
+ fdb->vid ? ALE_VLAN : 0, fdb->vid);
break;
default:
break;
diff --git a/drivers/net/ethernet/ti/cpsw_ale.c b/drivers/net/ethernet/ti/cpsw_ale.c
index e202bba494807..86bcea5744e01 100644
--- a/drivers/net/ethernet/ti/cpsw_ale.c
+++ b/drivers/net/ethernet/ti/cpsw_ale.c
@@ -249,7 +249,7 @@ DEFINE_ALE_FIELD_SET(mcast_state, 62, 2)
DEFINE_ALE_FIELD1(port_mask, 66)
DEFINE_ALE_FIELD(super, 65, 1)
DEFINE_ALE_FIELD(ucast_type, 62, 2)
-DEFINE_ALE_FIELD1_SET(port_num, 66)
+DEFINE_ALE_FIELD1(port_num, 66)
DEFINE_ALE_FIELD_SET(blocked, 65, 1)
DEFINE_ALE_FIELD_SET(secure, 64, 1)
DEFINE_ALE_FIELD_GET(mcast, 40, 1)
@@ -441,6 +441,48 @@ static int cpsw_ale_find_ageable(struct cpsw_ale *ale)
return -ENOENT;
}
+int cpsw_ale_del_ucast_dynamic_by_port(struct cpsw_ale *ale, const u8 *addr,
+ int port, u16 vid)
+{
+ u32 ale_entry[ALE_ENTRY_WORDS];
+ int type, ucast_type, idx;
+ u8 entry_addr[6];
+ int deleted = 0;
+ int entry_vid;
+
+ for (idx = 0; idx < ale->params.ale_entries; idx++) {
+ cpsw_ale_read(ale, idx, ale_entry);
+ type = cpsw_ale_get_entry_type(ale_entry);
+ if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR)
+ continue;
+ if (cpsw_ale_get_mcast(ale_entry))
+ continue;
+ ucast_type = cpsw_ale_get_ucast_type(ale_entry);
+ if (ucast_type == ALE_UCAST_PERSISTANT ||
+ ucast_type == ALE_UCAST_OUI)
+ continue;
+ if (cpsw_ale_get_port_num(ale_entry, ale->port_num_bits) != port)
+ continue;
+ cpsw_ale_get_addr(ale_entry, entry_addr);
+ if (!ether_addr_equal(entry_addr, addr))
+ continue;
+ entry_vid = cpsw_ale_get_vlan_id(ale_entry);
+ if (vid && entry_vid != vid)
+ continue;
+
+ memset(ale_entry, 0, sizeof(ale_entry));
+ cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
+ cpsw_ale_write(ale, idx, ale_entry);
+ deleted++;
+
+ if (vid)
+ return 0;
+ }
+
+ return deleted ? 0 : -ENOENT;
+}
+EXPORT_SYMBOL_GPL(cpsw_ale_del_ucast_dynamic_by_port);
+
static void cpsw_ale_flush_mcast(struct cpsw_ale *ale, u32 *ale_entry,
int port_mask)
{
diff --git a/drivers/net/ethernet/ti/cpsw_ale.h b/drivers/net/ethernet/ti/cpsw_ale.h
index 87b7d1b3a34a9..3c48e4da18202 100644
--- a/drivers/net/ethernet/ti/cpsw_ale.h
+++ b/drivers/net/ethernet/ti/cpsw_ale.h
@@ -166,6 +166,8 @@ int cpsw_ale_add_ucast(struct cpsw_ale *ale, const u8 *addr, int port,
int flags, u16 vid);
int cpsw_ale_del_ucast(struct cpsw_ale *ale, const u8 *addr, int port,
int flags, u16 vid);
+int cpsw_ale_del_ucast_dynamic_by_port(struct cpsw_ale *ale, const u8 *addr,
+ int port, u16 vid);
int cpsw_ale_add_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
int flags, u16 vid, int mcast_state);
int cpsw_ale_del_mcast(struct cpsw_ale *ale, const u8 *addr, int port_mask,
base-commit: c9151088f1674fd29ff26a20f5fc687acf53a2f0
--
2.34.1
© 2016 - 2026 Red Hat, Inc.