* [PATCH net-next v4 0/2] net: phylink: wait for a PHY that probes after the MAC
@ 2026-09-25 0:12 Aleksei Sviridkin
2026-09-25 0:12 ` [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
2026-09-25 0:12 ` [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin
0 siblings, 2 replies; 6+ messages in thread
From: Aleksei Sviridkin @ 2026-09-25 0:12 UTC (permalink / raw)
To: netdev
Cc: Russell King, Andrew Lunn, Andrew Lunn, Heiner Kallweit,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Conor Dooley, Florian Fainelli, devicetree, linux-kernel
On the Keenetic KN-1012 (MT7981B with an MT7531 switch), the Airoha
EN8811H behind lan4 has its PHY driver built as a module on the root
filesystem. The switch sets up its ports before that filesystem is
mounted, so the port is validated against the generic driver, fails its
phy-mode and stays dead for the uptime. DSA does not retry it.
Let the PHY node say so with needs-host-firmware and have phylink poll
for the PHY instead of giving up. Patch 1 adds the property, patch 2
does the waiting; the reasoning is in the patches.
A late bringup failure has to leave pl->phydev clear, because the
poller reads a set pl->phydev as a PHY that arrived by another path.
v3 depended on a pending phylink fix for that; it went in as commit
a940003f44e7 ("net: phylink: record the PHY only once bringup cannot
fail"), so this series applies to net-next on its own.
Tested on that board with both patches and a940003f44e7 backported to
its OpenWrt 6.18 kernel. The retry runs used a local debug parameter,
not part of this series, that fails the connect after a successful
attach a given number of times; the switch was unbound and rebound
with the PHY driver unbound, so the poller was waiting when the driver
was bound by hand:
- at boot the switch set up its ports at 2.0 s, the PHY driver loaded
its firmware at 6.3 s and the port attached at 7.2 s
- two injected failures: "failed to connect late PHY: -EIO" twice, a
second apart, and the third attempt attached
- failures that do not stop: four attempts, then one "giving up on
/soc/ethernet@15100000/mdio-bus/ethernet-phy@d after 4 attempts",
and no further poll in the 25 s that followed
- unbinding the switch while the poller waited: the poll stopped,
nothing oopsed, and the port attached normally after a rebind
Unbinding the switch after the poller had attached lan4's PHY prints a
kernfs WARN: dsa_user_destroy() unregisters the netdev before
disconnecting the PHY, and only a PHY attached after registration has a
"phydev" link to remove. That ordering predates this series.
In one rebind the EN8811H's own probe failed (MCU not ready); the
poller kept polling at 1 Hz, as patch 2 describes for a driver whose
probe has failed (the run was too short to reach the backoff).
Conor, on 2026-09-10 [3] I said the property would go and the wait would
key off firmware-name. I went back on that. firmware-name names the file
to load, and the EN8811H driver keeps its two blob names in code, so it
would only be read as a presence flag. If firmware-name is still
preferred, I will redo it that way in the next version.
No in-tree device tree sets needs-host-firmware yet. The board is
supported out of tree, in OpenWrt.
Alternatives considered. The poll follows the plan in [1]; a
BUS_NOTIFY_BOUND_DRIVER notifier would give the exact edge, at the cost
of registering a bus notifier for each phylink instance that waits. The
poller repeats what phylink_fwnode_phy_connect() does - choose the
interface, attach, bring up, detach on failure - but drops its reference
at a different point, so the two are not merged into one helper.
Changes since v3:
- Rebased onto net-next, which now carries a940003f44e7; the code is
unchanged, and the prerequisite line is gone.
- No longer RFC. The two open questions are settled above.
- The board is named, and each patch says how the problem was found.
- The binding description keeps only what the hardware does, and the
comments in patch 2 are shorter.
- Tested again on the rebased code, including the teardown cancel that
v3 could not reach before the mt7530 unbind fixes were merged.
- The NULL phydev->drv window mentioned in v3 is left to phylib; a
separate series for net addresses it [2].
v3: https://lore.kernel.org/r/20260914211137.2760618-1-f@lex.la/
[1] https://lore.kernel.org/r/a230d199-5d4d-4637-aff3-e725a37e1da1@lunn.ch/
[2] https://lore.kernel.org/r/20260924215951.2127682-1-f@lex.la/
[3] https://lore.kernel.org/r/20260910153811.778387-1-f@lex.la/
Aleksei Sviridkin (2):
dt-bindings: net: ethernet-phy: add needs-host-firmware
net: phylink: wait for PHYs that are known to probe late
.../devicetree/bindings/net/ethernet-phy.yaml | 6 +
drivers/net/phy/phylink.c | 220 +++++++++++++++++-
2 files changed, 219 insertions(+), 7 deletions(-)
base-commit: 42a9fb3382fc2573e92f41d203b095d9a372cfc9
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware 2026-09-25 0:12 [PATCH net-next v4 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin @ 2026-09-25 0:12 ` Aleksei Sviridkin 2026-09-25 16:37 ` Conor Dooley 2026-09-29 0:31 ` netdev-bot+sashiko 2026-09-25 0:12 ` [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin 1 sibling, 2 replies; 6+ messages in thread From: Aleksei Sviridkin @ 2026-09-25 0:12 UTC (permalink / raw) To: netdev Cc: Russell King, Andrew Lunn, Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Conor Dooley, Florian Fainelli, devicetree, linux-kernel A PHY can be one that the host has to load firmware into before it can be driven at all. A controller that connects to such a PHY at setup, before its driver has loaded, gets the generic driver or no PHY at all, and one that connects only once gets no working PHY on that port for the rest of the uptime, even though the PHY works seconds later. The flag declares that. A consumer that sees it keeps the port and connects the PHY once its driver binds. It describes the PHY, so it sits on the PHY node and needs no prefix naming one. firmware-name is not used for this: it names the file to load, and the EN8811H driver keeps its two blob names in code, so it would only be read as a presence flag. The need is not derived from the compatible because the knowledge that an ID needs host firmware lives in the PHY driver, and that driver is a module not yet loaded when the MAC connects, so it has to come from the device tree. Found on a Keenetic KN-1012, where the EN8811H behind lan4 has its driver on the root filesystem and the switch sets its ports up before that is mounted, so lan4 stayed dead for the uptime. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la> --- Documentation/devicetree/bindings/net/ethernet-phy.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/net/ethernet-phy.yaml b/Documentation/devicetree/bindings/net/ethernet-phy.yaml index c3ebb3af8b52..73827662ddce 100644 --- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml +++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml @@ -215,6 +215,12 @@ properties: used. The absence of this property indicates the muxers should be configured so that the external PHY is used. + needs-host-firmware: + $ref: /schemas/types.yaml#/definitions/flag + description: + This PHY runs firmware that the host must load before it can be + driven, and is not usable until then. + resets: maxItems: 1 -- 2.53.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware 2026-09-25 0:12 ` [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin @ 2026-09-25 16:37 ` Conor Dooley 2026-09-29 0:31 ` netdev-bot+sashiko 1 sibling, 0 replies; 6+ messages in thread From: Conor Dooley @ 2026-09-25 16:37 UTC (permalink / raw) To: Aleksei Sviridkin Cc: netdev, Russell King, Andrew Lunn, Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Florian Fainelli, devicetree, linux-kernel [-- Attachment #1: Type: text/plain, Size: 53 bytes --] Acked-by: Conor Dooley <conor.dooley@microchip.com> [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware 2026-09-25 0:12 ` [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin 2026-09-25 16:37 ` Conor Dooley @ 2026-09-29 0:31 ` netdev-bot+sashiko 1 sibling, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-09-29 0:31 UTC (permalink / raw) To: f Cc: netdev, linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba, pabeni, horms, robh, krzk+dt, conor+dt, conor, f.fainelli, devicetree, linux-kernel Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · 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). - [Medium] For the only PHY the patch names, the new generic `needs-host- firmware` flag can be deduced from the compatible. - [Low] Concern: the new `needs-host-firmware` property in Documentation/devicetree/bindings/net/ethernet-phy.yaml has no… -- dt-bindings: net: ethernet-phy: add needs-host-firmware This adds a generic needs-host-firmware flag to the ethernet-phy binding. The flag says the host has to load firmware into the PHY before it can be driven. A consumer that sees the flag keeps the port and connects the PHY once its real driver binds, which fixes a dead lan4 port on a Keenetic KN-1012 with an EN8811H PHY. > diff --git a/Documentation/devicetree/bindings/net/ethernet-phy.yaml b/Documentation/devicetree/bindings/net/ethernet-phy.yaml > index c3ebb3af8b528..73827662ddce2 100644 > --- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml > +++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml > @@ -215,6 +215,12 @@ properties: > used. The absence of this property indicates the muxers > should be configured so that the external PHY is used. > > + needs-host-firmware: > + $ref: /schemas/types.yaml#/definitions/flag [Severity: Low] In this patch alone, nothing reads needs-host-firmware. Is anything in this commit meant to consume it? A later patch in the series, "net: phylink: wait for PHYs that are known to probe late", appears to resolve this. There, phylink_fwnode_phy_connect() checks: if (fwnode_property_present(phy_fwnode, "needs-host-firmware") && !phylink_phy_is_usable(phy_dev)) { When the check matches, it arms the phylink_late_phy_poll() delayed work instead of attaching genphy. This is noted only for completeness, since the binding lands before its user in the same series. > + description: > + This PHY runs firmware that the host must load before it can be > + driven, and is not usable until then. [Severity: Medium] For the EN8811H, doesn't the compatible already imply this flag? Documentation/devicetree/bindings/writing-bindings.rst says: - DON'T add properties to avoid a specific compatible. DON'T add properties if they are implied by (deducible from) the compatible. airoha,en8811h.yaml fixes the compatible to ethernet-phy-id03a2.a411. The driver loads host firmware for every instance without any condition: drivers/net/phy/air_en8811h.c:en8811h_probe() { ... ret = en8811h_load_firmware(phydev); if (ret < 0) return ret; ... } en8811h_load_firmware() always calls request_firmware_direct() for EN8811H_MD32_DM and EN8811H_MD32_DSP. The AN8811HB probe calls an8811hb_load_firmware() the same way. The commit message says: The need is not derived from the compatible because the knowledge that an ID needs host firmware lives in the PHY driver, and that driver is a module not yet loaded when the MAC connects, so it has to come from the device tree. Isn't that a question of Linux module loading and rootfs timing, rather than something about the hardware? The ethernet-phy-id compatible is already in the DT and can be seen before any PHY module loads. The EN8811H binding is also not updated for the flag. airoha,en8811h.yaml neither requires it nor shows it in its example, and its required list only has: required: - reg The existing in-tree EN8811H node in mt7981b-openwrt-one.dts does not get the flag either: phy15: ethernet-phy@f { compatible = "ethernet-phy-id03a2.a411"; Would an EN8811H node without the flag still pass dt_binding_check? If so, it would keep the dead-port behavior whenever the driver is a module that loads late. Could the late-connect behavior come from the compatible instead? -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260925001209.2334139-1-f%40lex.la ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late 2026-09-25 0:12 [PATCH net-next v4 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin 2026-09-25 0:12 ` [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin @ 2026-09-25 0:12 ` Aleksei Sviridkin 2026-09-29 0:32 ` netdev-bot+sashiko 1 sibling, 1 reply; 6+ messages in thread From: Aleksei Sviridkin @ 2026-09-25 0:12 UTC (permalink / raw) To: netdev Cc: Russell King, Andrew Lunn, Andrew Lunn, Heiner Kallweit, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Conor Dooley, Florian Fainelli, devicetree, linux-kernel A PHY that needs firmware from the host and whose driver has not bound when the MAC sets up its port is either taken by the generic driver, which cannot drive it, or not found at all; a MAC that connects once at setup, as DSA does, gets no working PHY on that port for the rest of the uptime. The case this reaches is a driver built as a module on a filesystem that is mounted after the MAC probes. Let the PHY declare it with needs-host-firmware and poll until the driver binds instead of failing. A driver that has bound is not covered, whatever it does about firmware afterwards. Neither is one whose probe has already failed: the driver core does not retry it, and the poller cannot tell that apart from a driver that has yet to load, so it keeps polling. Deferring the MAC's own probe is not an option: it keeps every port of that MAC down until the module loads, and forever if it never does, and those ports can include the one needed to mount the filesystem that holds the module. Return 0 rather than -ENODEV, because DSA reads -ENODEV as permission to look for the PHY on the switch's internal MDIO bus, which is the wrong device. Wait for a driver that has bound, not for a device that exists, because the generic driver would otherwise bind and cannot drive such a PHY. If the real driver goes away between that test and the attach, the generic one binds instead; the poll detaches it and keeps waiting. The attach-versus-unbind window itself is phylib's to close and is not closed here. A connect that fails with the real driver bound is retried a few times and then given up on with one line, because silence from a poller reads like success. Each retry re-runs the PHY's init and, on boards whose DT gives it a reset line, pulses that reset, at a cost that depends on the board and the PHY, so the retries are bounded. Stopping after the first failure would leave a DSA port, which connects once, dead until the switch driver is rebound. Until a PHY attaches, report no link modes and refuse the ethtool settings that would configure the MAC alone for a link that cannot come up. Found on a Keenetic KN-1012 (MT7981B with an MT7531 switch): the EN8811H behind lan4 has its driver on the root filesystem, the switch sets its ports up before that is mounted, and lan4 was lost for the uptime. With this change lan4 attaches once the module loads. The retry path was driven there by a local debug parameter that fails the connect after a successful attach: two injected failures were retried a second apart and the third attempt attached, and with failures that never stop, four attempts ended in one "giving up" line and no further polls. Assisted-by: LLM Signed-off-by: Aleksei Sviridkin <f@lex.la> --- drivers/net/phy/phylink.c | 220 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 213 insertions(+), 7 deletions(-) diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c index a7d086cdc9b2..4a5b98b55889 100644 --- a/drivers/net/phy/phylink.c +++ b/drivers/net/phy/phylink.c @@ -98,6 +98,15 @@ struct phylink { u32 wolopts_mac; u8 wol_sopass[SOPASS_MAX]; + + /* The poller writes these while it runs; arming cancels it first. */ + struct fwnode_handle *late_phy_fwnode; + u32 late_phy_flags; + struct delayed_work late_phy_poll; + unsigned int late_phy_poll_ms; + unsigned int late_phy_waited_ms; + u8 late_phy_retries; + bool late_phy_warned; }; #define phylink_printk(level, pl, fmt, ...) \ @@ -1831,6 +1840,18 @@ int phylink_set_fixed_link(struct phylink *pl, } EXPORT_SYMBOL_GPL(phylink_set_fixed_link); +static void phylink_late_phy_poll(struct work_struct *work); + +/* Synchronous: the poller reads the node put here. It only trylocks + * rtnl, so a caller holding rtnl cannot deadlock on it. + */ +static void phylink_late_phy_cancel(struct phylink *pl) +{ + cancel_delayed_work_sync(&pl->late_phy_poll); + fwnode_handle_put(pl->late_phy_fwnode); + pl->late_phy_fwnode = NULL; +} + /** * phylink_update_pause_state() - Update the phylink pause frame configuration * @pl: a pointer to a &struct phylink instance @@ -1989,6 +2010,7 @@ struct phylink *phylink_create(struct phylink_config *config, mutex_init(&pl->phydev_mutex); mutex_init(&pl->state_mutex); INIT_WORK(&pl->resolve, phylink_resolve); + INIT_DELAYED_WORK(&pl->late_phy_poll, phylink_late_phy_poll); pl->config = config; if (config->type == PHYLINK_NETDEV) { @@ -2068,6 +2090,8 @@ EXPORT_SYMBOL_GPL(phylink_create); */ void phylink_destroy(struct phylink *pl) { + phylink_late_phy_cancel(pl); + sfp_bus_del_upstream(pl->sfp_bus); if (pl->link_gpio) gpiod_put(pl->link_gpio); @@ -2337,10 +2361,8 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy, } static int phylink_attach_phy(struct phylink *pl, struct phy_device *phy, - phy_interface_t interface) + phy_interface_t interface, u32 flags) { - u32 flags = 0; - if (WARN_ON(pl->cfg_link_an_mode == MLO_AN_FIXED)) return -EINVAL; @@ -2378,7 +2400,7 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) pl->link_config.interface = pl->link_interface; } - ret = phylink_attach_phy(pl, phy, pl->link_interface); + ret = phylink_attach_phy(pl, phy, pl->link_interface, 0); if (ret < 0) return ret; @@ -2390,6 +2412,143 @@ int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) } EXPORT_SYMBOL_GPL(phylink_connect_phy); +#define PHYLINK_LATE_PHY_POLL_MS 1000 +#define PHYLINK_LATE_PHY_WARN_MS 60000 +#define PHYLINK_LATE_PHY_POLL_MAX_MS 30000 +#define PHYLINK_LATE_PHY_RETRIES 3 + +static bool phylink_late_phy_pending(struct phylink *pl) +{ + return pl->late_phy_fwnode && !pl->phydev; +} + +/* Stale the moment it returns: the device lock this wants cannot be held + * across the attach, whose own failure path takes it again. + */ +static bool phylink_phy_is_usable(struct phy_device *phy_dev) +{ + return phy_dev && device_is_bound(&phy_dev->mdio.dev) && phy_dev->drv; +} + +static void phylink_late_phy_backoff(struct phylink *pl) +{ + pl->late_phy_poll_ms = min_t(unsigned int, pl->late_phy_poll_ms * 2, + PHYLINK_LATE_PHY_POLL_MAX_MS); +} + +static void phylink_late_phy_poll(struct work_struct *work) +{ + struct phylink *pl = container_of(to_delayed_work(work), struct phylink, + late_phy_poll); + struct phy_device *phy_dev; + bool again = false, lost_race = false; + int ret; + + if (!rtnl_trylock()) { + pl->late_phy_waited_ms += pl->late_phy_poll_ms; + goto requeue; + } + + /* A PHY arrived by another path, an SFP for one, while queued. */ + if (!phylink_late_phy_pending(pl)) { + rtnl_unlock(); + return; + } + + /* Stable here: whoever clears it waits for this work first. */ + phy_dev = fwnode_phy_find_device(pl->late_phy_fwnode); + if (!phylink_phy_is_usable(phy_dev)) { + if (phy_dev) + phy_device_free(phy_dev); + + if (!pl->late_phy_warned && + pl->late_phy_waited_ms >= PHYLINK_LATE_PHY_WARN_MS) { + pl->late_phy_warned = true; + phylink_warn(pl, + "still waiting for %pfw (needs-host-firmware)\n", + pl->late_phy_fwnode); + } + /* Past the warn it may never come: stop paying 1 Hz for it. */ + if (pl->late_phy_waited_ms >= PHYLINK_LATE_PHY_WARN_MS) + phylink_late_phy_backoff(pl); + /* The first run is immediate, so count the sleep ahead. */ + pl->late_phy_waited_ms += pl->late_phy_poll_ms; + rtnl_unlock(); + goto requeue; + } + + /* Under the mutex, unlike at connect: this port may be live. */ + if (pl->link_interface == PHY_INTERFACE_MODE_NA) { + mutex_lock(&pl->state_mutex); + pl->link_interface = phy_dev->interface; + pl->link_config.interface = pl->link_interface; + mutex_unlock(&pl->state_mutex); + } + + ret = phylink_attach_phy(pl, phy_dev, pl->link_interface, + pl->late_phy_flags); + if (!ret && phy_driver_is_genphy(phy_dev)) { + /* Lost the race: the attach bound the generic driver, which + * is the outcome this poller exists to avoid. + */ + phy_detach(phy_dev); + lost_race = true; + ret = -EAGAIN; + } + if (!ret) { + ret = phylink_bringup_phy(pl, phy_dev, + pl->link_config.interface); + if (ret) { + phy_detach(phy_dev); + } else { + /* Only a major config programs the masks bringup + * narrowed. + */ + if (!test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) { + mutex_lock(&pl->state_mutex); + pl->force_major_config = true; + mutex_unlock(&pl->state_mutex); + /* MAC before the PHY, the order a start + * uses. + */ + phylink_run_resolve(pl); + flush_work(&pl->resolve); + phy_start(phy_dev); + } + } + } + if (lost_race) { + /* Not a failed connect: the next poll waits for the real + * driver. + */ + again = true; + } else if (ret) { + phylink_err(pl, "failed to connect late PHY: %pe\n", + ERR_PTR(ret)); + /* Bounded: each retry re-runs the PHY's init, maybe its reset. */ + if (pl->late_phy_retries) { + pl->late_phy_retries--; + again = true; + } else { + /* Silence from here reads as success otherwise. */ + phylink_err(pl, "giving up on %pfw after %u attempts\n", + pl->late_phy_fwnode, + PHYLINK_LATE_PHY_RETRIES + 1); + } + } + phy_device_free(phy_dev); + rtnl_unlock(); + + if (!again) + return; + +requeue: + queue_delayed_work(system_freezable_power_efficient_wq, + &pl->late_phy_poll, + msecs_to_jiffies(pl->late_phy_poll_ms)); +} + /** * phylink_of_phy_connect() - connect the PHY specified in the DT mode. * @pl: a pointer to a &struct phylink returned from phylink_create() @@ -2400,7 +2559,8 @@ EXPORT_SYMBOL_GPL(phylink_connect_phy); * specified by @pl. Actions specified in phylink_connect_phy() will be * performed. * - * Returns 0 on success or a negative errno. + * Returns what phylink_fwnode_phy_connect() returns, including 0 for a + * deferred connect with no PHY attached yet. */ int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn, u32 flags) @@ -2418,7 +2578,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect); * Connect the phy specified @fwnode to the phylink instance specified * by @pl. * - * Returns 0 on success or a negative errno. + * If the PHY node carries the needs-host-firmware property and the + * PHY is not usable yet, 0 is returned with no PHY connected: a poller + * connects it once its driver has probed. Until then the MAC runs + * without a PHY and ethtool reports no link modes. + * + * Returns 0 on success - the PHY connected, or the deferred connect + * armed - or a negative errno. */ int phylink_fwnode_phy_connect(struct phylink *pl, const struct fwnode_handle *fwnode, @@ -2428,6 +2594,8 @@ int phylink_fwnode_phy_connect(struct phylink *pl, struct phy_device *phy_dev; int ret; + phylink_late_phy_cancel(pl); + if (!phylink_expects_phy(pl)) return 0; @@ -2440,6 +2608,23 @@ int phylink_fwnode_phy_connect(struct phylink *pl, } phy_dev = fwnode_phy_find_device(phy_fwnode); + if (fwnode_property_present(phy_fwnode, "needs-host-firmware") && + !phylink_phy_is_usable(phy_dev)) { + /* -ENODEV here would also send DSA to the switch's own bus. */ + if (phy_dev) + phy_device_free(phy_dev); + + pl->late_phy_fwnode = phy_fwnode; + pl->late_phy_flags = flags; + pl->late_phy_poll_ms = PHYLINK_LATE_PHY_POLL_MS; + pl->late_phy_waited_ms = 0; + pl->late_phy_retries = PHYLINK_LATE_PHY_RETRIES; + pl->late_phy_warned = false; + queue_delayed_work(system_freezable_power_efficient_wq, + &pl->late_phy_poll, 0); + return 0; + } + /* We're done with the phy_node handle */ fwnode_handle_put(phy_fwnode); if (!phy_dev) @@ -2481,6 +2666,8 @@ void phylink_disconnect_phy(struct phylink *pl) ASSERT_RTNL(); + phylink_late_phy_cancel(pl); + mutex_lock(&pl->phydev_mutex); phy = pl->phydev; if (phy) { @@ -3047,6 +3234,14 @@ int phylink_ethtool_ksettings_get(struct phylink *pl, ASSERT_RTNL(); + /* No PHY yet: the port supports nothing, not what the MAC alone can. */ + if (phylink_late_phy_pending(pl)) { + kset->base.port = pl->link_port; + kset->base.speed = SPEED_UNKNOWN; + kset->base.duplex = DUPLEX_UNKNOWN; + return 0; + } + if (pl->phydev) phy_ethtool_ksettings_get(pl->phydev, kset); else @@ -3119,6 +3314,10 @@ int phylink_ethtool_ksettings_set(struct phylink *pl, ASSERT_RTNL(); + /* Would configure the MAC alone, for a link that cannot come up. */ + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (pl->phydev) { struct ethtool_link_ksettings phy_kset = *kset; @@ -3292,6 +3491,9 @@ int phylink_ethtool_nway_reset(struct phylink *pl) ASSERT_RTNL(); + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (pl->phydev) ret = phy_restart_aneg(pl->phydev); phylink_pcs_an_restart(pl); @@ -3331,6 +3533,10 @@ int phylink_ethtool_set_pauseparam(struct phylink *pl, if (pl->req_link_an_mode == MLO_AN_FIXED) return -EOPNOTSUPP; + /* pl->supported still describes the MAC, so the test below passes. */ + if (phylink_late_phy_pending(pl)) + return -EOPNOTSUPP; + if (!phylink_test(pl->supported, Pause) && !phylink_test(pl->supported, Asym_Pause)) return -EOPNOTSUPP; @@ -3817,7 +4023,7 @@ static int phylink_sfp_config_phy(struct phylink *pl, struct phy_device *phy) /* Attach the PHY so that the PHY is present when we do the major * configuration step. */ - ret = phylink_attach_phy(pl, phy, config.interface); + ret = phylink_attach_phy(pl, phy, config.interface, 0); if (ret < 0) return ret; -- 2.53.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late 2026-09-25 0:12 ` [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin @ 2026-09-29 0:32 ` netdev-bot+sashiko 0 siblings, 0 replies; 6+ messages in thread From: netdev-bot+sashiko @ 2026-09-29 0:32 UTC (permalink / raw) To: f Cc: netdev, linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba, pabeni, horms, robh, krzk+dt, conor+dt, conor, f.fainelli, devicetree, linux-kernel Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider. Critical: 0 · High: 1 · Medium: 2 · Low: 2 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] Returning success before the PHY is attached causes a NULL pointer dereference in ucc_geth_open() on CONFIG_PPC_85xx. - [Medium] The patch changes what a 0 return from phylink_fwnode_phy_connect()/phylink_of_phy_connect() means: it can… - [Medium] The poller attaches the PHY based on a lockless readiness check, phylink_phy_is_usable() (`device_is_bound()` &&… - [Low] Before this patch, a successful phylink_fwnode_phy_connect() in MLO_AN_PHY mode always replaced a PHY_INTERFACE_MODE_NA link_interface… - [Low] The new kernel-doc of phylink_fwnode_phy_connect() says that for a needs-host-firmware PHY that is not usable yet, 'a poller connects… -- net: phylink: wait for PHYs that are known to probe late This commit changes phylink_fwnode_phy_connect() for PHYs whose DT node has needs-host-firmware and whose driver has not bound yet. It now returns 0 and arms a delayed work poller, which attaches the PHY once the driver binds. Until then, the ethtool paths report no link modes and refuse configuration changes. > diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c > index a7d086cdc9b25..4a5b98b558891 100644 > --- a/drivers/net/phy/phylink.c > +++ b/drivers/net/phy/phylink.c [ ... ] > +/* Stale the moment it returns: the device lock this wants cannot be held > + * across the attach, whose own failure path takes it again. > + */ > +static bool phylink_phy_is_usable(struct phy_device *phy_dev) > +{ > + return phy_dev && device_is_bound(&phy_dev->mdio.dev) && phy_dev->drv; > +} [ ... ] > + ret = phylink_attach_phy(pl, phy_dev, pl->link_interface, > + pl->late_phy_flags); > + if (!ret && phy_driver_is_genphy(phy_dev)) { > + /* Lost the race: the attach bound the generic driver, which > + * is the outcome this poller exists to avoid. > + */ > + phy_detach(phy_dev); > + lost_race = true; > + ret = -EAGAIN; > + } > + if (!ret) { > + ret = phylink_bringup_phy(pl, phy_dev, > + pl->link_config.interface); [Severity: Medium] What happens if an unbind of the PHY driver overlaps this attach? Sysfs unbind, rmmod and MDIO bus teardown take only the device lock, not rtnl. __device_release_driver() runs its steps in this order: __device_release_driver() device_remove(dev) phy_remove() phydev->drv = NULL; ... device_unbind_cleanup(dev) <- clears dev->driver klist_remove(&dev->p->knode_driver); In that window phy_attach_direct() still sees d->driver non-NULL. It does not select genphy and leaves is_genphy_driven at 0. Then it calls: if (!phy_drv_supports_irq(phydev->drv) && phy_interrupt_is_valid(phydev)) That dereferences the NULL drv. If the attach does get through, phy_driver_is_genphy() is false here, so the lost_race recovery does not run. phylink_bringup_phy() then does: phylink_info(pl, "PHY [%s] driver [%s] (irq=%s)\n", dev_name(&phy->mdio.dev), phy->drv->name, irq_str); Under rmmod, try_module_get(d->driver->owner) fails with -EIO instead. That uses up one of the bounded retries as if the connect had really failed. The commit message says the attach-versus-unbind window is phylib's to close. But the poller decides when to attach, based on a check it documents as stale. Could this oops the work item while it holds rtnl? [ ... ] > @@ -2418,7 +2578,13 @@ EXPORT_SYMBOL_GPL(phylink_of_phy_connect); > * Connect the phy specified @fwnode to the phylink instance specified > * by @pl. > * > - * Returns 0 on success or a negative errno. > + * If the PHY node carries the needs-host-firmware property and the > + * PHY is not usable yet, 0 is returned with no PHY connected: a poller > + * connects it once its driver has probed. Until then the MAC runs > + * without a PHY and ethtool reports no link modes. [Severity: Low] Is this description complete? phylink_late_phy_poll() can stop for good after PHYLINK_LATE_PHY_RETRIES + 1 failed connects with the real driver bound: } else { /* Silence from here reads as success otherwise. */ phylink_err(pl, "giving up on %pfw after %u attempts\n", pl->late_phy_fwnode, PHYLINK_LATE_PHY_RETRIES + 1); } That branch leaves pl->late_phy_fwnode set, so phylink_late_phy_pending() stays true. The ethtool paths then keep reporting no link modes and returning -EOPNOTSUPP until the MAC calls disconnect and connect again. Rebinding the PHY driver does not restart the poller. Could the kernel-doc mention this terminal state? [ ... ] > @@ -2440,6 +2608,23 @@ int phylink_fwnode_phy_connect(struct phylink *pl, > } > > phy_dev = fwnode_phy_find_device(phy_fwnode); > + if (fwnode_property_present(phy_fwnode, "needs-host-firmware") && > + !phylink_phy_is_usable(phy_dev)) { > + /* -ENODEV here would also send DSA to the switch's own bus. */ > + if (phy_dev) > + phy_device_free(phy_dev); > + > + pl->late_phy_fwnode = phy_fwnode; > + pl->late_phy_flags = flags; > + pl->late_phy_poll_ms = PHYLINK_LATE_PHY_POLL_MS; > + pl->late_phy_waited_ms = 0; > + pl->late_phy_retries = PHYLINK_LATE_PHY_RETRIES; > + pl->late_phy_warned = false; > + queue_delayed_work(system_freezable_power_efficient_wq, > + &pl->late_phy_poll, 0); > + return 0; > + } [Severity: High] Can this leave dev->phydev NULL for callers that take 0 to mean the PHY is attached? ucc_geth_open() is one of them: err = phylink_of_phy_connect(ugeth->phylink, ugeth->dev->of_node, 0); if (err) { dev_err(&dev->dev, "Could not attach to PHY\n"); return -ENODEV; } ... device_set_wakeup_capable(&dev->dev, qe_alive_during_sleep() || dev->phydev->irq); With CONFIG_PPC_85xx, qe_alive_during_sleep() always returns 0, so dev->phydev->irq is evaluated. The deferral branch never calls phy_attach_direct(), so dev->phydev is still NULL at that point. Would this oops in ndo_open with rtnl held on an 85xx QE board? The conditions are a PHY node with needs-host-firmware and a PHY driver that is not loaded yet. Before this patch, the same situation either bound genphy or returned -ENODEV, and ucc_geth_open() handles both. [Severity: Medium] A related question: several callers do one-time PHY setup right after a successful connect. The late attach in phylink_late_phy_poll() only does phylink_attach_phy(), phylink_bringup_phy() and phy_start(), so that setup is never repeated. enetc_phylink_connect(): /* disable EEE autoneg, until ENETC driver supports it */ memset(&edata, 0, sizeof(struct ethtool_keee)); phylink_ethtool_set_eee(priv->phylink, &edata); With pl->phydev NULL, phylink_ethtool_set_eee() returns -EOPNOTSUPP and changes nothing. ENETC has no LPI mac ops, so phylink_bringup_phy() does not touch EEE either. Does the late PHY end up advertising EEE on a MAC that explicitly tries to turn it off? stmmac_init_phy() passes priv->tx_lpi_timer to phylib through phylink_ethtool_get_eee() and phylink_ethtool_set_eee(). Both fail when there is no PHY, so a timer set before the first open looks to be lost. mvneta_mdio_probe() calls device_set_wakeup_capable() once, from phylink_ethtool_get_wol(), right after the connect. Nothing updates it after the late attach, so PHY WoL would never be reported. sparx5_port_open() takes a NULL phydev after a successful connect to mean the port has no PHY, and powers up the SerDes: if (!ndev->phydev) { /* power up serdes */ port->conf.power_down = false; The phylink_of_phy_connect() kernel-doc also still says "Actions specified in phylink_connect_phy() will be performed". That is not true on the deferred path. Is there a way for these callers to learn that the connect was deferred, or for the poller to re-run their post-connect setup? [Severity: Low] Does returning here skip the PHY_INTERFACE_MODE_NA fix-up that the non-deferred path does further down? /* Use PHY device/driver interface */ if (pl->link_interface == PHY_INTERFACE_MODE_NA) { pl->link_interface = phy_dev->interface; pl->link_config.interface = pl->link_interface; } A DSA user port with no phy-mode in DT gets PHY_INTERFACE_MODE_NA from dsa_port_phylink_create(): err = of_get_phy_mode(dp->dn, &mode); if (err) mode = PHY_INTERFACE_MODE_NA; Before this patch, a successful connect in MLO_AN_PHY mode meant the interface was set before phylink_start() ran. Now phylink_start()->phylink_mac_initial_config() copies pl->phy_state with the interface still NA. phylink_major_config() then passes it to mac_select_pcs(), mac_prepare() and mac_config(). The poller's forced major config fixes this later. In the meantime, can the MAC/PCS be programmed with PHY_INTERFACE_MODE_NA, or fail on it and set major_config_failed? > /* We're done with the phy_node handle */ > fwnode_handle_put(phy_fwnode); > if (!phy_dev) [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260925001209.2334139-1-f%40lex.la ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-29 0:32 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-09-25 0:12 [PATCH net-next v4 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin 2026-09-25 0:12 ` [PATCH net-next v4 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin 2026-09-25 16:37 ` Conor Dooley 2026-09-29 0:31 ` netdev-bot+sashiko 2026-09-25 0:12 ` [PATCH net-next v4 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin 2026-09-29 0:32 ` 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
all inboxes | Powered by JetHome®