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 5/7] net: dsa: mediatek: support MDIO switch downstream of MMIO switch
Date: Fri, 30 Oct 2026 19:22:21 +0000 [thread overview]
Message-ID: <sashiko-outbox-191440@kernel.org> (raw)
In-Reply-To: <20261029184202.1859843-6-cjd@cjdns.fr>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] net: dsa: mt7530: broken PHY IRQs due to late mt7530_setup_mdio_irq()
- [High] net: dsa: mt7530: premature and re-entrant mt7530_setup_mdio() in probe
- [High] net: dsa: mt7530: unchecked mt7530_setup_mdio() error in mt7988_probe()
- [High] net: dsa: mt7530: reg_mutex lockdep recursion with downstream switch
--
commit 51caf2efd42e68935e207fbaf1614c12f27f5efa
Author: Caleb James DeLisle <cjd@cjdns.fr>
net: dsa: mediatek: support MDIO switch downstream of MMIO switch
This commit exports mt7530_setup_mdio(), makes it idempotent via a check on
priv->child_bus, moves mt7530_setup_mdio_irq() to mt753x_setup(), and calls
mt7530_setup_mdio() from mt7988_probe() after dsa_register_switch() to allow a
downstream MDIO switch to probe and complete the DSA switch tree.
> diff --git a/drivers/net/dsa/mt7530-mmio.c b/drivers/net/dsa/mt7530-mmio.c
> index cf3bdb23a2d54..746a0a4622497 100644
> --- a/drivers/net/dsa/mt7530-mmio.c
> +++ b/drivers/net/dsa/mt7530-mmio.c
> @@ -61,7 +61,21 @@ mt7988_probe(struct platform_device *pdev)
> if (IS_ERR(priv->regmap))
> return PTR_ERR(priv->regmap);
>
> - return dsa_register_switch(priv->ds);
> + ret = dsa_register_switch(priv->ds);
> +
> + if (ret)
> + return ret;
> +
> + /*
> + * If not all switches in the hierarchy have been probed,
> + * dsa_register_switch() returns 0 without calling mt753x_setup().
> + * Call mt7530_setup_mdio() because there's a chance that the missing
> + * switch is on the MDIO bus. Do not call this before
> + * dsa_register_switch() as it may exit with an error.
> + */
> + mt7530_setup_mdio(priv);
> +
> + return 0;
[Severity: High]
What happens if mt7530_setup_mdio(priv) fails here in mt7988_probe(), for
example with -ENOMEM from devm_mdiobus_alloc() or -EPROBE_DEFER from
devm_of_mdiobus_register()?
Because the return value of mt7530_setup_mdio(priv) is ignored and
mt7988_probe() returns 0, the driver core will treat probe as successful and
never retry deferred probing, leaving the downstream switch unprobed, the DSA
tree permanently incomplete, and priv->ds registered in the switch tree
holding allocated dsa_port instances and conduit net_device references without
unwinding via mt7530_remove_common(priv) / dsa_unregister_switch(priv->ds).
Should mt7988_probe() check the return value of mt7530_setup_mdio(priv) and
unregister the switch on error?
[Severity: High]
Also, when a downstream MDIO switch is registered on the MMIO switch's MDIO
bus (upstream_priv->child_bus), won't register accesses on the downstream
switch trigger a lockdep recursive locking warning on priv->reg_mutex?
Both the upstream MMIO switch and the downstream MDIO switch initialize
priv->reg_mutex at the same callsite in mt7530_probe_common():
drivers/net/dsa/mt7530.c:mt7530_probe_common() {
...
mutex_init(&priv->reg_mutex);
...
}
This assigns both mutexes to the same static lockdep class. When any operation
on the downstream switch holds mutex_lock(&downstream_priv->reg_mutex) at
subclass 0 and performs a regmap access, such as in mt7530_port_disable():
drivers/net/dsa/mt7530.c:mt7530_port_disable() {
mutex_lock(&priv->reg_mutex);
priv->ports[port].enable = false;
regmap_update_bits(priv->regmap, MT7530_PCR_P(port), PCR_MATRIX_MASK,
PCR_MATRIX_CLR);
mutex_unlock(&priv->reg_mutex);
}
the underlying MDIO transfer goes through mt7530_regmap_read() /
mt7530_regmap_write() on downstream_priv->bus (which is
upstream_priv->child_bus) and calls mt7531_ind_c22_phy_read() /
mt7531_ind_c22_phy_write(), which acquires mutex_lock(&upstream_priv->reg_mutex)
at subclass 0 of the same lockdep class:
drivers/net/dsa/mt7530.c:mt7531_ind_c22_phy_write() {
mutex_lock(&priv->reg_mutex);
...
}
Does priv->reg_mutex need a separate lockdep key or nesting annotation when
switches are stacked this way?
> diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
> index d45388e2efc83..24e4485624d39 100644
> --- a/drivers/net/dsa/mt7530.c
> +++ b/drivers/net/dsa/mt7530.c
> @@ -2509,7 +2509,7 @@ mt7530_free_mdio_irq(struct mt7530_priv *priv)
> }
> }
>
> -static int
> +int
> mt7530_setup_mdio(struct mt7530_priv *priv)
> {
> struct device_node *mnp, *np = priv->dev->of_node;
> @@ -2519,6 +2519,10 @@ mt7530_setup_mdio(struct mt7530_priv *priv)
> static int idx;
> int ret = 0;
>
> + /* Already done */
> + if (priv->child_bus)
> + return 0;
> +
> mnp = of_get_child_by_name(np, "mdio");
>
> if (mnp && !of_device_is_available(mnp))
> @@ -2543,9 +2547,6 @@ mt7530_setup_mdio(struct mt7530_priv *priv)
> bus->parent = dev;
> bus->phy_mask = ~ds->phys_mii_mask;
>
> - if (priv->irq_domain && !mnp)
> - mt7530_setup_mdio_irq(priv);
> -
> priv->child_bus = bus;
[Severity: High]
When mt7988_probe() calls mt7530_setup_mdio(priv) after dsa_register_switch()
returns 0 on an incomplete DSA tree, neither dsa_switch_setup() nor
mt753x_setup() has executed yet. Can calling mt7530_setup_mdio() at that point
cause four ordering and re-entrancy regressions?
1. Hardware reset and PHY clock setup in mt7988_setup() have not run yet when
mt7988_probe() calls mt7530_setup_mdio(priv). In mt7988_setup():
drivers/net/dsa/mt7530.c:mt7988_setup() {
/* Reset the switch */
reset_control_assert(priv->rstc);
usleep_range(20, 50);
reset_control_deassert(priv->rstc);
usleep_range(20, 50);
/* AN7583 require additional tweak to CONN_CFG */
if (priv->id == ID_AN7583)
regmap_update_bits(priv->regmap, AN7583_GEPHY_CONN_CFG, ...);
/* Reset the switch PHYs */
regmap_write(priv->regmap, MT7530_SYS_CTRL, SYS_CTRL_PHY_RST);
}
MDIO accesses in mt7530_setup_mdio() happen before priv->rstc is deasserted
and before AN7583_GEPHY_CONN_CFG is configured. Then, when the downstream
switch registers and completes the DSA tree, dsa_tree_setup() invokes
mt753x_setup() -> mt7988_setup() on the upstream switch, asserting priv->rstc
and writing SYS_CTRL_PHY_RST after MDIO devices and PHYs on the bus have
already been probed.
2. mt7530_setup_irq(priv) is only called from mt753x_setup(), so
priv->irq_domain is still NULL when mt7988_probe() calls
mt7530_setup_mdio(priv). Any child PHY or switch node under the mdio DT node
whose interrupt-parent is the upstream switch will fail fwnode_irq_get() with
-EPROBE_DEFER during devm_of_mdiobus_register().
3. ds->phys_mii_mask is only populated by dsa_switch_setup():
net/dsa/dsa.c:dsa_switch_setup() {
...
ds->phys_mii_mask |= dsa_user_ports(ds);
...
}
When mt7988_probe() calls mt7530_setup_mdio(priv) on an incomplete tree,
ds->phys_mii_mask is still 0, so bus->phy_mask = ~ds->phys_mii_mask is
0xffffffff. If there is no mdio child DT node (mnp == NULL),
devm_of_mdiobus_register(dev, bus, NULL) scans no PHYs and sets
priv->child_bus = bus. When the tree later completes and mt753x_setup() calls
mt7530_setup_mdio(priv), it returns 0 immediately because priv->child_bus is
already set, leaving ds->user_mii_bus with no PHYs.
4. Because priv->child_bus = bus is set before devm_of_mdiobus_register(dev,
bus, mnp) finishes iterating all child nodes of mnp in
__of_mdiobus_parse_phys(), probing the downstream switch child node
synchronously calls:
mt7530_probe()
-> dsa_register_switch()
-> dsa_tree_setup()
-> dsa_tree_setup_switches()
-> mt753x_setup()
-> mt7530_setup_mdio()
That re-entrant mt7530_setup_mdio() call sees priv->child_bus != NULL and
returns 0 while the outer __of_mdiobus_parse_phys() loop is still mid-iteration
and has not yet registered any PHY child nodes listed after the downstream
switch node in mnp. dsa_tree_setup_ports() then runs immediately and fails
phylink_of_phy_connect() with -ENODEV for those upstream user ports.
[ ... ]
> @@ -3386,10 +3388,17 @@ mt753x_setup(struct dsa_switch *ds)
> if (ret)
> return ret;
>
> + /*
> + * This must be called before mt753x_setup() returns because the DSA
> + * core synchronously sets up ports and expects PHYs to be operational.
> + */
> ret = mt7530_setup_mdio(priv);
> if (ret)
> return ret;
>
> + if (priv->irq_domain && ds->user_mii_bus)
> + mt7530_setup_mdio_irq(priv);
[Severity: High]
Doesn't moving mt7530_setup_mdio_irq(priv) after mt7530_setup_mdio(priv) break
PHY interrupts for all switches using ds->user_mii_bus?
In mt7530_setup_mdio(), devm_mdiobus_alloc() initializes all bus->irq[i]
entries to PHY_POLL. When mnp is NULL, devm_of_mdiobus_register(dev, bus, NULL)
synchronously scans the bus and creates the phy_device instances via:
devm_of_mdiobus_register()
-> __of_mdiobus_register()
-> __mdiobus_register()
-> mdiobus_scan_bus_c22()
-> mdiobus_scan_c22()
-> get_phy_device()
-> phy_device_create()
During phy_device_create(), phylib copies bus->irq[addr] into each struct
phy_device:
drivers/net/phy/phy_device.c:phy_device_create() {
...
dev->irq = bus->irq[addr];
...
}
Because mt7530_setup_mdio_irq(priv) now runs after mt7530_setup_mdio(priv) has
already registered the bus and created the PHY devices, bus->irq[addr] is still
PHY_POLL at creation time, so every PHY is created with phydev->irq == PHY_POLL
and populating ds->user_mii_bus->irq[p] afterwards has no effect on the
already-created PHY devices (while leaking the mapped IRQ descriptors).
In addition, the error path in mt7530_setup_mdio() still calls
mt7530_free_mdio_irq(priv) when devm_of_mdiobus_register() fails:
drivers/net/dsa/mt7530.c:mt7530_setup_mdio() {
...
ret = devm_of_mdiobus_register(dev, bus, mnp);
if (ret) {
priv->child_bus = NULL;
dev_err(dev, "failed to register MDIO bus: %d\n", ret);
if (priv->irq_domain && !mnp)
mt7530_free_mdio_irq(priv);
}
}
even though mt7530_setup_mdio_irq(priv) was not called before
devm_of_mdiobus_register().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029184202.1859843-1-cjd@cjdns.fr?part=5
next prev parent 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
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 [this message]
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-191440@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.