mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Carlo Szelinsky <github@szelinsky.de>
To: Oleksij Rempel <o.rempel@pengutronix.de>,
	Kory Maincent <kory.maincent@bootlin.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	Heiner Kallweit <hkallweit1@gmail.com>,
	Russell King <linux@armlinux.org.uk>,
	"David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: Corey Leavitt <corey@leavitt.info>,
	Jonas Jelonek <jelonek.jonas@gmail.com>,
	Simon Horman <horms@kernel.org>,
	Aleksander Jan Bajkowski <olek2@wp.pl>,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	Carlo Szelinsky <github@szelinsky.de>
Subject: [PATCH net-next v5 4/5] net: phy: use a dedicated mutex instead of rtnl for PSE control attach
Date: Thu, 27 Aug 2026 00:03:43 +0200	[thread overview]
Message-ID: <20260826220344.121865-5-github@szelinsky.de> (raw)
In-Reply-To: <20260826220344.121865-1-github@szelinsky.de>

phy_device_register() took rtnl_lock() around phy_try_attach_pse() to
serialise phydev->psec against the PSE controller notifier walk. But an
MDIO bus registered from ndo_init() runs with rtnl already held:

  register_netdevice()			# holds rtnl
    ndo_init() == ltq_etop_init()
      ltq_etop_mdio_init()
        mdiobus_register()
          mdiobus_scan()
            phy_device_register()
              rtnl_lock()		# deadlock

so any such driver (lantiq_etop, sni_ave, netsec) deadlocks on probe.

Replace rtnl with a dedicated phy_pse_lock mutex for the attach, for the
notifier attach/detach walks, and for the ethtool PSE paths that
dereference phydev->psec. A private lock cannot recurse against the
caller's rtnl, so the register path no longer deadlocks, while attach vs
notifier and detach vs ethtool stay mutually excluded.

rtnl also kept the ethtool PSE reads from racing the PSE_UNREGISTERED
detach that frees phydev->psec, so net/ethtool/pse-pd.c takes the same
lock across its phydev->psec accesses; guarding only the phy side would
reopen a use-after-free there. The lock order is
phy_pse_lock -> pse_list_mutex -> pcdev->lock, and the notifier walks
enter at phy_pse_lock and never take rtnl.

Reported-by: Aleksander Jan Bajkowski <olek2@wp.pl>
Closes: https://lore.kernel.org/netdev/bac5e6e9-7358-4ccb-87fc-9c40baa33682@wp.pl/
Tested-by: Aleksander Jan Bajkowski <olek2@wp.pl>
Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
---
 drivers/net/phy/phy_device.c | 53 +++++++++++++++++++++++++++---------
 include/linux/phy.h          |  2 ++
 net/ethtool/pse-pd.c         | 15 ++++++----
 3 files changed, 52 insertions(+), 18 deletions(-)

diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index f5febff4b00b..fa6c3d638b30 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -1113,18 +1113,21 @@ struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c45)
 }
 EXPORT_SYMBOL(get_phy_device);
 
+/* Serialises phydev->psec against the PSE notifier and ethtool, not rtnl. */
+static DEFINE_MUTEX(phy_pse_lock);
+
 /* 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.
+ * Caller must hold phy_pse_lock. 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.
  */
 static void phy_try_attach_pse(struct phy_device *phydev)
 {
 	struct pse_control *psec;
 	struct device_node *np;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&phy_pse_lock);
 
 	np = phydev->mdio.dev.of_node;
 	if (!np)
@@ -1146,7 +1149,7 @@ static void phy_try_attach_pse(struct phy_device *phydev)
 
 static int phy_pse_attach_one(struct device *dev, void *data __maybe_unused)
 {
-	ASSERT_RTNL();
+	lockdep_assert_held(&phy_pse_lock);
 
 	if (dev->type != &mdio_bus_phy_type)
 		return 0;
@@ -1161,7 +1164,7 @@ static int phy_pse_detach_one(struct device *dev, void *data)
 	struct phy_device *phydev;
 	struct pse_control *psec;
 
-	ASSERT_RTNL();
+	lockdep_assert_held(&phy_pse_lock);
 
 	if (dev->type != &mdio_bus_phy_type)
 		return 0;
@@ -1181,16 +1184,16 @@ static int phy_pse_notifier_event(struct notifier_block *nb,
 {
 	switch (event) {
 	case PSE_REGISTERED:
-		rtnl_lock();
+		mutex_lock(&phy_pse_lock);
 		bus_for_each_dev(&mdio_bus_type, NULL, NULL,
 				 phy_pse_attach_one);
-		rtnl_unlock();
+		mutex_unlock(&phy_pse_lock);
 		return NOTIFY_OK;
 	case PSE_UNREGISTERED:
-		rtnl_lock();
+		mutex_lock(&phy_pse_lock);
 		bus_for_each_dev(&mdio_bus_type, NULL, data,
 				 phy_pse_detach_one);
-		rtnl_unlock();
+		mutex_unlock(&phy_pse_lock);
 		return NOTIFY_OK;
 	default:
 		return NOTIFY_DONE;
@@ -1201,6 +1204,28 @@ static struct notifier_block phy_pse_notifier __read_mostly = {
 	.notifier_call = phy_pse_notifier_event,
 };
 
+/**
+ * phy_pse_control_lock - hold phydev->psec stable against PSE controller teardown
+ *
+ * The PSE_UNREGISTERED notifier detaches phydev->psec and drops its last
+ * reference. Callers that dereference phydev->psec (the ethtool PSE paths) must
+ * hold this lock across the access so the detach cannot run underneath them.
+ */
+void phy_pse_control_lock(void)
+{
+	mutex_lock(&phy_pse_lock);
+}
+EXPORT_SYMBOL_GPL(phy_pse_control_lock);
+
+/**
+ * phy_pse_control_unlock - release the lock taken by phy_pse_control_lock()
+ */
+void phy_pse_control_unlock(void)
+{
+	mutex_unlock(&phy_pse_lock);
+}
+EXPORT_SYMBOL_GPL(phy_pse_control_unlock);
+
 /* Core registration: add the phy to the MDIO bus. Does not touch rtnl or
  * PSE. phydev->psec is attached by the callers below, after device_add()
  * has made the phy visible on mdio_bus_type, so that a concurrent PSE
@@ -1260,7 +1285,9 @@ int phy_device_register_locked(struct phy_device *phydev)
 	if (err)
 		return err;
 
+	mutex_lock(&phy_pse_lock);
 	phy_try_attach_pse(phydev);
+	mutex_unlock(&phy_pse_lock);
 
 	return 0;
 }
@@ -1280,9 +1307,9 @@ int phy_device_register(struct phy_device *phydev)
 	if (err)
 		return err;
 
-	rtnl_lock();
+	mutex_lock(&phy_pse_lock);
 	phy_try_attach_pse(phydev);
-	rtnl_unlock();
+	mutex_unlock(&phy_pse_lock);
 
 	return 0;
 }
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 865b9baddb85..55a0049c6c2b 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -2162,6 +2162,8 @@ int phy_device_register(struct phy_device *phy);
 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);
+void phy_pse_control_lock(void);
+void phy_pse_control_unlock(void);
 int phy_get_c45_ids(struct phy_device *phydev);
 int phy_init_hw(struct phy_device *phydev);
 int phy_suspend(struct phy_device *phydev);
diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c
index 757c9e0cc856..4edd9a514de1 100644
--- a/net/ethtool/pse-pd.c
+++ b/net/ethtool/pse-pd.c
@@ -71,7 +71,9 @@ static int pse_prepare_data(const struct ethnl_req_info *req_base,
 	if (ret < 0)
 		return ret;
 
+	phy_pse_control_lock();
 	ret = pse_get_pse_attributes(phydev, info->extack, data);
+	phy_pse_control_unlock();
 
 	ethnl_ops_complete(dev);
 
@@ -281,9 +283,12 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
 
 	phydev = ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PSE_HEADER,
 				      info->extack);
+
+	phy_pse_control_lock();
+
 	ret = ethnl_set_pse_validate(phydev, info);
 	if (ret)
-		return ret;
+		goto out;
 
 	if (tb[ETHTOOL_A_PSE_PRIO]) {
 		unsigned int prio;
@@ -291,7 +296,7 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
 		prio = nla_get_u32(tb[ETHTOOL_A_PSE_PRIO]);
 		ret = pse_ethtool_set_prio(phydev->psec, info->extack, prio);
 		if (ret)
-			return ret;
+			goto out;
 	}
 
 	if (tb[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT]) {
@@ -301,7 +306,7 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
 		ret = pse_ethtool_set_pw_limit(phydev->psec, info->extack,
 					       pw_limit);
 		if (ret)
-			return ret;
+			goto out;
 	}
 
 	/* These values are already validated by the ethnl_pse_set_policy */
@@ -319,11 +324,11 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct genl_info *info)
 		 */
 		ret = pse_ethtool_set_config(phydev->psec, info->extack,
 					     &config);
-		if (ret)
-			return ret;
 	}
 
+out:
 	/* Return errno or zero - PSE has no notification */
+	phy_pse_control_unlock();
 	return ret;
 }
 
-- 
2.43.0


  parent reply	other threads:[~2026-08-26 22:04 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26 22:03 [PATCH net-next v5 0/5] net: pse-pd: decouple controller lookup from MDIO probe Carlo Szelinsky
2026-08-26 22:03 ` [PATCH net-next v5 1/5] net: pse-pd: add notifier chain for controller lifecycle events Carlo Szelinsky
2026-08-26 22:03 ` [PATCH net-next v5 2/5] net: pse-pd: fire lifecycle events on controller register/unregister Carlo Szelinsky
2026-08-26 22:03 ` [PATCH net-next v5 3/5] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook Carlo Szelinsky
2026-08-26 22:03 ` Carlo Szelinsky [this message]
2026-08-26 22:03 ` [PATCH net-next v5 5/5] net: phy: release phydev->psec from phy_device_remove() again Carlo Szelinsky
2026-08-27  8:22 ` [PATCH net-next v5 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=20260826220344.121865-5-github@szelinsky.de \
    --to=github@szelinsky.de \
    --cc=andrew+netdev@lunn.ch \
    --cc=corey@leavitt.info \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --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®