From: netdev-bot+sashiko@kernel.org
To: github@szelinsky.de
Cc: o.rempel@pengutronix.de, kory.maincent@bootlin.com,
andrew+netdev@lunn.ch, hkallweit1@gmail.com,
linux@armlinux.org.uk, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, corey@leavitt.info,
jelonek.jonas@gmail.com, horms@kernel.org, olek2@wp.pl,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next v6 3/5] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
Date: Wed, 09 Sep 2026 06:33:18 +0000 [thread overview]
Message-ID: <178893559852.219967.17408171091451392990@kernel.org> (raw)
In-Reply-To: <20260906153102.959217-4-github@szelinsky.de>
Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider:
Critical: 0 · High: 2 · Medium: 2 · Low: 2
- [High] Deferred psec put vs. bus-walk-based detach: moving the final
pse_control_put(phydev->psec) from phy_device_remove() to…
- [High] The PSE_UNREGISTERED detach walk does not actually close the
use-after-free class the commit claims to fix: the notifier callback…
- [Medium] pse_controller_unregister() frees the PI storage before
quiescing the notification worker, and the new PSE_UNREGISTERED detach…
- [Medium] phy_try_attach_pse() treats every of_pse_control_get() error
other than -ENOENT/-EPROBE_DEFER as permanent and warn-only, but that…
- [Low] The commit message claims that without the new PSE_UNREGISTERED
detach walk "the PSE driver module could not finish unloading while…
- [Low] The patch fixes a reported, user-visible regression (boot-time
probe-retry storm / PHYs failing to register when the PSE controller…
--
net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook
This moves ownership of phydev->psec from fwnode_mdio into phylib, which
now subscribes to the pse-pd notifier chain and attaches/detaches psec on
PSE controller register/unregister events. phy_device_register() is split
into a public form that takes rtnl around the attach and a
phy_device_register_locked() form for callers already holding rtnl, and
the final pse_control_put() moves from phy_device_remove() to
phy_device_release().
Closes: https://lore.kernel.org/netdev/e00048dd-1ed3-40c3-9912-59bccf015ad5@gmail.com/
A couple of questions on the changelog first.
[Severity: Low]
> - 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.
Is the module unload part of this rationale the right way round?
pse_control_get_internal() pins the controller driver for every psec:
if (!try_module_get(pcdev->owner)) {
ret = -ENODEV;
goto free_psec;
}
and the matching module_put() only happens in __pse_control_release().
While a phy holds a psec the module refcount is non-zero, so
delete_module() fails with -EWOULDBLOCK before the module exit path (and
therefore pse_controller_unregister(), the only emitter of
PSE_UNREGISTERED) is ever entered.
The detach walk does help for driver unbind and device removal, where
pse_controller_unregister() is reached with the module still loaded, but
it cannot make an rmmod that was already refused succeed. Could that
sentence be reworded to talk about unbind/removal instead?
[Severity: Low]
> Reported-by: Jonas Jelonek <jelonek.jonas@gmail.com>
> Closes: https://lore.kernel.org/netdev/e00048dd-1ed3-40c3-9912-59bccf015ad5@gmail.com/
This carries Reported-by:, Closes: and Tested-by: for a user-visible
regression (PHYs failing to register / probe-retry storm with a modular
PSE controller driver), but there is no Fixes: tag and no indication of
the target tree.
git blame on the removed lookup in fwnode_mdiobus_register_phy() points at
5e82147de1cbd7 for the original PSE lookup, later touched by
fa2f0454174c2f, so the affected releases can be named. Could a Fixes: tag
be added, and could the intended tree (net vs net-next) be stated given
the size of the refactor and the new exported symbol?
> diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
> index 0615228459ef4..f5febff4b00b8 100644
> --- a/drivers/net/phy/phy_device.c
> +++ b/drivers/net/phy/phy_device.c
> @@ -223,8 +223,19 @@ static void phy_mdio_device_free(struct mdio_device *mdiodev)
>
> static void phy_device_release(struct device *dev)
> {
> + struct phy_device *phydev = to_phy_device(dev);
> +
> + /* bus_for_each_dev() holds get_device() across each iteration
> + * step, deferring this release callback until any in-flight PSE
> + * notifier walk has advanced past this phy. pse_control_put()
> + * takes pse_list_mutex, so this path must run in sleepable
> + * context.
> + */
> + might_sleep();
> + pse_control_put(phydev->psec);
> +
> fwnode_handle_put(dev->fwnode);
> - kfree(to_phy_device(dev));
> + kfree(phydev);
> }
[Severity: High]
Can a psec survive here past the point where the detach walk can find it?
Once phy_device_remove() has run, device_del() -> bus_remove_device() ->
klist_del() takes the phy off the mdio_bus_type klist, but the phy itself
can still be pinned by a get_device() from phy_attach_direct() or
of_phy_find_device(). The detach is driven only by the klist walk:
case PSE_UNREGISTERED:
rtnl_lock();
bus_for_each_dev(&mdio_bus_type, NULL, data,
phy_pse_detach_one);
so an off-bus phy keeps its psec, while pse_controller_unregister()
continues straight on:
blocking_notifier_call_chain(&pse_controller_notifier,
PSE_UNREGISTERED, pcdev);
pse_flush_pw_ds(pcdev);
pse_release_pis(pcdev); /* kfree(pcdev->pi) */
When the last device reference finally drops and this release callback
runs, __pse_control_release() does:
if (psec->pcdev->pi[psec->id].admin_state_enabled)
regulator_disable(psec->ps);
which reads the freed pi array and may act on it.
The last patch of this series ("net: phy: release phydev->psec from
phy_device_remove() again") restores the put plus phydev->psec = NULL
under pse_phy_lock() in phy_device_remove() before device_del(), which is
the ordering that avoids this. Would it be better to keep the put in
phy_device_remove() from this patch onwards so the intermediate tree is
not left with the window open?
> @@ -1102,11 +1113,103 @@ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
> }
> EXPORT_SYMBOL(get_phy_device);
>
> -/**
> - * phy_device_register - Register the phy device on the MDIO bus
> - * @phydev: phy_device structure to be added to the MDIO bus
> +/* Best-effort attach of phydev->psec from a DT `pses = <&...>` phandle.
> + * Caller must hold rtnl. A missing phandle (-ENOENT) or a not-yet-registered
> + * controller (-EPROBE_DEFER) is silent; the notifier retries the latter at
> + * PSE_REGISTERED time. Any other error means a broken binding and is warned
> + * about, but left non-fatal so the phy still registers.
> */
> -int phy_device_register(struct phy_device *phydev)
> +static void phy_try_attach_pse(struct phy_device *phydev)
> +{
> + struct pse_control *psec;
> + struct device_node *np;
> +
> + ASSERT_RTNL();
> +
> + np = phydev->mdio.dev.of_node;
> + if (!np)
> + return;
> +
> + if (phydev->psec)
> + return;
> +
> + psec = of_pse_control_get(np, phydev);
> + if (IS_ERR(psec)) {
> + if (PTR_ERR(psec) != -EPROBE_DEFER && PTR_ERR(psec) != -ENOENT)
> + phydev_warn(phydev, "failed to get PSE control: %pe\n",
> + psec);
> + return;
> + }
> +
> + phydev->psec = psec;
> +}
[Severity: Medium]
Is every error other than -ENOENT and -EPROBE_DEFER really a broken
binding? of_pse_control_get() does hardware traffic on this path:
drivers/net/pse-pd/pse_core.c:pse_control_get_internal() {
...
ret = pse_pi_is_hw_enabled(pcdev, index);
if (ret < 0)
goto free_psec;
pcdev->pi[index].admin_state_enabled = ret;
...
psec->ps = devm_regulator_get_exclusive(...);
}
pse_pi_is_hw_enabled() calls pcdev->ops->pi_get_admin_state(), which is an
I2C/SMBus read on tps23881, si3474 and pd692x0, so a transient -EIO,
-EBUSY or -ENOMEM is possible. Here that is only warned about, phydev->psec
stays NULL, and the only retry left is a fresh PSE_REGISTERED event.
That is aggravated by PSE_REGISTERED firing synchronously from the tail of
pse_controller_register(), i.e. mid-probe of the controller driver:
drivers/net/pse-pd/tps23881.c:tps23881_probe() {
...
ret = devm_pse_controller_register(dev, &priv->pcdev);
...
ret = tps23881_setup_irq(priv, client->irq);
}
Previously the same failure propagated out of fwnode_mdiobus_register_phy()
and deferred probe retried it. With this change, does a single transient
bus error permanently lose the PSE binding for that port, leaving the PI
without a consumer and unreachable from ethtool even though
admin_state_enabled may have been seeded from the chip?
> +
> +static int phy_pse_attach_one(struct device *dev, void *data __maybe_unused)
> +{
> + ASSERT_RTNL();
> +
> + if (dev->type != &mdio_bus_phy_type)
> + return 0;
> +
> + phy_try_attach_pse(to_phy_device(dev));
> + return 0;
> +}
> +
> +static int phy_pse_detach_one(struct device *dev, void *data)
> +{
> + struct pse_controller_dev *pcdev = data;
> + struct phy_device *phydev;
> + struct pse_control *psec;
> +
> + ASSERT_RTNL();
> +
> + if (dev->type != &mdio_bus_phy_type)
> + return 0;
> +
> + phydev = to_phy_device(dev);
> + psec = phydev->psec;
> + if (!psec || !pse_control_matches_pcdev(psec, pcdev))
> + return 0;
> +
> + phydev->psec = NULL;
> + pse_control_put(psec);
> + return 0;
> +}
[Severity: Medium]
Once this walk drops the phy's reference, can the notification worker end
up being the last holder of the psec?
pse_send_ntf_worker() takes its own reference and then blocks on rtnl:
psec = pse_control_find_by_id(pcdev, ntf.id);
rtnl_lock();
netdev = pse_control_get_netdev(psec);
...
rtnl_unlock();
pse_control_put(psec);
The detach walk runs under rtnl, so it can complete in that window and the
psec then survives only on the worker's reference. Meanwhile
pse_controller_unregister() frees the PI storage before quiescing the
worker:
pse_release_pis(pcdev); /* kfree(pcdev->pi) */
if (pcdev->irq)
disable_irq(pcdev->irq);
cancel_work_sync(&pcdev->ntf_work);
so the worker's pse_control_put() reaches __pse_control_release() and reads
psec->pcdev->pi[psec->id].admin_state_enabled after the array is gone. The
still-enabled IRQ can also queue more notification work before
cancel_work_sync(). Should disable_irq() and cancel_work_sync() move ahead
of pse_flush_pw_ds()/pse_release_pis()?
> +
> +static int phy_pse_notifier_event(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + switch (event) {
> + case PSE_REGISTERED:
> + rtnl_lock();
> + bus_for_each_dev(&mdio_bus_type, NULL, NULL,
> + phy_pse_attach_one);
> + rtnl_unlock();
> + return NOTIFY_OK;
> + case PSE_UNREGISTERED:
> + rtnl_lock();
> + bus_for_each_dev(&mdio_bus_type, NULL, data,
> + phy_pse_detach_one);
> + rtnl_unlock();
> + return NOTIFY_OK;
> + default:
> + return NOTIFY_DONE;
> + }
> +}
[Severity: High]
Does the detach walk actually close the use-after-free the changelog
describes, given that rtnl is dropped before the notifier returns?
pse_controller_unregister() unlinks the controller last:
blocking_notifier_call_chain(&pse_controller_notifier,
PSE_UNREGISTERED, pcdev);
pse_flush_pw_ds(pcdev);
pse_release_pis(pcdev); /* kfree(pcdev->pi) */
...
mutex_lock(&pse_list_mutex);
list_del(&pcdev->list);
In the window after the notifier returns and before that list_del(), a phy
registering on another CPU runs phy_device_register() ->
phy_try_attach_pse() -> of_pse_control_get(), still finds the departing
pcdev on pse_controller_list, and creates a fresh psec on it. That path
touches the already freed pi array: of_pse_match_pi() reads
pcdev->pi[i].np, and pse_control_get_internal() reads pcdev->pi[index].rdev
and writes pcdev->pi[index].admin_state_enabled = ret.
Since the attach decision is taken under rtnl but the rest of the teardown
is not, this looks like a check-then-attach race against controller
teardown. Would unlinking pcdev from pse_controller_list before the
notifier fires, or publishing a "dying" state under the same lock the
attach uses, be needed here?
> @@ -1152,8 +1299,6 @@ EXPORT_SYMBOL(phy_device_register);
> void phy_device_remove(struct phy_device *phydev)
> {
> unregister_mii_timestamper(phydev->mii_ts);
> - pse_control_put(phydev->psec);
> -
> device_del(&phydev->mdio.dev);
>
> /* Assert the reset signal */
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260906153102.959217-1-github%40szelinsky.de
next prev parent reply other threads:[~2026-09-09 6:33 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-06 15:30 [PATCH net-next v6 0/5] net: pse-pd: decouple controller lookup from MDIO probe Carlo Szelinsky
2026-09-06 15:30 ` [PATCH net-next v6 1/5] net: pse-pd: add notifier chain for controller lifecycle events Carlo Szelinsky
2026-09-09 6:33 ` netdev-bot+sashiko
2026-09-06 15:30 ` [PATCH net-next v6 2/5] net: pse-pd: fire lifecycle events on controller register/unregister Carlo Szelinsky
2026-09-09 6:33 ` netdev-bot+sashiko
2026-09-06 15:31 ` [PATCH net-next v6 3/5] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook Carlo Szelinsky
2026-09-09 6:33 ` netdev-bot+sashiko [this message]
2026-09-06 15:31 ` [PATCH net-next v6 4/5] net: phy: use a dedicated mutex instead of rtnl for PSE control attach Carlo Szelinsky
2026-09-09 6:33 ` netdev-bot+sashiko
2026-09-06 15:31 ` [PATCH net-next v6 5/5] net: phy: release phydev->psec from phy_device_remove() again Carlo Szelinsky
2026-09-09 6:33 ` netdev-bot+sashiko
2026-09-10 9:06 ` [PATCH net-next v6 0/5] net: pse-pd: decouple controller lookup from MDIO probe Paolo Abeni
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178893559852.219967.17408171091451392990@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=corey@leavitt.info \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=github@szelinsky.de \
--cc=hkallweit1@gmail.com \
--cc=horms@kernel.org \
--cc=jelonek.jonas@gmail.com \
--cc=kory.maincent@bootlin.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=o.rempel@pengutronix.de \
--cc=olek2@wp.pl \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®