All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Caleb James DeLisle" <cjd@cjdns.fr>
Cc: devicetree@vger.kernel.org, robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH v4 3/7] net: dsa: mediatek: add support for passthrough mode
Date: Fri, 30 Oct 2026 19:22:20 +0000	[thread overview]
Message-ID: <sashiko-outbox-191438@kernel.org> (raw)
In-Reply-To: <20261029184202.1859843-4-cjd@cjdns.fr>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] net: dsa: mt7530: mt753x_check_passthrough() breaks dual-CPU-port boards
- [High] net: dsa: mt7530: mt753x_trap_frames() blocks CPU control frames in passthrough
- [High] net: dsa: mt7530: downstream switch callbacks assume local CPU port
- [Medium] net: dsa: mt7530: setup overwrites PVC_EG_TAG on passthrough CPU port
- [Medium] net: dsa: mt7530: mt7530_port_enable() ignores per-port CPU conduit
--

commit 89ff16dda9d512576bf6153ac6b119283ed62bea
Author: Caleb James DeLisle <cjd@cjdns.fr>

net: dsa: mediatek: add support for passthrough mode

This commit adds passthrough mode support for MT7530 switches so that an
upstream SoC-integrated switch can pass traffic to and from a downstream
MT7530 switch without altering the MediaTek DSA tag.

> diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
> index d10194488bdee..c5b3b00acb956 100644
> --- a/drivers/net/dsa/mt7530.c
> +++ b/drivers/net/dsa/mt7530.c

[ ... ]

> @@ -1286,13 +1297,81 @@ mt753x_trap_frames(struct mt7530_priv *priv)
>  				   TO_CPU_FW_CPU_ONLY);
>  }

[ ... ]

> +/* If this switch is downstream of another switch that is in passthrough mode,
> + * the "CPU" port is actually a DSA port.
> + */
>  static void
>  mt753x_cpu_port_enable(struct dsa_switch *ds, int port)
>  {
>  	struct mt7530_priv *priv = ds->priv;
>  
> -	/* Enable Mediatek header mode on the cpu port */
> -	regmap_write(priv->regmap, MT7530_PVC_P(port), PORT_SPEC_TAG);
> +	if (priv->is_passthrough) {
> +		/* Disable parsing of the DSA tag, it will be forwarded blindly
> +		 * to the downstream switch.
> +		 */
> +		regmap_write(priv->regmap, MT7530_PVC_P(port),
> +			     VLAN_ATTR(MT7530_VLAN_TRANSPARENT) |
> +			     PVC_EG_TAG(MT7530_VLAN_EG_DISABLED));
> +
> +		/* The port is not configured to parse DSA tags, so they are
> +		 * liable to be confused for length fields, so length check is
> +		 * disabled.
> +		 */
> +		regmap_clear_bits(priv->regmap, MT753X_AGC, AGC_L2LEN_CHK);
> +
> +		/* In passthrough mode, MTU is only enforced downstream */
> +		mt7530_port_change_mtu(ds, port, MT7530_MAX_MTU);
> +
> +		/* Loop detection has no value in passthrough mode */
> +		regmap_set_bits(priv->regmap, MT753X_MTRAP,
> +				MT7530_LOOP_DET_DISABLE);
> +	} else {
> +		/* Not passthrough, enable DSA tag handling on CPU port. */
> +		regmap_write(priv->regmap, MT7530_PVC_P(port), PORT_SPEC_TAG);
> +	}
>  
>  	/* Enable flooding on the CPU port */
>  	regmap_set_bits(priv->regmap, MT753X_MFC,
> @@ -1322,6 +1401,7 @@ static int
>  mt7530_port_enable(struct dsa_switch *ds, int port,
>  		   struct phy_device *phy)
>  {
> +	int upstream_pt = dsa_switch_upstream_port(ds);
>  	struct dsa_port *dp = dsa_to_port(ds, port);
>  	struct mt7530_priv *priv = ds->priv;
>  
> @@ -1332,13 +1412,38 @@ mt7530_port_enable(struct dsa_switch *ds, int port,
>  	 * bridge.
>  	 */
>  	if (dsa_port_is_user(dp)) {
> -		struct dsa_port *cpu_dp = dp->cpu_dp;
> +		priv->ports[port].pm |= PCR_MATRIX(BIT(upstream_pt));
>  
> -		priv->ports[port].pm |= PCR_MATRIX(BIT(cpu_dp->index));

[Severity: Medium]
On switches with multiple CPU ports (such as MT7531 where per-port conduit
assignment is supported via mt753x_port_change_conduit()), does replacing
dp->cpu_dp->index with dsa_switch_upstream_port(ds) in mt7530_port_enable()
connect a user port to the wrong CPU port when its assigned conduit differs
from that of the first available port?

dsa_switch_upstream_port(ds) returns dsa_upstream_port(ds, dp->index) for the
first available port on the switch rather than dsa_upstream_port(ds, port)
for the port being enabled. When a user port assigned to the secondary CPU
conduit is brought up via dsa_user_open() -> mt7530_port_enable(), shouldn't
this use dsa_upstream_port(ds, port) instead?

[ ... ]

> @@ -2511,7 +2671,7 @@ mt7530_setup(struct dsa_switch *ds)
>  		/* Disable learning by default on all ports */
>  		regmap_set_bits(priv->regmap, MT7530_PSC_P(i), SA_DIS);
>  
> -		if (dsa_is_cpu_port(ds, i)) {
> +		if (dsa_is_upstream_port(ds, i)) {
>  			mt753x_cpu_port_enable(ds, i);
>  		} else {
>  			mt7530_port_disable(ds, i);

[Severity: Medium]
In both mt7530_setup() and mt7531_setup_common(), mt753x_cpu_port_enable()
sets MT7530_PVC_P(port) with PVC_EG_TAG(MT7530_VLAN_EG_DISABLED) when
priv->is_passthrough is true, but right after the if/else block in the loop:

drivers/net/dsa/mt7530.c:mt7530_setup() {
    ...
		if (dsa_is_upstream_port(ds, i)) {
			mt753x_cpu_port_enable(ds, i);
		} else {
			mt7530_port_disable(ds, i);

			/* Set default PVID to 0 on all user ports */
			regmap_update_bits(priv->regmap, MT7530_PPBV1_P(i),
					   G0_PORT_VID_MASK, G0_PORT_VID_DEF);
		}
		/* Enable consistent egress tag */
		regmap_update_bits(priv->regmap, MT7530_PVC_P(i),
				   PVC_EG_TAG_MASK,
				   PVC_EG_TAG(MT7530_VLAN_EG_CONSISTENT));
    ...
}

Doesn't this unconditionally overwrite PVC_EG_TAG_MASK on the passthrough
upstream CPU port with MT7530_VLAN_EG_CONSISTENT, since mt7530_port_enable()
only rewrites MT7530_PVC_P(port) for the downstream DSA link port?

[Severity: High]
Also in mt7530_setup() and mt7531_setup_common(), mt753x_trap_frames(priv)
is still called unconditionally even when priv->is_passthrough is true:

drivers/net/dsa/mt7530.c:mt753x_trap_frames() {
	regmap_update_bits(priv->regmap, MT753X_BPC,
			   PAE_BPDU_FR | PAE_EG_TAG_MASK | PAE_PORT_FW_MASK |
				   BPDU_EG_TAG_MASK | BPDU_PORT_FW_MASK,
			   PAE_BPDU_FR | PAE_EG_TAG(MT7530_VLAN_EG_DISABLED) |
				   PAE_PORT_FW(TO_CPU_FW_CPU_ONLY) |
				   BPDU_EG_TAG(MT7530_VLAN_EG_DISABLED) |
				   TO_CPU_FW_CPU_ONLY);
    ...
}

On a normal switch, PORT_SPEC_TAG is enabled on the CPU port so frames sent
by the CPU are directed by the MediaTek special tag. On a passthrough switch,
mt753x_cpu_port_enable() leaves PORT_SPEC_TAG unset on the CPU port so frames
are forwarded via the port matrix.

When the CPU transmits STP BPDUs, 802.1X PAE, LACP, PAUSE, or LLDP frames
(matching 01:80:c2:00:00:00/01/02/03/0e in BPC, RGAC1, and RGAC2) towards a
downstream switch port, won't those frames ingressing the passthrough
switch's CPU port match the trap rules and be restricted to
TO_CPU_FW_CPU_ONLY (the passthrough switch's own CPU port) instead of being
forwarded to the downstream DSA link port?

[ ... ]

> @@ -3304,7 +3472,7 @@ mt753x_conduit_state_change(struct dsa_switch *ds,
>  
>  	/* Set the CPU port to trap frames to for MT7530. Trapped frames will be
>  	 * forwarded to the numerically smallest CPU port whose conduit
> -	 * interface is up.
> +	 * interface is up. NOTE: "CPU port" can also mean an upstream DSA link.
>  	 */
>  	if (priv->id != ID_MT7530 && priv->id != ID_MT7621 &&
>  	    priv->id != ID_EN7528)

[Severity: High]
On the downstream switch behind a passthrough switch, the upstream port is a
DSA link port (DSA_PORT_TYPE_DSA) rather than a CPU port (DSA_PORT_TYPE_CPU),
and dp->cpu_dp / conduit->dsa_ptr point to the CPU port on the upstream
switch. How are the following paths handled on the downstream switch?

First, in mt753x_conduit_state_change(), mt7530_update_port_member(), and
mt7530_port_vlan_filtering(), the code uses cpu_dp->index directly:

drivers/net/dsa/mt7530.c:mt753x_conduit_state_change() {
	struct dsa_port *cpu_dp = conduit->dsa_ptr;
    ...
	mask = BIT(cpu_dp->index);
}

drivers/net/dsa/mt7530.c:mt7530_update_port_member() {
	struct dsa_port *dp = dsa_to_port(priv->ds, port), *other_dp;
	struct mt7530_port *p = &priv->ports[port], *other_p;
	struct dsa_port *cpu_dp = dp->cpu_dp;
	u32 port_bitmap = BIT(cpu_dp->index);
    ...
}

drivers/net/dsa/mt7530.c:mt7530_port_vlan_filtering() {
	struct dsa_port *dp = dsa_to_port(ds, port);
	struct dsa_port *cpu_dp = dp->cpu_dp;

	if (vlan_filtering) {
		mt7530_port_set_vlan_aware(ds, port);
		mt7530_port_set_vlan_aware(ds, cpu_dp->index);
    ...
}

If the upstream switch's CPU port index differs from the downstream switch's
upstream DSA link port index (for example, port 6 on the upstream switch vs
port 5 on the downstream switch), won't these program the wrong port on the
downstream switch?

Second, when a VLAN is added or deleted on the downstream switch, DSA calls
mt7530_port_vlan_add() and mt7530_port_vlan_del() on the downstream switch's
upstream DSA link port (passing the user port's untagged and pvid flags). In
mt7530_hw_vlan_add():

drivers/net/dsa/mt7530.c:mt7530_hw_vlan_add() {
    ...
	if (dsa_port_is_cpu(dp))
		val = MT7530_VLAN_EGRESS_STACK;
	else if (entry->untagged)
		val = MT7530_VLAN_EGRESS_UNTAG;
	else
		val = MT7530_VLAN_EGRESS_TAG;
	regmap_update_bits(priv->regmap, MT7530_VAWD2,
			   ETAG_CTRL_P_MASK(entry->port),
			   ETAG_CTRL_P(entry->port, val));
}

Because dsa_port_is_cpu(dp) is false for the downstream switch's upstream DSA
link port, won't mt7530_hw_vlan_add() fail to set MT7530_VLAN_EGRESS_STACK
and instead set UNTAG or TAG, while mt7530_port_vlan_add() and
mt7530_port_vlan_del() also overwrite PVID and ACC_FRM_MASK on the upstream
port?

[ ... ]

> @@ -3399,6 +3574,99 @@ static int mt7988_setup(struct dsa_switch *ds)
>  	return mt7531_setup_common(ds);
>  }
>  
> +/* 1 if passthrough, negative if error. */
> +static int mt753x_check_passthrough(struct device *dev)
> +{
> +	struct device_node *ports, *port;
> +	int passthrough_ports = 0;
> +	int enabled_ports = 0;
> +
> +	ports = of_get_child_by_name(dev->of_node, "ports");
> +	if (!ports)
> +		ports = of_get_child_by_name(dev->of_node, "ethernet-ports");
> +
> +	if (!ports) {
> +		dev_err(dev, "no ports child node found\n");
> +		return -EINVAL;
> +	}
> +
> +	for_each_available_child_of_node(ports, port) {
> +		struct device_node *link;
> +
> +		enabled_ports++;
> +
> +		link = of_parse_phandle(port, "ethernet", 0);
> +		if (!link)
> +			link = of_parse_phandle(port, "link", 0);
> +
> +		if (!link)
> +			continue;
> +
> +		of_node_put(link);
> +
> +		passthrough_ports++;
> +	}
> +
> +	of_node_put(ports);
> +
> +	/*
> +	 * A switch is considered passthrough if exactly two available
> +	 * ports have an "ethernet" or "link" phandle.
> +	 */
> +	if (passthrough_ports > 2 ||
> +	    (passthrough_ports == 2 && enabled_ports != 2)
> +	) {
> +		dev_err(dev, "Only two ports allowed in passthrough mode\n");
> +		return -EINVAL;
> +	}
> +
> +	return passthrough_ports == 2;
> +}

[Severity: High]
What happens in mt7530_probe_common() -> mt753x_check_passthrough() on
standalone dual-CPU-port boards where two ports (such as port@5 and port@6)
both have "ethernet" phandles and no port has a "link" phandle?

Because mt753x_check_passthrough() counts both "ethernet" and "link" phandles
into passthrough_ports without checking whether any "link" phandle is
present, passthrough_ports is 2 whenever two CPU ports are enabled. If any
user ports are also enabled (enabled_ports > 2, as on mt7621, mt7622,
mt7623n, and mt7986 dual-CPU-port boards), doesn't mt753x_check_passthrough()
return -EINVAL and abort the switch probe? And if all user ports happen to be
disabled (enabled_ports == 2), wouldn't it falsely enable passthrough mode on
a switch with two CPU ports and no downstream switch?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261029184202.1859843-1-cjd@cjdns.fr?part=3

  reply	other threads:[~2026-10-30 19:22 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-29 18:41 [PATCH v4 0/7] net: dsa: mt7530: support EcoNet EN751221 Caleb James DeLisle
2026-10-29 18:41 ` [PATCH v4 1/7] net: dsa: mt7530: get ctrl phy addr using a function Caleb James DeLisle
2026-10-29 18:41 ` [PATCH v4 2/7] dt-bindings: net: dsa: mediatek,mt7530: describe 'passthrough' hierarchy Caleb James DeLisle
2026-10-29 18:41 ` [PATCH v4 3/7] net: dsa: mediatek: add support for passthrough mode Caleb James DeLisle
2026-10-30 19:22   ` sashiko-bot [this message]
2026-10-29 18:41 ` [PATCH v4 4/7] net: dsa: mediatek: support PLL setup on MMIO MT7530 Caleb James DeLisle
2026-10-30 19:22   ` sashiko-bot
2026-10-29 18:42 ` [PATCH v4 5/7] net: dsa: mediatek: support MDIO switch downstream of MMIO switch Caleb James DeLisle
2026-10-30 19:22   ` sashiko-bot
2026-10-29 18:42 ` [PATCH v4 6/7] dt-bindings: net: dsa: mediatek,mt7530: add EcoNet EN751221 switches Caleb James DeLisle
2026-10-29 18:42 ` [PATCH v4 7/7] net: dsa: mediatek: support EN751221 switch Caleb James DeLisle
2026-10-30 19:22   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-191438@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=cjd@cjdns.fr \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.