Netdev List
 help / color / mirror / Atom feed
* [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes
@ 2026-08-22 15:52 Aleksei Sviridkin
  2026-08-22 15:52 ` [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-22 15:52 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, Heiner Kallweit, Russell King
  Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, netdev, linux-kernel, Aleksei Sviridkin

A PHY that loads firmware at probe keeps its driver in a module on the
rootfs: built in, request_firmware_direct() fails against a rootfs that
is not mounted yet and the error comes straight out of probe. A DSA
switch probes long before that module can load, and three things go
wrong, one per layer.

phylink can fail its PHY bringup after it has already recorded the PHY
in pl->phydev, leaving a pointer to a PHY that phy_detach() has since
released. Depending on the caller that is either a permanent -EBUSY or
a stale pointer handed to phy_disconnect() later, which detaches the
same PHY twice (patch 1). Keeping a port across a failed connect makes
that window reachable, so it comes first.

phylib binds the generic driver during the attach, and once the failed
connect unwinds, the interrupt the firmware node declared is gone:
phy_probe() parked the PHY in polling mode and nothing after MDIO bus
registration ever brings the irq back (patch 2).

DSA drops the user port when the connect at setup fails, so the port
never exists, no matter that the driver shows up seconds later
(patch 3).

With the series applied the port survives setup and connects its PHY on
the first ifup after the module loads, with the interrupt the device
tree declares.

Tested on an MT7981B board (mt7530 switch, Airoha EN8811H with its
INT_B line in the device tree) running a 6.18 backport of everything
here except the retry in patch 3: the attach line reports a real
interrupt instead of irq=POLL, the EINT is claimed and its counter
advances on link changes forced from the link partner, and the port
passes traffic. On net-next all three files are compile-tested; the
board runs an OpenWrt 6.18 kernel.

Aleksei Sviridkin (3):
  net: phylink: unwind the PHY binding when bringup fails late
  net: phy: restore the interrupt after a generic-driver bind cycle
  net: dsa: connect a late-arriving PHY at ifup

 drivers/net/phy/phy_device.c | 13 +++++
 drivers/net/phy/phylink.c    | 12 +++++
 include/linux/phy.h          |  6 +++
 net/dsa/user.c               | 98 ++++++++++++++++++++++++++++++++++++
 4 files changed, 129 insertions(+)

-- 
2.43.0


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

* [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late
  2026-08-22 15:52 [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Aleksei Sviridkin
@ 2026-08-22 15:52 ` Aleksei Sviridkin
  2026-08-22 17:30   ` Andrew Lunn
  2026-08-22 15:52 ` [PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-22 15:52 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, Heiner Kallweit, Russell King
  Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, netdev, linux-kernel, Aleksei Sviridkin

phylink_bringup_phy() records the PHY in pl->phydev before its last
fallible step: on a MAC whose phylink ops implement LPI,
phy_eee_rx_clock_stop() can fail with a real MDIO error. The callers
unwind with phy_detach(), which knows nothing about pl->phydev, so a
pointer to a PHY that is no longer attached outlives the failed
connect.

What that costs depends on how the caller got here.
phylink_connect_phy() and the SFP path go through
phylink_attach_phy(), which refuses to attach while pl->phydev is set
and turns a transient MDIO error into a permanent -EBUSY.
phylink_fwnode_phy_connect() has no such check, so a later connect
overwrites the stale pointer and hides the problem. A disconnect does
not: phylink_disconnect_phy() hands that pointer to phy_disconnect(),
and the second phy_detach() on the same PHY drops a device reference
and two module references that were only ever taken once.

Clear the binding on the failure path, the same three fields
phylink_disconnect_phy() clears, under the same locks. The PHY-side
fields are left to phy_detach(), which every caller already runs on
this path.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Reachability

The failing step needs pl->mac_supports_eee_ops, i.e. a MAC whose
phylink ops implement the LPI callbacks; mt7530 is one, and on the
board I tested ethtool --show-eee returns -EOPNOTSUPP, which is what
phylink reports when mac_supports_eee_ops is set and mac_supports_eee
is not, so that tail runs on every bringup there. The error itself is
an MDIO transaction failure inside phy_eee_rx_clock_stop(), which
cannot be produced deliberately, so this patch is compile-tested and
the series it belongs to ran on hardware with it in place.

The double-detach path needs a port that outlives a failed connect,
which is what patch 3 introduces; before that, DSA destroyed the port
immediately and the stale pointer went with it.
 drivers/net/phy/phylink.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c
index 5b8e95690..9d403ff1b 100644
--- a/drivers/net/phy/phylink.c
+++ b/drivers/net/phy/phylink.c
@@ -2197,6 +2197,18 @@ static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy,
 	if (ret == 0 && phy_interrupt_is_valid(phy))
 		phy_request_interrupt(phy);
 
+	if (ret) {
+		mutex_lock(&pl->phydev_mutex);
+		mutex_lock(&phy->lock);
+		mutex_lock(&pl->state_mutex);
+		pl->phydev = NULL;
+		pl->phy_enable_tx_lpi = false;
+		pl->mac_tx_clk_stop = false;
+		mutex_unlock(&pl->state_mutex);
+		mutex_unlock(&phy->lock);
+		mutex_unlock(&pl->phydev_mutex);
+	}
+
 	return ret;
 }
 
-- 
2.43.0


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

* [PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle
  2026-08-22 15:52 [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Aleksei Sviridkin
  2026-08-22 15:52 ` [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin
@ 2026-08-22 15:52 ` Aleksei Sviridkin
  2026-08-22 19:28   ` Andrew Lunn
       [not found] ` <20260822155259.87146-4-f@lex.la>
  2026-08-24 16:25 ` [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Andrew Lunn
  3 siblings, 1 reply; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-22 15:52 UTC (permalink / raw)
  To: Andrew Lunn, Vladimir Oltean, Heiner Kallweit, Russell King
  Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, netdev, linux-kernel, Aleksei Sviridkin

fwnode_mdiobus_phy_device_register() resolves the interrupt declared
for a PHY once, at MDIO bus registration. If no specific driver is
available when the PHY is attached, the generic driver binds and
phy_probe() parks the device in polling mode, since the generic driver
has no interrupt callbacks. phy_detach() releases the generic driver
so a specific driver can bind later, but nothing brings the interrupt
back: the firmware node is never re-read after bus registration, so
the specific driver attaches with irq == PHY_POLL,
phy_request_interrupt() is never reached, and the PHY is polled for
the rest of the uptime with nothing in the logs but the "irq=POLL"
attach line.

A DSA switch probing before the rootfs is mounted produces exactly
that cycle for a PHY whose driver is a module: the generic driver
binds and fails validation during switch setup, and the real driver
binds at ifup. Observed on an MT7981B board with an Airoha EN8811H on
an MT7531 port: the device tree declares the INT_B line, yet the
attach says irq=POLL and the interrupt is never claimed.

Save the interrupt when the generic driver binds and give it back when
that driver is released. The restore runs before the device becomes
bindable again, so a concurrently arriving specific driver cannot
observe or overwrite the intermediate state. Only the value the
generic-driver cycle took is restored. A PHY already parked in polling
mode before that cycle, by a failed phy_request_interrupt() or by a
driver that chose PHY_POLL in its own probe, had PHY_POLL saved, so the
restore is skipped. The PHY_F_NO_IRQ and no-interrupt-support checks in
phy_attach_direct() still apply to whichever driver binds next.

Signed-off-by: Aleksei Sviridkin <f@lex.la>
---
Testing

MT7981B board, mt7530 switch, Airoha EN8811H whose INT_B line is in
the device tree, driver in a module on the rootfs, together with the
next patch: the attach line reports irq=15 instead of irq=POLL, the
EINT is claimed, its counter advances on link changes forced from the
link partner, and there is no interrupt storm. The SoC's internal PHY
on the same board, which has no interrupt in its bus table, keeps
irq=POLL through the same boot, so the save-restore pair does not
resurrect an interrupt the device never had. Consistent across
reboots. Hardware testing was done on 6.18 with this exact
shape of the change; on net-next the files are compile-tested.

The saved value uses zero as "nothing saved"; no registration path
produces a valid interrupt number of zero, and non-positive values are
never restored.
 drivers/net/phy/phy_device.c | 13 +++++++++++++
 include/linux/phy.h          |  6 ++++++
 2 files changed, 19 insertions(+)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e0..6047dce61 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1780,6 +1780,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 		else
 			d->driver = &genphy_driver.mdiodrv.driver;
 
+		phydev->genphy_saved_irq = phydev->irq;
 		phydev->is_genphy_driven = 1;
 	}
 
@@ -1897,6 +1898,9 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
 error_module_put:
 	module_put(d->driver->owner);
 	phydev->is_genphy_driven = 0;
+	if (phydev->genphy_saved_irq > 0 && phydev->irq == PHY_POLL)
+		phydev->irq = phydev->genphy_saved_irq;
+	phydev->genphy_saved_irq = 0;
 	d->driver = NULL;
 error_put_device:
 	put_device(d);
@@ -1965,6 +1969,15 @@ void phy_detach(struct phy_device *phydev)
 	 * real driver could be loaded
 	 */
 	if (phydev->is_genphy_driven) {
+		/* Give back the interrupt phy_probe() parked when the generic
+		 * driver bound, before the device becomes bindable again. A
+		 * PHY that was in polling mode for any other reason had
+		 * PHY_POLL saved, and the restore is skipped.
+		 */
+		if (phydev->genphy_saved_irq > 0 && phydev->irq == PHY_POLL)
+			phydev->irq = phydev->genphy_saved_irq;
+		phydev->genphy_saved_irq = 0;
+
 		device_release_driver(&phydev->mdio.dev);
 		phydev->is_genphy_driven = 0;
 	}
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 5f8d65868..43e20b19e 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -591,6 +591,10 @@ struct phy_oatc14_sqi_capability {
  *      - Bits [31:24] are reserved for defining generic
  *        PHY driver behavior.
  * @irq: IRQ number of the PHY's interrupt (-1 if none)
+ * @genphy_saved_irq: value of @irq before the generic driver bound, given
+ *                    back when that driver is released; zero outside a
+ *                    generic bind cycle, and non-positive values are
+ *                    never restored
  * @phylink: Pointer to phylink instance for this PHY
  * @sfp_bus_attached: Flag indicating whether the SFP bus has been attached
  * @sfp_bus: SFP bus attached to this PHY's fiber port
@@ -762,6 +766,8 @@ struct phy_device {
 	 */
 	int irq;
 
+	int genphy_saved_irq;
+
 	/* private data pointer */
 	/* For use by PHYs to maintain extra state */
 	void *priv;
-- 
2.43.0


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

* Re: [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late
  2026-08-22 15:52 ` [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin
@ 2026-08-22 17:30   ` Andrew Lunn
  0 siblings, 0 replies; 14+ messages in thread
From: Andrew Lunn @ 2026-08-22 17:30 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

> +	if (ret) {
> +		mutex_lock(&pl->phydev_mutex);
> +		mutex_lock(&phy->lock);
> +		mutex_lock(&pl->state_mutex);
> +		pl->phydev = NULL;
> +		pl->phy_enable_tx_lpi = false;
> +		pl->mac_tx_clk_stop = false;
> +		mutex_unlock(&pl->state_mutex);
> +		mutex_unlock(&phy->lock);
> +		mutex_unlock(&pl->phydev_mutex);
> +	}

This is the same as the inner part of phylink_disconnect_phy(). Maybe
pull it out into a helper?

     Andrew

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

* Re: [PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle
  2026-08-22 15:52 ` [PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin
@ 2026-08-22 19:28   ` Andrew Lunn
  0 siblings, 0 replies; 14+ messages in thread
From: Andrew Lunn @ 2026-08-22 19:28 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

>  	if (phydev->is_genphy_driven) {
> +		/* Give back the interrupt phy_probe() parked when the generic
> +		 * driver bound, before the device becomes bindable again. A
> +		 * PHY that was in polling mode for any other reason had
> +		 * PHY_POLL saved, and the restore is skipped.
> +		 */
> +		if (phydev->genphy_saved_irq > 0 && phydev->irq == PHY_POLL)
> +			phydev->irq = phydev->genphy_saved_irq;
> +		phydev->genphy_saved_irq = 0;
> +

I _think_ it can be simpler:

int fwnode_mdiobus_phy_device_register(struct mii_bus *mdio,
				       struct phy_device *phy,
				       struct fwnode_handle *child, u32 addr)
{
	int rc;

	rc = fwnode_irq_get(child, 0);
	/* Don't wait forever if the IRQ provider doesn't become available,
	 * just fall back to poll mode
	 */
	if (rc == -EPROBE_DEFER)
		rc = driver_deferred_probe_check_state(&phy->mdio.dev);
	if (rc == -EPROBE_DEFER)
		return rc;

	if (rc > 0) {
		phy->irq = rc;
		mdio->irq[addr] = rc;
	} else {
		phy->irq = mdio->irq[addr];
	}

So if there was an interrupt in DT, mdio->irq[addr] has been set to
it. So all i think you need is

    phydev->irq = mdio->irq[addr];

And a comment.

    Andrew

---
pw-bot: cr

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
       [not found] ` <20260822155259.87146-4-f@lex.la>
@ 2026-08-22 19:38   ` Andrew Lunn
  2026-08-23  0:05     ` Aleksei Sviridkin
  0 siblings, 1 reply; 14+ messages in thread
From: Andrew Lunn @ 2026-08-22 19:38 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

On Sat, Aug 22, 2026 at 06:52:59PM +0300, Aleksei Sviridkin wrote:
> A PHY whose driver loads firmware at probe has no driver bound while
> that module still sits in an unmounted rootfs. phy_attach_direct()
> falls back to the generic driver, whose feature set lacks the modes the
> port is wired for, and the port is dropped for the rest of the uptime:
> 
>   mt7530-mdio mdio-bus:1f lan4: validation of 2500base-x [...] failed: -EINVAL
>   mt7530-mdio mdio-bus:1f lan4: error -22 setting up PHY for tree 0, switch 0, port 5
> 
> The same PHY wired to a MAC on the same SoC comes up. The difference is
> when the connect happens: the MAC driver connects from ndo_open, DSA
> connects during setup, at 1.9 s, before any rootfs exists.
> 
> Keep the port when the connect fails on a PHY that has no driver of its
> own, and connect it from the open path instead

The problem is, this is not guaranteed to work. The driver might still
not be loaded, or it is still downloading firmware to the PHY.

Think about the case of NFS root. The kernel will open() the interface
as soon as netdev_register() is called.

I think you need to look at the PHY driver. Make its probe function
return success, but start a thread downloading the firmware. While
firmware is downloading, either soft_reset() or config_init() needs to
block. That should allow the MAC to bind to the PHY, blocking
everything until the PHY is ready.

	   Andrew

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
  2026-08-22 19:38   ` [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup Andrew Lunn
@ 2026-08-23  0:05     ` Aleksei Sviridkin
  2026-08-23  1:24       ` Andrew Lunn
  0 siblings, 1 reply; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-23  0:05 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel, Aleksei Sviridkin

> The problem is, this is not guaranteed to work. The driver might still
> not be loaded, or it is still downloading firmware to the PHY.

These are two separate halves, and the driver can only reach one of
them. When the firmware is late, your scheme works, and I want to do it
as a follow-up. When the module itself is still on an unmounted rootfs,
there is no probe to return success from, so the connect at switch
setup still gets the genphy fallback and the port is gone for good.
This patch is for that half, and it is the half the board in the
commit message hits: the driver is a module on the rootfs.

> Think about the case of NFS root. The kernel will open() the interface
> as soon as netdev_register() is called.

With NFS root the module and the firmware sit behind the network they
are supposed to bring up, so that setup already requires building them
in or an initramfs. With those in place the connect at setup succeeds
and this patch stays out of the way. Without them, today's kernel drops
the port at setup and NFS root is just as dead, so this does not
regress it.

> I think you need to look at the PHY driver. Make its probe function
> return success, but start a thread downloading the firmware. While
> firmware is downloading, either soft_reset() or config_init() needs to
> block.

I would rather not block: config_init runs inside phy_attach_direct, so
on a DSA switch the blocked port holds up the whole switch probe and
every other port with it. For the follow-up I want to attach with the
real driver's features immediately and keep the link down until the
firmware lands, then trigger aneg, the way an SFP port sits linkless
without a module. aquantia and mscc load firmware from probe too, so a
phylib helper for this would have three users.

If the async firmware part should come first and this patch second, I
can reorder.

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
  2026-08-23  0:05     ` Aleksei Sviridkin
@ 2026-08-23  1:24       ` Andrew Lunn
  2026-08-23 12:37         ` Aleksei Sviridkin
  2026-08-24  2:40         ` Aleksei Sviridkin
  0 siblings, 2 replies; 14+ messages in thread
From: Andrew Lunn @ 2026-08-23  1:24 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

On Sun, Aug 23, 2026 at 03:05:49AM +0300, Aleksei Sviridkin wrote:
> > The problem is, this is not guaranteed to work. The driver might still
> > not be loaded, or it is still downloading firmware to the PHY.
> 
> These are two separate halves, and the driver can only reach one of
> them. When the firmware is late, your scheme works, and I want to do it
> as a follow-up. When the module itself is still on an unmounted rootfs,
> there is no probe to return success from, so the connect at switch
> setup still gets the genphy fallback and the port is gone for good.

How about making the DSA driver depend on the PHY. That would be much
simpler. Both are then builtin or bother are modules, so both should
be available at the same time. You might also be able to use
MODULE_SOFTDEP() to get dracut to put the PHY module in the initramfs.

> > I think you need to look at the PHY driver. Make its probe function
> > return success, but start a thread downloading the firmware. While
> > firmware is downloading, either soft_reset() or config_init() needs to
> > block.
> 
> I would rather not block: config_init runs inside phy_attach_direct, so
> on a DSA switch the blocked port holds up the whole switch probe and
> every other port with it.

Given the poor hardware design, your choices are limited. We really
try hard not to put workarounds for bad designs in core code. We try
to hide it within the drivers. So it might be the whole switch needs
to wait.

> For the follow-up I want to attach with the
> real driver's features immediately and keep the link down until the
> firmware lands, then trigger aneg, the way an SFP port sits linkless
> without a module.

SFPs are different. The Linux code was designed from the ground up to
handle hot plugable devices. phylib itself is much older, and does not
handle hot plugable PHYs. The SFP case is made easier by the fact the
MDIO bus is hot plugged at the same time as the PHY. So from phylibs
perspective, it is not hot plugged.

> aquantia and mscc load firmware from probe too, so a phylib helper
> for this would have three users.

I agree a general solution would be nice, and people have put some
thought into trying to find one, but it is not easy.

	Andrew

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
  2026-08-23  1:24       ` Andrew Lunn
@ 2026-08-23 12:37         ` Aleksei Sviridkin
  2026-08-23 15:20           ` Andrew Lunn
  2026-08-24  2:40         ` Aleksei Sviridkin
  1 sibling, 1 reply; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-23 12:37 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel, Aleksei Sviridkin

> How about making the DSA driver depend on the PHY. That would be much
> simpler.

I worked through both halves of that for OpenWrt, where one kernel
image serves every board of a target, so every kilobyte lands on all of
them.

Built-in: the PHY driver itself is cheap, under 8k of text and data on
arm64. The firmware still lives on the rootfs though, and a built-in
driver probes from an initcall, before mount_root(), so blocking in
config_init() until the firmware shows up deadlocks the boot. Putting
the firmware in the kernel image costs 144k on every board of the
target, and almost none of them have this PHY.

Modules: that works, and the kernel gets smaller. OpenWrt already does
it elsewhere, kmod-dsa-mv88e6xxx depends on kmod-phy-marvell. What
stops me is that it moves the switch driver out of the kernel for every
board of the target, and NFS root goes with it. That is a bigger change
than I want to push on my own, and it gives up the case you asked
about.

Do you see a third option, or is the module direction the one you would
take, with NFS root treated as acceptable to lose on boards like these?
MODULE_SOFTDEP looks useful where dracut builds the initramfs, OpenWrt
builds its own, so I am not sure it applies here.

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
  2026-08-23 12:37         ` Aleksei Sviridkin
@ 2026-08-23 15:20           ` Andrew Lunn
  0 siblings, 0 replies; 14+ messages in thread
From: Andrew Lunn @ 2026-08-23 15:20 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

On Sun, Aug 23, 2026 at 03:37:29PM +0300, Aleksei Sviridkin wrote:
> > How about making the DSA driver depend on the PHY. That would be much
> > simpler.
> 
> I worked through both halves of that for OpenWrt, where one kernel
> image serves every board of a target, so every kilobyte lands on all of
> them.
> 
> Built-in: the PHY driver itself is cheap, under 8k of text and data on
> arm64. The firmware still lives on the rootfs though, and a built-in
> driver probes from an initcall, before mount_root(), so blocking in
> config_init() until the firmware shows up deadlocks the boot. Putting
> the firmware in the kernel image costs 144k on every board of the
> target, and almost none of them have this PHY.
> 
> Modules: that works, and the kernel gets smaller. OpenWrt already does
> it elsewhere, kmod-dsa-mv88e6xxx depends on kmod-phy-marvell. What
> stops me is that it moves the switch driver out of the kernel for every
> board of the target, and NFS root goes with it. That is a bigger change
> than I want to push on my own, and it gives up the case you asked
> about.

Another option is move the firmware download into the bootloader.

Going back to the big picture...

My opinion is that moving the binding of MAC to PHY into open is
wrong. We should be building on phylinks support for hotplug of SFP
modules.

But i've not yet figured out how that would work. I need to think on
it for a while.

   Andrew

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

* Re: [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup
  2026-08-23  1:24       ` Andrew Lunn
  2026-08-23 12:37         ` Aleksei Sviridkin
@ 2026-08-24  2:40         ` Aleksei Sviridkin
  1 sibling, 0 replies; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-24  2:40 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel, Aleksei Sviridkin

> Another option is move the firmware download into the bootloader.

I went and looked at what that would take, and it does not help here,
for a reason that also corrects something in my own commit message.

The firmware is not what makes this PHY late. air_en8811h is a module
in the rootfs, and on this board it probes at 6.25 s, while the port is
already dropped at 1.878 s. Moving the blobs earlier changes nothing
while the driver itself arrives that late.

Even with the firmware preloaded, en8811h_probe() calls
en8811h_load_firmware() unconditionally, and that starts with
request_firmware_direct(). Nothing reads the version register or the
ready bit first, so a preloaded PHY is reloaded, and a PHY with no
blobs available still fails probe. A bootloader preload would need a
kernel-side early-out to have any effect at all.

And it would be gone before then anyway: the failed bringup ends in
phy_detach(), which asserts the reset line, and this PHY has
reset-gpios. So whatever the bootloader put in the MD32 is wiped at
1.88 s.

U-Boot does already carry the whole loader, in v2026.07, which is what
we build. What it cannot do here is read the blobs out of a squashfs
inside UBI, and mtk_eth_probe() takes the switch branch and never
connects a PHY on this board, so it would be new code either way.

None of that argues against your main point. I am dropping this patch
and sending the phylink and phylib fixes on their own, since they stand
without it. If the phylink hotplug direction ends up wanting someone to
test it on hardware that reproduces this, I have the board.

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

* Re: [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes
  2026-08-22 15:52 [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Aleksei Sviridkin
                   ` (2 preceding siblings ...)
       [not found] ` <20260822155259.87146-4-f@lex.la>
@ 2026-08-24 16:25 ` Andrew Lunn
  2026-08-25  8:25   ` Aleksei Sviridkin
  3 siblings, 1 reply; 14+ messages in thread
From: Andrew Lunn @ 2026-08-24 16:25 UTC (permalink / raw)
  To: Aleksei Sviridkin
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, netdev,
	linux-kernel

Hi Aleksei

I've had time to think about this, and now have a architecture to
solve the problem which i think it better.

It splits into two parts, getting the PHY firmware downloaded and
registered with phylib, and the phylink handling "hotplug" PHYs.

When power is applied to the "PHY", or after a reset, it is not
actually a PHY. It is a microcontroller sat in its bootloader waiting
for firmware to be downloaded. At that point, it has no PHY
functionality. So lets represent it this way in DT:

    davinci_mdio: mdio@5c030000 {
        reg = <0x5c030000 0x1000>;
        #address-cells = <1>;
        #size-cells = <0>;

        reset-gpios = <&gpio2 5 1>;
        reset-delay-us = <2>;

        ethphy0: ethernet-phy@1 {
            reg = <1>;
        };

	mcu: mcu@3 {
	    compatible = "airoha,en8811h-mcu";
	    reg = <3>;
	}

The compatible here makes it an MDIO device, not a PHY device. The
MDIO subsystem will load an MDIO driver for that compatible, and the
driver can then access device 3 on the MDIO bus. That driver will then
poll the filesystem for the firmware and download it. It might need to
do that in a thread, rather than probe(), i don't know.

Once the firmware starts, we have a PHY. And thinking ahead a bit,
there is no reason this MCU is for a single PHY, it could be a quad
PHY. We need to be able to represent this PHY in DT:

    davinci_mdio: mdio@5c030000 {
        reg = <0x5c030000 0x1000>;
        #address-cells = <1>;
        #size-cells = <0>;

        reset-gpios = <&gpio2 5 1>;
        reset-delay-us = <2>;

        ethphy0: ethernet-phy@1 {
            reg = <1>;
        };

	mcu: mcu@3 {
	    compatible = "airoha,en8811h-mcu";
	    reg = <3>;

	    mdio {
	        ethphy3: ethernet-phy@3 {
                reg = <3>;
            };
	};
    };

Have the MDIO device create a new MDIO bus, with pass through
operations to access the underlying MDIO bus, but just for one
address. For all other addresses return -ENODEV. When you register
this MDIO bus, it will get scanned and the PHY found. Since the PHY is
now actually up and running phylib is happy, its usual semantics are
true, the device is ready to go as soon a probe() returns.

As you pointed out, there are currently 3 devices which need to
download firmware. I _guess_ 3/4 of the code can be shared, so please
put must of it into a library, and only have code for actually
downloading to the PHY in the driver.

Then there is a phylink part. This is inspired by how SFP works. We
need some property in the MAC node which indicates the PHY is going to
arrive late. I'm not sure 'hotplug' is the correct description here,
since we know it is there, it is described in DT, it cannot be
exchanged for something else. For the moment, lets just call this
property 'slow-to-probe'. phylink_of_phy_connect() will look for this
property. If it finds 'slow-to-probe', there must also be a phy-handle
pointing to the PHY. phylink then sets itself up to handle this slow
PHY. It needs to poll the phy-handle until it resolves. It can then
call its own phylink_connect_phy() function to connect up the PHY.

As with an SFP, ksetting_get() should return no link modes if the PHY
is not connected yet. ksetting_set() will automatically return EINVAL
when asked to enable a link mode, since none are supported.
eee_get/eee_set should do the same. Since this is how SFPs work, it
should not be too hard to make user space understand an interface can
start out not supporting anything, and then later have various link
modes, autoneg etc.

Please have a think about this architecture, and see if you can find
any holes in it.

    Andrew

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

* Re: [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes
  2026-08-24 16:25 ` [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Andrew Lunn
@ 2026-08-25  8:25   ` Aleksei Sviridkin
  2026-08-28 13:30     ` Andrew Lunn
  0 siblings, 1 reply; 14+ messages in thread
From: Aleksei Sviridkin @ 2026-08-25  8:25 UTC (permalink / raw)
  To: Andrew Lunn
  Cc: Vladimir Oltean, Heiner Kallweit, Russell King, netdev,
	Aleksei Sviridkin

Hi Andrew

I built it: both halves, as an MVP on my board. The MDIO device with a
nested pass-through bus for one address, and slow-to-probe in phylink
with a poller modeled on the SFP path. The port survives the switch
probe, the poller connects the real driver a few seconds later with its
interrupt from DT, and it held across reboots in both topologies. Holes
I hit, all on real boots:

1. "Polls until it resolves" needs a stricter resolve than the device
existing. A phy_device with no driver gets genphy bound by
phy_attach_direct(), which cannot drive this PHY;
phylink_sfp_connect_phy() already refuses a driverless PHY for the same
reason. And "driver bound" is still not enough, because the core
assigns dev->driver before probe runs. A poll tick landing mid-probe
connected a PHY with an all-zero supported mask:

  lan4: validation of 2500base-x with support 0000000,00000000,00000000,00000000 ... failed: -EINVAL

What survived hardware: driver bound, not genphy, state past PHY_DOWN.

2. The poller needs rtnl to connect, and phylink_disconnect_phy() runs
under rtnl, so a synchronous cancel there deadlocks. Trylock plus
requeue in the worker, non-sync cancel at disconnect, sync cancel only
in phylink_destroy(). A failed connect must not stop the polling, errno
cannot tell the race above from a permanent failure. The retry needs
backoff though: failed bringup goes through phy_detach(), which pulses
the reset line, and this chip's firmware lives behind that line.

3. Returning 0 for the armed case matters beyond the error code: on
-ENODEV, DSA goes looking on the switch's internal MDIO bus and
attaches the internal GbE PHY to the 2500base-x port, which then fails
validation.

4. Polling costs one interval of latency. The tick phase is set when
the port arms, so the attach lands anywhere in [0, interval) after the
PHY becomes ready; I measured 0.7s of a 1s interval. The exact event
exists, BUS_NOTIFY_BOUND_DRIVER fires at probe completion, but
mdio_bus_type is internal to phylib, so the notifier has to live there
behind a small API, it still needs the genphy filter, and the callback
runs under device_lock so it still defers to a workqueue. My plan is
polling first and the notifier as a follow-up, unless you want it
event-driven from the start.

Two questions before I send the series.

My MCU driver only waits for the firmware files and registers the
nested bus; the download stays in the PHY driver, which already does
it. Moving it into the MDIO driver duplicates the loader for a quad-PHY
case no board has yet. Is the MCU-side download a hard part of the
design for you?

On this board the minimal variant, slow-to-probe alone with the PHY
node directly on the bus, carries everything by itself: the chip
answers its ID from the bootloader, and the driver and firmware arrive
together. The MDIO-device layer buys the general case (chips silent
before firmware, quad PHY, reset ownership). Both layers in one series,
or slow-to-probe first?

	Aleksei

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

* Re: [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes
  2026-08-25  8:25   ` Aleksei Sviridkin
@ 2026-08-28 13:30     ` Andrew Lunn
  0 siblings, 0 replies; 14+ messages in thread
From: Andrew Lunn @ 2026-08-28 13:30 UTC (permalink / raw)
  To: Aleksei Sviridkin; +Cc: Vladimir Oltean, Heiner Kallweit, Russell King, netdev

> Two questions before I send the series.
> 
> My MCU driver only waits for the firmware files and registers the
> nested bus; the download stays in the PHY driver, which already does
> it. Moving it into the MDIO driver duplicates the loader for a quad-PHY
> case no board has yet. Is the MCU-side download a hard part of the
> design for you?

I think logically it belongs in the MDIO device. But we have to keep
backwards compatibility. I would refactor it out into a helper, which
both the PHY driver and the MDIO driver can use. At the same time, i
think you need to extend it to check if firmware is already running on
the MCU, and is so, don't do the download.

> On this board the minimal variant, slow-to-probe alone with the PHY
> node directly on the bus, carries everything by itself: the chip
> answers its ID from the bootloader, and the driver and firmware arrive
> together. The MDIO-device layer buys the general case (chips silent
> before firmware, quad PHY, reset ownership). Both layers in one series,
> or slow-to-probe first?

So long as you have not hit the limit of patches in a patch set, i
would keep it as a single patchset.

      Andrew

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

end of thread, other threads:[~2026-08-28 13:30 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-22 15:52 [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Aleksei Sviridkin
2026-08-22 15:52 ` [PATCH net-next 1/3] net: phylink: unwind the PHY binding when bringup fails late Aleksei Sviridkin
2026-08-22 17:30   ` Andrew Lunn
2026-08-22 15:52 ` [PATCH net-next 2/3] net: phy: restore the interrupt after a generic-driver bind cycle Aleksei Sviridkin
2026-08-22 19:28   ` Andrew Lunn
     [not found] ` <20260822155259.87146-4-f@lex.la>
2026-08-22 19:38   ` [PATCH net-next 3/3] net: dsa: connect a late-arriving PHY at ifup Andrew Lunn
2026-08-23  0:05     ` Aleksei Sviridkin
2026-08-23  1:24       ` Andrew Lunn
2026-08-23 12:37         ` Aleksei Sviridkin
2026-08-23 15:20           ` Andrew Lunn
2026-08-24  2:40         ` Aleksei Sviridkin
2026-08-24 16:25 ` [PATCH net-next 0/3] net: survive a PHY whose driver arrives after the switch probes Andrew Lunn
2026-08-25  8:25   ` Aleksei Sviridkin
2026-08-28 13:30     ` Andrew Lunn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox