[PATCH net 1/2] net: dsa: let drivers offload 8021q uppers on standalone ports
From: Semih Baskan
Date: Thu Aug 06 2026 - 03:31:53 EST
Some switches cannot deliver a tagged frame to the CPU while its VID is
absent from the VLAN table, not even with VLAN filtering turned off.
b53 is one of them: its VID lookup is always active, and disabling it
moves the ARL to shared VLAN learning, where ARL operations force VID 0
and the hardware table drifts away from the bridge fdb. On such
hardware a standalone port can only receive the traffic of its 8021q
uppers if their VIDs are programmed into the table.
The existing opt-in for this class of problem,
ds->needs_standalone_vlan_filtering, delivers those VIDs but does more:
dsa_port_reset_vlan_filtering() also forces vlan_filtering=1 on a port
that leaves a VLAN-unaware bridge. hellcreek wants exactly that. b53
must not have it, because it sets vlan_filtering_is_global, so the
forced flip would turn the whole switch into a VLAN filtering device
the first time any port leaves a VLAN-unaware bridge and change
behaviour for every other port.
Add ds->needs_standalone_vlan_offload for the narrower need. It
advertises NETIF_F_HW_VLAN_CTAG_FILTER on user ports, so the 8021q
layer reports upper VIDs to .port_vlan_add, and it leaves the
vlan_filtering state alone.
Upper offload of such a switch never depends on vlan_filtering: every
VID was already delivered when the upper was created, since the
feature bit is always on. dsa_port_vlan_filtering() therefore skips
its ports entirely when a bridge toggles VLAN awareness. Restoring
them on the way up would add VIDs that were never cleared, and
clearing them on the way down would strip the driver's record of a
bridged port's uppers and the feature bit, leaving a port that later
leaves the bridge with uppers that cannot receive and no way to
re-offload them. The conduit change path keeps its explicit teardown
and restore of standalone VLANs, and now also runs it for a standalone
port of such a switch while VLAN filtering is off, because that port
has VLANs on the CPU port too.
The Fixes tag is for backport dependency tracking: the b53 fix in the
next patch needs this flag to exist.
Fixes: 06cfb2df7eb0 ("net: dsa: don't advertise 'rx-vlan-filter' when not needed")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Semih Baskan <strst.gs@xxxxxxxxx>
---
include/net/dsa.h | 3 +++
net/dsa/port.c | 21 ++++++++++++++-------
net/dsa/user.c | 4 +++-
3 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/include/net/dsa.h b/include/net/dsa.h
index 6f7f5c17b532..6f3a60c23d14 100644
--- a/include/net/dsa.h
+++ b/include/net/dsa.h
@@ -403,6 +403,9 @@ struct dsa_switch {
/* Keep VLAN filtering enabled on ports not offloading any upper */
u32 needs_standalone_vlan_filtering:1;
+ /* Offload 8021q uppers of standalone ports even when not filtering */
+ u32 needs_standalone_vlan_offload:1;
+
/* Pass .port_vlan_add and .port_vlan_del to drivers even for bridges
* that have vlan_filtering=0. All drivers should ideally set this (and
* then the option would get removed), but it is unknown whether this
diff --git a/net/dsa/port.c b/net/dsa/port.c
index 1f5536c0dffc..23d1c5ae6934 100644
--- a/net/dsa/port.c
+++ b/net/dsa/port.c
@@ -831,6 +831,9 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
if (!user)
continue;
+ if (ds->needs_standalone_vlan_offload)
+ continue;
+
err = dsa_user_manage_vlan_filtering(user,
vlan_filtering);
if (err)
@@ -839,10 +842,12 @@ int dsa_port_vlan_filtering(struct dsa_port *dp, bool vlan_filtering,
} else {
dp->vlan_filtering = vlan_filtering;
- err = dsa_user_manage_vlan_filtering(dp->user,
- vlan_filtering);
- if (err)
- goto restore;
+ if (!ds->needs_standalone_vlan_offload) {
+ err = dsa_user_manage_vlan_filtering(dp->user,
+ vlan_filtering);
+ if (err)
+ goto restore;
+ }
}
return 0;
@@ -1445,10 +1450,12 @@ int dsa_port_change_conduit(struct dsa_port *dp, struct net_device *conduit,
/* The port might still be VLAN filtering even if it's no longer
* under a bridge, either due to ds->vlan_filtering_is_global or
- * ds->needs_standalone_vlan_filtering. In turn this means VLANs
- * on the CPU port.
+ * ds->needs_standalone_vlan_filtering, and standalone ports of a
+ * ds->needs_standalone_vlan_offload switch keep their VLANs without
+ * filtering. In turn this means VLANs on the CPU port.
*/
- vlan_filtering = dsa_port_is_vlan_filtering(dp);
+ vlan_filtering = dsa_port_is_vlan_filtering(dp) ||
+ (ds->needs_standalone_vlan_offload && !bridge_dev);
if (vlan_filtering) {
err = dsa_user_manage_vlan_filtering(dev, false);
if (err) {
diff --git a/net/dsa/user.c b/net/dsa/user.c
index 03c7af6abe18..2b1695b386ef 100644
--- a/net/dsa/user.c
+++ b/net/dsa/user.c
@@ -1946,6 +1946,7 @@ static int dsa_user_clear_vlan(struct net_device *vdev, int vid, void *arg)
*
* - If standalone (this includes software bridge, software LAG):
* - if ds->needs_standalone_vlan_filtering = true, OR if
+ * ds->needs_standalone_vlan_offload = true, OR if
* (ds->vlan_filtering_is_global = true AND there are bridges spanning
* this switch chip which have vlan_filtering=1)
* - the 8021q upper VLANs
@@ -2718,7 +2719,8 @@ void dsa_user_setup_tagger(struct net_device *user)
user->hw_features |= NETIF_F_HW_TC;
if (user->needed_tailroom)
user->features &= ~(NETIF_F_SG | NETIF_F_FRAGLIST);
- if (ds->needs_standalone_vlan_filtering)
+ if (ds->needs_standalone_vlan_filtering ||
+ ds->needs_standalone_vlan_offload)
user->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
user->lltx = true;