From: Chenguang Zhao <chenguang.zhao@linux.dev>
To: andrew@lunn.ch, hkallweit1@gmail.com, linux@armlinux.org.uk,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com
Cc: netdev@vger.kernel.org, chenguang.zhao@linux.dev,
Chenguang Zhao <zhaochenguang@kylinos.cn>,
syzbot+694b49f41098a5df4fd7@syzkaller.appspotmail.com
Subject: [PATCH net-next v3] net: phy: make the hwtstamp provider lifetime symmetric with the PHY
Date: Wed, 23 Sep 2026 15:11:32 +0800 [thread overview]
Message-ID: <20260923071132.908826-1-chenguang.zhao@linux.dev> (raw)
From: Chenguang Zhao <zhaochenguang@kylinos.cn>
phy_detach() removes dev->hwprov when it points at the PHY being
detached, but phy_attach() does not install anything: the setup and
teardown are not symmetric. The teardown path is also reached from
the probe error path with no RTNL and an unregistered netdev, making
rtnl_dereference() of dev->hwprov trigger a lockdep splat.
Track the provider with the PHY instead: dev_attach_hwtstamp_phylib()
installs a default provider in phy_attach_direct(), and
dev_clear_hwtstamp_phylib() removes it in phy_detach(). Both sit
next to dev_set_hwtstamp_phylib() so the locking is obviously the
same, relaxed for unregistered netdevs via
netdev_ops_lock_dereference_or_invisible(). No driver callback is
invoked; installation is skipped when the PHY is not the default
hwtstamp provider or when ethtool already set one.
Fixes: 35f7cad1743e ("net: Add the possibility to support a selected hwtstamp in netdevice")
Reported-by: syzbot+694b49f41098a5df4fd7@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=694b49f41098a5df4fd7
Signed-off-by: Chenguang Zhao <zhaochenguang@kylinos.cn>
---
v3:
The original patch only skipped the hwprov cleanup when the netdev
was NETREG_UNINITIALIZED. The review asked to make the provider
setup/teardown symmetric instead.
Calling dev_set_hwtstamp_phylib() directly from phy_attach() does not
work, for a few reasons:
- It reads dev->hwprov under the netdev ops lock, and the phy_ts
branch goes through phy_hwtstamp_set(), which ASSERT_RTNL().
phy_attach() runs from the probe path with neither.
- The !phy_ts branch (the common case for non-PTP PHYs) calls
ops->ndo_hwtstamp_set() unconditionally, but drivers like
usbnet/asix do not implement that callback.
- Pushing a zero config into ndo_hwtstamp_set() would also wipe
the MAC hwtstamp configuration on SFP hotplug.
Instead, v3 controls the provider through metadata-only helpers that
never call back into any driver. A freshly attached PHY is in its
zero config state by definition, so no explicit zeroing is needed.
The two new helpers match the locking in dev_set_hwtstamp_phylib(),
with netdev_assert_locked_ops_compat_or_invisible() extended to a
dereference helper so that unregistered netdevs are accepted.
Both live in dev_ioctl.c next to dev_set_hwtstamp_phylib().
ethnl_set_tsconfig() is left unchanged: it keeps its existing
zero-then-switch sequence and only replaces the setting.
Installation is skipped when:
- ethtool already chose a provider (hwprov is set);
- the PHY is not the default hwtstamp provider;
- the PHY has no ts_info or phc_index < 0;
- the allocation fails.
All four degrade to the existing NULL fallback, so the patch changes
no observable behavior.
v2:
https://lore.kernel.org/all/20260918022316.237646-1-chenguang.zhao@linux.dev/
v1:
https://lore.kernel.org/all/20260916021505.238990-1-chenguang.zhao@linux.dev/
drivers/net/phy/phy_device.c | 19 +++++-------
include/linux/net_tstamp.h | 8 +++++
include/net/netdev_lock.h | 18 +++++++++++
net/core/dev_ioctl.c | 71 ++++++++++++++++++++++++++++++++++++
4 files changed, 105 insertions(+), 11 deletions(-)
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 94b2e85e00a3..2ddb4b214003 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -17,6 +17,7 @@
#include <linux/ethtool.h>
#include <linux/init.h>
#include <linux/interrupt.h>
+#include <linux/net_tstamp.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/list.h>
@@ -1878,6 +1879,12 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev,
phy_resume(phydev);
+ /* Track the default hwtstamp provider of the netdev, if the
+ * attached PHY is the one. Cleared symmetrically by phy_detach().
+ */
+ if (dev)
+ dev_attach_hwtstamp_phylib(dev, phydev);
+
/**
* If the external phy used by current mac interface is managed by
* another mac interface, so we should create a device link between
@@ -1936,17 +1943,7 @@ void phy_detach(struct phy_device *phydev)
phy_suspend(phydev);
if (dev) {
- struct hwtstamp_provider *hwprov;
-
- /* hwprov may technically be protected by ops lock but
- * not for devices with a phydev, see phy_link_topo_add_phy()
- */
- hwprov = rtnl_dereference(dev->hwprov);
- /* Disable timestamp if it is the one selected */
- if (hwprov && hwprov->phydev == phydev) {
- rcu_assign_pointer(dev->hwprov, NULL);
- kfree_rcu(hwprov, rcu_head);
- }
+ dev_clear_hwtstamp_phylib(dev, phydev);
phydev->attached_dev->phydev = NULL;
phydev->attached_dev = NULL;
diff --git a/include/linux/net_tstamp.h b/include/linux/net_tstamp.h
index f4936d9c2b3c..d214253df2d3 100644
--- a/include/linux/net_tstamp.h
+++ b/include/linux/net_tstamp.h
@@ -41,6 +41,14 @@ struct hwtstamp_provider {
struct hwtstamp_provider_desc desc;
};
+struct net_device;
+struct phy_device;
+
+void dev_attach_hwtstamp_phylib(struct net_device *dev,
+ struct phy_device *phydev);
+void dev_clear_hwtstamp_phylib(struct net_device *dev,
+ struct phy_device *phydev);
+
/**
* struct kernel_hwtstamp_config - Kernel copy of struct hwtstamp_config
*
diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h
index 9fb3e93857c3..b2f94902afff 100644
--- a/include/net/netdev_lock.h
+++ b/include/net/netdev_lock.h
@@ -149,6 +149,24 @@ static inline int netdev_lock_cmp_fn(const struct lockdep_map *a,
#define netdev_ops_lock_dereference(p, dev) \
rcu_dereference_protected(p, netdev_is_locked_ops_compat(dev))
+/* Same as netdev_is_locked_ops_compat(), but a netdev which is not
+ * registered cannot be reached from user space, so its state cannot
+ * be accessed concurrently and no lock is needed.
+ */
+static inline int
+netdev_is_locked_ops_compat_or_invisible(const struct net_device *dev)
+{
+ if (dev->reg_state != NETREG_REGISTERED &&
+ dev->reg_state != NETREG_UNREGISTERING)
+ return 1;
+
+ return netdev_is_locked_ops_compat(dev);
+}
+
+#define netdev_ops_lock_dereference_or_invisible(p, dev) \
+ rcu_dereference_protected(p, \
+ netdev_is_locked_ops_compat_or_invisible(dev))
+
int netdev_debug_event(struct notifier_block *nb, unsigned long event,
void *ptr);
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c
index a320e264eaaf..fb586bcbda1f 100644
--- a/net/core/dev_ioctl.c
+++ b/net/core/dev_ioctl.c
@@ -5,6 +5,8 @@
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
#include <linux/net_tstamp.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
#include <linux/phylib_stubs.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/wireless.h>
@@ -388,6 +390,75 @@ int dev_set_hwtstamp_phylib(struct net_device *dev,
return 0;
}
+/**
+ * dev_attach_hwtstamp_phylib() - Install the default hwtstamp provider of a
+ * netdev for a newly attached PHY, if the PHY qualifies.
+ * @dev: Network device
+ * @phydev: PHY device being attached
+ *
+ * Only sets metadata (source, phydev pointer, PHC descriptor); it never
+ * calls into any driver callback. A freshly attached PHY is by definition
+ * in its zero-config state so no zeroing is needed. Installation is skipped
+ * when the PHY is not the default hwtstamp provider, has no ts_info, or when
+ * a provider was already set (by ethtool). The locking convention is the
+ * same as for dev_set_hwtstamp_phylib(), but also accepts unregistered netdevs.
+ */
+void dev_attach_hwtstamp_phylib(struct net_device *dev,
+ struct phy_device *phydev)
+{
+ struct kernel_ethtool_ts_info ts_info = {};
+ struct hwtstamp_provider *hwprov;
+
+ /* Don't override an explicitly selected provider */
+ hwprov = netdev_ops_lock_dereference_or_invisible(dev->hwprov, dev);
+ if (hwprov)
+ return;
+
+ if (!phy_is_default_hwtstamp(phydev) || !phy_has_tsinfo(phydev))
+ return;
+
+ if (phy_ts_info(phydev, &ts_info) || ts_info.phc_index < 0)
+ /* No usable PTP hardware clock description */
+ return;
+
+ hwprov = kzalloc_obj(*hwprov);
+ if (!hwprov)
+ /* Degrade to the NULL fallback */
+ return;
+
+ hwprov->source = HWTSTAMP_SOURCE_PHYLIB;
+ hwprov->phydev = phydev;
+ hwprov->desc.index = ts_info.phc_index;
+ hwprov->desc.qualifier = ts_info.phc_qualifier;
+
+ rcu_assign_pointer(dev->hwprov, hwprov);
+}
+EXPORT_SYMBOL(dev_attach_hwtstamp_phylib);
+
+/**
+ * dev_clear_hwtstamp_phylib() - Remove the hwtstamp provider if it matches
+ * a PHY that is being detached.
+ * @dev: Network device
+ * @phydev: PHY device being detached
+ *
+ * Clears dev->hwprov when it points at @phydev, whether the provider was
+ * installed by dev_attach_hwtstamp_phylib() or by dev_set_hwtstamp_phylib()
+ * (via ethtool). Called from phy_detach() for symmetry with attach.
+ */
+void dev_clear_hwtstamp_phylib(struct net_device *dev,
+ struct phy_device *phydev)
+{
+ struct hwtstamp_provider *hwprov;
+
+ hwprov = netdev_ops_lock_dereference_or_invisible(dev->hwprov, dev);
+ /* Disable timestamping if the provider is the detached PHY */
+ if (hwprov && hwprov->phydev == phydev) {
+ rcu_assign_pointer(dev->hwprov, NULL);
+ kfree_rcu(hwprov, rcu_head);
+ }
+}
+EXPORT_SYMBOL(dev_clear_hwtstamp_phylib);
+
static int dev_set_hwtstamp(struct net_device *dev, struct ifreq *ifr)
{
const struct net_device_ops *ops = dev->netdev_ops;
--
2.25.1
next reply other threads:[~2026-09-23 7:11 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 7:11 Chenguang Zhao [this message]
2026-09-30 0:03 ` [PATCH net-next v3] net: phy: make the hwtstamp provider lifetime symmetric with the PHY Jakub Kicinski
2026-10-02 9:43 ` Chenguang Zhao
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=20260923071132.908826-1-chenguang.zhao@linux.dev \
--to=chenguang.zhao@linux.dev \
--cc=andrew@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=hkallweit1@gmail.com \
--cc=kuba@kernel.org \
--cc=linux@armlinux.org.uk \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=syzbot+694b49f41098a5df4fd7@syzkaller.appspotmail.com \
--cc=zhaochenguang@kylinos.cn \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.