* [PATCH net 0/2] net: dsa: realtek: rtl8365mb: fix FDB lookups in VLAN-unaware bridges
@ 2026-11-04 8:25 Stijn Cleynhens
2026-11-04 8:25 ` [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0 Stijn Cleynhens
2026-11-04 8:25 ` [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges Stijn Cleynhens
0 siblings, 2 replies; 5+ messages in thread
From: Stijn Cleynhens @ 2026-11-04 8:25 UTC (permalink / raw)
To: netdev
Cc: Linus Walleij, Luiz Angelo Daros de Luca, Alvin Šipraga,
Andrew Lunn, Vladimir Oltean, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Mieczyslaw Nalewaj, linux-kernel
In a VLAN-unaware bridge, DSA installs FDB and MDB entries with VID 0,
but the switch classifies untagged frames to the bridge PVID and looks up
VID 0 by FID, while the driver installs IVL entries. Nothing matches, so
a station that roams from behind a user port to behind the CPU port (a
Wi-Fi client moving between access points) stays unreachable until the
learned entry ages out.
Patch 1 makes VID 0 use IVL; patch 2 depends on it. Patch 2 keeps the
neutral PVID (VID 0) on ports of a VLAN-unaware bridge and only programs
the bridge PVID while the port filters VLANs. Tagged frames in a
VLAN-unaware bridge are not covered, see patch 2.
Tested on OpenWrt's 6.18 kernel, which carries the rtl8365mb backports up
to v7.4; the code these patches touch is the same as in net. On an
RTL8367S-VB (TP-Link Deco X50-PoE, with the family D series applied): a
station learned behind the uplink that moves behind the CPU port answers
10/10 pings right away, its stale entry replaced by the CPU one, and
switching VLAN filtering off and on at runtime keeps traffic flowing. An
earlier revision of patch 2, with the same logic, was tested the same way
on an RTL8367S (Deco X50-Outdoor), 0/10 before and 10/10 after. Both
patches build without warnings with W=1 C=1.
Note for Mieczyslaw's RTL8367S-VB series in net-next (v8): patch 2 adds a
vlan_filtering argument to rtl8365mb_vlan_pvid_port_clear(). The family D
path that series adds there reads dsa_port_is_vlan_filtering(), which
still returns the old state while filtering is being turned off, so it
should take the new argument instead; otherwise family D drops untagged
frames after filtering is disabled. Happy to send that as a follow-up
once both are in.
Stijn Cleynhens (2):
net: dsa: realtek: rtl8365mb: use IVL in VLAN 0
net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware
bridges
drivers/net/dsa/realtek/rtl8365mb_main.c | 147 ++++++++++++++++-------
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 40 +++++-
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 4 +-
3 files changed, 141 insertions(+), 50 deletions(-)
base-commit: 787268686c64be60023743b527645a907e38ccca
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0
2026-11-04 8:25 [PATCH net 0/2] net: dsa: realtek: rtl8365mb: fix FDB lookups in VLAN-unaware bridges Stijn Cleynhens
@ 2026-11-04 8:25 ` Stijn Cleynhens
2026-11-04 15:22 ` Alvin Šipraga
2026-11-04 8:25 ` [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges Stijn Cleynhens
1 sibling, 1 reply; 5+ messages in thread
From: Stijn Cleynhens @ 2026-11-04 8:25 UTC (permalink / raw)
To: netdev
Cc: Linus Walleij, Luiz Angelo Daros de Luca, Alvin Šipraga,
Andrew Lunn, Vladimir Oltean, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Mieczyslaw Nalewaj, linux-kernel
DSA installs the FDB and MDB entries of standalone ports and of
VLAN-unaware bridges with VID 0, and the driver writes them with
Independent VLAN Learning. No bridge VLAN sets up the VLAN4k entry of
VID 0 though, and it defaults to Shared VLAN Learning, so the switch
looks up and learns frames classified to VID 0 by FID. Lookups never hit
the IVL-keyed static entries, and a learned entry for the same MAC sits
alongside the static one.
Set up VID 0 with IVL, like every other VLAN the driver programs.
On its own this has no visible effect: standalone ports do not learn and
the driver does not control host flooding, so the unused static entries
only meant that traffic was flooded to the CPU. The next patch, which
moves VLAN-unaware bridges onto VID 0, depends on it.
Fixes: 336e3e4a1ab3 ("net: dsa: realtek: rtl8365mb: add FDB support")
Signed-off-by: Stijn Cleynhens <stijncleynhens@gmail.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 8 ++++++
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 31 ++++++++++++++++++++++++
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 1 +
3 files changed, 40 insertions(+)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index da06f952a..b90e46c16 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -2232,6 +2232,14 @@ static int rtl8365mb_vlan_setup(struct dsa_switch *ds)
}
}
+ mutex_lock(&priv->vlan_lock);
+ ret = rtl8365mb_vlan_4k_setup_neutral(priv);
+ mutex_unlock(&priv->vlan_lock);
+ if (ret) {
+ dev_err(priv->dev, "Failed to set up VLAN 0\n");
+ return ret;
+ }
+
/* VLAN is always enabled. */
ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG,
RTL8365MB_VLAN_CTRL_EN_MASK,
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 8d49ffadc..58948e76f 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -322,6 +322,37 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
return 0;
}
+/*
+ * rtl8365mb_vlan_4k_setup_neutral() - Use IVL in the neutral VLAN
+ * @priv: realtek_priv pointer
+ *
+ * Untagged frames of standalone ports and of ports in a VLAN-unaware bridge
+ * are classified to VID 0, the VID of the neutral PVID. No bridge VLAN sets
+ * up its VLAN4k entry, which by default uses Shared VLAN Learning, so the
+ * switch would learn these frames by FID while the driver installs its FDB
+ * and MDB entries for VID 0 with Independent VLAN Learning. Use IVL in VID 0
+ * as in every other VLAN.
+ *
+ * Context: Can sleep. Must be called with &priv->vlan_lock held.
+ * Takes and releases &priv->map_lock.
+ * Return: 0 on success, or a negative error code on failure.
+ */
+int rtl8365mb_vlan_4k_setup_neutral(struct realtek_priv *priv)
+{
+ struct rtl8365mb_vlan4k vlan4k = {0};
+ int ret;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ ret = rtl8365mb_vlan_4k_read(priv, 0, &vlan4k);
+ if (ret)
+ return ret;
+
+ vlan4k.ivl_en = true;
+
+ return rtl8365mb_vlan_4k_write(priv, &vlan4k);
+}
+
/*
* rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry
* @ds: dsa switch instance
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
index 27a526b61..809034009 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -28,6 +28,7 @@ int
rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
int port,
enum rtl8365mb_frame_ingress frame_type);
+int rtl8365mb_vlan_4k_setup_neutral(struct realtek_priv *priv);
int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack);
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges
2026-11-04 8:25 [PATCH net 0/2] net: dsa: realtek: rtl8365mb: fix FDB lookups in VLAN-unaware bridges Stijn Cleynhens
2026-11-04 8:25 ` [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0 Stijn Cleynhens
@ 2026-11-04 8:25 ` Stijn Cleynhens
2026-11-04 15:57 ` Alvin Šipraga
1 sibling, 1 reply; 5+ messages in thread
From: Stijn Cleynhens @ 2026-11-04 8:25 UTC (permalink / raw)
To: netdev
Cc: Linus Walleij, Luiz Angelo Daros de Luca, Alvin Šipraga,
Andrew Lunn, Vladimir Oltean, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Mieczyslaw Nalewaj, linux-kernel
The switch looks up its forwarding database by the VLAN that it
classifies a frame to, and untagged frames are classified to the port
PVID. With configure_vlan_while_not_filtering, the bridge PVID is
programmed into the port also while the bridge is VLAN-unaware, so
untagged frames of such a bridge are looked up in VLAN 1. DSA however
installs the FDB and MDB entries of a VLAN-unaware bridge with VID 0,
so these entries never match a lookup.
This breaks assisted learning on the CPU port: when a station that was
first learned behind a user port moves behind the CPU port (e.g. a
wireless client roaming between access points), the static entry that
DSA installs for it on the CPU port does not override the learned one,
and unicast to the station is dropped as a hairpin until the learned
entry ages out. Clients that need a DHCP reply in that window do not
get one.
Keep the PVID requested by the bridge per port and only program it
while the port filters VLANs. Otherwise keep the neutral PVID (VLANMC
index 0, VID 0) that standalone ports already use, so that untagged
frames are classified to the VID of the bridge entries. Commit the PVID
again when VLAN filtering is toggled.
This only covers untagged frames. Tagged frames in a VLAN-unaware bridge
are still classified by their tag, so a tagged station that roams keeps
hitting the stale learned entry until it ages out, as before. Fixing
that would need the switch to ignore the C-tag on ingress of these
ports, which is left for a separate change.
Fixes: 336e3e4a1ab3 ("net: dsa: realtek: rtl8365mb: add FDB support")
Signed-off-by: Stijn Cleynhens <stijncleynhens@gmail.com>
---
drivers/net/dsa/realtek/rtl8365mb_main.c | 139 +++++++++++++++--------
drivers/net/dsa/realtek/rtl8365mb_vlan.c | 9 +-
drivers/net/dsa/realtek/rtl8365mb_vlan.h | 3 +-
3 files changed, 101 insertions(+), 50 deletions(-)
diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
index b90e46c16..a7db267bc 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_main.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
@@ -838,6 +838,8 @@ struct rtl8365mb_cpu {
* access via rtl8365mb_get_stats64
* @stats_lock: protect the stats structure during read/update
* @mib_work: delayed work for polling MIB counters
+ * @bridge_pvid: PVID requested by the bridge, 0 if none. Only programmed
+ * while the port filters VLANs. Protected by vlan_lock.
*/
struct rtl8365mb_port {
struct realtek_priv *priv;
@@ -845,6 +847,7 @@ struct rtl8365mb_port {
struct rtnl_link_stats64 stats;
spinlock_t stats_lock;
struct delayed_work mib_work;
+ u16 bridge_pvid;
};
/**
@@ -2004,6 +2007,41 @@ rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port,
RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val);
}
+/* Program the PVID that the port should use for untagged frames: the PVID of
+ * the bridge while the port filters VLANs, and the neutral PVID (VID 0)
+ * otherwise. A VLAN-unaware bridge, like a standalone port, gets its FDB and
+ * MDB entries with VID 0 from DSA. As the switch looks up its forwarding
+ * database by the VLAN that it classifies a frame to, a bridge PVID would make
+ * those entries miss every lookup.
+ */
+static int rtl8365mb_port_commit_pvid(struct dsa_switch *ds, int port,
+ bool vlan_filtering,
+ struct netlink_ext_ack *extack)
+{
+ struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+ u16 hw_pvid, pvid = 0;
+ int ret;
+
+ lockdep_assert_held(&priv->vlan_lock);
+
+ if (vlan_filtering)
+ pvid = mb->ports[port].bridge_pvid;
+
+ ret = rtl8365mb_vlan_port_get_pvid(priv, port, &hw_pvid);
+ if (ret)
+ return ret;
+
+ if (pvid == hw_pvid)
+ return 0;
+
+ if (pvid)
+ return rtl8365mb_vlan_pvid_port_set(ds, port, pvid, extack);
+
+ return rtl8365mb_vlan_pvid_port_clear(ds, port, hw_pvid,
+ vlan_filtering);
+}
+
static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
bool vlan_filtering,
struct netlink_ext_ack *extack)
@@ -2011,35 +2049,29 @@ static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
enum rtl8365mb_vlan_egress_mode mode;
struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
u32 configured_ports = 0;
struct dsa_port *dp;
- u16 pvid_vid;
int ret;
dev_dbg(priv->dev, "port %d: %s VLAN filtering\n", port,
vlan_filtering ? "enable" : "disable");
+ mutex_lock(&priv->vlan_lock);
+
ret = rtl8365mb_vlan_port_get_framefilter(priv, port,
&prev_accepted_frame);
if (ret) {
NL_SET_ERR_MSG_MOD(extack,
"Failed to get current framefilter");
- return ret;
+ goto out_unlock;
}
/* While filtering, only accepts untagged frames if PVID is enabled */
- if (vlan_filtering) {
- ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid);
- if (ret)
- return ret;
-
- if (pvid_vid)
- accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
- else
- accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
- } else {
+ if (vlan_filtering && !mb->ports[port].bridge_pvid)
+ accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
+ else
accepted_frame = RTL8365MB_FRAME_TYPE_ANY_FRAME;
- }
/* When vlan filter is enable/disabled in a bridge, this function is
* called for all member ports. We need to enable/disable ingress
@@ -2047,7 +2079,7 @@ static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
*/
ret = rtl8365mb_port_set_ingress_filtering(priv, port, vlan_filtering);
if (ret)
- return ret;
+ goto out_unlock;
/* However, we also enable/disable egress filtering because the switch
* still consider the egress interface VLAN membership to forward the
@@ -2083,12 +2115,18 @@ static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
else
mode = RTL8365MB_VLAN_EGRESS_MODE_REAL_KEEP;
- ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode);
+ ret = rtl8365mb_port_commit_pvid(ds, port, vlan_filtering, extack);
if (ret)
goto undo_set_framefilter;
- return ret;
+ ret = rtl8365mb_port_set_vlan_egress_mode(priv, port, mode);
+ if (ret)
+ goto undo_commit_pvid;
+
+ goto out_unlock;
+undo_commit_pvid:
+ (void)rtl8365mb_port_commit_pvid(ds, port, !vlan_filtering, NULL);
undo_set_framefilter:
if (prev_accepted_frame != accepted_frame)
rtl8365mb_vlan_port_set_framefilter(priv, port,
@@ -2111,6 +2149,8 @@ static int rtl8365mb_port_vlan_filtering(struct dsa_switch *ds, int port,
rtl8365mb_port_set_ingress_filtering(priv, port, !vlan_filtering);
+out_unlock:
+ mutex_unlock(&priv->vlan_lock);
return ret;
}
@@ -2120,8 +2160,10 @@ static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port,
{
bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
- u16 pvid_vid;
struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+ u16 prev_bridge_pvid, *bridge_pvid;
+ bool vlan_filtering;
int ret;
dev_dbg(priv->dev, "add VLAN %d on port %d, %s, %s\n",
@@ -2137,41 +2179,33 @@ static int rtl8365mb_port_vlan_add(struct dsa_switch *ds, int port,
mutex_lock(&priv->vlan_lock);
- ret = rtl8365mb_vlan_port_get_pvid(priv, port, &pvid_vid);
- if (ret)
- goto out_unlock;
+ vlan_filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));
+ bridge_pvid = &mb->ports[port].bridge_pvid;
- /* Set PVID if needed */
- if (pvid) {
- ret = rtl8365mb_vlan_pvid_port_set(ds, port, vlan->vid,
- extack);
- if (ret)
- goto out_unlock;
- } else {
- /* or try to unset it if not */
- ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid);
- if (ret)
- goto out_unlock;
- }
+ /* Set PVID if needed, or unset it if this VLAN was the PVID */
+ prev_bridge_pvid = *bridge_pvid;
+ if (pvid)
+ *bridge_pvid = vlan->vid;
+ else if (*bridge_pvid == vlan->vid)
+ *bridge_pvid = 0;
+
+ ret = rtl8365mb_port_commit_pvid(ds, port, vlan_filtering, extack);
+ if (ret)
+ goto undo_bridge_pvid;
/* add port to vlan4k. It knows nothing about PVID */
ret = rtl8365mb_vlan_4k_port_add(ds, port, vlan, extack);
if (ret)
- goto undo_set_pvid;
+ goto undo_commit_pvid;
- ret = 0;
goto out_unlock;
-undo_set_pvid:
- /* undo the pvid definition */
- if (pvid != (pvid_vid == vlan->vid)) {
- if (pvid_vid)
- (void)rtl8365mb_vlan_pvid_port_set(ds, port, pvid_vid,
- NULL);
- else
- (void)rtl8365mb_vlan_pvid_port_clear(ds, port,
- vlan->vid);
- }
+undo_commit_pvid:
+ *bridge_pvid = prev_bridge_pvid;
+ (void)rtl8365mb_port_commit_pvid(ds, port, vlan_filtering, NULL);
+ goto out_unlock;
+undo_bridge_pvid:
+ *bridge_pvid = prev_bridge_pvid;
out_unlock:
mutex_unlock(&priv->vlan_lock);
return ret;
@@ -2183,6 +2217,9 @@ static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port,
bool untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED);
bool pvid = !!(vlan->flags & BRIDGE_VLAN_INFO_PVID);
struct realtek_priv *priv = ds->priv;
+ struct rtl8365mb *mb = priv->chip_data;
+ u16 prev_bridge_pvid, *bridge_pvid;
+ bool vlan_filtering;
int ret;
dev_dbg(priv->dev, "del VLAN %d on port %d, %s, %s\n",
@@ -2193,10 +2230,20 @@ static int rtl8365mb_port_vlan_del(struct dsa_switch *ds, int port,
if (vlan->vid == 0)
return -EOPNOTSUPP;
+ vlan_filtering = dsa_port_is_vlan_filtering(dsa_to_port(ds, port));
+ bridge_pvid = &mb->ports[port].bridge_pvid;
+
mutex_lock(&priv->vlan_lock);
- ret = rtl8365mb_vlan_pvid_port_clear(ds, port, vlan->vid);
- if (ret)
+ prev_bridge_pvid = *bridge_pvid;
+ if (*bridge_pvid == vlan->vid)
+ *bridge_pvid = 0;
+
+ ret = rtl8365mb_port_commit_pvid(ds, port, vlan_filtering, NULL);
+ if (ret) {
+ /* keep the state in line with the PVID still in hardware */
+ *bridge_pvid = prev_bridge_pvid;
goto out_unlock;
+ }
ret = rtl8365mb_vlan_4k_port_del(ds, port, vlan);
/* There is little incentive to try to undo the removal of PVID (if it
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
index 58948e76f..43e0efd8e 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
@@ -42,7 +42,8 @@
* may be useful for ACL rules which specify alternative forwarding
* decisions.
*
- * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports.
+ * VLANMC index 0 is reserved as a neutral PVID, used for standalone ports and
+ * for the ports of a VLAN-unaware bridge.
*
*/
@@ -892,6 +893,7 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
* @ds: dsa switch instance
* @port: port index
* @vid: VLAN VID for PVID
+ * @vlan_filtering: whether the port filters VLANs once the PVID is cleared
*
* Resets the target port's hardware PVID allocation to 0. Cleans up and frees
* the associated VLANMC entry if no other ports are referencing it.
@@ -900,7 +902,8 @@ int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
* Takes and releases &priv->map_lock.
* Return: 0 on success, or a negative error code on failure.
*/
-int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
+int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid,
+ bool vlan_filtering)
{
enum rtl8365mb_frame_ingress accepted_frame, prev_accepted_frame;
struct realtek_priv *priv = ds->priv;
@@ -935,7 +938,7 @@ int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid)
/* Changing accept frame is what really removes PVID. But only do
* that if we are filtering vlan
*/
- if (dsa_port_is_vlan_filtering(dsa_to_port(ds, port))) {
+ if (vlan_filtering) {
accepted_frame = RTL8365MB_FRAME_TYPE_TAGGED_ONLY;
ret = rtl8365mb_vlan_port_set_framefilter(priv, port,
diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
index 809034009..515b2e591 100644
--- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
+++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
@@ -36,5 +36,6 @@ int rtl8365mb_vlan_4k_port_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan);
int rtl8365mb_vlan_pvid_port_set(struct dsa_switch *ds, int port, u16 vid,
struct netlink_ext_ack *extack);
-int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid);
+int rtl8365mb_vlan_pvid_port_clear(struct dsa_switch *ds, int port, u16 vid,
+ bool vlan_filtering);
#endif /* _REALTEK_RTL8365MB_VLAN_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0
2026-11-04 8:25 ` [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0 Stijn Cleynhens
@ 2026-11-04 15:22 ` Alvin Šipraga
0 siblings, 0 replies; 5+ messages in thread
From: Alvin Šipraga @ 2026-11-04 15:22 UTC (permalink / raw)
To: Stijn Cleynhens
Cc: netdev, Linus Walleij, Luiz Angelo Daros de Luca, Andrew Lunn,
Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Mieczyslaw Nalewaj, linux-kernel
On Wed, Nov 04, 2026 at 09:25:13AM +0100, Stijn Cleynhens wrote:
> DSA installs the FDB and MDB entries of standalone ports and of
> VLAN-unaware bridges with VID 0, and the driver writes them with
> Independent VLAN Learning. No bridge VLAN sets up the VLAN4k entry of
> VID 0 though, and it defaults to Shared VLAN Learning, so the switch
> looks up and learns frames classified to VID 0 by FID. Lookups never hit
> the IVL-keyed static entries, and a learned entry for the same MAC sits
> alongside the static one.
>
> Set up VID 0 with IVL, like every other VLAN the driver programs.
>
> On its own this has no visible effect: standalone ports do not learn and
> the driver does not control host flooding, so the unused static entries
> only meant that traffic was flooded to the CPU. The next patch, which
> moves VLAN-unaware bridges onto VID 0, depends on it.
Yes, all this makes sense. I actually remember doing basically the same
thing back when I was working on this, but it seems not to have survived
the final work by Luiz. It would be good to get his input on why. I
think he asked me at some point, but I couldn't remember, and so he
rightfully dropped it. But I could be wrong.
>
> Fixes: 336e3e4a1ab3 ("net: dsa: realtek: rtl8365mb: add FDB support")
> Signed-off-by: Stijn Cleynhens <stijncleynhens@gmail.com>
> ---
> drivers/net/dsa/realtek/rtl8365mb_main.c | 8 ++++++
> drivers/net/dsa/realtek/rtl8365mb_vlan.c | 31 ++++++++++++++++++++++++
> drivers/net/dsa/realtek/rtl8365mb_vlan.h | 1 +
> 3 files changed, 40 insertions(+)
>
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index da06f952a..b90e46c16 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
> @@ -2232,6 +2232,14 @@ static int rtl8365mb_vlan_setup(struct dsa_switch *ds)
> }
> }
>
> + mutex_lock(&priv->vlan_lock);
> + ret = rtl8365mb_vlan_4k_setup_neutral(priv);
> + mutex_unlock(&priv->vlan_lock);
So I'm not sure I like this function being added to the vlan.c
library. Semantically it makes more sense to me for the main.c driver to
actually call rtl8365mb_vlan4k_port_add() for each port, so as to
explicitly say: "all ports are, one way or another, members of this
neutral VLAN". Then you would get IVL automatically.
But that API uses a struct switchdev_obj, which would be a bit crude to
construct here... So I see why you did it like this.
If you could give it a second thought and see if there's a neater way,
that would be nice. But either way I think this is fine, so:
Reviewed-by: Alvin Šipraga <alvin.sipraga@analog.com>
> + if (ret) {
> + dev_err(priv->dev, "Failed to set up VLAN 0\n");
> + return ret;
> + }
> +
> /* VLAN is always enabled. */
> ret = regmap_update_bits(priv->map, RTL8365MB_VLAN_CTRL_REG,
> RTL8365MB_VLAN_CTRL_EN_MASK,
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.c b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> index 8d49ffadc..58948e76f 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.c
> @@ -322,6 +322,37 @@ rtl8365mb_vlan_4k_port_set(struct dsa_switch *ds, int port,
> return 0;
> }
>
> +/*
> + * rtl8365mb_vlan_4k_setup_neutral() - Use IVL in the neutral VLAN
> + * @priv: realtek_priv pointer
> + *
> + * Untagged frames of standalone ports and of ports in a VLAN-unaware bridge
> + * are classified to VID 0, the VID of the neutral PVID. No bridge VLAN sets
> + * up its VLAN4k entry, which by default uses Shared VLAN Learning, so the
> + * switch would learn these frames by FID while the driver installs its FDB
> + * and MDB entries for VID 0 with Independent VLAN Learning. Use IVL in VID 0
> + * as in every other VLAN.
> + *
> + * Context: Can sleep. Must be called with &priv->vlan_lock held.
> + * Takes and releases &priv->map_lock.
> + * Return: 0 on success, or a negative error code on failure.
> + */
> +int rtl8365mb_vlan_4k_setup_neutral(struct realtek_priv *priv)
> +{
> + struct rtl8365mb_vlan4k vlan4k = {0};
> + int ret;
> +
> + lockdep_assert_held(&priv->vlan_lock);
> +
> + ret = rtl8365mb_vlan_4k_read(priv, 0, &vlan4k);
> + if (ret)
> + return ret;
> +
> + vlan4k.ivl_en = true;
> +
> + return rtl8365mb_vlan_4k_write(priv, &vlan4k);
> +}
> +
> /*
> * rtl8365mb_vlan_4k_port_add() - Add a port to a VLAN 4K table entry
> * @ds: dsa switch instance
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_vlan.h b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
> index 27a526b61..809034009 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_vlan.h
> +++ b/drivers/net/dsa/realtek/rtl8365mb_vlan.h
> @@ -28,6 +28,7 @@ int
> rtl8365mb_vlan_port_set_framefilter(struct realtek_priv *priv,
> int port,
> enum rtl8365mb_frame_ingress frame_type);
> +int rtl8365mb_vlan_4k_setup_neutral(struct realtek_priv *priv);
> int rtl8365mb_vlan_4k_port_add(struct dsa_switch *ds, int port,
> const struct switchdev_obj_port_vlan *vlan,
> struct netlink_ext_ack *extack);
> --
> 2.56.0
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges
2026-11-04 8:25 ` [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges Stijn Cleynhens
@ 2026-11-04 15:57 ` Alvin Šipraga
0 siblings, 0 replies; 5+ messages in thread
From: Alvin Šipraga @ 2026-11-04 15:57 UTC (permalink / raw)
To: Stijn Cleynhens
Cc: netdev, Linus Walleij, Luiz Angelo Daros de Luca, Andrew Lunn,
Vladimir Oltean, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Mieczyslaw Nalewaj, linux-kernel
On Wed, Nov 04, 2026 at 09:25:14AM +0100, Stijn Cleynhens wrote:
> The switch looks up its forwarding database by the VLAN that it
> classifies a frame to, and untagged frames are classified to the port
> PVID. With configure_vlan_while_not_filtering, the bridge PVID is
> programmed into the port also while the bridge is VLAN-unaware, so
> untagged frames of such a bridge are looked up in VLAN 1. DSA however
> installs the FDB and MDB entries of a VLAN-unaware bridge with VID 0,
> so these entries never match a lookup.
>
> This breaks assisted learning on the CPU port: when a station that was
> first learned behind a user port moves behind the CPU port (e.g. a
> wireless client roaming between access points), the static entry that
> DSA installs for it on the CPU port does not override the learned one,
> and unicast to the station is dropped as a hairpin until the learned
> entry ages out. Clients that need a DHCP reply in that window do not
> get one.
>
> Keep the PVID requested by the bridge per port and only program it
> while the port filters VLANs. Otherwise keep the neutral PVID (VLANMC
> index 0, VID 0) that standalone ports already use,
Makes sense.
> so that untagged frames are classified to the VID of the bridge
> entries.
This part threw me off a bit. What do you mean by "VID of the bridge
entries"? If the neutral PVID is programmed on the port of a
VLAN-unaware bridge, I would expect all untagged frames to be classified
VID=0. And with IVL this should be classified local to the bridge. Is
that what you want to say, or am I missing something?
> Commit the PVID
> again when VLAN filtering is toggled.
>
> This only covers untagged frames. Tagged frames in a VLAN-unaware bridge
> are still classified by their tag, so a tagged station that roams keeps
> hitting the stale learned entry until it ages out, as before. Fixing
> that would need the switch to ignore the C-tag on ingress of these
> ports, which is left for a separate change.
>
> Fixes: 336e3e4a1ab3 ("net: dsa: realtek: rtl8365mb: add FDB support")
> Signed-off-by: Stijn Cleynhens <stijncleynhens@gmail.com>
> ---
> drivers/net/dsa/realtek/rtl8365mb_main.c | 139 +++++++++++++++--------
> drivers/net/dsa/realtek/rtl8365mb_vlan.c | 9 +-
> drivers/net/dsa/realtek/rtl8365mb_vlan.h | 3 +-
> 3 files changed, 101 insertions(+), 50 deletions(-)
>
> diff --git a/drivers/net/dsa/realtek/rtl8365mb_main.c b/drivers/net/dsa/realtek/rtl8365mb_main.c
> index b90e46c16..a7db267bc 100644
> --- a/drivers/net/dsa/realtek/rtl8365mb_main.c
> +++ b/drivers/net/dsa/realtek/rtl8365mb_main.c
> @@ -838,6 +838,8 @@ struct rtl8365mb_cpu {
> * access via rtl8365mb_get_stats64
> * @stats_lock: protect the stats structure during read/update
> * @mib_work: delayed work for polling MIB counters
> + * @bridge_pvid: PVID requested by the bridge, 0 if none. Only programmed
> + * while the port filters VLANs. Protected by vlan_lock.
> */
> struct rtl8365mb_port {
> struct realtek_priv *priv;
> @@ -845,6 +847,7 @@ struct rtl8365mb_port {
> struct rtnl_link_stats64 stats;
> spinlock_t stats_lock;
> struct delayed_work mib_work;
> + u16 bridge_pvid;
> };
>
> /**
> @@ -2004,6 +2007,41 @@ rtl8365mb_port_set_vlan_egress_mode(struct realtek_priv *priv, int port,
> RTL8365MB_PORT_MISC_CFG_VLAN_EGRESS_MODE_MASK, val);
> }
>
> +/* Program the PVID that the port should use for untagged frames: the PVID of
> + * the bridge while the port filters VLANs, and the neutral PVID (VID 0)
> + * otherwise. A VLAN-unaware bridge, like a standalone port, gets its FDB and
> + * MDB entries with VID 0 from DSA. As the switch looks up its forwarding
> + * database by the VLAN that it classifies a frame to, a bridge PVID would make
> + * those entries miss every lookup.
> + */
> +static int rtl8365mb_port_commit_pvid(struct dsa_switch *ds, int port,
> + bool vlan_filtering,
> + struct netlink_ext_ack *extack)
> +{
> + struct realtek_priv *priv = ds->priv;
> + struct rtl8365mb *mb = priv->chip_data;
> + u16 hw_pvid, pvid = 0;
Please separate the variables on their own lines.
> + int ret;
> +
> + lockdep_assert_held(&priv->vlan_lock);
> +
> + if (vlan_filtering)
> + pvid = mb->ports[port].bridge_pvid;
I think an else { pvid = 0; } would be more explicit than setting it
upstairs, but it's a nitpick.
> +
> + ret = rtl8365mb_vlan_port_get_pvid(priv, port, &hw_pvid);
> + if (ret)
> + return ret;
> +
> + if (pvid == hw_pvid)
> + return 0;
> +
> + if (pvid)
> + return rtl8365mb_vlan_pvid_port_set(ds, port, pvid, extack);
> +
> + return rtl8365mb_vlan_pvid_port_clear(ds, port, hw_pvid,
> + vlan_filtering);
Can we unify _set and _clear somehow? The code gets quite confusing with
the two variants, and it seems now that with the vlan_filtering
argument, you can unify the logic.
In general the patch makes sense, I'm just straining a bit to follow the
logic reliably. If not something like the above, can you give a thought
as to how it might otherwise be simplified?
Kind regards,
Alvin
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-04 15:57 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 8:25 [PATCH net 0/2] net: dsa: realtek: rtl8365mb: fix FDB lookups in VLAN-unaware bridges Stijn Cleynhens
2026-11-04 8:25 ` [PATCH net 1/2] net: dsa: realtek: rtl8365mb: use IVL in VLAN 0 Stijn Cleynhens
2026-11-04 15:22 ` Alvin Šipraga
2026-11-04 8:25 ` [PATCH net 2/2] net: dsa: realtek: rtl8365mb: keep the neutral PVID in VLAN-unaware bridges Stijn Cleynhens
2026-11-04 15:57 ` Alvin Šipraga
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox