net: pse-pd: decouple controller lookup from MDIO probe
Subsystems (5)
cover net: pse-pd: decouple controller lookup from MDIO probe
On systems where a PSE controller driver loads as a module and a device-tree PHY node carries a pses = <&pse_pi> reference, fwnode_mdiobus_register_phy() tries to resolve the PSE handle before the controller driver has probed. of_pse_control_get() returns -EPROBE_DEFER, the enclosing MDIO/DSA probe fails, and driver-core re-queues the work. The retry loop spins until the PSE driver module loads and its controller registers.
Commit fa2f0454174c ("net: pse-pd: Introduce attached_phydev to pse control") made each retry expensive. It reordered fwnode_mdiobus_register_phy() so the PHY is registered before the PSE lookup. Every deferral now performs a full phy_device_register() / phy_device_remove() cycle. On a board with a sufficiently tight watchdog the retry loop can starve the watchdog kthread. On the reporting hardware (MT7621 + gpio-wdt, 1-second margin) the retry loop converts a slow probe phase into a reset before userspace loads.
The affected population today looks small. OpenWrt, where PSE actually ships, is still on 6.12 (pre-regression), and most environments with CONFIG_PSE_*=m do not have boards whose DT references a PSE controller from a PHY. Still, the mechanism is general. Any modular PSE driver combined with the documented pses = <&...> binding reproduces the retry loop. Whether it reaches brick-grade or merely slow/flaky boot depends on local watchdog timing. More exposure is expected as distribution and embedded kernels move to 6.13 and later.
The narrow fix would be to partially revert the ordering in fa2f0454174c so each defer is cheap again. That keeps the same architecture (fwnode_mdio holding PSE knowledge, -EPROBE_DEFER flowing across the subsystem boundary), and any future reorder reintroduces the same class of bug. This series takes the larger
fix: decouple PSE controller lookup from MDIO registration entirely. pse_core now publishes a BLOCKING_NOTIFIER chain with REGISTERED and UNREGISTERED events. phy_device subscribes, owns phydev->psec lifetime, and attaches PSE handles in response to controller lifecycle rather than during probe. fwnode_mdio loses its PSE awareness, and -EPROBE_DEFER no longer flows out of fwnode_mdio.
Patch breakdown:
1. Scope the pse_control regulator handle to kref lifetime (Fixes: d83e13761d5b). A latent bug that patch 4 makes reachable.
2. Add the notifier chain (enum, head, register/unregister helpers). Pure infrastructure. No subscribers yet, no observable change.
3. Fire REGISTERED and UNREGISTERED events from the controller register/unregister paths. Still no subscribers, still no observable change.
4. Subscribe from the PHY layer, take ownership of phydev->psec via the notifier, and remove fwnode_find_pse_control() from fwnode_mdio.
Patch 1 is bundled here per stable-kernel-rules section 4 reachability guidance. On mainline today, with no notifier subscriber, no caller drives the dangling regulator-handle sequence. Patches 2 and 3 are deliberately split to separate "add infrastructure" from "wire it up". Happy to fold them if maintainers prefer the combined form.
Validated on a Cudy C200P (MT7621 + IP804AR) running an OpenWrt build of 6.18.21 with the series applied. A lockdep build (CONFIG_PROVE_LOCKING + CONFIG_DEBUG_ATOMIC_SLEEP) shows no splats from the series' code paths during boot, PHY attach, PHY detach, or a full controller unbind/rebind cycle. ethtool --set-pse drives all four PoE-capable LAN ports, and a Ruckus H510 class-4 PD plugged into lan3 negotiates and receives 48 V.
The C200P has no SFP cage, so the SFP path change in sfp.c (phy_device_register -> phy_device_register_locked) isn't exercised on the bench. Verified by call-graph audit: every path reaching sfp_sm_probe_phy() holds rtnl at entry, via sfp_timeout, sfp_check_state, sfp_probe, sfp_remove, or sfp_bus_{add,del}_upstream.
Not addressed by this series: ethtool --show-pse returns "No data available" on DSA netdevs in 6.18, because dev->phydev is NULL for DSA-frontend netdevs and ethnl_req_get_phydev() therefore returns NULL. That's a DSA / ethtool integration quirk that predates this work.
Sending as RFC because this is my first net-next series. I'd appreciate maintainer guidance on whether patch 1 should go to net rather than net-next, and whether the patch 2/3 split is preferred to the combined form.
---
Corey Leavitt (4):
net: pse-pd: scope pse_control regulator handle to kref lifetime
net: pse-pd: add notifier chain for controller lifecycle events
net: pse-pd: fire lifecycle events on controller register/unregister
net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
drivers/net/mdio/fwnode_mdio.c | 34 ---------- drivers/net/phy/phy_device.c | 144 ++++++++++++++++++++++++++++++++++++++--- drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 60 ++++++++++++++++- include/linux/phy.h | 2 + include/linux/pse-pd/pse.h | 41 ++++++++++++ 6 files changed, 236 insertions(+), 47 deletions(-)
---
base-commit: 1f5ffc672165ff851063a5fd044b727ab2517ae3
change-id: 20260422-pse-notifier-decouple-efa80d77f4be
Best regards,
Signature · 1 line
Corey Leavitt <[email protected]>
RFC PATCH 1/4 net: pse-pd: scope pse_control regulator handle to kref lifetime
__pse_control_release() drops psec->ps via devm_regulator_put(), which only succeeds if the devres entry added by the matching devm_regulator_get_exclusive() is still present on pcdev->dev at the time the pse_control's kref hits zero.
In practice that assumption does not hold when the controller is unbound while any pse_control still has consumers: pcdev->dev's devres list is released LIFO, so every per-attach regulator-GET devres runs (and regulator_put()s the underlying regulator) before pse_controller_unregister() itself is invoked. Any later pse_control_put() from that unbind path then reads psec->ps as a dangling pointer inside devm_regulator_put() and WARNs at drivers/regulator/devres.c:232 (devres_release() fails to find the already-released match).
The pse_control's consumer handle is logically scoped to the pse_control's refcount, not to pcdev->dev's devres lifetime. Switch to the plain regulator_get_exclusive() / regulator_put() pair so __pse_control_release() does the right put regardless of whether the controller's devres has already been unwound.
No change to the regulator-framework-visible refcount or lifetime of the underlying regulator: a single get paired with a single put. The existing devm_regulator_register() for the per-PI rails is unchanged (those ARE correctly scoped to the controller's lifetime).
--- drivers/net/pse-pd/pse_core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/net/pse-pd/pse_core.c +3 -3
@@ -1362,7 +1362,7 @@ static void __pse_control_release(struct kref *kref)if (psec->pcdev->pi[psec->id].admin_state_enabled)regulator_disable (psec->ps); -devm_regulator_put (psec->ps); +regulator_put (psec->ps);module_put (psec->pcdev->owner);
@@ -1431,8 +1431,8 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, goto free_psec; pcdev->pi[index].admin_state_enabled = ret; - psec->ps =devm_regulator_get_exclusive (pcdev->dev, -rdev_get_name (pcdev->pi[index].rdev)); + psec->ps =regulator_get_exclusive (pcdev->dev, +rdev_get_name (pcdev->pi[index].rdev));if (IS_ERR (psec->ps)) { ret =PTR_ERR (psec->ps); goto put_module;
Signature · 1 line
2.53.0
Re: [PATCH RFC net-next 1/4] net: pse-pd: scope pse_control regulator handle to kref lifetime
Quoted 23 lines · On Thu, 23 Apr 2026 01:42:14 -0600 Corey Leavitt via B4 Relay…
From: Corey Leavitt <[email protected]>
__pse_control_release()dropspsec->psviadevm_regulator_put(), which only succeeds if the devres entry added by the matchingdevm_regulator_get_exclusive()is still present onpcdev->devat the time the pse_control's kref hits zero.In practice that assumption does not hold when the controller is unbound while any
pse_controlstill has consumers: pcdev->dev's devres list is released LIFO, so every per-attach regulator-GET devres runs (and regulator_put()s the underlying regulator) beforepse_controller_unregister()itself is invoked. Any laterpse_control_put()from that unbind path then readspsec->psas a dangling pointer insidedevm_regulator_put()and WARNs atdrivers/regulator/devres.c:232(devres_release()fails to find the already-released match).The pse_control's consumer handle is logically scoped to the pse_control's refcount, not to pcdev->dev's devres lifetime. Switch to the plain
regulator_get_exclusive()/regulator_put()pair so__pse_control_release()does the right put regardless of whether the controller's devres has already been unwound.
Quoted 4 lines
No change to the regulator-framework-visible refcount or lifetime of the underlying regulator: a single get paired with a single put. The existing
devm_regulator_register()for the per-PI rails is unchanged (those ARE correctly scoped to the controller's lifetime).
Thank you!
Signature · 3 lines
Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
RFC PATCH 2/4 net: pse-pd: add notifier chain for controller lifecycle events
In reply to Corey LeavittIntroduce a blocking notifier chain that allows other subsystems to be informed when a PSE controller is registered or unregistered, and provide pse_register_notifier() / pse_unregister_notifier() as the subscriber interface.
Subsequent patches will use this to let the phy subsystem own the phydev->psec lifecycle directly, decoupling PSE lookup from fwnode_mdiobus_register_phy() and removing the probe-time -EPROBE_DEFER coupling that currently exists between mdio, phy and pse-pd when the PSE controller driver is modular.
A blocking chain (rather than atomic) is used because callbacks will take rtnl_lock and call back into pse_core via of_pse_control_get().
The enum pse_controller_event is placed outside the IS_ENABLED(CONFIG_PSE_CONTROLLER) guard so that subscribers compiled into a kernel without PSE support can still reference the event values in dead-code paths without breaking the build.
This patch is pure infrastructure: nothing fires events yet, and nothing subscribes. No observable behavior change.
--- drivers/net/pse-pd/pse_core.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pse-pd/pse.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+)
drivers/net/pse-pd/pse_core.c +34 -0
@@ -8,6 +8,7 @@#include <linux/device.h> #include <linux/ethtool.h> #include <linux/ethtool_netlink.h> +#include <linux/notifier.h> #include <linux/of.h> #include <linux/phy.h> #include <linux/pse-pd/pse.h>
@@ -23,6 +24,39 @@ static LIST_HEAD(pse_controller_list);static DEFINE_XARRAY_ALLOC (pse_pw_d_map);static DEFINE_MUTEX (pse_pw_d_mutex); +static BLOCKING_NOTIFIER_HEAD (pse_controller_notifier); + +/** +* pse_register_notifier - register a callback for PSE controller events +* @nb: notifier block to register +* +* See enum pse_controller_event for events fired and their subscriber +* contract. Callbacks run in process context; they may sleep, take +* rtnl, and call of_pse_control_get(). The chain fires synchronously, +* so a PSE controller driver's probe/unbind path must not hold any +* such lock when calling pse_controller_register() or +* pse_controller_unregister(). +* +* Return: 0 on success, negative error code otherwise. +*/ +int pse_register_notifier (struct notifier_block *nb) +{ +return blocking_notifier_chain_register (&pse_controller_notifier, nb); +} +EXPORT_SYMBOL_GPL (pse_register_notifier); + +/** +* pse_unregister_notifier - unregister a previously registered callback +* @nb: notifier block previously passed to pse_register_notifier() +* +* Return: 0 on success, negative error code otherwise. +*/ +int pse_unregister_notifier (struct notifier_block *nb) +{ +return blocking_notifier_chain_unregister (&pse_controller_notifier, nb); +} +EXPORT_SYMBOL_GPL (pse_unregister_notifier); +/** * struct pse_control - a PSE control * @pcdev: a pointer to the PSE controller device
include/linux/pse-pd/pse.h +32 -0
@@ -21,6 +21,7 @@ struct net_device;struct phy_device ;struct pse_controller_dev ;struct netlink_ext_ack ; +struct notifier_block ;/* C33 PSE extended state and substate. */ struct ethtool_c33_pse_ext_state_info {
@@ -337,6 +338,24 @@ enum pse_budget_eval_strategies { PSE_BUDGET_EVAL_STRAT_DYNAMIC = 1 << 2, }; +/** +* enum pse_controller_event - PSE controller lifecycle events +* +* Event data in callbacks is always a pointer to the struct +* pse_controller_dev firing the event. +* +* @PSE_REGISTERED: controller added to pse_controller_list and +* resolvable by of_pse_control_get(). +* @PSE_UNREGISTERED: controller about to be removed from +* pse_controller_list. Subscribers holding pse_control references +* targeting it must drop them before returning and must not +* acquire new references for it. +*/ +enum pse_controller_event { +PSE_REGISTERED , +PSE_UNREGISTERED , +}; +#if IS_ENABLED (CONFIG_PSE_CONTROLLER )int pse_controller_register (struct pse_controller_dev *pcdev);void pse_controller_unregister (struct pse_controller_dev *pcdev);
@@ -366,6 +385,9 @@ int pse_ethtool_set_prio(struct pse_control *psec,bool pse_has_podl (struct pse_control *psec);bool pse_has_c33 (struct pse_control *psec); +int pse_register_notifier (struct notifier_block *nb); +int pse_unregister_notifier (struct notifier_block *nb); +#else static inline struct pse_control *of_pse_control_get(struct device_node *node,
@@ -416,6 +438,16 @@ static inline bool pse_has_c33(struct pse_control *psec)return false; } +static inline int pse_register_notifier (struct notifier_block *nb) +{ +return 0 ; +} + +static inline int pse_unregister_notifier (struct notifier_block *nb) +{ +return 0 ; +} +#endif #endif
Signature · 1 line
2.53.0
RFC PATCH 3/4 net: pse-pd: fire lifecycle events on controller register/unregister
In reply to Corey LeavittHook the newly-introduced pse_controller_notifier chain so that pse_controller_register() fires PSE_REGISTERED after the controller has been added to pse_controller_list (i.e. is now resolvable by of_pse_control_get()), and pse_controller_unregister() fires PSE_UNREGISTERED before the controller is removed from the list (while it is still valid to dereference from a subscriber's pse_control pointer targeting it).
With no subscribers yet, this is observably a no-op. A later change wires the phy subsystem in as the first subscriber.
--- drivers/net/pse-pd/pse_core.c | 6 ++++++ 1 file changed, 6 insertions(+)
drivers/net/pse-pd/pse_core.c +6 -0
@@ -1138,6 +1138,9 @@ int pse_controller_register(struct pse_controller_dev *pcdev)list_add (&pcdev->list, &pse_controller_list);mutex_unlock (&pse_list_mutex); +blocking_notifier_call_chain (&pse_controller_notifier, +PSE_REGISTERED , pcdev); +return 0 ; }EXPORT_SYMBOL_GPL (pse_controller_register);
@@ -1148,6 +1151,9 @@ EXPORT_SYMBOL_GPL(pse_controller_register);*/ void pse_controller_unregister (struct pse_controller_dev *pcdev) { +blocking_notifier_call_chain (&pse_controller_notifier, +PSE_UNREGISTERED , pcdev); +pse_flush_pw_ds (pcdev);pse_release_pis (pcdev);if (pcdev->irq)
Signature · 1 line
2.53.0
RFC PATCH 4/4 net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
In reply to Corey LeavittTransfer ownership of phydev->psec from fwnode_mdio to the phy subsystem itself. The phy subsystem now subscribes to the pse-pd notifier chain and manages psec attach/detach in response to PSE controller lifecycle events, while fwnode_mdio loses its PSE awareness entirely.
Split phy_device_register() into a public entry point that takes rtnl_lock() and a phy_device_register_locked() variant that assumes rtnl is already held. Callers that already hold rtnl (the SFP module state machine via __sfp_sm_event) use the _locked form to avoid deadlock; all other callers use the unchanged public API. This pair mirrors the register_netdevice() / register_netdev() split convention already established in the core networking stack. rtnl must span the full registration sequence through device_add(), not just phy_try_attach_pse(): a PSE_REGISTERED event firing between a narrow attach lock and device_add() would walk mdio_bus_type, find the phy not yet on the bus, and leave it permanently unattached.
With rtnl held across the full registration sequence:
- At phy_device_register_locked(), phy_try_attach_pse() attempts an of_pse_control_get() for phys whose DT pses phandle resolves now. If the controller is already registered, psec is attached before device_add() makes the phy visible on mdio_bus_type. If the controller is not yet registered, the swallow-error path leaves psec NULL and relies on the subsequent notifier event.
- On PSE_REGISTERED: an rtnl-guarded bus walk retries the attach for every registered phy whose psec is still NULL. This is the "phy was enumerated before the PSE controller loaded" case, which is the root cause of the boot-time probe-retry storm on systems with a modular PSE controller driver. Because the pse_controller_notifier is fired synchronously, a concurrent pse_controller_register() either (a) completes list_add and releases pse_list_mutex before this function takes rtnl, in which case phy_try_attach_pse() finds the controller in the list and attaches; or (b) fires its notifier during this function, in which case the callback blocks on rtnl until this function returns, then walks the bus and finds the phy fully registered (attaching if psec is still NULL).
- On PSE_UNREGISTERED: an rtnl-guarded bus walk releases every phydev->psec that targets the departing controller before pse_release_pis() frees pcdev->pi. Without this, a phy still holding a pse_control reference would cause a use-after-free in __pse_control_release's pcdev->pi[psec->id] access, and the PSE driver module could not finish unloading while any phy still held a reference via module_put().
Introduce phy_try_attach_pse() as the rtnl-guarded helper used by both the register path and the notifier walk. Holding rtnl across of_pse_control_get() is safe because pse_list_mutex is never held in the opposite order.
Expose pse_control_matches_pcdev() as a predicate so subscribers can identify which of their held pse_control references target a given controller, without leaking the struct pse_controller_dev * out of pse_control opacity.
Move the final pse_control_put() of phydev->psec from phy_device_remove() to phy_device_release(). The kobject release callback runs only after every reference on the device has been dropped, including the bus iterator references taken by bus_for_each_dev() in the notifier walk, which means by the time release fires no concurrent reader or writer of phydev->psec can exist. The mdio_bus_type klist is set up in bus_register() with klist_devices_get() / klist_devices_put() (drivers/base/bus.c), which bracket each iteration step with get_device() / put_device() on the underlying struct device; that reference defers the release callback from firing until the walk has advanced past this phy. Keeping phy_device_remove() unchanged avoids introducing a new locking contract on its many callers (sfp, fixed_phy, xgbe, hns, netsec, bcm_sf2, mdiobus_unregister).
Finally, delete fwnode_find_pse_control() and its call site in fwnode_mdiobus_register_phy(), and drop the PSE header from fwnode_mdio.c. This removes the probe-time -EPROBE_DEFER coupling between mdio and pse-pd that caused the boot-hang regression on systems with a modular PSE controller driver and a DT phy with a pses phandle: the MDIO/DSA probe no longer sees any PSE-originated -EPROBE_DEFER, so the probe-retry storm is gone. fwnode_mdio is now PSE-agnostic.
--- drivers/net/mdio/fwnode_mdio.c | 34 ---------- drivers/net/phy/phy_device.c | 144 ++++++++++++++++++++++++++++++++++++++--- drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 14 ++++ include/linux/phy.h | 2 + include/linux/pse-pd/pse.h | 9 +++ 6 files changed, 161 insertions(+), 44 deletions(-)
drivers/net/mdio/fwnode_mdio.c +0 -34
@@ -11,33 +11,11 @@#include <linux/fwnode_mdio.h> #include <linux/of.h> #include <linux/phy.h> -#include <linux/pse-pd/pse.h> MODULE_AUTHOR ("Calvin Johnson <[email protected]>" );MODULE_LICENSE ("GPL" );MODULE_DESCRIPTION ("FWNODE MDIO bus (Ethernet PHY) accessors" ); -static struct pse_control * -fwnode_find_pse_control (struct fwnode_handle *fwnode, -struct phy_device *phydev) -{ -struct pse_control *psec; -struct device_node *np; - -if (!IS_ENABLED (CONFIG_PSE_CONTROLLER )) -return NULL ; - - np =to_of_node (fwnode); -if (!np) -return NULL ; - - psec =of_pse_control_get (np, phydev); -if (PTR_ERR (psec) == -ENOENT ) -return NULL ; - -return psec; -} -static struct mii_timestamper *fwnode_find_mii_timestamper (struct fwnode_handle *fwnode) {
@@ -118,7 +96,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, struct fwnode_handle *child, u32 addr) {struct mii_timestamper *mii_ts =NULL ; -struct pse_control *psec =NULL ;struct phy_device *phy;bool is_c45;u32 phy_id;
@@ -159,14 +136,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, goto clean_phy; } - psec =fwnode_find_pse_control (child, phy); -if (IS_ERR (psec)) { - rc =PTR_ERR (psec); - goto unregister_phy; - } - - phy->psec = psec; -/* phy->mii_ts may already be defined by the PHY driver. A * mii_timestamper probed via the device tree will still have * precedence.
@@ -176,9 +145,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,return 0 ; -unregister_phy: -if (is_acpi_node (child) ||is_of_node (child)) -phy_device_remove (phy); clean_phy:phy_device_free (phy); clean_mii_ts:
drivers/net/phy/phy_device.c +135 -9
197 lines not on this page · raw message
drivers/net/phy/sfp.c +1 -1
@@ -1932,7 +1932,7 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45)/* Mark this PHY as being on a SFP module */ phy->is_on_sfp_module = true; - err =phy_device_register (phy); + err =phy_device_register_locked (phy);if (err) {phy_device_free (phy); dev_err(sfp->dev, "phy_device_register failed: %pe\n",
drivers/net/pse-pd/pse_core.c +14 -0
@@ -2016,3 +2016,17 @@ bool pse_has_c33(struct pse_control *psec)return psec->pcdev->types ÐTOOL_PSE_C33 ; }EXPORT_SYMBOL_GPL (pse_has_c33); + +/** +* pse_control_matches_pcdev - Test whether a pse_control targets a controller +* @psec: pse_control obtained from of_pse_control_get() +* @pcdev: PSE controller to compare against +* +* Return: %true if @psec was obtained from @pcdev, %false otherwise. +*/ +bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev) +{ +return psec->pcdev == pcdev; +} +EXPORT_SYMBOL_GPL (pse_control_matches_pcdev);
include/linux/phy.h +2 -0
@@ -2158,6 +2158,8 @@ struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode);struct fwnode_handle *fwnode_get_phy_node (const struct fwnode_handle *fwnode);struct phy_device *get_phy_device (struct mii_bus *bus,int addr,bool is_c45);int phy_device_register (struct phy_device *phy); +/* Caller must hold rtnl_lock(); see phy_device_register() for the public form. */ +int phy_device_register_locked (struct phy_device *phy);void phy_device_free (struct phy_device *phydev);void phy_device_remove (struct phy_device *phydev);int phy_get_c45_ids (struct phy_device *phydev);
include/linux/pse-pd/pse.h +9 -0
@@ -385,6 +385,9 @@ int pse_ethtool_set_prio(struct pse_control *psec,bool pse_has_podl (struct pse_control *psec);bool pse_has_c33 (struct pse_control *psec); +bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev); +int pse_register_notifier (struct notifier_block *nb);int pse_unregister_notifier (struct notifier_block *nb);
@@ -438,6 +441,12 @@ static inline bool pse_has_c33(struct pse_control *psec)return false; } +static inline bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev) +{ +return false; +} +static inline int pse_register_notifier (struct notifier_block *nb) {return 0 ;
Signature · 1 line
2.53.0
Kory Maincent
maintainerRe: [PATCH RFC net-next 4/4] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
Quoted 85 lines · On Thu, 23 Apr 2026 01:42:17 -0600 Corey Leavitt via B4 Relay…
From: Corey Leavitt <[email protected]>
Transfer ownership of
phydev->psecfromfwnode_mdioto the phy subsystem itself. The phy subsystem now subscribes to the pse-pd notifier chain and manages psec attach/detach in response to PSE controller lifecycle events, whilefwnode_mdioloses its PSE awareness entirely.Split
phy_device_register()into a public entry point that takesrtnl_lock()and aphy_device_register_locked()variant that assumes rtnl is already held. Callers that already hold rtnl (the SFP module state machine via__sfp_sm_event) use the_lockedform to avoid deadlock; all other callers use the unchanged public API. This pair mirrors theregister_netdevice()/register_netdev()split convention already established in the core networking stack. rtnl must span the full registration sequence throughdevice_add(), not justphy_try_attach_pse(): aPSE_REGISTEREDevent firing between a narrow attach lock anddevice_add()would walkmdio_bus_type, find the phy not yet on the bus, and leave it permanently unattached.With rtnl held across the full registration sequence:
- At
phy_device_register_locked(),phy_try_attach_pse()attempts anof_pse_control_get()for phys whose DT pses phandle resolves now. If the controller is already registered, psec is attached beforedevice_add()makes the phy visible onmdio_bus_type. If the controller is not yet registered, the swallow-error path leaves psec NULL and relies on the subsequent notifier event.- On
PSE_REGISTERED: an rtnl-guarded bus walk retries the attach for every registered phy whose psec is still NULL. This is the "phy was enumerated before the PSE controller loaded" case, which is the root cause of the boot-time probe-retry storm on systems with a modular PSE controller driver. Because thepse_controller_notifieris fired synchronously, a concurrentpse_controller_register()either (a) completeslist_addand releasespse_list_mutexbefore this function takes rtnl, in which casephy_try_attach_pse()finds the controller in the list and attaches; or (b) fires its notifier during this function, in which case the callback blocks on rtnl until this function returns, then walks the bus and finds the phy fully registered (attaching if psec is still NULL).- On
PSE_UNREGISTERED: an rtnl-guarded bus walk releases everyphydev->psecthat targets the departing controller beforepse_release_pis()freespcdev->pi. Without this, a phy still holding apse_controlreference would cause a use-after-free in __pse_control_release's pcdev->pi[psec->id] access, and the PSE driver module could not finish unloading while any phy still held a reference viamodule_put().Introduce
phy_try_attach_pse()as the rtnl-guarded helper used by both the register path and the notifier walk. Holding rtnl acrossof_pse_control_get()is safe becausepse_list_mutexis never held in the opposite order.Expose
pse_control_matches_pcdev()as a predicate so subscribers can identify which of their heldpse_controlreferences target a given controller, without leaking the structpse_controller_dev* out ofpse_controlopacity.Move the final
pse_control_put()ofphydev->psecfromphy_device_remove()tophy_device_release(). The kobject release callback runs only after every reference on the device has been dropped, including the bus iterator references taken bybus_for_each_dev()in the notifier walk, which means by the time release fires no concurrent reader or writer ofphydev->pseccan exist. Themdio_bus_typeklist is set up inbus_register()withklist_devices_get()/klist_devices_put()(drivers/base/bus.c), which bracket each iteration step withget_device()/put_device()on the underlying struct device; that reference defers the release callback from firing until the walk has advanced past this phy. Keepingphy_device_remove()unchanged avoids introducing a new locking contract on its many callers (sfp,fixed_phy, xgbe, hns, netsec,bcm_sf2,mdiobus_unregister).Finally, delete
fwnode_find_pse_control()and its call site infwnode_mdiobus_register_phy(), and drop the PSE header fromfwnode_mdio.c. This removes the probe-time -EPROBE_DEFER coupling between mdio and pse-pd that caused the boot-hang regression on systems with a modular PSE controller driver and a DT phy with a pses phandle: the MDIO/DSA probe no longer sees any PSE-originated -EPROBE_DEFER, so the probe-retry storm is gone.fwnode_mdiois now PSE-agnostic.
Quoted 1 line
Fixes fa2f0454174c ("net: pse-pd: Introduce attached_phydev to pse control")
This patch depends on the other patches (2 and 3) which do not have any fixes tag. So either all of them have the fixes tag or none of them have it.
Except from this, your design seems a great idea!
The only thing that puzzle me is to have the RTNLOCK hold in the phy_device_register function. This is an important change but I am not a netdev sufficient expert to know if that could break things.
Jakub, Russell what do you think?
Regards,
Signature · 3 lines
Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
Re: [PATCH RFC net-next 4/4] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
In reply to Corey LeavittHi,
this patch seems to cause a deadlock with PHYs which provide SFP ports. Noticed this downstream in OpenWrt with Realtek RTL8214FC using an extension to the upstream driver [1].
I suspect the deadlock happens because phy_device_register takes rtnl_lock with this patch. But further down in the call chain, phy_sfp_probe is called, calling sfp_bus_add_upstream which in turn tries to take rtnl_lock again.
While the downstream driver part still calls phy_sfp_probe itself, backporting the phy_port changes to 6.18 and adjusting the driver doesn't help. If I got it correctly, the phy_sfp_probe just moves into phylib core but still causes the deadlock.
Kind regards,
Jonas Jelonek
Kory Maincent
maintainerHello Corey,
Quoted 7 lines · On Thu, 23 Apr 2026 01:42:13 -0600 Corey Leavitt via B4 Relay…
On systems where a PSE controller driver loads as a module and a device-tree PHY node carries a
pses = <&pse_pi>reference,fwnode_mdiobus_register_phy()tries to resolve the PSE handle before the controller driver has probed.of_pse_control_get()returns -EPROBE_DEFER, the enclosing MDIO/DSA probe fails, and driver-core re-queues the work. The retry loop spins until the PSE driver module loads and its controller registers.
I will take a look at your series but FYI there was already a RFC series tackling this issue:
https://lore.kernel.org/lkml/[email protected]/
It rose a debate and there was currently no final solution.
Quoted 104 lines
Commit
fa2f0454174c("net: pse-pd: Introduceattached_phydevto pse control") made each retry expensive. It reorderedfwnode_mdiobus_register_phy()so the PHY is registered before the PSE lookup. Every deferral now performs a fullphy_device_register()/phy_device_remove()cycle. On a board with a sufficiently tight watchdog the retry loop can starve the watchdog kthread. On the reporting hardware (MT7621 + gpio-wdt, 1-second margin) the retry loop converts a slow probe phase into a reset before userspace loads.The affected population today looks small. OpenWrt, where PSE actually ships, is still on 6.12 (pre-regression), and most environments with CONFIG_PSE_*=m do not have boards whose DT references a PSE controller from a PHY. Still, the mechanism is general. Any modular PSE driver combined with the documented
pses = <&...>binding reproduces the retry loop. Whether it reaches brick-grade or merely slow/flaky boot depends on local watchdog timing. More exposure is expected as distribution and embedded kernels move to 6.13 and later.The narrow fix would be to partially revert the ordering in
fa2f0454174cso each defer is cheap again. That keeps the same architecture (fwnode_mdioholding PSE knowledge, -EPROBE_DEFER flowing across the subsystem boundary), and any future reorder reintroduces the same class of bug. This series takes the larger
fix: decouple PSE controller lookup from MDIO registration entirely.pse_corenow publishes aBLOCKING_NOTIFIERchain with REGISTERED and UNREGISTERED events.phy_devicesubscribes, ownsphydev->pseclifetime, and attaches PSE handles in response to controller lifecycle rather than during probe.fwnode_mdioloses its PSE awareness, and -EPROBE_DEFER no longer flows out offwnode_mdio.Patch breakdown:
1. Scope the
pse_controlregulator handle to kref lifetime (Fixes:d83e13761d5b). A latent bug that patch 4 makes reachable.
2. Add the notifier chain (enum, head, register/unregister helpers). Pure infrastructure. No subscribers yet, no observable change.
3. Fire REGISTERED and UNREGISTERED events from the controller register/unregister paths. Still no subscribers, still no observable change.
4. Subscribe from the PHY layer, take ownership ofphydev->psecvia the notifier, and removefwnode_find_pse_control()fromfwnode_mdio.Patch 1 is bundled here per stable-kernel-rules section 4 reachability guidance. On mainline today, with no notifier subscriber, no caller drives the dangling regulator-handle sequence. Patches 2 and 3 are deliberately split to separate "add infrastructure" from "wire it up". Happy to fold them if maintainers prefer the combined form.
Validated on a Cudy C200P (MT7621 + IP804AR) running an OpenWrt build of 6.18.21 with the series applied. A lockdep build (
CONFIG_PROVE_LOCKING+CONFIG_DEBUG_ATOMIC_SLEEP) shows no splats from the series' code paths during boot, PHY attach, PHY detach, or a full controller unbind/rebind cycle. ethtool --set-pse drives all four PoE-capable LAN ports, and a Ruckus H510 class-4 PD plugged into lan3 negotiates and receives 48 V.The C200P has no SFP cage, so the SFP path change in
sfp.c(phy_device_register->phy_device_register_locked) isn't exercised on the bench. Verified by call-graph audit: every path reachingsfp_sm_probe_phy()holds rtnl at entry, viasfp_timeout,sfp_check_state,sfp_probe,sfp_remove, or sfp_bus_{add,del}_upstream.Not addressed by this series: ethtool --show-pse returns "No data available" on DSA netdevs in 6.18, because
dev->phydevis NULL for DSA-frontend netdevs andethnl_req_get_phydev()therefore returns NULL. That's a DSA / ethtool integration quirk that predates this work.Sending as RFC because this is my first net-next series. I'd appreciate maintainer guidance on whether patch 1 should go to net rather than net-next, and whether the patch 2/3 split is preferred to the combined form.
Signed-off-by Corey Leavitt <[email protected]>---
Corey Leavitt (4):
net: pse-pd: scopepse_controlregulator handle to kref lifetime
net: pse-pd: add notifier chain for controller lifecycle events
net: pse-pd: fire lifecycle events on controller register/unregister
net: phy: ownphydev->psecvia PSE notifier and removefwnode_mdiohookdrivers/net/mdio/fwnode_mdio.c | 34 ---------- drivers/net/phy/phy_device.c | 144 ++++++++++++++++++++++++++++++++++++++--- drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 60 ++++++++++++++++- include/linux/phy.h | 2 + include/linux/pse-pd/pse.h | 41 ++++++++++++ 6 files changed, 236 insertions(+), 47 deletions(-)---
base-commit:1f5ffc672165ff851063a5fd044b727ab2517ae3
change-id: 20260422-pse-notifier-decouple-efa80d77f4beBest regards,
Signature · 1 line
Corey Leavitt <[email protected]>
Signature · 3 lines
Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
Hi Kory,
Thanks for the pointer -- I had not seen Carlo's thread; I should have searched lore before sending and will do so before v2. Adding Carlo on cc.
Having read it end-to-end, my read of the state as of 2026-04-13 was that the conversation had narrowed to two open directions: propagate EPROBE_DEFER further up into phylink/MAC probe (Andrew/Russell), or resolve psec at PSE controller register time (your msg on 9 Apr, "save the phandle ... then at PSE register time look for each PHY and try to resolve every unresolved phandle"). Nothing concrete had been posted for either.
This RFC implements the second direction. pse_core publishes a BLOCKING_NOTIFIER chain with REGISTERED / UNREGISTERED events, phy_device subscribes, and psec ownership moves from fwnode_mdio probe into the notifier handler. Concretely with respect to points raised in the earlier thread:
- fwnode_mdio loses PSE awareness entirely, so the MDIO bus scan no longer sees -EPROBE_DEFER from PSE lookup. Consistent with Andrew's point that bus and device lifecycles are separate.
- psec is acquired at PSE register time, before regulator_late_cleanup (30s) can run. Carlo's admin_state_synced guard (his patch 1) therefore isn't needed in this model. psec resolution happens eagerly on the REGISTERED event rather than lazily on first ethtool access, so his patch 2 is also not needed. And because fwnode_mdio no longer looks up PSE at all, the non-fatal EPROBE_DEFER handling there (patch 3) drops out. This series is a different architectural shape, not an increment on his v2.
- Oleksij's concern about lazy resolution dropping UAPI notifications is addressed: the notifier fires at register time, so boot-time observer semantics are preserved.
- One caveat I already owe a fix for in v2: the attach helper in phy_device currently treats every error from of_pse_control_get() as retry-on-notifier, including non-transient ones. Carlo's v2 patch 3 was careful to differentiate -EPROBE_DEFER from bad-DT errors at the fwnode_mdio lookup site (which matches his msg 1 concern about catching broken bindings at boot rather than silently later). I need to preserve that discrimination at the notifier-handler site -- phydev_warn() on anything other than -EPROBE_DEFER. Trivial, but worth flagging.
- The DSA genphy force-bind sequence Carlo hit (phy_attach_direct -> device_bind_driver -> deferred retry skipped) does not apply, because psec attachment is not tied to phy_probe.
- Patch 1 of this series scopes the regulator handle held by pse_control to its own kref lifetime, fixing a latent dangling-handle sequence that the notifier unregister path makes reachable. This is a separate regulator-lifetime bug from the one Carlo's patch 1 addresses.
Validated end-to-end on a Cudy C200P (MT7621 DSA + i2c IP804AR as module), with lockdep active, across the i2c driver unbind/rebind cycle that triggers UNREGISTERED -> REGISTERED on the live system. The cover letter has the full evidence.
I would welcome your view on whether this is the shape you had in mind on 9 Apr, or whether the MDI-based binding you raised with Maxime is the better endpoint and we should be reshaping around that. Happy to keep this RFC as the scaffolding either way.
Carlo -- your debugging work on the DSA phy_attach_direct interaction is what made it clear this kind of approach was needed; thanks for laying that groundwork. Would value your thoughts on the tradeoffs.
Best regards,
Corey
Kory Maincent
maintainerQuoted 53 lines · On Thu, 23 Apr 2026 09:48:53 +0000 Corey Leavitt <[email protected]> wrote:
Hi Kory,
Thanks for the pointer -- I had not seen Carlo's thread; I should have searched lore before sending and will do so before v2. Adding Carlo on cc.
Having read it end-to-end, my read of the state as of 2026-04-13 was that the conversation had narrowed to two open directions: propagate
EPROBE_DEFERfurther up into phylink/MAC probe (Andrew/Russell), or resolve psec at PSE controller register time (your msg on 9 Apr, "save the phandle ... then at PSE register time look for each PHY and try to resolve every unresolved phandle"). Nothing concrete had been posted for either.This RFC implements the second direction.
pse_corepublishes aBLOCKING_NOTIFIERchain with REGISTERED / UNREGISTERED events,phy_devicesubscribes, and psec ownership moves fromfwnode_mdioprobe into the notifier handler. Concretely with respect to points raised in the earlier thread:-
fwnode_mdioloses PSE awareness entirely, so the MDIO bus scan no longer sees -EPROBE_DEFER from PSE lookup. Consistent with Andrew's point that bus and device lifecycles are separate.- psec is acquired at PSE register time, before
regulator_late_cleanup(30s) can run. Carlo'sadmin_state_syncedguard (his patch 1) therefore isn't needed in this model. psec resolution happens eagerly on the REGISTERED event rather than lazily on first ethtool access, so his patch 2 is also not needed. And becausefwnode_mdiono longer looks up PSE at all, the non-fatalEPROBE_DEFERhandling there (patch 3) drops out. This series is a different architectural shape, not an increment on his v2.- Oleksij's concern about lazy resolution dropping UAPI notifications is addressed: the notifier fires at register time, so boot-time observer semantics are preserved.
- One caveat I already owe a fix for in v2: the attach helper in
phy_devicecurrently treats every error fromof_pse_control_get()as retry-on-notifier, including non-transient ones. Carlo's v2 patch 3 was careful to differentiate -EPROBE_DEFER from bad-DT errors at thefwnode_mdiolookup site (which matches his msg 1 concern about catching broken bindings at boot rather than silently later). I need to preserve that discrimination at the notifier-handler site --phydev_warn()on anything other than -EPROBE_DEFER. Trivial, but worth flagging.- The DSA genphy force-bind sequence Carlo hit (
phy_attach_direct->device_bind_driver-> deferred retry skipped) does not apply, because psec attachment is not tied tophy_probe.
Quoted 5 lines
- Patch 1 of this series scopes the regulator handle held by
pse_controlto its own kref lifetime, fixing a latent dangling-handle sequence that the notifier unregister path makes reachable. This is a separate regulator-lifetime bug from the one Carlo's patch 1 addresses.
This seems to provide a solution to all our problems, and it is well designed. Carlo, it would be nice if you could test it on your side.
Quoted 9 lines
Validated end-to-end on a Cudy C200P (MT7621 DSA + i2c IP804AR as module), with lockdep active, across the i2c driver unbind/rebind cycle that triggers UNREGISTERED -> REGISTERED on the live system. The cover letter has the full evidence.
I would welcome your view on whether this is the shape you had in mind on 9 Apr, or whether the MDI-based binding you raised with Maxime is the better endpoint and we should be reshaping around that. Happy to keep this RFC as the scaffolding either way.
After a second thought, MDI-based binding is currently not in the pipe and we should solve this without it for now.
Regards,
Signature · 3 lines
Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
Hey,
Thanks for all the help from multiple guys :-) I am currently busy with work, but I will take some time soon! cheers Carlo
Carlo Szelinsky
Tested-byHi,
I tested the series on a Hasivo S1100WP-8GT-SE with hs104 PSE chips. Boot is clean, no more probe loop. ethtool --set-pse on/off works, and a PD plugged into a port gets power. So the fix works for me.
One thing I noticed: rmmod alone does not work while ports are attached. Each PHY that holds a psec keeps a ref on the PSE driver, so the module count stays above 0 and rmmod fails.
Unbinding the i2c devices first works:
echo 0-000d > /sys/bus/i2c/drivers/hasivo-hs104/unbind echo 0-0015 > /sys/bus/i2c/drivers/hasivo-hs104/unbind rmmod hasivo-hs104
After unbind your PSE_UNREGISTERED notifier fires, the PHYs drop the psec, and the module count goes to 0. Then rmmod works.
Not a big deal, but maybe worth a short note in the cover letter so people know they need to unbind first. Or if there is a clean way to skip the per-consumer module_get and just rely on the unbind path, that would be nice too. What do other folks think?
One thing my test does not cover yet is the SFP path change in sfp.c, since the S1100WP-8GT-SE is copper only. I do have an S600WP-5GT-2SX-SE that uses the same hs104 chips plus SFP+ cages, but it is not on my desk right now. I can run that part on it later.
Anyway, thanks for the work on this. Happy to test a v2.
Carlo
Hi Carlo,
Thanks for testing, and for the clear writeup of the rmmod path. Will add your Tested-by to v2.
On the rmmod / module_get question: I think you are right that the per-consumer try_module_get is structurally redundant once the notifier exists, since PSE_UNREGISTERED already gives us a synchronous invalidation point at unregister time. The reason I did not strip it in v1 was caution: I want to walk every pse_control_get / pse_control_put consumer and confirm no path holds a transient reference outside of rtnl-lock (or some equivalent serialization) such that an in-flight use could race the unregister notifier.
That audit is on my plate now. I will share findings on this thread before v2, and let the result drive whether the removal lands in v2 or as a separate discussion.
Glad to hear the probe loop is gone on your bench and that ethtool plus PD power are behaving. I expect to have an SFP-capable board on my own bench later this week, so I will cover the sfp.c path change before sending v2; a retest from you on the SFP chassis whenever it is convenient is still very welcome as independent confirmation.
Kory, Jakub, Russell, others: input welcome on whether dropping try_module_get in favor of pure notifier-based invalidation is the direction you would prefer here, or whether the belt-and-suspenders refcount is worth keeping.
Thanks,
Corey
Hi Corey,
just checking in on this one. Did you get a chance to continue with the series, or is there anything I can help with to move it forward? I'm happy to test a v2, and I can still run the SFP path on the S600WP-5GT-2SX-SE once it's back on my desk.
Kory, Jakub, Russell :-) it would be great to hear your view on the approach so Corey can plan the next version. The series fixed the probe loop in my testing and I'd really like to see it land.
Thanks,
Carlo
Kory Maincent
maintainerHello Carlo,
Quoted 10 lines · On Mon, 15 Jun 2026 20:08:12 +0200 Carlo Szelinsky <[email protected]> wrote:
Hi Corey,
just checking in on this one. Did you get a chance to continue with the series, or is there anything I can help with to move it forward? I'm happy to test a v2, and I can still run the SFP path on the S600WP-5GT-2SX-SE once it's back on my desk.
Kory, Jakub, Russell :-) it would be great to hear your view on the approach so Corey can plan the next version. The series fixed the probe loop in my testing and I'd really like to see it land.
I haven't heard from Corey since this patch series, but I am in favor of this notifier design.
Corey, do you have time to continue this work? If not, would it be okay for Carlo to continue it for you?
Regards,
Signature · 3 lines
Köry Maincent, Bootlin Embedded Linux and kernel engineering https://bootlin.com
Hi,
just wanted to point out, whoever continues with this series, to take care of an issue I noticed with one of the patches here, see [1].
Apart from that, I'd really like to see this move forward since the PSE driver I'm currently working on would benefit from it.
Best regards,
Jonas
v2 cover net: pse-pd: decouple controller lookup from MDIO probe
In reply to Corey LeavittThis is v2 of Corey's RFC [1]. Corey is busy at the moment, so I'm picking it up to unblock everyone. The design is unchanged. The main thing v2 fixes is the SFP deadlock Jonas reported, plus a couple of smaller points from the review.
The problem:
When a PSE controller driver is built as a module and a DT PHY node has a "pses = <&...>" phandle, fwnode_mdiobus_register_phy() tries to resolve the PSE handle before the controller has probed. It gets -EPROBE_DEFER, the MDIO/DSA probe fails, and driver-core keeps retrying until the PSE module loads. Since fa2f0454174c each retry does a full phy_device_register() / phy_device_remove() cycle, so on a board with a tight watchdog the retry loop can reset the box before userspace is up.
Rather than make the retry cheaper, this takes the PSE lookup out of the MDIO probe path completely. pse_core gets a notifier chain (REGISTERED / UNREGISTERED), the phy layer subscribes, owns phydev->psec, and attaches the PSE handle when the controller actually shows up instead of during probe. fwnode_mdio no longer knows about PSE, so no -EPROBE_DEFER crosses that boundary and the retry loop is gone.
What changed since v1:
- v1 made phy_device_register() hold rtnl across the whole registration, including device_add(). That deadlocks a PHY that drives its own SFP cage:
device_add() -> phy_probe() -> phy_sfp_probe() -> sfp_bus_add_upstream(), and sfp_bus_add_upstream() takes rtnl again. Jonas hit this with RTL8214FC. v2 keeps device_add() out of rtnl and only takes rtnl around the psec attach, which now runs after device_add(). Doing the attach after the phy is on the bus keeps the PSE_REGISTERED race closed: either the notifier walk finds the phy and attaches it, or our own attach does, and the phydev->psec check makes that idempotent.
- A broken "pses" binding now gets a phydev_warn() instead of being swallowed. -ENOENT (no phandle) and -EPROBE_DEFER stay quiet.
Tested on a Realtek rtl93xx PoE switch with two HS104 PSE controllers on i2c:
- clean boot, no probe-retry loop, no watchdog reset
- 10G SFP+ port: module hotplug works, no deadlock (this is the path that hung with v1)
- ethtool --set-pse enable/disable cuts and restores power to a connected PD
- full i2c unbind -> rmmod -> modprobe cycle: PSE detaches on unbind (module refcount drops to 0 so rmmod works), and re-attaches on reload with power restored, no reboot. No lockdep splats.
One thing I'd like input on: the Fixes: tags. Patch 1 is a standalone regulator lifetime fix and carries its own Fixes:. The boot-hang itself is fixed by patches 2-4 together. Should those three carry
net-next only? I'm fine either way.
[1] https://lore.kernel.org/netdev/[email protected]/
Corey Leavitt (4):
net: pse-pd: scope pse_control regulator handle to kref lifetime
net: pse-pd: add notifier chain for controller lifecycle events
net: pse-pd: fire lifecycle events on controller register/unregister
net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio
hook
drivers/net/mdio/fwnode_mdio.c | 34 ------- drivers/net/phy/phy_device.c | 168 +++++++++++++++++++++++++++++++-- drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 60 +++++++++++- include/linux/phy.h | 2 + include/linux/pse-pd/pse.h | 41 ++++++++ 6 files changed, 261 insertions(+), 46 deletions(-)
base-commit: b85966adbf5de0668a815c6e3527f87e0c387fb4
Signature · 1 line
2.43.0
PATCH v2 1/4 net: pse-pd: scope pse_control regulator handle to kref lifetime
From: Corey Leavitt <[email protected]>
__pse_control_release() drops psec->ps via devm_regulator_put(), which only succeeds if the devres entry added by the matching devm_regulator_get_exclusive() is still present on pcdev->dev at the time the pse_control's kref hits zero.
In practice that assumption does not hold when the controller is unbound while any pse_control still has consumers: pcdev->dev's devres list is released LIFO, so every per-attach regulator-GET devres runs (and regulator_put()s the underlying regulator) before pse_controller_unregister() itself is invoked. Any later pse_control_put() from that unbind path then reads psec->ps as a dangling pointer inside devm_regulator_put() and WARNs at drivers/regulator/devres.c:232 (devres_release() fails to find the already-released match).
The pse_control's consumer handle is logically scoped to the pse_control's refcount, not to pcdev->dev's devres lifetime. Switch to the plain regulator_get_exclusive() / regulator_put() pair so __pse_control_release() does the right put regardless of whether the controller's devres has already been unwound.
No change to the regulator-framework-visible refcount or lifetime of the underlying regulator: a single get paired with a single put. The existing devm_regulator_register() for the per-PI rails is unchanged (those ARE correctly scoped to the controller's lifetime).
--- drivers/net/pse-pd/pse_core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/net/pse-pd/pse_core.c +3 -3
@@ -1367,7 +1367,7 @@ static void __pse_control_release(struct kref *kref)if (psec->pcdev->pi[psec->id].admin_state_enabled)regulator_disable (psec->ps); -devm_regulator_put (psec->ps); +regulator_put (psec->ps);module_put (psec->pcdev->owner);
@@ -1436,8 +1436,8 @@ pse_control_get_internal(struct pse_controller_dev *pcdev, unsigned int index, goto free_psec; pcdev->pi[index].admin_state_enabled = ret; - psec->ps =devm_regulator_get_exclusive (pcdev->dev, -rdev_get_name (pcdev->pi[index].rdev)); + psec->ps =regulator_get_exclusive (pcdev->dev, +rdev_get_name (pcdev->pi[index].rdev));if (IS_ERR (psec->ps)) { ret =PTR_ERR (psec->ps); goto put_module;
Signature · 1 line
2.43.0
PATCH v2 2/4 net: pse-pd: add notifier chain for controller lifecycle events
In reply to Carlo SzelinskyFrom: Corey Leavitt <[email protected]>
Introduce a blocking notifier chain that allows other subsystems to be informed when a PSE controller is registered or unregistered, and provide pse_register_notifier() / pse_unregister_notifier() as the subscriber interface.
Subsequent patches will use this to let the phy subsystem own the phydev->psec lifecycle directly, decoupling PSE lookup from fwnode_mdiobus_register_phy() and removing the probe-time -EPROBE_DEFER coupling that currently exists between mdio, phy and pse-pd when the PSE controller driver is modular.
A blocking chain (rather than atomic) is used because callbacks will take rtnl_lock and call back into pse_core via of_pse_control_get().
The enum pse_controller_event is placed outside the IS_ENABLED(CONFIG_PSE_CONTROLLER) guard so that subscribers compiled into a kernel without PSE support can still reference the event values in dead-code paths without breaking the build.
This patch is pure infrastructure: nothing fires events yet, and nothing subscribes. No observable behavior change.
--- drivers/net/pse-pd/pse_core.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pse-pd/pse.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+)
drivers/net/pse-pd/pse_core.c +34 -0
@@ -8,6 +8,7 @@#include <linux/device.h> #include <linux/ethtool.h> #include <linux/ethtool_netlink.h> +#include <linux/notifier.h> #include <linux/of.h> #include <linux/phy.h> #include <linux/pse-pd/pse.h>
@@ -23,6 +24,39 @@ static LIST_HEAD(pse_controller_list);static DEFINE_XARRAY_ALLOC (pse_pw_d_map);static DEFINE_MUTEX (pse_pw_d_mutex); +static BLOCKING_NOTIFIER_HEAD (pse_controller_notifier); + +/** +* pse_register_notifier - register a callback for PSE controller events +* @nb: notifier block to register +* +* See enum pse_controller_event for events fired and their subscriber +* contract. Callbacks run in process context; they may sleep, take +* rtnl, and call of_pse_control_get(). The chain fires synchronously, +* so a PSE controller driver's probe/unbind path must not hold any +* such lock when calling pse_controller_register() or +* pse_controller_unregister(). +* +* Return: 0 on success, negative error code otherwise. +*/ +int pse_register_notifier (struct notifier_block *nb) +{ +return blocking_notifier_chain_register (&pse_controller_notifier, nb); +} +EXPORT_SYMBOL_GPL (pse_register_notifier); + +/** +* pse_unregister_notifier - unregister a previously registered callback +* @nb: notifier block previously passed to pse_register_notifier() +* +* Return: 0 on success, negative error code otherwise. +*/ +int pse_unregister_notifier (struct notifier_block *nb) +{ +return blocking_notifier_chain_unregister (&pse_controller_notifier, nb); +} +EXPORT_SYMBOL_GPL (pse_unregister_notifier); +/** * struct pse_control - a PSE control * @pcdev: a pointer to the PSE controller device
include/linux/pse-pd/pse.h +32 -0
@@ -21,6 +21,7 @@ struct net_device;struct phy_device ;struct pse_controller_dev ;struct netlink_ext_ack ; +struct notifier_block ;/* C33 PSE extended state and substate. */ struct ethtool_c33_pse_ext_state_info {
@@ -337,6 +338,24 @@ enum pse_budget_eval_strategies { PSE_BUDGET_EVAL_STRAT_DYNAMIC = 1 << 2, }; +/** +* enum pse_controller_event - PSE controller lifecycle events +* +* Event data in callbacks is always a pointer to the struct +* pse_controller_dev firing the event. +* +* @PSE_REGISTERED: controller added to pse_controller_list and +* resolvable by of_pse_control_get(). +* @PSE_UNREGISTERED: controller about to be removed from +* pse_controller_list. Subscribers holding pse_control references +* targeting it must drop them before returning and must not +* acquire new references for it. +*/ +enum pse_controller_event { +PSE_REGISTERED , +PSE_UNREGISTERED , +}; +#if IS_ENABLED (CONFIG_PSE_CONTROLLER )int pse_controller_register (struct pse_controller_dev *pcdev);void pse_controller_unregister (struct pse_controller_dev *pcdev);
@@ -366,6 +385,9 @@ int pse_ethtool_set_prio(struct pse_control *psec,bool pse_has_podl (struct pse_control *psec);bool pse_has_c33 (struct pse_control *psec); +int pse_register_notifier (struct notifier_block *nb); +int pse_unregister_notifier (struct notifier_block *nb); +#else static inline struct pse_control *of_pse_control_get(struct device_node *node,
@@ -416,6 +438,16 @@ static inline bool pse_has_c33(struct pse_control *psec)return false; } +static inline int pse_register_notifier (struct notifier_block *nb) +{ +return 0 ; +} + +static inline int pse_unregister_notifier (struct notifier_block *nb) +{ +return 0 ; +} +#endif #endif
Signature · 1 line
2.43.0
PATCH v2 3/4 net: pse-pd: fire lifecycle events on controller register/unregister
In reply to Carlo SzelinskyFrom: Corey Leavitt <[email protected]>
Hook the newly-introduced pse_controller_notifier chain so that pse_controller_register() fires PSE_REGISTERED after the controller has been added to pse_controller_list (i.e. is now resolvable by of_pse_control_get()), and pse_controller_unregister() fires PSE_UNREGISTERED before the controller is removed from the list (while it is still valid to dereference from a subscriber's pse_control pointer targeting it).
With no subscribers yet, this is observably a no-op. A later change wires the phy subsystem in as the first subscriber.
--- drivers/net/pse-pd/pse_core.c | 6 ++++++ 1 file changed, 6 insertions(+)
drivers/net/pse-pd/pse_core.c +6 -0
@@ -1138,6 +1138,9 @@ int pse_controller_register(struct pse_controller_dev *pcdev)list_add (&pcdev->list, &pse_controller_list);mutex_unlock (&pse_list_mutex); +blocking_notifier_call_chain (&pse_controller_notifier, +PSE_REGISTERED , pcdev); +return 0 ; }EXPORT_SYMBOL_GPL (pse_controller_register);
@@ -1148,6 +1151,9 @@ EXPORT_SYMBOL_GPL(pse_controller_register);*/ void pse_controller_unregister (struct pse_controller_dev *pcdev) { +blocking_notifier_call_chain (&pse_controller_notifier, +PSE_UNREGISTERED , pcdev); +pse_flush_pw_ds (pcdev);pse_release_pis (pcdev);if (pcdev->irq)
Signature · 1 line
2.43.0
PATCH v2 4/4 net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
In reply to Carlo SzelinskyFrom: Corey Leavitt <[email protected]>
Transfer ownership of phydev->psec from fwnode_mdio to the phy subsystem itself. The phy subsystem now subscribes to the pse-pd notifier chain and manages psec attach/detach in response to PSE controller lifecycle events, while fwnode_mdio loses its PSE awareness entirely.
phydev->psec is attached after device_add() has made the phy visible on mdio_bus_type, under a narrow rtnl_lock() that covers only phy_try_attach_pse(). Ordering the attach after registration closes the race that would otherwise leave a phy unattached: a PSE_REGISTERED event firing during registration walks mdio_bus_type and either finds the phy already added (and attaches it) or runs before device_add(), in which case the post-add attach resolves it. The phydev->psec check in phy_try_attach_pse() makes the two paths idempotent. Holding rtnl across of_pse_control_get() is safe because pse_list_mutex is never taken in the opposite order.
device_add() is deliberately left outside rtnl. Binding a phy that itself provides an SFP cage reaches sfp_bus_add_upstream() through phy_probe() -> phy_setup_ports() -> phy_sfp_probe(), and sfp_bus_add_upstream() takes rtnl_lock(); holding rtnl across device_add() would deadlock such phys (reported on RTL8214FC).
phy_device_register() is split into the public form, which takes the narrow rtnl_lock() around the attach, and a phy_device_register_locked() form for callers that already hold rtnl (the SFP module state machine via __sfp_sm_event). This pair mirrors the register_netdevice() / register_netdev() split convention already established in the core networking stack. The _locked form runs device_add() under the caller's rtnl, which is safe because a phy resident on an SFP module does not itself provide a downstream cage, so phy_sfp_probe() is a no-op there.
- On PSE_REGISTERED: an rtnl-guarded bus walk retries the attach for every registered phy whose psec is still NULL. This is the "phy was enumerated before the PSE controller loaded" case, the root cause of the boot-time probe-retry storm on systems with a modular PSE controller driver.
- On PSE_UNREGISTERED: an rtnl-guarded bus walk releases every
phydev->psec that targets the departing controller before
pse_release_pis() frees pcdev->pi. Without this, a phy still
holding a pse_control reference would cause a use-after-free in
__pse_control_release()'s pcdev->pi[psec->id] access, and the PSE
driver module could not finish unloading while any phy still held a
reference.A bad pses binding -- an error from of_pse_control_get() other than -ENOENT (no phandle) or -EPROBE_DEFER (controller not yet registered) -- is reported with phydev_warn() rather than silently dropped, preserving the diagnostic that the removed fwnode_mdio lookup used to provide.
The final pse_control_put() of phydev->psec moves from phy_device_remove() to phy_device_release(), so it runs only after every reference on the device -- including the bus-iterator references taken by bus_for_each_dev() in the notifier walk -- has been dropped.
Finally, delete fwnode_find_pse_control() and its call site in fwnode_mdiobus_register_phy(), and drop the PSE header from fwnode_mdio.c. The MDIO/DSA probe no longer sees any PSE-originated -EPROBE_DEFER, so the probe-retry storm is gone and fwnode_mdio is now PSE-agnostic.
--- drivers/net/mdio/fwnode_mdio.c | 34 ------- drivers/net/phy/phy_device.c | 168 +++++++++++++++++++++++++++++++-- drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 14 +++ include/linux/phy.h | 2 + include/linux/pse-pd/pse.h | 9 ++ 6 files changed, 186 insertions(+), 43 deletions(-)
drivers/net/mdio/fwnode_mdio.c +0 -34
@@ -11,33 +11,11 @@#include <linux/fwnode_mdio.h> #include <linux/of.h> #include <linux/phy.h> -#include <linux/pse-pd/pse.h> MODULE_AUTHOR ("Calvin Johnson <[email protected]>" );MODULE_LICENSE ("GPL" );MODULE_DESCRIPTION ("FWNODE MDIO bus (Ethernet PHY) accessors" ); -static struct pse_control * -fwnode_find_pse_control (struct fwnode_handle *fwnode, -struct phy_device *phydev) -{ -struct pse_control *psec; -struct device_node *np; - -if (!IS_ENABLED (CONFIG_PSE_CONTROLLER )) -return NULL ; - - np =to_of_node (fwnode); -if (!np) -return NULL ; - - psec =of_pse_control_get (np, phydev); -if (PTR_ERR (psec) == -ENOENT ) -return NULL ; - -return psec; -} -static struct mii_timestamper *fwnode_find_mii_timestamper (struct fwnode_handle *fwnode) {
@@ -118,7 +96,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, struct fwnode_handle *child, u32 addr) {struct mii_timestamper *mii_ts =NULL ; -struct pse_control *psec =NULL ;struct phy_device *phy;bool is_c45;u32 phy_id;
@@ -159,14 +136,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus, goto clean_phy; } - psec =fwnode_find_pse_control (child, phy); -if (IS_ERR (psec)) { - rc =PTR_ERR (psec); - goto unregister_phy; - } - - phy->psec = psec; -/* phy->mii_ts may already be defined by the PHY driver. A * mii_timestamper probed via the device tree will still have * precedence.
@@ -176,9 +145,6 @@ int fwnode_mdiobus_register_phy(struct mii_bus *bus,return 0 ; -unregister_phy: -if (is_acpi_node (child) ||is_of_node (child)) -phy_device_remove (phy); clean_phy:phy_device_free (phy); clean_mii_ts:
drivers/net/phy/phy_device.c +160 -8
211 lines not on this page · raw message
drivers/net/phy/sfp.c +1 -1
@@ -2083,7 +2083,7 @@ static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45)/* Mark this PHY as being on a SFP module */ phy->is_on_sfp_module = true; - err =phy_device_register (phy); + err =phy_device_register_locked (phy);if (err) {phy_device_free (phy); dev_err(sfp->dev, "phy_device_register failed: %pe\n",
drivers/net/pse-pd/pse_core.c +14 -0
@@ -2021,3 +2021,17 @@ bool pse_has_c33(struct pse_control *psec)return psec->pcdev->types ÐTOOL_PSE_C33 ; }EXPORT_SYMBOL_GPL (pse_has_c33); + +/** +* pse_control_matches_pcdev - Test whether a pse_control targets a controller +* @psec: pse_control obtained from of_pse_control_get() +* @pcdev: PSE controller to compare against +* +* Return: %true if @psec was obtained from @pcdev, %false otherwise. +*/ +bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev) +{ +return psec->pcdev == pcdev; +} +EXPORT_SYMBOL_GPL (pse_control_matches_pcdev);
include/linux/phy.h +2 -0
@@ -2158,6 +2158,8 @@ struct phy_device *fwnode_phy_find_device(struct fwnode_handle *phy_fwnode);struct fwnode_handle *fwnode_get_phy_node (const struct fwnode_handle *fwnode);struct phy_device *get_phy_device (struct mii_bus *bus,int addr,bool is_c45);int phy_device_register (struct phy_device *phy); +/* Caller must hold rtnl_lock(); see phy_device_register() for the public form. */ +int phy_device_register_locked (struct phy_device *phy);void phy_device_free (struct phy_device *phydev);void phy_device_remove (struct phy_device *phydev);int phy_get_c45_ids (struct phy_device *phydev);
include/linux/pse-pd/pse.h +9 -0
@@ -385,6 +385,9 @@ int pse_ethtool_set_prio(struct pse_control *psec,bool pse_has_podl (struct pse_control *psec);bool pse_has_c33 (struct pse_control *psec); +bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev); +int pse_register_notifier (struct notifier_block *nb);int pse_unregister_notifier (struct notifier_block *nb);
@@ -438,6 +441,12 @@ static inline bool pse_has_c33(struct pse_control *psec)return false; } +static inline bool pse_control_matches_pcdev (struct pse_control *psec, +struct pse_controller_dev *pcdev) +{ +return false; +} +static inline int pse_register_notifier (struct notifier_block *nb) {return 0 ;
Signature · 1 line
2.43.0
Jakub Kicinski
maintainerQuoted 4 lines · On Sat, 20 Jun 2026 13:24:36 +0200 Carlo Szelinsky wrote:
This is v2 of Corey's RFC [1]. Corey is busy at the moment, so I'm picking it up to unblock everyone. The design is unchanged. The main thing v2 fixes is the SFP deadlock Jonas reported, plus a couple of smaller points from the review.
net-next is closed during the merge window. We can merge the first patch, tho, if you repost is separately for net, since it's a fix.
Signature · 1 line
pw-bot: defer