Netdev List
 help / color / mirror / Atom feed
* [RFC PATCH net-next v3 0/2] net: phylink: wait for a PHY that probes after the MAC
@ 2026-09-14 21:11 Aleksei Sviridkin
  2026-09-14 21:11 ` [RFC PATCH net-next v3 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
  2026-09-14 21:11 ` [RFC PATCH net-next v3 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-14 21:11 UTC (permalink / raw)
  To: netdev
  Cc: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
	pabeni, horms, robh, krzk+dt, conor+dt, devicetree, linux-kernel,
	Aleksei Sviridkin

On an MT7981B board 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. Nothing retries 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 behind each choice is in patch 2. On
that board the port now attaches at 7.0 s and links up on its own.

Changes since v2:

 - A connect that fails with the real driver bound is retried up to
   three times at the poll rate, then given up on with one error naming
   the PHY node. v2 did not retry and asked whether to retry unless the
   PHY node owns a reset line. That condition does not hold up: the
   detach asserts whatever reset the DT describes, but the re-attach
   runs phy_init_hw() with the driver's soft reset and config_init on
   every board, so what a retry costs depends on the board and the PHY
   in ways phylink cannot see. It is bounded rather than conditioned.
 - The poller returns without attaching if a PHY arrived by another
   path while it was queued.
 - phylink_destroy() cancels the poller before any teardown instead of
   in the middle of it.
 - The NULL phydev->drv window v2 asked about is not specific to this
   series; phy_attach_direct() meets it from any caller racing a driver
   unbind. The guard is posted for net on its own [2] and covers an
unbind already in flight.
 - The link_state fix v2 needed is in net-next now, as 113998aa372f
   ("net: phylink: initialise link_state before a forced major
   config"), so this applies without it.
 - The MDIO device driver posted beside v2 is withdrawn [3]; nothing
   here depends on it.
 - Rebased onto net-next.

Still needed underneath: patch 1 of the phylink/phylib pair pending for
net [4]. A late bringup failure has to leave pl->phydev clear: the
poller takes a set pl->phydev for a PHY that arrived by another path,
so without it the first such failure ends the wait with no retry and no
give-up line. It is the prerequisite listed below.

Tested on that board, with these two patches and the prerequisite
backported to its OpenWrt 6.18 tree, warm boots. The retry paths need a
connect that fails after a successful attach, which this chip does not
produce, so those runs used a debug-only module parameter that, after
a successful attach, detaches and fails the connect with -EIO a given
number of times, on an image without the PHY driver so the poller was
still waiting when the driver was loaded by hand:

 - one warning after the first minute naming the PHY node, then the
   interval doubling up to the 30 s ceiling
 - two injected failures: retried 30 s apart, the third attempt
   attaches and the link comes up, no give-up line
 - failures that do not stop: four attempts at 1 s intervals, one
   "giving up on ... after 4 attempts", and with per-iteration tracing
   on no further poll in the 237 s the capture covers after it

Retries inherit the interval the wait has reached rather than choosing
one, as the two runs show.

Not tested: cancelling the poller from a teardown while it runs, because
the only path to it on this board, unbinding the switch driver, oopsed
earlier in mt7530_remove() until the fixes in [5]; and a PHY arriving by
a second path, which this board does not offer - that rests on the check
at the top of the poll body.

The runs above also went through both attach branches: in the
two-failure run the PHY attached to a port up since 15.8 s and linked
2.9 s later, and with the driver present it attached at 7.0 s to a port
not yet started, which linked once brought up. Not repeated on v3:
ifdown/ifup with the PHY attached, and checking that the PHY interrupt
fires after a running-port attach.

No in-tree device tree sets needs-host-firmware yet. The board is
supported out of tree, in OpenWrt.

Still asking, which is why this stays RFC:

 1. The poller repeats what phylink_fwnode_phy_connect() does - choose
    the interface, attach, bring up, detach on failure - with a
    different point at which the reference is dropped. Do you want a
    shared helper before the rest?

 2. Polling was the plan set out in [6]. BUS_NOTIFY_BOUND_DRIVER
    gives the exact edge; do you want the notifier in this series
    instead?

[1] v2: https://lore.kernel.org/r/20260908155729.4164814-1-f@lex.la/
[2] https://lore.kernel.org/r/20260914204200.2743251-1-f@lex.la/
[3] https://lore.kernel.org/r/20260912130430.2246285-1-f@lex.la/
[4] https://lore.kernel.org/r/20260909204306.2374562-1-f@lex.la/
[5] https://lore.kernel.org/r/20260914202421.2737079-1-f@lex.la/
[6] https://lore.kernel.org/r/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                     | 227 +++++++++++++++++-
 2 files changed, 228 insertions(+), 7 deletions(-)


base-commit: 879e280b8486d4612ad1aa050d6fada2dd80cf1c
prerequisite-patch-id: 293623f600b825505376e5c5bf72df2ac1f58e1e
-- 
2.53.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [RFC PATCH net-next v3 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware
  2026-09-14 21:11 [RFC PATCH net-next v3 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
@ 2026-09-14 21:11 ` Aleksei Sviridkin
  2026-09-14 21:11 ` [RFC PATCH net-next v3 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-14 21:11 UTC (permalink / raw)
  To: netdev
  Cc: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
	pabeni, horms, robh, krzk+dt, conor+dt, 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 v3 2/2] net: phylink: wait for PHYs that are known to probe late
  2026-09-14 21:11 [RFC PATCH net-next v3 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
  2026-09-14 21:11 ` [RFC PATCH net-next v3 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
@ 2026-09-14 21:11 ` Aleksei Sviridkin
  1 sibling, 0 replies; 3+ messages in thread
From: Aleksei Sviridkin @ 2026-09-14 21:11 UTC (permalink / raw)
  To: netdev
  Cc: linux, andrew, andrew+netdev, hkallweit1, davem, edumazet, kuba,
	pabeni, horms, robh, krzk+dt, conor+dt, devicetree, linux-kernel,
	Aleksei Sviridkin

A PHY whose driver has not bound when the MAC sets up its port cannot
be connected, and the port is lost for the rest of the uptime. The
case this reaches is a driver modular 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: nothing re-probes it,
and the poller cannot tell that apart from a driver that has yet to
load, so it keeps polling at the backed-off rate.
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 at the poll rate. A connect that fails
with the real driver bound is retried a small fixed number of times,
then given up on with a line that says so, because silence from a
poller reads like success. Detach and attach is what an ordinary port
teardown and set-up already do, so a PHY driver has to survive one;
what phylink cannot know is the cost of repeating it. The detach
asserts whatever reset line the DT gave the PHY, so on one board a
retry is a reset pulse and on another it is not, and either way the
re-attach re-runs phy_init_hw() with the driver's own soft reset and
config_init. That varies by board and by PHY and is not priceable
from here, so it is bounded rather than conditioned on any one part
of it. The size of the bound matters less than its existence: a few
more attempts carry a bus error that clears itself, and any bound
keeps a failure that cannot change - a rejected link mode, rejected
again on every poll with the same two bitmaps printed - from
repeating for the uptime. Stopping after the first failure was the
alternative, but a port left disconnected by a transient error stays
dead until the switch driver is rebound, since DSA connects a user
port once. The retries go out at the rate the wait phase has reached,
which is not necessarily the initial one. A PHY that arrives by
another path, an SFP for one, stops the poller instead of racing it,
and one that was ready at connect time arms no poll at all. 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 | 227 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 220 insertions(+), 7 deletions(-)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index c7079f37d8c3..6b4aa18cfb6e 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,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
@@ -1989,6 +2012,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 +2092,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);
@@ -2341,10 +2367,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;
 
@@ -2382,7 +2406,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;
 
@@ -2394,6 +2418,148 @@ 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;
+
+	/* 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;
+	}
+
+	/* 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) {
+		/* 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) {
+		phylink_err(pl, "failed to connect late PHY: %pe\n",
+			    ERR_PTR(ret));
+		/* Bounded: a retry pulses any reset the DT describes and
+		 * re-runs the driver's init, at a cost that varies by
+		 * board and PHY.
+		 */
+		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()
@@ -2404,7 +2570,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)
@@ -2422,7 +2589,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,
@@ -2432,6 +2605,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;
 
@@ -2444,6 +2619,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)
@@ -2485,6 +2677,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)
@@ -3044,6 +3238,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
@@ -3116,6 +3318,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;
 
@@ -3289,6 +3495,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);
@@ -3328,6 +3537,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;
@@ -3814,7 +4027,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-14 21:11 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 21:11 [RFC PATCH net-next v3 0/2] net: phylink: wait for a PHY that probes after the MAC Aleksei Sviridkin
2026-09-14 21:11 ` [RFC PATCH net-next v3 1/2] dt-bindings: net: ethernet-phy: add needs-host-firmware Aleksei Sviridkin
2026-09-14 21:11 ` [RFC PATCH net-next v3 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