[RFC PATCH net-next 0/2] net: phylink: wait for a PHY that probes after the MAC
From: Aleksei Sviridkin
Date: Sun Sep 06 2026 - 13:52:59 EST
A PHY whose driver or firmware lives on a filesystem cannot be
connected when the MAC probes, because the files become readable long
after the MDIO bus was scanned. Today the port that names such a PHY is
dropped at probe and stays dead for the whole uptime, and nothing
retries it.
Let the port declare that with phy-needs-host-firmware and poll for the
PHY instead of failing. Patch 1 adds the property, patch 2 does the
waiting.
This is one half of an RFC last posted whole as v2 [1]. The other half
describes the chip that drove it - the Airoha EN8811H, an MD32
microcontroller that answers a PHY ID from power-on and becomes a PHY
only once the host writes firmware into its volatile RAM - as an MDIO
device that owns the download and the reset line. The halves touch no
common file and go to different reviewers, so they are posted apart;
the other one is at https://lore.kernel.org/r/cover.1788711797.git.f@xxxxxx/ . They are
not alternatives: this half alone carries a board
whose chip answers its ID before firmware and whose PHY driver is a
module, and the other buys the cases that are not that - chips mute
before firmware, a built-in PHY driver whose probe fails once on
missing files and is never retried, and reset ownership. The last one
matters here, and I say why below.
The poller waits 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. The test cannot be made to hold past
its own return: the device lock that would freeze it cannot be taken
under rtnl, and phy_attach_direct()'s own failure path takes it again.
What is caught instead is the outcome one step later, where the attach
bound the generic driver and returned success, and the poll puts that
back. The window before it, where phy_attach_direct() meets a NULL
phydev->drv, is open - see the questions at the end.
The connect returns 0 and not -ENODEV, because DSA reads
-ENODEV as permission to look for the PHY on the switch's internal MDIO
bus, which is the wrong device.
Waiting never gives up, since firmware or a module can arrive at any
time: a port with the property and no PHY polls at the 30 s ceiling for
the uptime, after one warning at the end of the first minute. A connect
that fails with the real driver bound stops there instead, for the
reason patch 2 gives. A port left in either state reports itself as
still waiting and nothing restarts it: DSA connects once, at port
setup, so an ifdown and ifup do not re-arm the poller - only unbinding
the switch driver does.
rtnl is taken with trylock so the poller never blocks on it, which
keeps it from parking a shared workqueue worker while another thread
holds rtnl. The attach lands within one poll interval of the PHY
becoming ready when rtnl is free; contention pushes it out by another
interval each time the trylock loses.
While the poll runs the port has no PHY, so it must not report the
MAC's own link modes as if they were the port's - that describes a link
that cannot come up, and ethtool would accept settings for it. The
pending path reports an empty set, stamps the unknown speed and duplex
over the ethtool core's zeroing, and refuses ksettings_set,
set_pauseparam and nway_reset. Reading pause parameters is left alone,
because it reports the configured request rather than a capability, and
the EEE calls already return -EOPNOTSUPP with no PHY attached.
Why not -EPROBE_DEFER and fw_devlink: there is no supplier link to wait
on. drivers/of/property.c parses no phy-handle, so fw_devlink never
builds one, and a deferral would park the MAC until something else
triggers the pending list - which need not coincide with the firmware
files appearing. Deferring the MAC's own probe is worse anyway: it
takes every port with it, including the one needed to mount the
filesystem that holds the firmware.
Cost in struct phylink: a delayed_work plus the fwnode, the connect
flags and the wait's own counters, appended at the end.
The flag sits on the MAC/port node because that is where the waiting
happens: phylink keeps the port and polls until the PHY turns up.
Question 2 asks whether it belongs there.
The flag is also a request for a dedicated PHY driver: a PHY meant to
run on the generic driver must not carry it, or the wait never ends.
Tested on an MT7981B board (MT7531 switch, EN8811H on a 2500base-x
port), warm boots only - I have no remote way to cut power. The board
runs OpenWrt, so what booted is these patches backported onto its 6.18
tree, not the mailed text byte-for-byte. What the board showed:
- the case this exists for, a PHY arriving while the port is already
running: attach at 67.44 s, carrier at 71.93 s, and the PHY's
interrupt fires without any port bounce. This needs [3]; without it
the same path left the port dead
- an ifdown/ifup cycle disconnects and reconnects cleanly
- the stopped-port path, reached by booting with the firmware out of
reach and putting the port down while the PHY cannot exist: the PHY
attaches to the stopped port, sits there attached and carrier-less,
and the later up starts it, with the link three seconds behind
- the wait itself: one warning at 65 s naming the property and the
missing PHY, then a 29.19 s gap between the PHY becoming usable and
the poller noticing - the ceiling doing its job, where the initial
one-second interval would have attached within a second
Not exercised: the retry after a failed connect, though nothing rules
it out. The validation route into it is closed on this chip, since the
EN8811H reports RATE_MATCH_PAUSE and phylink_validate_phy() then never
intersects the port's line-rate modes with the PHY's copper ones - but
any failure inside phy_attach_direct() reaches the same retry, and MDIO
accesses can fail. Neither is the lost-race branch, which needs an
unbind between the readiness test and the attach.
No in-tree device tree sets phy-needs-host-firmware yet. The board I
tested is supported out of tree, in OpenWrt; the in-tree
mt7986a-bananapi-bpi-r3-mini carries the same chip and would be the
first candidate, but I have no such board to test the conversion on.
Two out-of-tree patches are needed, and only one of them is declared
below. Patch 1 of the pending pair [2] is applied on top of the base
and format-patch lists it as a prerequisite: a late bringup failure has
to leave pl->phydev clear, or every retry hits -EBUSY. The other, [3],
is a fix now on the list for net and is not in this mbox at all - a
forced major configuration can run over an uninitialised link_state,
and this poller reaches it on a port that is already up when the PHY
arrives, because the attach reports the not-yet-started PHY as down and
the resolve then takes the link-failed branch. Applying the mbox alone
gets the first and not the second.
System sleep is worth naming even though this half does not touch it.
On the shape this half targets alone - the PHY node owns reset-gpios
and the PHY driver downloads in .probe() - a suspend that cuts power
wipes the firmware, the PHY's own resume writes into a dead chip, and
this poller offers nothing: it only runs while no PHY is attached, and
after a resume one still is. The other half's MCU driver reloads the
firmware there, which is one more thing the phylink half does not buy
on its own.
The poller repeats the sequence phylink_fwnode_phy_connect() runs -
choose the interface, attach, bring up, detach on failure - with a
different point at which the reference is dropped. A shared helper is
the obvious ask and I have not written one; say if you want it before
the rest.
What I am asking:
1. phylink_phy_is_usable() cannot stay true past its own return. An
unbind between it and the attach leaves phy_attach_direct() reading
a NULL phydev->drv, and the device lock that would close it cannot
be taken under rtnl. A guard inside phy_attach_direct(), or the bus
notifier this poll was always meant to become? The exact edge
exists - BUS_NOTIFY_BOUND_DRIVER fires from driver_bound() after
phy_probe() has set PHY_READY - so the follow-up is a notifier plus
a one-shot work item. Polling first was the plan agreed in [4]; say
if you want the notifier in this series instead.
2. The property sits on the MAC/port node, which is where [4] put it:
that is where the waiting happens. It describes the PHY, though,
and phylink resolves phy-handle to a fwnode before it needs the
device, so reading it from the referenced node is a one-token
change, and a PCS node could carry its own the same way. Andrew,
is the MAC node still where you want it?
3. A connect that fails with the real driver bound is not retried.
That is a policy borrowed from this chip: the failure path ends in
phy_detach(), which asserts a PHY-node reset line, and firmware
that lives in RAM does not survive it, so a retry loop would erase
it once a cycle for the uptime. For any other late PHY the same
rule turns a transient MDIO error into a port that is dead until
the switch driver is rebound. Retry, stop, or retry unless the PHY
node owns reset-gpios? And should the property be refused outright
on such a node, so the board learns at boot that it converted to
the wrong shape?
[1] https://lore.kernel.org/r/cover.1788548229.git.f@xxxxxx/
[2] https://lore.kernel.org/netdev/20260902080511.2211261-1-f@xxxxxx/
[3] https://lore.kernel.org/netdev/20260904185540.2844261-1-f@xxxxxx/
[4] https://lore.kernel.org/netdev/a230d199-5d4d-4637-aff3-e725a37e1da1@xxxxxxx/
Aleksei Sviridkin (2):
dt-bindings: net: ethernet-controller: add phy-needs-host-firmware
net: phylink: wait for PHYs that are known to probe late
.../bindings/net/ethernet-controller.yaml | 8 +
drivers/net/phy/phylink.c | 210 +++++++++++++++++-
2 files changed, 211 insertions(+), 7 deletions(-)
base-commit: 761ae184f850f33d1bbf6c4530c7f237be780d21
prerequisite-patch-id: 293623f600b825505376e5c5bf72df2ac1f58e1e
--
2.53.0