[PATCH 0/3] net: pse-pd: support module-based PSE controller drivers

Carlo Szelinsky posted 3 patches 3 days, 17 hours ago
There is a newer version of this series
drivers/net/mdio/fwnode_mdio.c |  8 ++++--
drivers/net/pse-pd/pse_core.c  | 49 ++++++++++++++++++++++++++++++++++
include/linux/pse-pd/pse.h     |  6 +++++
net/ethtool/pse-pd.c           |  4 +++
4 files changed, 65 insertions(+), 2 deletions(-)
[PATCH 0/3] net: pse-pd: support module-based PSE controller drivers
Posted by Carlo Szelinsky 3 days, 17 hours ago
When a PSE controller driver is built as a module, it may not be probed
yet when PHYs are registered on the MDIO bus. This causes
of_pse_control_get() to return -EPROBE_DEFER, destroying the PHY device.
Later, regulator_late_cleanup disables the unclaimed PSE regulators,
permanently killing PoE.

This series fixes the issue in three steps:

  1. Treat -EPROBE_DEFER as non-fatal during PHY registration, allowing
     the PHY to register with psec=NULL.

  2. Add an admin_state_synced flag to pse_pi so that pse_pi_is_enabled()
     reports unclaimed PIs as disabled, preventing regulator_late_cleanup
     from shutting them down. The existing dual-path behavior (software-
     tracked vs. hardware-queried state) is preserved for claimed PIs.

  3. Add pse_control_try_resolve() for lazy PSE control resolution on
     first ethtool access, serialized by RTNL.

This is tested on my setup, but I am not fully sure if this is the right
approach to solve this problem. I would love to get feedback from the
maintainers on whether the overall design direction makes sense, or if
there is a better way to handle the deferred PSE control acquisition.

Carlo Szelinsky (3):
  net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
  net: pse-pd: prevent regulator cleanup from disabling unclaimed PSE
    PIs
  net: pse-pd: add lazy PSE control resolution for modular drivers

 drivers/net/mdio/fwnode_mdio.c |  8 ++++--
 drivers/net/pse-pd/pse_core.c  | 49 ++++++++++++++++++++++++++++++++++
 include/linux/pse-pd/pse.h     |  6 +++++
 net/ethtool/pse-pd.c           |  4 +++
 4 files changed, 65 insertions(+), 2 deletions(-)

--
2.43.0
[PATCH net-next v2 0/3] net: pse-pd: support module-based PSE controller drivers
Posted by Carlo Szelinsky 2 days, 19 hours ago
When a PSE controller driver is built as a module, it may not be probed
yet when PHYs are registered on the MDIO bus. This causes
of_pse_control_get() to return -EPROBE_DEFER, destroying the PHY device.
Later, regulator_late_cleanup disables the unclaimed PSE regulators,
permanently killing PoE.

This series fixes the issue in three steps:

  1. Add an admin_state_synced flag to pse_pi so that pse_pi_is_enabled()
     reports unclaimed PIs as disabled, preventing regulator_late_cleanup
     from shutting them down. The existing dual-path behavior (software-
     tracked vs. hardware-queried state) is preserved for claimed PIs.

  2. Add pse_control_try_resolve() for lazy PSE control resolution on
     first ethtool access, serialized by RTNL.

  3. Treat -EPROBE_DEFER as non-fatal during PHY registration, allowing
     the PHY to register with psec=NULL. This is ordered last because it
     changes probe behavior and relies on patches 1 and 2 for correct
     lazy resolution.

Thanks to Kory Maincent for the review and feedback on v1.

Changes in v2:
  - Reordered patches: moved the EPROBE_DEFER patch last since it
    changes probe behavior and depends on the other two patches for
    correct lazy resolution (Kory Maincent)
  - Added net-next to subject prefix (Kory Maincent)
  - Collected Acked-by tags

Carlo Szelinsky (3):
  net: pse-pd: prevent regulator cleanup from disabling unclaimed PSE
    PIs
  net: pse-pd: add lazy PSE control resolution for modular drivers
  net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration

 drivers/net/mdio/fwnode_mdio.c |  8 ++++--
 drivers/net/pse-pd/pse_core.c  | 49 ++++++++++++++++++++++++++++++++++
 include/linux/pse-pd/pse.h     |  6 +++++
 net/ethtool/pse-pd.c           |  4 +++
 4 files changed, 65 insertions(+), 2 deletions(-)


base-commit: cf0d9080c6f795bc6be08babbffa29b62c06e9b0
--
2.43.0
[PATCH net-next v2 1/3] net: pse-pd: prevent regulator cleanup from disabling unclaimed PSE PIs
Posted by Carlo Szelinsky 2 days, 19 hours ago
When a PSE controller driver is loaded as a module, its PI regulators
are registered before any consumer (PHY) acquires the corresponding PSE
control via of_pse_control_get(). The regulator framework's
regulator_late_cleanup then calls pse_pi_is_enabled(), which queries
hardware and sees the PI is enabled. Since no consumer holds it
(use_count == 0), regulator_late_cleanup disables it, killing PoE.

Add an admin_state_synced flag to struct pse_pi that is set when a
consumer first acquires the PSE control and syncs admin_state_enabled
from hardware. In pse_pi_is_enabled(), report unsynchronized PIs as
disabled so regulator_late_cleanup skips them.

This preserves the existing dual-path behavior: software-tracked state
for software-controlled power domains, and hardware queries for
hardware-controlled domains. The admin_state_synced flag is only false
before the first consumer acquisition, which is the exact window where
regulator_late_cleanup could incorrectly disable the PI.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Acked-by: Kory Maincent <kory.maincent@bootlin.com>
---
 drivers/net/pse-pd/pse_core.c | 13 +++++++++++++
 include/linux/pse-pd/pse.h    |  1 +
 2 files changed, 14 insertions(+)

diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 3beaaaeec9e1..566b07c336bf 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -421,6 +421,18 @@ static int pse_pi_is_enabled(struct regulator_dev *rdev)
 
 	id = rdev_get_id(rdev);
 	mutex_lock(&pcdev->lock);
+
+	/*
+	 * Report the PI as disabled until a consumer has acquired it
+	 * and synced admin_state_enabled from hardware. This prevents
+	 * regulator_late_cleanup from disabling unclaimed PSE PIs
+	 * when the PSE controller driver loads as a module.
+	 */
+	if (!pcdev->pi[id].admin_state_synced) {
+		ret = 0;
+		goto out;
+	}
+
 	if (pse_pw_d_is_sw_pw_control(pcdev, pcdev->pi[id].pw_d)) {
 		ret = pcdev->pi[id].admin_state_enabled;
 		goto out;
@@ -1431,6 +1443,7 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index,
 		goto free_psec;
 
 	pcdev->pi[index].admin_state_enabled = ret;
+	pcdev->pi[index].admin_state_synced = true;
 	psec->ps = devm_regulator_get_exclusive(pcdev->dev,
 						rdev_get_name(pcdev->pi[index].rdev));
 	if (IS_ERR(psec->ps)) {
diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h
index 4e5696cfade7..b86cce740551 100644
--- a/include/linux/pse-pd/pse.h
+++ b/include/linux/pse-pd/pse.h
@@ -260,6 +260,7 @@ struct pse_pi {
 	struct device_node *np;
 	struct regulator_dev *rdev;
 	bool admin_state_enabled;
+	bool admin_state_synced;
 	struct pse_power_domain *pw_d;
 	int prio;
 	bool isr_pd_detected;
-- 
2.43.0
Re: [PATCH net-next v2 1/3] net: pse-pd: prevent regulator cleanup from disabling unclaimed PSE PIs
Posted by Jakub Kicinski 1 day, 6 hours ago
On Mon, 30 Mar 2026 15:29:50 +0200 Carlo Szelinsky wrote:
> --- a/include/linux/pse-pd/pse.h
> +++ b/include/linux/pse-pd/pse.h
> @@ -260,6 +260,7 @@ struct pse_pi {
>  	struct device_node *np;
>  	struct regulator_dev *rdev;
>  	bool admin_state_enabled;
> +	bool admin_state_synced;
>  	struct pse_power_domain *pw_d;

new field is missing from the kdoc:

Warning: include/linux/pse-pd/pse.h:267 struct member 'admin_state_synced' not described in 'pse_pi'
-- 
pw-bot: cr
[PATCH net-next v2 2/3] net: pse-pd: add lazy PSE control resolution for modular drivers
Posted by Carlo Szelinsky 2 days, 19 hours ago
When a PSE controller driver is built as a module and was not probed
during PHY registration, the PHY ends up with psec=NULL. Add
pse_control_try_resolve() to lazily resolve the PSE control on first
ethtool access.

Call pse_control_try_resolve() in both the GET and SET ethtool PSE
handlers, before checking phydev->psec. The function is serialized by
RTNL (enforced via ASSERT_RTNL), preventing concurrent callers from
double-allocating a PSE control.

If resolution fails (e.g. the module still has not loaded), a debug
message is emitted via phydev_dbg() and the handler falls through to
the existing "No PSE is attached" error path.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Acked-by: Kory Maincent <kory.maincent@bootlin.com>
---
 drivers/net/pse-pd/pse_core.c | 36 +++++++++++++++++++++++++++++++++++
 include/linux/pse-pd/pse.h    |  5 +++++
 net/ethtool/pse-pd.c          |  4 ++++
 3 files changed, 45 insertions(+)

diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 566b07c336bf..390df23a991c 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -1569,6 +1569,42 @@ struct pse_control *of_pse_control_get(struct device_node *node,
 }
 EXPORT_SYMBOL_GPL(of_pse_control_get);
 
+/**
+ * pse_control_try_resolve - attempt to resolve a deferred PSE control
+ * @phydev: the PHY device whose PSE control may need resolution
+ *
+ * When a PSE controller driver is built as a module, it may not have
+ * probed when PHYs were registered on the MDIO bus. This function
+ * retries PSE control acquisition and should be called before
+ * accessing phydev->psec in ethtool handlers.
+ *
+ * Context: Caller must hold RTNL.
+ */
+void pse_control_try_resolve(struct phy_device *phydev)
+{
+	struct device_node *np;
+	struct pse_control *psec;
+
+	ASSERT_RTNL();
+
+	if (phydev->psec)
+		return;
+
+	np = phydev->mdio.dev.of_node;
+	if (!np || !of_property_present(np, "pses"))
+		return;
+
+	psec = of_pse_control_get(np, phydev);
+	if (IS_ERR(psec)) {
+		phydev_dbg(phydev, "failed to resolve PSE control: %pe\n",
+			   psec);
+		return;
+	}
+
+	phydev->psec = psec;
+}
+EXPORT_SYMBOL_GPL(pse_control_try_resolve);
+
 /**
  * pse_get_sw_admin_state - Convert the software admin state to c33 or podl
  *			    admin state value used in the standard
diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h
index b86cce740551..d2f9b7c1acdf 100644
--- a/include/linux/pse-pd/pse.h
+++ b/include/linux/pse-pd/pse.h
@@ -350,6 +350,7 @@ int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
 struct pse_control *of_pse_control_get(struct device_node *node,
 				       struct phy_device *phydev);
 void pse_control_put(struct pse_control *psec);
+void pse_control_try_resolve(struct phy_device *phydev);
 
 int pse_ethtool_get_status(struct pse_control *psec,
 			   struct netlink_ext_ack *extack,
@@ -379,6 +380,10 @@ static inline void pse_control_put(struct pse_control *psec)
 {
 }
 
+static inline void pse_control_try_resolve(struct phy_device *phydev)
+{
+}
+
 static inline int pse_ethtool_get_status(struct pse_control *psec,
 					 struct netlink_ext_ack *extack,
 					 struct ethtool_pse_control_status *status)
diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
index 2eb9bdc2dcb9..adffc230acd6 100644
--- a/net/ethtool/pse-pd.c
+++ b/net/ethtool/pse-pd.c
@@ -42,6 +42,8 @@ static int pse_get_pse_attributes(struct phy_device *phydev,
 		return -EOPNOTSUPP;
 	}
 
+	pse_control_try_resolve(phydev);
+
 	if (!phydev->psec) {
 		NL_SET_ERR_MSG(extack, "No PSE is attached");
 		return -EOPNOTSUPP;
@@ -249,6 +251,8 @@ ethnl_set_pse_validate(struct phy_device *phydev, struct genl_info *info)
 		return -EOPNOTSUPP;
 	}
 
+	pse_control_try_resolve(phydev);
+
 	if (!phydev->psec) {
 		NL_SET_ERR_MSG(info->extack, "No PSE is attached");
 		return -EOPNOTSUPP;
-- 
2.43.0
[PATCH net-next v2 3/3] net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
Posted by Carlo Szelinsky 2 days, 19 hours ago
When a PSE controller driver is built as a module, it may not be probed
yet when PHYs are registered on the MDIO bus. This causes
fwnode_find_pse_control() -> of_pse_control_get() to return
-EPROBE_DEFER, which currently propagates up and destroys the PHY
device.

Treat -EPROBE_DEFER as non-fatal, allowing the PHY to register
successfully with psec=NULL. The PSE control can be resolved lazily
when first needed.

Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
Acked-by: Kory Maincent <kory.maincent@bootlin.com>
---
 drivers/net/mdio/fwnode_mdio.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/net/mdio/fwnode_mdio.c b/drivers/net/mdio/fwnode_mdio.c
index ba7091518265..2a03b3fc41e6 100644
--- a/drivers/net/mdio/fwnode_mdio.c
+++ b/drivers/net/mdio/fwnode_mdio.c
@@ -161,8 +161,12 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,
 
 	psec = fwnode_find_pse_control(child, phy);
 	if (IS_ERR(psec)) {
-		rc = PTR_ERR(psec);
-		goto unregister_phy;
+		if (PTR_ERR(psec) == -EPROBE_DEFER) {
+			psec = NULL;
+		} else {
+			rc = PTR_ERR(psec);
+			goto unregister_phy;
+		}
 	}
 
 	phy->psec = psec;
-- 
2.43.0
Re: [PATCH net-next v2 3/3] net: mdio: treat PSE EPROBE_DEFER as non-fatal during PHY registration
Posted by Andrew Lunn 2 days, 18 hours ago
> Treat -EPROBE_DEFER as non-fatal, allowing the PHY to register
> successfully with psec=NULL. The PSE control can be resolved lazily
> when first needed.

Is there a reason not to do lazily resolution all the time?

   Andrew
Re: [PATCH 0/3] net: pse-pd: support module-based PSE controller drivers
Posted by Kory Maincent 2 days, 22 hours ago
On Sun, 29 Mar 2026 18:10:11 +0200
Carlo Szelinsky <github@szelinsky.de> wrote:

> When a PSE controller driver is built as a module, it may not be probed
> yet when PHYs are registered on the MDIO bus. This causes
> of_pse_control_get() to return -EPROBE_DEFER, destroying the PHY device.
> Later, regulator_late_cleanup disables the unclaimed PSE regulators,
> permanently killing PoE.

That's a good idea. I still think that one day I will remove registering
a regulator for every PIs as it only brings pain and fixups, but well until
then lets continue dealing with it.

You forgot net-next in the patch subject.
 
> This series fixes the issue in three steps:
> 
>   1. Treat -EPROBE_DEFER as non-fatal during PHY registration, allowing
>      the PHY to register with psec=NULL.
> 
>   2. Add an admin_state_synced flag to pse_pi so that pse_pi_is_enabled()
>      reports unclaimed PIs as disabled, preventing regulator_late_cleanup
>      from shutting them down. The existing dual-path behavior (software-
>      tracked vs. hardware-queried state) is preserved for claimed PIs.
> 
>   3. Add pse_control_try_resolve() for lazy PSE control resolution on
>      first ethtool access, serialized by RTNL.
> 
> This is tested on my setup, but I am not fully sure if this is the right
> approach to solve this problem. I would love to get feedback from the
> maintainers on whether the overall design direction makes sense, or if
> there is a better way to handle the deferred PSE control acquisition.

You approach seems good to me.

-- 
Köry Maincent, Bootlin
Embedded Linux and kernel engineering
https://bootlin.com