From nobody Sun Sep 27 02:43:32 2026 Received: from szelinsky.de (szelinsky.de [85.214.127.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0BA68364942; Sun, 6 Sep 2026 15:39:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.214.127.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788709183; cv=none; b=FwDZ5tz5kYudRbdFQijd67/DCr8orSPXNDxvaxODT5YTzyqw/7qdkKIAXlpfOLVUEBSRatkzd35OmGXxw621RDs6cVgfXE504CVtCFASyFVsi5TI2jL0Zw6I49ijFm3iMSpZAjPVql4YXbiDvNUtiHI3mtpwP9zdKXOTbNiG3yc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788709183; c=relaxed/simple; bh=4fHxGeXjo/4OXBi8jhnGGqUU/Ygon4FWYtEd4idbkWc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RIYWaiOnc3tJhUcw5sc0KI7M5UxkoJLGHZ6xzmcA+bJSZBufePrJDn+Do3w3YyVjKCoTUAB2MQegsifBijvqSI7CFYkSNhTnu5yfJUSdrU7aP0E0OQ2vowaE2frrb/KaLl+uCNlFCuQRN2zxOiQERU+QO2vLtivXQcAFrJ0ClnU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de; spf=pass smtp.mailfrom=szelinsky.de; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b=S+Sx4h49; arc=none smtp.client-ip=85.214.127.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b="S+Sx4h49" Received: from localhost (localhost [127.0.0.1]) by szelinsky.de (Postfix) with ESMTP id 7A68AE8392E; Sun, 06 Sep 2026 17:31:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=szelinsky.de; s=mail; t=1788708700; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=bec0yhM2e6J0DHWUR9ij9INSwz0Ba/y7Pys3eGuwrro=; b=S+Sx4h49U56+QoJpFECOKfod4mu42XueKRUBML18f9gDBGfGNXNh+pXJ/5wt1mUaWDN29O HCX67LS2FhApGQAWGA6+sFZ4yHPiJ82Zq/thi8arF2cuC+uiNEKm46bIpBz3j41WFyWp7M EGEbETZPRq0vn5e6301JqaUOoum48vyYsIMveaEy6WOoWP8DHUS44FWHFPI8tVBbPcVyjx afHas8jWDmvKGEatmDVd4QAzLVGX2ubMCWDmT3NTmIIblKx6ueex8DbaWbI3gFvMbBt+84 IWzkqdugQpJApjrJJEbSQWglu6sdbt3mBqnDU0piB/YGfMeg5OSttEohZL+akg== X-Virus-Scanned: Debian amavis at szelinsky.de Received: from szelinsky.de ([127.0.0.1]) by localhost (szelinsky.de [127.0.0.1]) (amavis, port 10025) with ESMTP id 9HTVk9JC-fd7; Sun, 6 Sep 2026 17:31:40 +0200 (CEST) Received: from p14sgen5.lan (p578611d6.dip0.t-ipconnect.de [87.134.17.214]) by szelinsky.de (Postfix) with ESMTPSA; Sun, 06 Sep 2026 17:31:39 +0200 (CEST) From: Carlo Szelinsky To: Oleksij Rempel , Kory Maincent , Andrew Lunn , Heiner Kallweit , Russell King , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Corey Leavitt , Jonas Jelonek , Simon Horman , Aleksander Jan Bajkowski , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Carlo Szelinsky Subject: [PATCH net-next v6 1/5] net: pse-pd: add notifier chain for controller lifecycle events Date: Sun, 6 Sep 2026 17:30:58 +0200 Message-ID: <20260906153102.959217-2-github@szelinsky.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260906153102.959217-1-github@szelinsky.de> References: <20260906153102.959217-1-github@szelinsky.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Corey Leavitt 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. Signed-off-by: Corey Leavitt Signed-off-by: Carlo Szelinsky Tested-by: Jonas Jelonek Tested-by: Carlo Szelinsky --- drivers/net/pse-pd/pse_core.c | 34 ++++++++++++++++++++++++++++++++++ include/linux/pse-pd/pse.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 69dbdbde9d71..3c4d09f1d6e4 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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); =20 +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 diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 4e5696cfade7..78fe3a2b1ea8 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -21,6 +21,7 @@ struct net_device; struct phy_device; struct pse_controller_dev; struct netlink_ext_ack; +struct notifier_block; =20 /* 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 =3D 1 << 2, }; =20 +/** + * 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); =20 +int pse_register_notifier(struct notifier_block *nb); +int pse_unregister_notifier(struct notifier_block *nb); + #else =20 static inline struct pse_control *of_pse_control_get(struct device_node *n= ode, @@ -416,6 +438,16 @@ static inline bool pse_has_c33(struct pse_control *pse= c) return false; } =20 +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 =20 #endif --=20 2.43.0 From nobody Sun Sep 27 02:43:32 2026 Received: from szelinsky.de (szelinsky.de [85.214.127.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0B98C363C5A; Sun, 6 Sep 2026 15:39:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.214.127.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788709183; cv=none; b=OFDnyp6JQpgOKgm+Ry1q0rhy0ZAnXkraKT8aeNnIcgHxLI/oMqV8Ui8ueLxyIv4Djb+cwCVvyKv1udWDzWBIKxXhr3WjXVOddC3J0CHy1Q9gAs9XmkwMYQPe4pHNNOK+TiNCDVeDfIvTOYeiJc+fbMd/sCwmEQK7JeQLnkqNvrM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788709183; c=relaxed/simple; bh=Xa/8+x3fbN/3+Jk52j6knMgpA06QVo/ahFSa00X/hN0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=dJFMDBU7lBr8M4K2Wlxh9VDEfTCBP7E5JQRx/0ibw8lA7Ktiaet/0SoxhOK67OhfbB8QU9yT0KSnYbEKwhc/XhgzzUw136x76viFr5312Ds3UnLKVRAY6A4daBqrhjvx6WAkPrzOBfN8IIqiiogytxp7kUlHT5Y2FzG5Y2/NLuo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de; spf=pass smtp.mailfrom=szelinsky.de; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b=knzWzB8b; arc=none smtp.client-ip=85.214.127.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b="knzWzB8b" Received: from localhost (localhost [127.0.0.1]) by szelinsky.de (Postfix) with ESMTP id 81F8DE8393B; Sun, 06 Sep 2026 17:31:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=szelinsky.de; s=mail; t=1788708703; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=n8STXjNlKV0wvUn+1PYv95+ojGlTOJB8a3/kKnQWGP4=; b=knzWzB8bZRbrXBnRJD24n8OvzycjNOrKWW4ebduPEsWAfaFuu52QK+UHjqpmELHUCXmoKB 51ylqXnNLqILlsimNRWKqRqwAErs7KVgefTY0FsMTDTteKtwgShD2asNyfJ/Zj+pbTNlpe HogkyDYCV0kaUU4sK9cX00JAnxYI/gpQDE8FPDnoP1yJ9qINxYpNMV5ejFuf6R4PIjP2iC /WT1oPh2qnAc+OJsEsG2N29rlCSeYc4Xzmzv3eZhyozgl1XIu0rWd9ky42Z9wknexzayif qhHJmq/5PbEpj2qxkbpCP7e+ViiCG26SbQZ2MJk5fhsNDqc1Ores7a9wZacwgw== X-Virus-Scanned: Debian amavis at szelinsky.de Received: from szelinsky.de ([127.0.0.1]) by localhost (szelinsky.de [127.0.0.1]) (amavis, port 10025) with ESMTP id Exu2nY8tdKaB; Sun, 6 Sep 2026 17:31:43 +0200 (CEST) Received: from p14sgen5.lan (p578611d6.dip0.t-ipconnect.de [87.134.17.214]) by szelinsky.de (Postfix) with ESMTPSA; Sun, 06 Sep 2026 17:31:42 +0200 (CEST) From: Carlo Szelinsky To: Oleksij Rempel , Kory Maincent , Andrew Lunn , Heiner Kallweit , Russell King , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Corey Leavitt , Jonas Jelonek , Simon Horman , Aleksander Jan Bajkowski , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Carlo Szelinsky Subject: [PATCH net-next v6 2/5] net: pse-pd: fire lifecycle events on controller register/unregister Date: Sun, 6 Sep 2026 17:30:59 +0200 Message-ID: <20260906153102.959217-3-github@szelinsky.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260906153102.959217-1-github@szelinsky.de> References: <20260906153102.959217-1-github@szelinsky.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Corey Leavitt 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. Signed-off-by: Corey Leavitt Signed-off-by: Carlo Szelinsky Tested-by: Jonas Jelonek Tested-by: Carlo Szelinsky --- drivers/net/pse-pd/pse_core.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 3c4d09f1d6e4..0190fac33c78 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -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); =20 + 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) --=20 2.43.0 From nobody Sun Sep 27 02:43:32 2026 Received: from szelinsky.de (szelinsky.de [85.214.127.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4D3A7433AD; Sun, 6 Sep 2026 15:31:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.214.127.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708711; cv=none; b=HASo50XnZu6VeOjbSS/B9Qn2hH1OER1HKgrydwF2MzFL553xsHS/VkYWz+qrAQnTlAGBXF9mOdA6EWexeboHMk9sblWODq1OSc2O8KViOKcPvQDPdc3tLTEsLAITtLe07k2y4OaM6HA5MoT32/2VQ53ANMxoiiVCA7xpJt+Vw38= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708711; c=relaxed/simple; bh=wuKgzSZE39fhInSJ7YR6ABNUiVulUP/guj3fPxTsHnQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QLH8yvNnamofG8WswqchrwN2IDV5vQH9OFD1hzp2QqOaN4jNTF03BMnMqb6ZflBOiUyVFNmIdEpM7lefxUzJytH/qOr9FxCwZLhc5jiCGUSq39fUKWOf3DE9/X4vqc9NSiuGk8Fka8k2y3746Poc6FF5VjrIIGhUfD+fa9EFr4A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de; spf=pass smtp.mailfrom=szelinsky.de; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b=hOUfeb0E; arc=none smtp.client-ip=85.214.127.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b="hOUfeb0E" Received: from localhost (localhost [127.0.0.1]) by szelinsky.de (Postfix) with ESMTP id A1E01E8394E; Sun, 06 Sep 2026 17:31:45 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=szelinsky.de; s=mail; t=1788708705; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=CKr78nSxDqjXSWyUcQbx5FqngWRX1roCEV6kxr0QOFY=; b=hOUfeb0E8G5AeVspk8VTg+2qUwxmGcQ0NH2EqZDSzEpOEd75InsC6VtaaXp2klSe3uatWD njlMFy0XWihrGkxaQOP/pf2s2+4yHfb6bko+LgSbt1Xdb3ZkDnAsEDYZ71dHlmx2qynI/Q znH19GywxKN0mUMqiWRn88qzE+VApIgOzdKDbywMqGunh9/c8Mx9y8L1cm5ExRrlVTk2jr 2aySiT9pjBg3pekIP3g4xaUxkeFknFmXSt4+eCsa0YoW2dEkvQ5Te0Mip1JiGxNplSbEin pyM1StRI2dc0Y4KF+bAHDdMkClBsz2AxAQgZVP4n/ouXUrFweHemGZ9vqAQ/jg== X-Virus-Scanned: Debian amavis at szelinsky.de Received: from szelinsky.de ([127.0.0.1]) by localhost (szelinsky.de [127.0.0.1]) (amavis, port 10025) with ESMTP id Vfblhw6Ee6X9; Sun, 6 Sep 2026 17:31:45 +0200 (CEST) Received: from p14sgen5.lan (p578611d6.dip0.t-ipconnect.de [87.134.17.214]) by szelinsky.de (Postfix) with ESMTPSA; Sun, 06 Sep 2026 17:31:44 +0200 (CEST) From: Carlo Szelinsky To: Oleksij Rempel , Kory Maincent , Andrew Lunn , Heiner Kallweit , Russell King , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Corey Leavitt , Jonas Jelonek , Simon Horman , Aleksander Jan Bajkowski , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Carlo Szelinsky Subject: [PATCH net-next v6 3/5] net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook Date: Sun, 6 Sep 2026 17:31:00 +0200 Message-ID: <20260906153102.959217-4-github@szelinsky.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260906153102.959217-1-github@szelinsky.de> References: <20260906153102.959217-1-github@szelinsky.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Corey Leavitt 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. Reported-by: Jonas Jelonek Closes: https://lore.kernel.org/netdev/e00048dd-1ed3-40c3-9912-59bccf015ad5= @gmail.com/ Signed-off-by: Corey Leavitt Co-developed-by: Carlo Szelinsky Signed-off-by: Carlo Szelinsky Tested-by: Jonas Jelonek Tested-by: Carlo Szelinsky --- 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(-) diff --git a/drivers/net/mdio/fwnode_mdio.c b/drivers/net/mdio/fwnode_mdio.c index ba7091518265..7bd979b59f49 100644 --- a/drivers/net/mdio/fwnode_mdio.c +++ b/drivers/net/mdio/fwnode_mdio.c @@ -11,33 +11,11 @@ #include #include #include -#include =20 MODULE_AUTHOR("Calvin Johnson "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("FWNODE MDIO bus (Ethernet PHY) accessors"); =20 -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 =3D to_of_node(fwnode); - if (!np) - return NULL; - - psec =3D of_pse_control_get(np, phydev); - if (PTR_ERR(psec) =3D=3D -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 =3D NULL; - struct pse_control *psec =3D 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; } =20 - psec =3D fwnode_find_pse_control(child, phy); - if (IS_ERR(psec)) { - rc =3D PTR_ERR(psec); - goto unregister_phy; - } - - phy->psec =3D 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, =20 return 0; =20 -unregister_phy: - if (is_acpi_node(child) || is_of_node(child)) - phy_device_remove(phy); clean_phy: phy_device_free(phy); clean_mii_ts: diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 0615228459ef..f5febff4b00b 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 *m= diodev) =20 static void phy_device_release(struct device *dev) { + struct phy_device *phydev =3D 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); } =20 static void phy_mdio_device_remove(struct mdio_device *mdiodev) @@ -1102,11 +1113,103 @@ struct phy_device *get_phy_device(struct mii_bus *= bus, int addr, bool is_c45) } EXPORT_SYMBOL(get_phy_device); =20 -/** - * 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 =3D <&...>` phandle. + * Caller must hold rtnl. A missing phandle (-ENOENT) or a not-yet-registe= red + * controller (-EPROBE_DEFER) is silent; the notifier retries the latter at + * PSE_REGISTERED time. Any other error means a broken binding and is warn= ed + * 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 =3D phydev->mdio.dev.of_node; + if (!np) + return; + + if (phydev->psec) + return; + + psec =3D of_pse_control_get(np, phydev); + if (IS_ERR(psec)) { + if (PTR_ERR(psec) !=3D -EPROBE_DEFER && PTR_ERR(psec) !=3D -ENOENT) + phydev_warn(phydev, "failed to get PSE control: %pe\n", + psec); + return; + } + + phydev->psec =3D psec; +} + +static int phy_pse_attach_one(struct device *dev, void *data __maybe_unuse= d) +{ + ASSERT_RTNL(); + + if (dev->type !=3D &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 =3D data; + struct phy_device *phydev; + struct pse_control *psec; + + ASSERT_RTNL(); + + if (dev->type !=3D &mdio_bus_phy_type) + return 0; + + phydev =3D to_phy_device(dev); + psec =3D phydev->psec; + if (!psec || !pse_control_matches_pcdev(psec, pcdev)) + return 0; + + phydev->psec =3D NULL; + pse_control_put(psec); + return 0; +} + +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; + } +} + +static struct notifier_block phy_pse_notifier __read_mostly =3D { + .notifier_call =3D phy_pse_notifier_event, +}; + +/* 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 + * notifier walk and the attach can never leave the phy unattached. Keeping + * device_add() out of rtnl also avoids deadlocking when binding a phy that + * itself provides an SFP cage (phy_probe() -> phy_sfp_probe() -> + * sfp_bus_add_upstream() takes rtnl). + */ +static int __phy_device_register(struct phy_device *phydev) { int err; =20 @@ -1135,10 +1238,54 @@ int phy_device_register(struct phy_device *phydev) out: /* Assert the reset signal */ phy_device_reset(phydev, 1); - mdiobus_unregister_device(&phydev->mdio); return err; } + +/** + * phy_device_register_locked - Register the phy device on the MDIO bus + * @phydev: phy_device structure to be added to the MDIO bus + * + * Same as phy_device_register() but caller must already hold rtnl_lock(). + * + * Return: 0 on success, negative error code on failure. + */ +int phy_device_register_locked(struct phy_device *phydev) +{ + int err; + + ASSERT_RTNL(); + + err =3D __phy_device_register(phydev); + if (err) + return err; + + phy_try_attach_pse(phydev); + + return 0; +} +EXPORT_SYMBOL(phy_device_register_locked); + +/** + * phy_device_register - Register the phy device on the MDIO bus + * @phydev: phy_device structure to be added to the MDIO bus + * + * Return: 0 on success, negative error code on failure. + */ +int phy_device_register(struct phy_device *phydev) +{ + int err; + + err =3D __phy_device_register(phydev); + if (err) + return err; + + rtnl_lock(); + phy_try_attach_pse(phydev); + rtnl_unlock(); + + return 0; +} EXPORT_SYMBOL(phy_device_register); =20 /** @@ -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); =20 /* Assert the reset signal */ @@ -3981,8 +4126,14 @@ static int __init phy_init(void) if (rc) goto err_c45; =20 + rc =3D pse_register_notifier(&phy_pse_notifier); + if (rc) + goto err_genphy; + return 0; =20 +err_genphy: + phy_driver_unregister(&genphy_driver); err_c45: phy_driver_unregister(&genphy_c45_driver); err_ethtool_phy_ops: @@ -3999,6 +4150,7 @@ static int __init phy_init(void) =20 static void __exit phy_exit(void) { + pse_unregister_notifier(&phy_pse_notifier); phy_driver_unregister(&genphy_c45_driver); phy_driver_unregister(&genphy_driver); rtnl_lock(); diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 03bfd8640db9..18868bdd6485 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -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 =3D true; =20 - err =3D phy_device_register(phy); + err =3D phy_device_register_locked(phy); if (err) { phy_device_free(phy); dev_err(sfp->dev, "phy_device_register failed: %pe\n", diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 0190fac33c78..611540df43bb 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -2021,3 +2021,17 @@ bool pse_has_c33(struct pse_control *psec) return psec->pcdev->types & ETHTOOL_PSE_C33; } EXPORT_SYMBOL_GPL(pse_has_c33); + +/** + * pse_control_matches_pcdev - Test whether a pse_control targets a contro= ller + * @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 =3D=3D pcdev; +} +EXPORT_SYMBOL_GPL(pse_control_matches_pcdev); diff --git a/include/linux/phy.h b/include/linux/phy.h index 199a7aaa341b..865b9baddb85 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2158,6 +2158,8 @@ struct phy_device *fwnode_phy_find_device(struct fwno= de_handle *phy_fwnode); struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwno= de); struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c= 45); 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); diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index 78fe3a2b1ea8..d4310ca71a3e 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -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); =20 +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); =20 @@ -438,6 +441,12 @@ static inline bool pse_has_c33(struct pse_control *pse= c) return false; } =20 +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; --=20 2.43.0 From nobody Sun Sep 27 02:43:32 2026 Received: from szelinsky.de (szelinsky.de [85.214.127.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2118C154425; Sun, 6 Sep 2026 15:31:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.214.127.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708712; cv=none; b=FPdAr0IDFaAAcyhWmRFzQhElfNAc/wzuGOTua2CnhfmvPdli/o53A+UdiWGULpccZDXa4VHfk9Hw/2+L3pzx7td4UPVjLVsEYE50lgGyvBrlb6fB1OVg4ruzNenWbDFA5qyq1ekiX/3v62RR4fWf8bXOfeoVGRMCCYM1o8S7tlc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708712; c=relaxed/simple; bh=CszSWPSVLzEtOEwbz1lpYS1BOquBSLwwkTg/t5tY0EA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qOqBQyIz55T6cQ02mIpFjL96RPxwUfpjTILOJd0BnT5mLJTA0QEid1uXyDV6++o00SEyNbvDkSEwKhq18RmnsQVhkr4SCIJ2oQevuKHubNemO4ELXv4nHZK/SBx94ZrPAHWqiSyl5sremROi9K5Y4spFfivvhT0zhFEWO/hBoYo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de; spf=pass smtp.mailfrom=szelinsky.de; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b=Ynk0h7My; arc=none smtp.client-ip=85.214.127.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b="Ynk0h7My" Received: from localhost (localhost [127.0.0.1]) by szelinsky.de (Postfix) with ESMTP id 1E6DFE8394F; Sun, 06 Sep 2026 17:31:48 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=szelinsky.de; s=mail; t=1788708708; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=8kciAGtZEViiiIZi+P6dRGkQUAiiJE7shkwonzXg4Wc=; b=Ynk0h7MybEwv1ff0uJL5SmQYFABV2PXrdr3cPVwxe/peeoxc022gngcswbR2KPbFxzhtRm +tF0MXd4k/rZCnFnnevAb0vAPSMVCdpYxtNtIUJrauZE/5hoAbPAt+NKZEo4MemfCy591y +T+ubpptMyaCOfnu+ZSXm/fqGzVsay2u/cZk6KKvrK84ItvoOsg/KOZuYBHBxA81bcuyfP gKzxQeMe8aaBq67CMGUxPh2pkePhmyHRZZRrIQix/iI40Dctlacw5lbJtO99SCibdojWa+ X3glNbgIzeMpfNfqa6w4uRT6SOjWKVZ81V1+na4sEkeGQmxa7suV7X8d+UX0mw== X-Virus-Scanned: Debian amavis at szelinsky.de Received: from szelinsky.de ([127.0.0.1]) by localhost (szelinsky.de [127.0.0.1]) (amavis, port 10025) with ESMTP id 64s2A-TXJ-9l; Sun, 6 Sep 2026 17:31:48 +0200 (CEST) Received: from p14sgen5.lan (p578611d6.dip0.t-ipconnect.de [87.134.17.214]) by szelinsky.de (Postfix) with ESMTPSA; Sun, 06 Sep 2026 17:31:47 +0200 (CEST) From: Carlo Szelinsky To: Oleksij Rempel , Kory Maincent , Andrew Lunn , Heiner Kallweit , Russell King , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Corey Leavitt , Jonas Jelonek , Simon Horman , Aleksander Jan Bajkowski , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Carlo Szelinsky Subject: [PATCH net-next v6 4/5] net: phy: use a dedicated mutex instead of rtnl for PSE control attach Date: Sun, 6 Sep 2026 17:31:01 +0200 Message-ID: <20260906153102.959217-5-github@szelinsky.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260906153102.959217-1-github@szelinsky.de> References: <20260906153102.959217-1-github@szelinsky.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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() =3D=3D 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 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. The mutex lives in pse_core, not phylib, and is reached through pse_phy_lock() / pse_phy_unlock(). net/ethtool/pse-pd.c is built into vmlinux unconditionally while PHYLIB is tristate, so ethtool cannot call a phylib export: with CONFIG_PHYLIB=3Dm or =3Dn the link fails. PSE_CONTROL= LER is bool, so pse_core is always in vmlinux (or absent, with no-op stubs for !PSE_CONTROLLER), and both built-in and modular callers reach the lock. It does not protect a phylib field but the phy<->controller binding against PSE controller teardown, and it sits directly above pse_list_mutex, which pse_core already owns. 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 rtnl -> pse_phy_mutex -> pse_list_mutex -> pcdev->lock, and the notifier walks enter at pse_phy_lock() and never take rtnl. Because the attach no longer takes rtnl, phy_device_register() and the phy_device_register_locked() variant that was added for the rtnl-holding sfp caller became identical, so fold them back into phy_device_register(). Reported-by: Aleksander Jan Bajkowski Closes: https://lore.kernel.org/netdev/bac5e6e9-7358-4ccb-87fc-9c40baa33682= @wp.pl/ Tested-by: Aleksander Jan Bajkowski Signed-off-by: Carlo Szelinsky Tested-by: Carlo Szelinsky --- drivers/net/phy/phy_device.c | 94 +++++++++++------------------------ drivers/net/phy/sfp.c | 2 +- drivers/net/pse-pd/pse_core.c | 46 +++++++++++++++++ include/linux/phy.h | 2 - include/linux/pse-pd/pse.h | 23 +++++++++ net/ethtool/pse-pd.c | 16 ++++-- 6 files changed, 111 insertions(+), 72 deletions(-) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index f5febff4b00b..e8d894bbfa7a 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1114,17 +1114,17 @@ struct phy_device *get_phy_device(struct mii_bus *b= us, int addr, bool is_c45) EXPORT_SYMBOL(get_phy_device); =20 /* Best-effort attach of phydev->psec from a DT `pses =3D <&...>` phandle. - * Caller must hold rtnl. A missing phandle (-ENOENT) or a not-yet-registe= red - * controller (-EPROBE_DEFER) is silent; the notifier retries the latter at - * PSE_REGISTERED time. Any other error means a broken binding and is warn= ed - * about, but left non-fatal so the phy still registers. + * Caller must hold pse_phy_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 brok= en + * binding and is warned about, but left non-fatal so the phy still regist= ers. */ static void phy_try_attach_pse(struct phy_device *phydev) { struct pse_control *psec; struct device_node *np; =20 - ASSERT_RTNL(); + pse_phy_lock_assert_held(); =20 np =3D phydev->mdio.dev.of_node; if (!np) @@ -1146,7 +1146,7 @@ static void phy_try_attach_pse(struct phy_device *phy= dev) =20 static int phy_pse_attach_one(struct device *dev, void *data __maybe_unuse= d) { - ASSERT_RTNL(); + pse_phy_lock_assert_held(); =20 if (dev->type !=3D &mdio_bus_phy_type) return 0; @@ -1161,7 +1161,7 @@ static int phy_pse_detach_one(struct device *dev, voi= d *data) struct phy_device *phydev; struct pse_control *psec; =20 - ASSERT_RTNL(); + pse_phy_lock_assert_held(); =20 if (dev->type !=3D &mdio_bus_phy_type) return 0; @@ -1181,16 +1181,16 @@ static int phy_pse_notifier_event(struct notifier_b= lock *nb, { switch (event) { case PSE_REGISTERED: - rtnl_lock(); + pse_phy_lock(); bus_for_each_dev(&mdio_bus_type, NULL, NULL, phy_pse_attach_one); - rtnl_unlock(); + pse_phy_unlock(); return NOTIFY_OK; case PSE_UNREGISTERED: - rtnl_lock(); + pse_phy_lock(); bus_for_each_dev(&mdio_bus_type, NULL, data, phy_pse_detach_one); - rtnl_unlock(); + pse_phy_unlock(); return NOTIFY_OK; default: return NOTIFY_DONE; @@ -1201,15 +1201,22 @@ static struct notifier_block phy_pse_notifier __rea= d_mostly =3D { .notifier_call =3D phy_pse_notifier_event, }; =20 -/* 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 - * notifier walk and the attach can never leave the phy unattached. Keeping - * device_add() out of rtnl also avoids deadlocking when binding a phy that - * itself provides an SFP cage (phy_probe() -> phy_sfp_probe() -> - * sfp_bus_add_upstream() takes rtnl). +/** + * phy_device_register - Register the phy device on the MDIO bus + * @phydev: phy_device structure to be added to the MDIO bus + * + * phydev->psec is attached after device_add() has made the phy visible on + * mdio_bus_type, so that a concurrent PSE notifier walk and the attach can + * never leave the phy unattached. Neither step takes rtnl: keeping + * device_add() out of rtnl avoids deadlocking when binding a phy that its= elf + * provides an SFP cage (phy_probe() -> phy_sfp_probe() -> + * sfp_bus_add_upstream() takes rtnl), and pse_phy_lock() rather than rtnl + * guards the attach so a bus registered from ndo_init (which already holds + * rtnl) does not recurse on it. + * + * Return: 0 on success, negative error code on failure. */ -static int __phy_device_register(struct phy_device *phydev) +int phy_device_register(struct phy_device *phydev) { int err; =20 @@ -1233,6 +1240,10 @@ static int __phy_device_register(struct phy_device *= phydev) goto out; } =20 + pse_phy_lock(); + phy_try_attach_pse(phydev); + pse_phy_unlock(); + return 0; =20 out: @@ -1241,51 +1252,6 @@ static int __phy_device_register(struct phy_device *= phydev) mdiobus_unregister_device(&phydev->mdio); return err; } - -/** - * phy_device_register_locked - Register the phy device on the MDIO bus - * @phydev: phy_device structure to be added to the MDIO bus - * - * Same as phy_device_register() but caller must already hold rtnl_lock(). - * - * Return: 0 on success, negative error code on failure. - */ -int phy_device_register_locked(struct phy_device *phydev) -{ - int err; - - ASSERT_RTNL(); - - err =3D __phy_device_register(phydev); - if (err) - return err; - - phy_try_attach_pse(phydev); - - return 0; -} -EXPORT_SYMBOL(phy_device_register_locked); - -/** - * phy_device_register - Register the phy device on the MDIO bus - * @phydev: phy_device structure to be added to the MDIO bus - * - * Return: 0 on success, negative error code on failure. - */ -int phy_device_register(struct phy_device *phydev) -{ - int err; - - err =3D __phy_device_register(phydev); - if (err) - return err; - - rtnl_lock(); - phy_try_attach_pse(phydev); - rtnl_unlock(); - - return 0; -} EXPORT_SYMBOL(phy_device_register); =20 /** diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c index 18868bdd6485..03bfd8640db9 100644 --- a/drivers/net/phy/sfp.c +++ b/drivers/net/phy/sfp.c @@ -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 =3D true; =20 - err =3D phy_device_register_locked(phy); + err =3D phy_device_register(phy); if (err) { phy_device_free(phy); dev_err(sfp->dev, "phy_device_register failed: %pe\n", diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c index 611540df43bb..b771f5a2a83f 100644 --- a/drivers/net/pse-pd/pse_core.c +++ b/drivers/net/pse-pd/pse_core.c @@ -24,8 +24,54 @@ static LIST_HEAD(pse_controller_list); static DEFINE_XARRAY_ALLOC(pse_pw_d_map); static DEFINE_MUTEX(pse_pw_d_mutex); =20 +/* Serialises phydev->psec against the PSE controller lifecycle notifier a= nd + * the ethtool PSE paths, in place of rtnl. The attach must not take rtnl:= an + * MDIO bus registered from ndo_init (e.g. lantiq_etop) calls + * phy_device_register() with rtnl already held, so taking rtnl for the at= tach + * would deadlock. It lives here rather than in phylib because PSE_CONTROL= LER + * is bool, so pse_core is always built into vmlinux and net/ethtool can c= all + * these directly; phylib is tristate and must not be linked against from + * built-in code. Lock order: rtnl -> pse_phy_mutex -> pse_list_mutex -> + * pcdev->lock. + */ +static DEFINE_MUTEX(pse_phy_mutex); + static BLOCKING_NOTIFIER_HEAD(pse_controller_notifier); =20 +/** + * pse_phy_lock - hold phydev->psec stable against PSE controller teardown + * + * The PSE_UNREGISTERED notifier clears phydev->psec and drops the last + * reference on the pse_control before the controller frees its state. Cal= lers + * that attach, detach or dereference phydev->psec must hold this lock acr= oss + * the whole access so the detach cannot run underneath them. + */ +void pse_phy_lock(void) +{ + mutex_lock(&pse_phy_mutex); +} +EXPORT_SYMBOL_GPL(pse_phy_lock); + +/** + * pse_phy_unlock - release the lock taken by pse_phy_lock() + */ +void pse_phy_unlock(void) +{ + mutex_unlock(&pse_phy_mutex); +} +EXPORT_SYMBOL_GPL(pse_phy_unlock); + +#ifdef CONFIG_LOCKDEP +/** + * pse_phy_lock_assert_held - assert that pse_phy_lock() is held + */ +void pse_phy_lock_assert_held(void) +{ + lockdep_assert_held(&pse_phy_mutex); +} +EXPORT_SYMBOL_GPL(pse_phy_lock_assert_held); +#endif + /** * pse_register_notifier - register a callback for PSE controller events * @nb: notifier block to register diff --git a/include/linux/phy.h b/include/linux/phy.h index 865b9baddb85..199a7aaa341b 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -2158,8 +2158,6 @@ struct phy_device *fwnode_phy_find_device(struct fwno= de_handle *phy_fwnode); struct fwnode_handle *fwnode_get_phy_node(const struct fwnode_handle *fwno= de); struct phy_device *get_phy_device(struct mii_bus *bus, int addr, bool is_c= 45); 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); diff --git a/include/linux/pse-pd/pse.h b/include/linux/pse-pd/pse.h index d4310ca71a3e..8c10613d9c51 100644 --- a/include/linux/pse-pd/pse.h +++ b/include/linux/pse-pd/pse.h @@ -391,6 +391,17 @@ bool pse_control_matches_pcdev(struct pse_control *pse= c, int pse_register_notifier(struct notifier_block *nb); int pse_unregister_notifier(struct notifier_block *nb); =20 +void pse_phy_lock(void); +void pse_phy_unlock(void); + +#ifdef CONFIG_LOCKDEP +void pse_phy_lock_assert_held(void); +#else +static inline void pse_phy_lock_assert_held(void) +{ +} +#endif + #else =20 static inline struct pse_control *of_pse_control_get(struct device_node *n= ode, @@ -457,6 +468,18 @@ static inline int pse_unregister_notifier(struct notif= ier_block *nb) return 0; } =20 +static inline void pse_phy_lock(void) +{ +} + +static inline void pse_phy_unlock(void) +{ +} + +static inline void pse_phy_lock_assert_held(void) +{ +} + #endif =20 #endif diff --git a/net/ethtool/pse-pd.c b/net/ethtool/pse-pd.c index 757c9e0cc856..654325946aaa 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; =20 + pse_phy_lock(); ret =3D pse_get_pse_attributes(phydev, info->extack, data); + pse_phy_unlock(); =20 ethnl_ops_complete(dev); =20 @@ -281,9 +283,12 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct = genl_info *info) =20 phydev =3D ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PSE_HEADER, info->extack); + + pse_phy_lock(); + ret =3D ethnl_set_pse_validate(phydev, info); if (ret) - return ret; + goto out; =20 if (tb[ETHTOOL_A_PSE_PRIO]) { unsigned int prio; @@ -291,7 +296,7 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct g= enl_info *info) prio =3D nla_get_u32(tb[ETHTOOL_A_PSE_PRIO]); ret =3D pse_ethtool_set_prio(phydev->psec, info->extack, prio); if (ret) - return ret; + goto out; } =20 if (tb[ETHTOOL_A_C33_PSE_AVAIL_PW_LIMIT]) { @@ -301,7 +306,7 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct g= enl_info *info) ret =3D pse_ethtool_set_pw_limit(phydev->psec, info->extack, pw_limit); if (ret) - return ret; + goto out; } =20 /* These values are already validated by the ethnl_pse_set_policy */ @@ -319,10 +324,11 @@ ethnl_set_pse(struct ethnl_req_info *req_info, struct= genl_info *info) */ ret =3D pse_ethtool_set_config(phydev->psec, info->extack, &config); - if (ret) - return ret; } =20 +out: + pse_phy_unlock(); + /* Return errno or zero - PSE has no notification */ return ret; } --=20 2.43.0 From nobody Sun Sep 27 02:43:32 2026 Received: from szelinsky.de (szelinsky.de [85.214.127.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4EFC034B1A3; Sun, 6 Sep 2026 15:31:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.214.127.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708715; cv=none; b=EAaqli9G3Qc20yoWMMREDylAF4r2GSxAEqYrFPaBDn8HdQxr3aU0ImokHPWyxhkbxcyKhTpo1OQsFhcpL1I0dgdzVFXNMjbUNYBRFLxwKpDELEUeeR8e4L6lKWbla/ecYGdlIDL4t3J5fQunLqEfCYuQnhaFGRQeVeitXBGg1BQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788708715; c=relaxed/simple; bh=qszTxD9hwOH6ZwSU8LuqVb0fQe6/Uose6JALSkNvyeo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jN6b+3gnXjrbDNGHUSP4quPyi1WmoBW6PAm37xXTGqROuESPh0ycy3bqzcEwqcZ1juaVUE8djjVXUPerj29hcHCEW+dppAXHi6onn4rYF4jMmPdY1dIEeervgwGJTdqYLE10FQ/B+fT7Fcf+nmZrXmfUIiBbl0GNm3Vz+4UNJ94= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de; spf=pass smtp.mailfrom=szelinsky.de; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b=i9gAS3LB; arc=none smtp.client-ip=85.214.127.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=szelinsky.de Authentication-Results: smtp.subspace.kernel.org; dkim=temperror (0-bit key) header.d=szelinsky.de header.i=@szelinsky.de header.b="i9gAS3LB" Received: from localhost (localhost [127.0.0.1]) by szelinsky.de (Postfix) with ESMTP id 5F0D3E83952; Sun, 06 Sep 2026 17:31:50 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=szelinsky.de; s=mail; t=1788708710; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=PnrgME3345SjzlmVa4gF5D1UU17DNJls4cjw0wkDHcA=; b=i9gAS3LBsMkJDm+X/sIwgkcWRpR8k5x44f3eABbR9drZIHEh4E5qcT/3etK+F9CjDUW2I0 OZrPGOh9D125sGEZdFMoXmE8cmUvpRyvWvcUtvZVLrrDoKuSsGhmGC+gWqcGEBU2RJLftB /jNxjCjShZoC6Kd8asmJscThA0qftKhidKOCBmouxU3STc+0MT6XoeOV+Ilz8AMRm9gjIg M9VdfGm0xXTCbKceG9+Jp9JQZVbfdcIjwmeiz4+0W2NYsLheraKS1d2jUIP8i3CrYAo09h hV5oe1P0ew+VPYowESAZpm5nKjLQy++0tJFNcIcxhJfGosiG7CrttID591isBg== X-Virus-Scanned: Debian amavis at szelinsky.de Received: from szelinsky.de ([127.0.0.1]) by localhost (szelinsky.de [127.0.0.1]) (amavis, port 10025) with ESMTP id 7vc8dVwUbajL; Sun, 6 Sep 2026 17:31:50 +0200 (CEST) Received: from p14sgen5.lan (p578611d6.dip0.t-ipconnect.de [87.134.17.214]) by szelinsky.de (Postfix) with ESMTPSA; Sun, 06 Sep 2026 17:31:49 +0200 (CEST) From: Carlo Szelinsky To: Oleksij Rempel , Kory Maincent , Andrew Lunn , Heiner Kallweit , Russell King , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni Cc: Corey Leavitt , Jonas Jelonek , Simon Horman , Aleksander Jan Bajkowski , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Carlo Szelinsky Subject: [PATCH net-next v6 5/5] net: phy: release phydev->psec from phy_device_remove() again Date: Sun, 6 Sep 2026 17:31:02 +0200 Message-ID: <20260906153102.959217-6-github@szelinsky.de> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260906153102.959217-1-github@szelinsky.de> References: <20260906153102.959217-1-github@szelinsky.de> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" "net: phy: own phydev->psec via PSE notifier and remove fwnode_mdio hook" deferred the final pse_control_put() of phydev->psec from phy_device_remove() to phy_device_release(), so it would run only after the PSE_UNREGISTERED notifier walk had dropped its bus-iterator reference on the phy. But bus_for_each_dev() only reaches phys still on the mdio_bus_type klist: a phy that has been device_del()'d yet is still pinned (e.g. by an attached netdev) is invisible to the walk, so phy_pse_detach_one() never clears its phydev->psec. Its deferred put then runs after pse_controller_unregister() -> pse_release_pis() has freed pcdev->pi[], and __pse_control_release() dereferences the freed array: use-after-free. Put phydev->psec back in phy_device_remove(), before device_del(), so the detach is synchronous and ordered ahead of the phy leaving the bus; it can no longer outlive the PSE controller. "net: phy: use a dedicated mutex instead of rtnl for PSE control attach" replaced rtnl with pse_phy_lock() for the attach/detach, so this put can take that same lock without the rtnl recursion that originally motivated the deferral, and it serialises against the notifier walk: whichever runs first clears phydev->psec, the other sees NULL. Suggested-by: Paolo Abeni Link: https://lore.kernel.org/netdev/20260703071025.100797-1-pabeni@redhat.= com/ Signed-off-by: Carlo Szelinsky Tested-by: Carlo Szelinsky --- drivers/net/phy/phy_device.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index e8d894bbfa7a..bb120045c406 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -223,19 +223,8 @@ static void phy_mdio_device_free(struct mdio_device *m= diodev) =20 static void phy_device_release(struct device *dev) { - struct phy_device *phydev =3D 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(phydev); + kfree(to_phy_device(dev)); } =20 static void phy_mdio_device_remove(struct mdio_device *mdiodev) @@ -1265,6 +1254,16 @@ EXPORT_SYMBOL(phy_device_register); void phy_device_remove(struct phy_device *phydev) { unregister_mii_timestamper(phydev->mii_ts); + + /* Detach synchronously, before the phy leaves the bus, so the put cannot + * outlive the PSE controller (an off-bus but still-pinned phy is missed = by + * the PSE_UNREGISTERED walk). pse_phy_lock() serialises against that wal= k. + */ + pse_phy_lock(); + pse_control_put(phydev->psec); + phydev->psec =3D NULL; + pse_phy_unlock(); + device_del(&phydev->mdio.dev); =20 /* Assert the reset signal */ --=20 2.43.0