Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/3] net: dsa: motorcomm: FDB fixup
@ 2026-11-11 23:41 David Yang
  2026-11-11 23:41 ` [PATCH net 1/3] net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries David Yang
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: David Yang @ 2026-11-11 23:41 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel

Various FDB defects. Assisted and verified on the hardware by LLM.

There are more gotcha about multi-bridge setup, but I'd leave that for
net-next since multi-bridge has always been tricky among DSA drivers.

David Yang (3):
  net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries
  net: dsa: motorcomm: Rewrite FDB entry on port migration
  net: dsa: motorcomm: Use VID 0 as the unaware PVID

 drivers/net/dsa/motorcomm/chip.c | 111 +++++++++++++++----------------
 1 file changed, 52 insertions(+), 59 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net 1/3] net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries
  2026-11-11 23:41 [PATCH net 0/3] net: dsa: motorcomm: FDB fixup David Yang
@ 2026-11-11 23:41 ` David Yang
  2026-11-11 23:41 ` [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration David Yang
  2026-11-11 23:41 ` [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID David Yang
  2 siblings, 0 replies; 6+ messages in thread
From: David Yang @ 2026-11-11 23:41 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel

When adding an fdb/mdb entry whose (address, VID) key already exists in
hardware, the existing egress ports were silently merged into the new
static entry. Ports learned by hardware are untracked by DSA and can
never be deleted. Replace the port mask instead of merging when the
existing entry is not static.

Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 drivers/net/dsa/motorcomm/chip.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 33e5fc127244..150c51f1aa35 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -799,7 +799,10 @@ yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
 
 	ctrl1 = val1 & ~YT921X_FDB_IO1_STATUS_M;
 	ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
-	ctrl2 = val2 | YT921X_FDB_IO2_EGR_PORTS(ports_mask);
+	ctrl2 = val2;
+	if (ctrl1 != val1)
+		ctrl2 &= ~YT921X_FDB_IO2_EGR_PORTS_M;
+	ctrl2 |= YT921X_FDB_IO2_EGR_PORTS(ports_mask);
 	if (ctrl1 == val1 && ctrl2 == val2)
 		return 0;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration
  2026-11-11 23:41 [PATCH net 0/3] net: dsa: motorcomm: FDB fixup David Yang
  2026-11-11 23:41 ` [PATCH net 1/3] net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries David Yang
@ 2026-11-11 23:41 ` David Yang
  2026-11-15 23:01   ` netdev-bot+sashiko
  2026-11-11 23:41 ` [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID David Yang
  2 siblings, 1 reply; 6+ messages in thread
From: David Yang @ 2026-11-11 23:41 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel

When unicast addresses move between ports, DSA calls .port_fdb_add()
without a prior .port_fdb_del() call, making the new port directly OR
into the existing entries. The old port mask then loses track in DSA and
can never be deleted.

Fuse FDB join/leave into one change function, and rewrite the whole
entry on add with the bridge port mask as the leave mask, so that ports
left behind by the migration are dropped from the entry.

Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 drivers/net/dsa/motorcomm/chip.c | 96 ++++++++++++++++----------------
 1 file changed, 49 insertions(+), 47 deletions(-)

diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index 150c51f1aa35..a2be819ccbed 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -729,13 +729,14 @@ yt921x_fdb_add(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
 }
 
 static int
-yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
-		 u16 vid, u16 ports_mask)
+yt921x_fdb_update(struct yt921x_priv *priv, const unsigned char *addr,
+		  u16 vid, u16 leave_ports_mask, u16 join_ports_mask)
 {
 	u16 index;
 	u32 ctrl1;
 	u32 ctrl2;
 	u32 ctrl;
+	u32 val1;
 	u32 val2;
 	u32 val;
 	int res;
@@ -744,69 +745,63 @@ yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
 	res = yt921x_fdb_has(priv, addr, vid, &index);
 	if (res)
 		return res;
-	if (index >= YT921X_FDB_NUM)
-		return 0;
+	if (index >= YT921X_FDB_NUM) {
+		if (!join_ports_mask)
+			return 0;
+
+		return yt921x_fdb_add(priv, addr, vid, join_ports_mask);
+	}
 
 	/* Check if action required */
 	res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
 	if (res)
 		return res;
 
-	ctrl2 = val2 & ~YT921X_FDB_IO2_EGR_PORTS(ports_mask);
-	if (ctrl2 == val2)
-		return 0;
-	if (!(ctrl2 & YT921X_FDB_IO2_EGR_PORTS_M)) {
-		ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
-		res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
-		if (res)
-			return res;
+	ctrl2 = val2 & ~YT921X_FDB_IO2_EGR_PORTS(leave_ports_mask);
+	if (!join_ports_mask) {
+		if (ctrl2 == val2)
+			return 0;
 
-		return yt921x_fdb_wait(priv, &val);
+		if (!(ctrl2 & YT921X_FDB_IO2_EGR_PORTS_M)) {
+			ctrl = YT921X_FDB_OP_OP_DEL | YT921X_FDB_OP_START;
+			res = yt921x_reg_write(priv, YT921X_FDB_OP, ctrl);
+			if (res)
+				return res;
+
+			return yt921x_fdb_wait(priv, &val);
+		}
 	}
 
-	res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &ctrl1);
+	res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &val1);
 	if (res)
 		return res;
 
+	ctrl1 = val1;
+	if (join_ports_mask) {
+		ctrl1 &= ~YT921X_FDB_IO1_STATUS_M;
+		ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
+		if (ctrl1 != val1)
+			ctrl2 &= ~YT921X_FDB_IO2_EGR_PORTS_M;
+	}
+	ctrl2 |= YT921X_FDB_IO2_EGR_PORTS(join_ports_mask);
+	if (ctrl1 == val1 && ctrl2 == val2)
+		return 0;
+
 	return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
 }
 
+static int
+yt921x_fdb_leave(struct yt921x_priv *priv, const unsigned char *addr,
+		 u16 vid, u16 ports_mask)
+{
+	return yt921x_fdb_update(priv, addr, vid, ports_mask, 0);
+}
+
 static int
 yt921x_fdb_join(struct yt921x_priv *priv, const unsigned char *addr, u16 vid,
 		u16 ports_mask)
 {
-	u16 index;
-	u32 ctrl1;
-	u32 ctrl2;
-	u32 val1;
-	u32 val2;
-	int res;
-
-	/* Check for presence */
-	res = yt921x_fdb_has(priv, addr, vid, &index);
-	if (res)
-		return res;
-	if (index >= YT921X_FDB_NUM)
-		return yt921x_fdb_add(priv, addr, vid, ports_mask);
-
-	/* Check if action required */
-	res = yt921x_reg_read(priv, YT921X_FDB_OUT1, &val1);
-	if (res)
-		return res;
-	res = yt921x_reg_read(priv, YT921X_FDB_OUT2, &val2);
-	if (res)
-		return res;
-
-	ctrl1 = val1 & ~YT921X_FDB_IO1_STATUS_M;
-	ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
-	ctrl2 = val2;
-	if (ctrl1 != val1)
-		ctrl2 &= ~YT921X_FDB_IO2_EGR_PORTS_M;
-	ctrl2 |= YT921X_FDB_IO2_EGR_PORTS(ports_mask);
-	if (ctrl1 == val1 && ctrl2 == val2)
-		return 0;
-
-	return yt921x_fdb_add_index_in12(priv, index, ctrl1, ctrl2);
+	return yt921x_fdb_update(priv, addr, vid, 0, ports_mask);
 }
 
 static int
@@ -877,10 +872,17 @@ yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port,
 			const unsigned char *addr, u16 vid, struct dsa_db db)
 {
 	struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
+	struct dsa_port *dp = dsa_to_port(ds, port);
+	u32 bridge_ports_mask = 0;
+	struct net_device *bdev;
 	int res;
 
+	bdev = dsa_port_bridge_dev_get(dp);
+	if (bdev)
+		bridge_ports_mask = dsa_bridge_ports(ds, bdev);
+
 	mutex_lock(&priv->reg_lock);
-	res = yt921x_fdb_join(priv, addr, vid, BIT(port));
+	res = yt921x_fdb_update(priv, addr, vid, bridge_ports_mask, BIT(port));
 	mutex_unlock(&priv->reg_lock);
 
 	return res;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID
  2026-11-11 23:41 [PATCH net 0/3] net: dsa: motorcomm: FDB fixup David Yang
  2026-11-11 23:41 ` [PATCH net 1/3] net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries David Yang
  2026-11-11 23:41 ` [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration David Yang
@ 2026-11-11 23:41 ` David Yang
  2026-11-15 23:01   ` netdev-bot+sashiko
  2 siblings, 1 reply; 6+ messages in thread
From: David Yang @ 2026-11-11 23:41 UTC (permalink / raw)
  To: netdev
  Cc: David Yang, Andrew Lunn, Vladimir Oltean, David S. Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, linux-kernel

The hardware classifies untagged and priority-tagged frames to the
port's PVID before the VLAN table lookup, and the FDB lookup key equals
the classified VID (measured on YT9215S).

The driver assigned PVID 4095 to the VLAN-unaware ports, while switchdev
reports the bridge's FDB entries with VID 0. These entries were then
installed under FID 0 and could never be hit by the hardware lookups.
Dynamic entries learnt under VID 4095, if any, took effect instead.

Lift the restriction on VLAN 0, and use it as the PVID of the
VLAN-unaware ports instead. The standalone default of 4095 stays, so
mislearnt entries, if any, would not pollute the bridge FDB.

Fixes: 186623f4aa72 ("net: dsa: yt921x: Add support for Motorcomm YT921x")
Signed-off-by: David Yang <mmyangfl@gmail.com>
---
 drivers/net/dsa/motorcomm/chip.c | 18 +++---------------
 1 file changed, 3 insertions(+), 15 deletions(-)

diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
index a2be819ccbed..376f16466cdd 100644
--- a/drivers/net/dsa/motorcomm/chip.c
+++ b/drivers/net/dsa/motorcomm/chip.c
@@ -79,7 +79,6 @@ static const struct yt921x_info yt921x_infos[] = {
 	{}
 };
 
-#define YT921X_VID_UNWARE	4095
 
 struct yt921x_reg_mdio {
 	struct mii_bus *bus;
@@ -962,7 +961,7 @@ yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
 	bdev = dsa_port_bridge_dev_get(dp);
 
 	if (!bdev || !vlan_filtering)
-		pvid = YT921X_VID_UNWARE;
+		pvid = 0;
 	else
 		br_vlan_get_pvid(bdev, &pvid);
 	res = yt921x_port_set_pvid(priv, port, pvid);
@@ -1030,8 +1029,7 @@ yt921x_pvid_clear(struct yt921x_priv *priv, int port)
 
 	vlan_filtering = dsa_port_is_vlan_filtering(dp);
 
-	res = yt921x_port_set_pvid(priv, port,
-				   vlan_filtering ? 0 : YT921X_VID_UNWARE);
+	res = yt921x_port_set_pvid(priv, port, 0);
 	if (res)
 		return res;
 
@@ -1188,7 +1186,7 @@ static int yt921x_userport_standalone(struct yt921x_priv *priv, int port)
 	/* Unrelated since learning is off and all packets are trapped;
 	 * set it anyway
 	 */
-	res = yt921x_port_set_pvid(priv, port, YT921X_VID_UNWARE);
+	res = yt921x_port_set_pvid(priv, port, 4095);
 	if (res)
 		return res;
 
@@ -2270,7 +2268,6 @@ static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
 {
 	struct dsa_switch *ds = &priv->ds;
 	unsigned long cpu_ports_mask;
-	u32 ctrls[2];
 	u32 ctrl;
 	int port;
 	int res;
@@ -2328,15 +2325,6 @@ static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
 	if (res)
 		return res;
 
-	/* Tagged VID 0 should be treated as untagged, which confuses the
-	 * hardware a lot
-	 */
-	ctrls[0] = YT921X_VLAN_CTRLa_LEARN_DIS | YT921X_VLAN_CTRLa_PORTS_M;
-	ctrls[1] = 0;
-	res = yt921x_reg64_write(priv, YT921X_VLANn_CTRL(0), ctrls);
-	if (res)
-		return res;
-
 	return 0;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration
  2026-11-11 23:41 ` [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration David Yang
@ 2026-11-15 23:01   ` netdev-bot+sashiko
  0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15 23:01 UTC (permalink / raw)
  To: mmyangfl
  Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni,
	linux-kernel

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The fix is incomplete: an address that moves between a user port
  and the host (CPU port) still leaves a stale port bit in the hardware…
- [Low] yt921x_dsa_port_fdb_add() now calls dsa_bridge_ports(ds, bdev).

--

net: dsa: motorcomm: Rewrite FDB entry on port migration

This commit merges yt921x_fdb_join() and yt921x_fdb_leave() into a single
yt921x_fdb_update() helper that takes separate leave and join port masks.
yt921x_dsa_port_fdb_add() now passes the bridge port mask as the leave
mask, so ports left behind when an address migrates are dropped from the
hardware entry.

> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index f34f37b736249..d84e1f6dad843 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c

[ ... ]

> @@ -877,10 +872,17 @@ yt921x_dsa_port_fdb_add(struct dsa_switch *ds, int port,
>  			const unsigned char *addr, u16 vid, struct dsa_db db)
>  {
>  	struct yt921x_priv *priv = dsa_to_yt921x_priv(ds);
> +	struct dsa_port *dp = dsa_to_port(ds, port);
> +	u32 bridge_ports_mask = 0;
> +	struct net_device *bdev;
>  	int res;
>  
> +	bdev = dsa_port_bridge_dev_get(dp);
> +	if (bdev)
> +		bridge_ports_mask = dsa_bridge_ports(ds, bdev);

[Severity: Low]
Is it safe to walk every port's dp->bridge here without rtnl?

dsa_bridge_ports() does a plain load of dp->bridge, then reads
dp->bridge->dev, for each user port via dsa_port_bridge_dev_get(). This
path runs from dsa_user_switchdev_event_work() on dsa_owq, and rtnl_lock is
not held there. Before this patch, the driver only called
dsa_bridge_ports() from the bridge_join and bridge_flags paths, which hold
rtnl.

The bridge leave path looks covered. dsa_port_pre_bridge_leave() flushes
dsa_owq under rtnl before dsa_port_bridge_destroy() runs. The rollback in
dsa_port_bridge_join() in net/dsa/port.c does not flush first, though:

out_rollback:
	dsa_port_bridge_destroy(dp, br);

Say FDB work queued by an earlier static fdb add on port 1 is still running
when a bridge join on port 2 fails. The work could load dp2->bridge just
before the kfree() and then read ->dev from freed memory.

The loaded value is only compared with bdev, so the practical impact is
small. Separately, bridge_join stores to dp->bridge on another port with a
plain write. That is a data race with this reader that KCSAN can see.

DSA core already does similar lockless cross-port reads in
dsa_foreign_dev_check() and dsa_tree_offloads_bridge_dev().

Could the port's own bridge come from the db argument (db.bridge) rather
than re-reading dp->bridge outside rtnl?

> +
>  	mutex_lock(&priv->reg_lock);
> -	res = yt921x_fdb_join(priv, addr, vid, BIT(port));
> +	res = yt921x_fdb_update(priv, addr, vid, bridge_ports_mask, BIT(port));

[Severity: High]
Does this still leave a stale port bit when an address moves between a user
port and the host?

The leave mask comes from the target port's own dp->bridge. This driver
sets ds->assisted_learning_on_cpu_port, so DSA installs host addresses by
calling .port_fdb_add() with port set to the CPU port. dp->bridge is only
set on user ports, so bdev is NULL and bridge_ports_mask is 0.

yt921x_fdb_update(priv, addr, vid, 0, BIT(cpu)) then keeps every existing
egress bit. The existing entry is already STATIC, so ctrl1 == val1 and the
full clear in yt921x_fdb_update() is skipped as well:

	ctrl1 = val1;
	if (join_ports_mask) {
		ctrl1 &= ~YT921X_FDB_IO1_STATUS_M;
		ctrl1 |= YT921X_FDB_IO1_STATUS_STATIC;
		if (ctrl1 != val1)
			ctrl2 &= ~YT921X_FDB_IO2_EGR_PORTS_M;
	}
	ctrl2 |= YT921X_FDB_IO2_EGR_PORTS(join_ports_mask);

The result is still an OR-in, not a rewrite of the whole entry.

One way to reach this: take a static entry on swp1 and use bridge fdb
replace to point it at a foreign interface in the same bridge, or replace
it with a local/permanent entry. fdb_add_entry()->fdb_update_dst() only
sends RTM_NEWNEIGH, so swp1 never gets a SWITCHDEV_FDB_DEL_TO_DEVICE:

fdb_add_entry()
  fdb_update_dst()
  fdb_notify(br, fdb, RTM_NEWNEIGH, true)
    dsa_user_fdb_event()            /* host_addr = true */
      dsa_port_bridge_host_fdb_add()
        dsa_switch_host_fdb_add()
          dsa_port_do_fdb_add(cpu_dp)
            yt921x_dsa_port_fdb_add()   /* port == CPU port */

The swp1 bit then stays in a STATIC entry that DSA no longer tracks. A
later host fdb del clears only the CPU bit. Fast age through
yt921x_fdb_flush_port(..., false) does not flush static entries. Unicast
frames for that MAC keep going out of swp1.

The reverse direction seems to have the same problem.
dsa_switch_for_each_bridge_member() only iterates user ports, so the mask
from dsa_bridge_ports() never includes a CPU port. When a host address is
re-pointed to swp1, the old CPU port bit is never cleared, and frames for
that MAC also go to the CPU.

The commit message says:

  "rewrite the whole entry on add with the bridge port mask as the leave
  mask, so that ports left behind by the migration are dropped from the
  entry."

but egress bits for ports outside the bridge's user ports are kept.

The later patches in the series, up to "Use VID 0 as the unaware PVID",
don't appear to change yt921x_dsa_port_fdb_add() or yt921x_fdb_update().

For DSA_DB_BRIDGE host adds, db.bridge is a copy of *dp->bridge. Only .num
is zeroed, because fdb_isolation is not set.

Could db.bridge.dev be used to work out the bridge mask for CPU port
calls? Could the CPU ports also be added to the leave mask for user port
calls?

>  	mutex_unlock(&priv->reg_lock);
>  
>  	return res;

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261111234110.1449602-1-mmyangfl%40gmail.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID
  2026-11-11 23:41 ` [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID David Yang
@ 2026-11-15 23:01   ` netdev-bot+sashiko
  0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15 23:01 UTC (permalink / raw)
  To: mmyangfl
  Cc: netdev, andrew, olteanv, davem, edumazet, kuba, pabeni,
	linux-kernel

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 2 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] In the most common case the fix never takes effect.
- [Medium] A port that leaves a VLAN-aware bridge ends up standalone with
  PVID 0 instead of 4095.
- [Medium] The patch removes the setup-time write in
  yt921x_chip_setup_dsa() that set the VID 0 VLAN entry to LEARN_DIS,…

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but
  yt921x_vlan_filtering(), yt921x_dsa_port_vlan_add() and…

--

net: dsa: motorcomm: Use VID 0 as the unaware PVID

This patch changes the PVID of VLAN-unaware bridge ports in the yt921x
driver from 4095 to 0. The goal is for the switchdev FDB entries, which are
reported with VID 0, to be hit by hardware lookups. It also removes the
setup-time programming of the VID 0 VLAN entry, and standalone ports keep
PVID 4095.

> diff --git a/drivers/net/dsa/motorcomm/chip.c b/drivers/net/dsa/motorcomm/chip.c
> index d84e1f6dad843..5cc8a21c9d55f 100644
> --- a/drivers/net/dsa/motorcomm/chip.c
> +++ b/drivers/net/dsa/motorcomm/chip.c

[ ... ]

> @@ -962,7 +961,7 @@ yt921x_vlan_filtering(struct yt921x_priv *priv, int port, bool vlan_filtering)
>  	bdev = dsa_port_bridge_dev_get(dp);
>  
>  	if (!bdev || !vlan_filtering)
> -		pvid = YT921X_VID_UNWARE;
> +		pvid = 0;

[Severity: High]

Does this assignment run when a standalone port joins a VLAN-unaware
bridge?

yt921x_port_setup() calls yt921x_userport_standalone(), so the port starts
at PVID 4095. The join path never touches PORTn_VLAN_CTRL:

yt921x_dsa_port_bridge_join()
  yt921x_bridge_join()
    yt921x_userport_bridge()   /* only clears YT921X_PORT_LEARN_DIS */
    yt921x_bridge()            /* only rewrites PORTn_ISOLATION */

The DSA core then calls dsa_port_vlan_filtering(dp, false) from
dsa_port_switchdev_sync_attrs(). That call returns early because the port
is already unaware, and the driver sets neither
needs_standalone_vlan_filtering nor vlan_filtering_is_global:

net/dsa/port.c:dsa_port_vlan_filtering() {
    ...
	if (dsa_port_is_vlan_filtering(dp) == vlan_filtering)
		return 0;
    ...
}

So yt921x_vlan_filtering() is never called. The replayed default_pvid VLAN
goes to yt921x_pvid_set(), which also skips the PVID write when the port is
not filtering:

	if (vlan_filtering) {
		res = yt921x_port_set_pvid(priv, port, vid);
		...
	}

In the default "ip link set lanX master br0" case with vlan_filtering=0,
does the hardware keep classifying untagged and priority-tagged frames to
VID 4095? If so, the VID 0 entries installed by yt921x_dsa_port_fdb_add()
would still never be hit, which is the problem this patch sets out to fix.

Before this patch, standalone and unaware ports both used 4095, so join did
not need a PVID write. Should yt921x_bridge_join() or
yt921x_userport_bridge() now set PVID 0 when the bridge is not VLAN
filtering?

[Severity: Medium]

The !bdev case now gets PVID 0 as well. What happens when a port leaves a
VLAN-aware bridge?

The commit message says "The standalone default of 4095 stays". However,
dsa_port_bridge_leave() runs things in this order:

dsa_port_bridge_leave()
  dsa_port_bridge_destroy()           /* dp->bridge = NULL */
  DSA_NOTIFIER_BRIDGE_LEAVE
    yt921x_bridge_leave()
      yt921x_userport_standalone()    /* PVID 4095 */
  dsa_port_switchdev_unsync_attrs()
    dsa_port_reset_vlan_filtering()
      dsa_port_vlan_filtering(dp, false)
        yt921x_vlan_filtering(priv, port, false)

At the last step dsa_port_bridge_dev_get(dp) returns NULL, so this branch
overwrites 4095 with 0. The old code wrote 4095 here.

Can the standalone port then classify into FID 0, which the VLAN-unaware
bridges now use? The comment in yt921x_chip_setup_dsa() says port
isolation is applied after the FDB lookup. A frame on the standalone port
whose DA hits a FID 0 entry for a bridged port would then be dropped by
isolation, rather than trapped to the CPU as unknown unicast.

The standalone PVID also starts to depend on history. It is 4095 after
setup or after leaving an unaware bridge, but 0 after leaving an aware
bridge. Should the !bdev case keep 4095?

>  	else
>  		br_vlan_get_pvid(bdev, &pvid);

[Severity: High]

This isn't a bug introduced by this patch, but is br_vlan_get_pvid() given
the right device here?

bdev comes from dsa_port_bridge_dev_get(dp), so it is the bridge master.
For a bridge master, br_vlan_get_pvid() returns the PVID of the bridge's
own VLAN group:

net/bridge/br_vlan.c:br_vlan_get_pvid() {
    ...
	else if (netif_is_bridge_master(dev))
		vg = br_vlan_group(netdev_priv(dev));
    ...
}

That is the host PVID, not the PVID of the port being configured. When
vlan_filtering is enabled, a port set up with pvid 10 would get the
bridge's PVID (for example 1) written into PORTn_VLAN_CTRL.CVID.

yt921x_dsa_port_vlan_add() and yt921x_dsa_port_vlan_del() make the same
comparison:

		bdev = dsa_port_bridge_dev_get(dp);
		if (bdev) {
			br_vlan_get_pvid(bdev, &pvid);
			if (pvid == vid)
				res = yt921x_pvid_clear(priv, port);
		}

Take the common sequence "bridge vlan add dev lanX vid 10 pvid untagged;
bridge vlan del dev lanX vid 1". When vid 1 is deleted, the bridge
master's PVID is still 1.

Would that call yt921x_pvid_clear(), write PVID 0 and set
CVLAN_DROP_UNTAGGED? All untagged ingress on lanX would then be dropped,
while the software bridge still thinks lanX has PVID 10.

>  	res = yt921x_port_set_pvid(priv, port, pvid);

[ ... ]

> @@ -2336,15 +2333,6 @@ static int yt921x_chip_setup_dsa(struct yt921x_priv *priv)
>  	if (res)
>  		return res;
>  
> -	/* Tagged VID 0 should be treated as untagged, which confuses the
> -	 * hardware a lot
> -	 */
> -	ctrls[0] = YT921X_VLAN_CTRLa_LEARN_DIS | YT921X_VLAN_CTRLa_PORTS_M;
> -	ctrls[1] = 0;
> -	res = yt921x_reg64_write(priv, YT921X_VLANn_CTRL(0), ctrls);
> -	if (res)
> -		return res;
> -

[Severity: Medium]

VID 0 is now the classification VLAN and FID for every VLAN-unaware
bridge. With this write removed, what programs the VID 0 entry?

The entry is left at hardware reset defaults for learning, membership and
untag. yt921x_dsa_port_vlan_add() and yt921x_dsa_port_vlan_del() have no
special case for VID 0, so the 8021q auto VID 0 can rewrite it.

8021q adds VID 0 through vlan_vid0_add() on NETDEV_UP when
NETIF_F_HW_VLAN_CTAG_FILTER is set. DSA sets that feature while the port
is VLAN filtering. The add reaches yt921x_vlan_add(priv, port, 0, false),
which sets the port and CPU member bits and clears UNTAG_PORTn.

When the bridge then switches to vlan_filtering=0:

dsa_user_manage_vlan_filtering(false)
  vlan_for_each(user, dsa_user_clear_vlan, user)
    yt921x_dsa_port_vlan_del(vid 0)
      yt921x_vlan_del(priv, port, 0)

and yt921x_vlan_del() does:

	masks[0] = YT921X_VLAN_CTRLa_PORTn(port);
	masks[1] = YT921X_VLAN_CTRLb_UNTAG_PORTn(port);

	return yt921x_reg64_clear_bits(priv, YT921X_VLANn_CTRL(vid), masks);

Does this remove the port from the VLAN entry that the unaware bridge now
forwards in?

VLAN-aware ports with no PVID also classify to VID 0, through
yt921x_pvid_clear() or yt921x_vlan_filtering() with pvid 0. LEARN_DIS is no
longer set on VID 0. Can frames accepted there, such as priority-tagged
frames if CVLAN_DROP_UNTAGGED does not cover them, be learnt into the FID 0
table that the unaware bridges use?

The comment in yt921x_dsa_port_vlan_add() also looks stale for VID 0:

	/* CPU port is supposed to be a member of every VLAN; see
	 * yt921x_vlan_add() and yt921x_port_setup()
	 */

Apart from 8021q, nothing adds the CPU port to VLAN 0 any more.

>  	return 0;
>  }

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261111234110.1449602-1-mmyangfl%40gmail.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-15 23:01 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11 23:41 [PATCH net 0/3] net: dsa: motorcomm: FDB fixup David Yang
2026-11-11 23:41 ` [PATCH net 1/3] net: dsa: motorcomm: Do not inherit ports from dynamic FDB entries David Yang
2026-11-11 23:41 ` [PATCH net 2/3] net: dsa: motorcomm: Rewrite FDB entry on port migration David Yang
2026-11-15 23:01   ` netdev-bot+sashiko
2026-11-11 23:41 ` [PATCH net 3/3] net: dsa: motorcomm: Use VID 0 as the unaware PVID David Yang
2026-11-15 23:01   ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox