* [RFC PATCH net-next v2 0/2] net: phylink: wait for a PHY that probes after the MAC
@ 2026-09-08 15:57 Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin
0 siblings, 2 replies; 3+ messages in thread
From: Aleksei Sviridkin @ 2026-09-08 15:57 UTC (permalink / raw)
To: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
pabeni, horms, robh, krzk+dt, conor+dt
Cc: conor, netdev, devicetree, linux-kernel, Aleksei Sviridkin
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 PHY declare that with 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 posted alongside this, and its v1 thread is at
https://lore.kernel.org/r/cover.1788711797.git.f@lex.la/
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 PHY node, because that is what it describes.
phylink resolves phy-handle to a fwnode before it needs the device, so
reading it from there costs nothing, and a PCS could carry its own the
same way.
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 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. 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?
Changes since v1:
- the flag moved from the controller node to the PHY node and lost the
phy- prefix that named the entity it pointed at, so it is now
needs-host-firmware on the PHY. Conor Dooley asked for this and the
v1 cover had offered it as question 2, which is therefore gone.
- phylink reads it from the phy-handle target rather than from the
port.
- v1: https://lore.kernel.org/r/cover.1788711837.git.f@lex.la/
[1] https://lore.kernel.org/r/cover.1788548229.git.f@lex.la/
[2] https://lore.kernel.org/netdev/20260902080511.2211261-1-f@lex.la/
[3] https://lore.kernel.org/netdev/20260904185540.2844261-1-f@lex.la/
[4] https://lore.kernel.org/netdev/a230d199-5d4d-4637-aff3-e725a37e1da1@lunn.ch/
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 | 8 +
drivers/net/phy/phylink.c | 210 +++++++++++++++++-
2 files changed, 211 insertions(+), 7 deletions(-)
base-commit: ab217fbb9b2169ce677b09a66558d5c3adcfbb76
prerequisite-patch-id: 293623f600b825505376e5c5bf72df2ac1f58e1e
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [RFC PATCH net-next v2 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware
2026-09-08 15:57 [RFC PATCH net-next v2 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
@ 2026-09-08 15:57 ` Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin
1 sibling, 0 replies; 3+ messages in thread
From: Aleksei Sviridkin @ 2026-09-08 15:57 UTC (permalink / raw)
To: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
pabeni, horms, robh, krzk+dt, conor+dt
Cc: conor, netdev, devicetree, linux-kernel, Aleksei Sviridkin
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
finds nothing usable and drops the port for the whole 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.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Documentation/devicetree/bindings/net/ethernet-phy.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/ethernet-phy.yaml b/Documentation/devicetree/bindings/net/ethernet-phy.yaml
index c3ebb3af8b52..e217101fd54a 100644
--- a/Documentation/devicetree/bindings/net/ethernet-phy.yaml
+++ b/Documentation/devicetree/bindings/net/ethernet-phy.yaml
@@ -215,6 +215,14 @@ 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 for as long as that has not happened. A
+ controller referencing it keeps the port and connects the PHY once
+ its driver binds, instead of failing at setup.
+
resets:
maxItems: 1
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [RFC PATCH net-next v2 2/2] net: phylink: wait for PHYs that are known to probe late
2026-09-08 15:57 [RFC PATCH net-next v2 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
@ 2026-09-08 15:57 ` Aleksei Sviridkin
1 sibling, 0 replies; 3+ messages in thread
From: Aleksei Sviridkin @ 2026-09-08 15:57 UTC (permalink / raw)
To: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
pabeni, horms, robh, krzk+dt, conor+dt
Cc: conor, netdev, devicetree, linux-kernel, Aleksei Sviridkin
A PHY whose driver or firmware lives on a filesystem mounted after the
MAC probes cannot be connected when the port is set up, and the port is
lost for the rest of the uptime. Let the PHY declare that with
needs-host-firmware and poll for it instead of failing.
Deferring the MAC's own probe is not an option: it would take every
port with it, including the one needed to mount the filesystem that
holds the firmware. 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.
The test cannot be made to hold past its own return: the device lock it
wants cannot be held across the attach, whose failure path takes it
again. What is caught instead is the outcome one step later, where the
attach bound a generic driver and returned success, and the poll puts
that back. The window before it, where phy_attach_direct() meets a NULL
phydev->drv, stays open; closing it wants a check inside that function,
or an event from the bind instead of this poll.
Only that lost race is retried. A connect that fails with the real
driver bound is not, because the failure path ends in phy_detach(),
which asserts a PHY-node reset line - and on the boards this exists for
that erases the firmware a retry would need, once per attempt for the
uptime. A PHY that was ready at connect time arms no poll and keeps the
old behaviour. Every path that arms the poller cancels it first and
waits, so nothing else has to keep the poller and its state apart.
While the poll runs the port has no PHY, so reporting the MAC's own
link modes would describe a link that cannot come up and would let
ethtool accept settings for it. Report an empty set instead, and refuse
to configure, to set pause parameters, and to restart autonegotiation,
which has nothing to renegotiate with. The reply says autonegotiation
is off, which is the ethtool core's zero left in place and agrees with
the empty set: a port advertising nothing is negotiating nothing.
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.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
drivers/net/phy/phylink.c | 210 ++++++++++++++++++++++++++++++++++++--
1 file changed, 203 insertions(+), 7 deletions(-)
diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 6ed2219961fb..a872f000204a 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -98,6 +98,14 @@ struct phylink {
u32 wolopts_mac;
u8 wol_sopass[SOPASS_MAX];
+
+ /* The poller owns these; every other writer 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;
+ bool late_phy_warned;
};
#define phylink_printk(level, pl, fmt, ...) \
@@ -1829,6 +1837,20 @@ 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 because the node is put here and the poller reads it, and
+ * not every caller holds the rtnl that would keep them apart. It cannot
+ * deadlock on a caller that does: the poller only ever takes rtnl with
+ * trylock, so it never waits for the lock this may be called under.
+ */
+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
@@ -1987,6 +2009,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) {
@@ -2070,6 +2093,8 @@ void phylink_destroy(struct phylink *pl)
if (pl->link_gpio)
gpiod_put(pl->link_gpio);
+ phylink_late_phy_cancel(pl);
+
cancel_work_sync(&pl->resolve);
kfree(pl);
}
@@ -2339,10 +2364,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;
@@ -2380,7 +2403,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;
@@ -2392,6 +2415,133 @@ 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
+
+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;
+
+ /* Never block on rtnl: this runs on a shared workqueue. */
+ if (!rtnl_trylock()) {
+ pl->late_phy_waited_ms += pl->late_phy_poll_ms;
+ goto requeue;
+ }
+
+ /* 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; on a port already running that is a
+ * forced major config, not an initial one.
+ */
+ phylink_run_resolve(pl);
+ flush_work(&pl->resolve);
+ phy_start(phy_dev);
+ }
+ }
+ }
+ if (lost_race) {
+ /* The lost race unbound the generic driver again, and the
+ * real one is arriving, so look again at the current rate
+ * without spending the wait's budget.
+ */
+ again = true;
+ } else if (ret) {
+ /* Not retried: every attempt ends in phy_detach(), which
+ * asserts a PHY-node reset line, and on the boards this
+ * exists for that erases the firmware a retry would need.
+ */
+ phylink_err(pl, "failed to connect late PHY: %pe\n",
+ ERR_PTR(ret));
+ }
+ 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()
@@ -2402,7 +2552,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)
@@ -2420,7 +2571,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,
@@ -2430,6 +2587,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;
@@ -2442,6 +2601,22 @@ 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_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)
@@ -2483,6 +2658,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)
@@ -3042,6 +3219,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
@@ -3114,6 +3299,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;
@@ -3287,6 +3476,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);
@@ -3326,6 +3518,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;
@@ -3812,7 +4008,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 related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-08 15:57 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 15:57 [RFC PATCH net-next v2 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
2026-09-08 15:57 ` [RFC PATCH net-next v2 2/2] net: phylink: wait for PHYs that are known to probe late Aleksei Sviridkin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox