mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net-next v2 0/2] net: phy: realtek: add PTP support for RTL8127a
@ 2026-11-11  8:38 javen
  2026-11-11  8:38 ` [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock " javen
  2026-11-11  8:38 ` [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping " javen
  0 siblings, 2 replies; 5+ messages in thread
From: javen @ 2026-11-11  8:38 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu

From: Javen Xu <javen_xu@realsil.com.cn>

The PTP block of RTL8127a lives in its internal PHY (RTL8261C), so this
series adds PTP support to the Realtek PHY driver instead of r8169.

Changes in v2:
- Move the PTP support from r8169 to the Realtek PHY driver
- Drop the r8169 PHY OCP access patch, use phy_read_mmd()/phy_write_mmd()
- Drop PPS support, only minimal PTP support is added

Javen Xu (2):
  net: phy: realtek: add PTP clock support for RTL8127A internal PHY
  net: phy: realtek: add hardware timestamping for RTL8127A internal PHY

 drivers/net/phy/realtek/Kconfig        |  11 +
 drivers/net/phy/realtek/Makefile       |   1 +
 drivers/net/phy/realtek/realtek.h      |  20 +
 drivers/net/phy/realtek/realtek_main.c |  19 +-
 drivers/net/phy/realtek/realtek_ptp.c  | 719 +++++++++++++++++++++++++
 5 files changed, 769 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/phy/realtek/realtek_ptp.c

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock for RTL8127a
  2026-11-11  8:38 [PATCH net-next v2 0/2] net: phy: realtek: add PTP support for RTL8127a javen
@ 2026-11-11  8:38 ` javen
  2026-11-15  9:05   ` netdev-bot+sashiko
  2026-11-11  8:38 ` [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping " javen
  1 sibling, 1 reply; 5+ messages in thread
From: javen @ 2026-11-11  8:38 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu

From: Javen Xu <javen_xu@realsil.com.cn>

The internal PHY of RTL8127a (RTL8261C, PHY ID 0x001cc890) contains a
PTP block with a free running clock that can be read, set, stepped and
frequency adjusted through vendor MMD registers.

Register a PTP hardware clock for it. All register accesses go through
the normal blocking MDIO path, multi register sequences are serialized
by a driver mutex. The clock is started from the system time each time
the PHY is initialized.

Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
 drivers/net/phy/realtek/Kconfig        |  11 +
 drivers/net/phy/realtek/Makefile       |   1 +
 drivers/net/phy/realtek/realtek.h      |  20 ++
 drivers/net/phy/realtek/realtek_main.c |  19 +-
 drivers/net/phy/realtek/realtek_ptp.c  | 374 +++++++++++++++++++++++++
 5 files changed, 424 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/phy/realtek/realtek_ptp.c

diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig
index a741b34d193e..93a8fb552834 100644
--- a/drivers/net/phy/realtek/Kconfig
+++ b/drivers/net/phy/realtek/Kconfig
@@ -13,4 +13,15 @@ config REALTEK_PHY_HWMON
 	help
 	  Optional hwmon support for the temperature sensor
 
+config REALTEK_PHY_PTP
+	bool "PTP support for Realtek PHYs"
+	default y
+	depends on PTP_1588_CLOCK
+	depends on !(REALTEK_PHY=y && PTP_1588_CLOCK=m)
+	help
+	  Optional PTP hardware clock support for the internal PHY of
+	  RTL8127A. The clock is provided by the PHY itself and can be
+	  read and adjusted through the PTP clock device, no support from
+	  the MAC driver is needed.
+
 endif # REALTEK_PHY
diff --git a/drivers/net/phy/realtek/Makefile b/drivers/net/phy/realtek/Makefile
index dd21cf87f2f1..450bd05b7ef9 100644
--- a/drivers/net/phy/realtek/Makefile
+++ b/drivers/net/phy/realtek/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 realtek-y			+= realtek_main.o
 realtek-$(CONFIG_REALTEK_PHY_HWMON) += realtek_hwmon.o
+realtek-$(CONFIG_REALTEK_PHY_PTP) += realtek_ptp.o
 obj-$(CONFIG_REALTEK_PHY)	+= realtek.o
diff --git a/drivers/net/phy/realtek/realtek.h b/drivers/net/phy/realtek/realtek.h
index a39b44fa18a0..384bde5fff3b 100644
--- a/drivers/net/phy/realtek/realtek.h
+++ b/drivers/net/phy/realtek/realtek.h
@@ -7,4 +7,24 @@
 
 int rtl822x_hwmon_init(struct phy_device *phydev);
 
+#if IS_ENABLED(CONFIG_REALTEK_PHY_PTP)
+int rtl8127a_ptp_probe(struct phy_device *phydev);
+void rtl8127a_ptp_remove(struct phy_device *phydev);
+int rtl8127a_ptp_config_init(struct phy_device *phydev);
+#else
+static inline int rtl8127a_ptp_probe(struct phy_device *phydev)
+{
+	return 0;
+}
+
+static inline void rtl8127a_ptp_remove(struct phy_device *phydev)
+{
+}
+
+static inline int rtl8127a_ptp_config_init(struct phy_device *phydev)
+{
+	return 0;
+}
+#endif
+
 #endif /* REALTEK_H */
diff --git a/drivers/net/phy/realtek/realtek_main.c b/drivers/net/phy/realtek/realtek_main.c
index 4927ee838de2..433fcce8615b 100644
--- a/drivers/net/phy/realtek/realtek_main.c
+++ b/drivers/net/phy/realtek/realtek_main.c
@@ -1839,6 +1839,21 @@ static int rtl822x_probe(struct phy_device *phydev)
 	return 0;
 }
 
+static int rtl_internal_nbaset_probe(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = rtl822x_probe(phydev);
+	if (ret)
+		return ret;
+
+	/* Only the internal PHY of RTL8127A has a PTP block */
+	if (phydev->phy_id == RTL_8261C)
+		return rtl8127a_ptp_probe(phydev);
+
+	return 0;
+}
+
 /* RTL822x cannot access MDIO_MMD_VEND2 via MII_MMD_CTRL/MII_MMD_DATA.
  * A mapping to use paged access needs to be used instead.
  * All other MMD devices can be accessed as usual.
@@ -3413,7 +3428,9 @@ static struct phy_driver realtek_drvs[] = {
 		.match_phy_device = rtl_internal_nbaset_match_phy_device,
 		.name		= "Realtek Internal NBASE-T PHY",
 		.flags		= PHY_IS_INTERNAL,
-		.probe		= rtl822x_probe,
+		.probe		= rtl_internal_nbaset_probe,
+		.remove		= rtl8127a_ptp_remove,
+		.config_init	= rtl8127a_ptp_config_init,
 		.get_features	= rtl822x_get_features,
 		.config_aneg	= rtl822x_config_aneg,
 		.read_status	= rtl822x_read_status,
diff --git a/drivers/net/phy/realtek/realtek_ptp.c b/drivers/net/phy/realtek/realtek_ptp.c
new file mode 100644
index 000000000000..bd5c0d6988da
--- /dev/null
+++ b/drivers/net/phy/realtek/realtek_ptp.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PTP hardware clock support for the internal PHY of RTL8127A
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+#include <linux/ptp_clock_kernel.h>
+#include <linux/timekeeping.h>
+
+#include "realtek.h"
+
+#define RTL8127A_PTP_CTL		0xe400
+#define  RTL8127A_PTP_CTL_EN		BIT(0)
+#define  RTL8127A_PTP_CTL_TYPE_3	0x003f
+#define RTL8127A_PTP_INER		0xe402
+#define RTL8127A_PTP_INSR		0xe404
+#define  RTL8127A_PTP_INT_RX_TS		BIT(2)
+#define  RTL8127A_PTP_INT_TX_TS		BIT(3)
+#define RTL8127A_PTP_SYNCE_CTL		0xe406
+#define  RTL8127A_PTP_SYNCE_EN		BIT(0)
+#define RTL8127A_PTP_GEN_CFG		0xe408
+#define  RTL8127A_PTP_INT_CLR_MODE	BIT(0)
+#define RTL8127A_PTP_CLK_CFG		0xe410
+#define  RTL8127A_PTP_CLK_CFG_EXEC	BIT(0)
+#define  RTL8127A_PTP_CLK_CFG_MODE	GENMASK(3, 1)
+/* Time registers */
+#define RTL8127A_PTP_CFG_TIME		0xe412
+#define  RTL8127A_PTP_NS_LO		0x0
+#define  RTL8127A_PTP_NS_HI		0x2
+#define  RTL8127A_PTP_S_LO		0x4
+#define  RTL8127A_PTP_S_MI		0x6
+#define  RTL8127A_PTP_S_HI		0x8
+#define  RTL8127A_PTP_NS_HI_MASK	GENMASK(13, 0)
+#define  RTL8127A_PTP_NSEC_MASK		GENMASK(29, 0)
+#define  RTL8127A_PTP_SEC_MASK		GENMASK_ULL(47, 0)
+#define  RTL8127A_PTP_RATE_HI_MASK	GENMASK(21, 16)
+
+#define RTL8127A_PTP_MAX_ADJ		488281
+
+/* RTL8127A_PTP_CLK_CFG clock adjust modes */
+enum rtl8127a_ptp_clkadj_mode {
+	RTL8127A_PTP_DIRECT_READ	= 4,
+	RTL8127A_PTP_DIRECT_WRITE	= 6,
+	RTL8127A_PTP_INC_STEP		= 8,
+	RTL8127A_PTP_DEC_STEP		= 10,
+	RTL8127A_PTP_RATE_WRITE		= 14,
+};
+
+struct rtl8127a_ptp {
+	struct phy_device *phydev;
+	struct ptp_clock_info info;
+	struct ptp_clock *clock;
+	struct mutex lock;	/* serializes PTP register access sequences */
+};
+
+static int rtl8127a_ptp_read(struct rtl8127a_ptp *ptp, u16 reg)
+{
+	return phy_read_mmd(ptp->phydev, MDIO_MMD_VEND2, reg);
+}
+
+static int rtl8127a_ptp_write(struct rtl8127a_ptp *ptp, u16 reg, u16 val)
+{
+	return phy_write_mmd(ptp->phydev, MDIO_MMD_VEND2, reg, val);
+}
+
+/* Unlike phy_modify_mmd() always write the register back, some of the
+ * bits are commands to the PTP block.
+ */
+static int rtl8127a_ptp_modify(struct rtl8127a_ptp *ptp, u16 reg, u16 mask,
+			       u16 set)
+{
+	int val = rtl8127a_ptp_read(ptp, reg);
+
+	if (val < 0)
+		return val;
+
+	return rtl8127a_ptp_write(ptp, reg, (val & ~mask) | set);
+}
+
+/* Poll until (reg & mask) is non-zero (set == true) or zero (set == false).
+ * Returns the last register value read, or a negative error code.
+ */
+static int rtl8127a_ptp_poll(struct rtl8127a_ptp *ptp, u16 reg, u16 mask,
+			     bool set)
+{
+	int ret, val;
+
+	ret = read_poll_timeout(rtl8127a_ptp_read, val,
+				val < 0 || !!(val & mask) == set, 1, 1000,
+				false, ptp, reg);
+	if (val < 0)
+		ret = val;
+	if (ret) {
+		if (net_ratelimit())
+			phydev_err(ptp->phydev,
+				   "PTP reg 0x%04x poll failed: %d\n", reg, ret);
+		return ret;
+	}
+
+	return val;
+}
+
+static int rtl8127a_ptp_clkadj(struct rtl8127a_ptp *ptp,
+			       enum rtl8127a_ptp_clkadj_mode mode)
+{
+	int ret;
+
+	ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_CLK_CFG,
+				  RTL8127A_PTP_CLK_CFG_MODE,
+				  RTL8127A_PTP_CLK_CFG_EXEC | mode);
+	if (ret)
+		return ret;
+
+	ret = rtl8127a_ptp_poll(ptp, RTL8127A_PTP_CLK_CFG,
+				RTL8127A_PTP_CLK_CFG_EXEC, false);
+
+	return ret < 0 ? ret : 0;
+}
+
+static int rtl8127a_ptp_read_time(struct rtl8127a_ptp *ptp, u16 base,
+				  struct timespec64 *ts)
+{
+	static const u16 regs[] = {
+		RTL8127A_PTP_NS_HI, RTL8127A_PTP_NS_LO,
+		RTL8127A_PTP_S_HI, RTL8127A_PTP_S_MI, RTL8127A_PTP_S_LO,
+	};
+	int val[ARRAY_SIZE(regs)];
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		val[i] = rtl8127a_ptp_read(ptp, base + regs[i]);
+		if (val[i] < 0)
+			return val[i];
+	}
+
+	ts->tv_nsec = (val[0] & RTL8127A_PTP_NS_HI_MASK) << 16 | val[1];
+	ts->tv_sec = (u64)val[2] << 32 | (u64)val[3] << 16 | val[4];
+
+	return 0;
+}
+
+static int rtl8127a_ptp_write_time(struct rtl8127a_ptp *ptp,
+				   const struct timespec64 *ts)
+{
+	u64 sec = ts->tv_sec & RTL8127A_PTP_SEC_MASK;
+	u32 nsec = ts->tv_nsec & RTL8127A_PTP_NSEC_MASK;
+	const struct {
+		u16 reg;
+		u16 val;
+	} regs[] = {
+		{ RTL8127A_PTP_NS_LO, nsec },
+		{ RTL8127A_PTP_NS_HI, nsec >> 16 },
+		{ RTL8127A_PTP_S_LO, sec },
+		{ RTL8127A_PTP_S_MI, sec >> 16 },
+		{ RTL8127A_PTP_S_HI, sec >> 32 },
+	};
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_CFG_TIME + regs[i].reg,
+					 regs[i].val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int rtl8127a_ptp_hwtstamp_enable(struct rtl8127a_ptp *ptp, bool enable)
+{
+	int ret;
+
+	mutex_lock(&ptp->lock);
+
+	ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_SYNCE_CTL,
+				  RTL8127A_PTP_SYNCE_EN, 0);
+	if (ret)
+		goto out;
+
+	if (enable) {
+		ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_INER, 0,
+					  RTL8127A_PTP_INT_RX_TS |
+					  RTL8127A_PTP_INT_TX_TS);
+		if (ret)
+			goto out;
+
+		ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_GEN_CFG, 0,
+					  RTL8127A_PTP_INT_CLR_MODE);
+		if (ret)
+			goto out;
+
+		ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR, 0xffff);
+		if (ret)
+			goto out;
+
+		ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_CTL,
+					 RTL8127A_PTP_CTL_TYPE_3 | BIT(12));
+	} else {
+		ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_INER,
+					  RTL8127A_PTP_INT_RX_TS |
+					  RTL8127A_PTP_INT_TX_TS, 0);
+		if (ret)
+			goto out;
+
+		ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_CTL,
+					  RTL8127A_PTP_CTL_EN, 0);
+	}
+	if (ret)
+		goto out;
+
+	ret = rtl8127a_ptp_modify(ptp, 0xa640, 0, BIT(15));
+out:
+	mutex_unlock(&ptp->lock);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_settime(struct rtl8127a_ptp *ptp,
+				const struct timespec64 *ts)
+{
+	int ret;
+
+	mutex_lock(&ptp->lock);
+	ret = rtl8127a_ptp_write_time(ptp, ts);
+	if (!ret)
+		ret = rtl8127a_ptp_clkadj(ptp, RTL8127A_PTP_DIRECT_WRITE);
+	mutex_unlock(&ptp->lock);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_adjfine(struct ptp_clock_info *info, long scaled_ppm)
+{
+	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
+	s32 ppb = scaled_ppm_to_ppb(scaled_ppm);
+	u32 rate;
+	int ret;
+
+	rate = div_u64((u64)abs(ppb) << 32, NSEC_PER_SEC);
+	if (ppb < 0)
+		rate = -rate;
+
+	mutex_lock(&ptp->lock);
+	ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_CFG_TIME + RTL8127A_PTP_NS_LO,
+				 rate);
+	if (ret)
+		goto out;
+
+	ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_CFG_TIME + RTL8127A_PTP_NS_HI,
+				 FIELD_GET(RTL8127A_PTP_RATE_HI_MASK, rate));
+	if (ret)
+		goto out;
+
+	ret = rtl8127a_ptp_clkadj(ptp, RTL8127A_PTP_RATE_WRITE);
+out:
+	mutex_unlock(&ptp->lock);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_adjtime(struct ptp_clock_info *info, s64 delta)
+{
+	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
+	struct timespec64 d = ns_to_timespec64(abs(delta));
+	int ret;
+
+	mutex_lock(&ptp->lock);
+	ret = rtl8127a_ptp_write_time(ptp, &d);
+	if (!ret)
+		ret = rtl8127a_ptp_clkadj(ptp, delta < 0 ?
+						RTL8127A_PTP_DEC_STEP :
+						RTL8127A_PTP_INC_STEP);
+	mutex_unlock(&ptp->lock);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_gettimex64(struct ptp_clock_info *info,
+				   struct timespec64 *ts,
+				   struct ptp_system_timestamp *sts)
+{
+	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
+	int ret;
+
+	mutex_lock(&ptp->lock);
+	ptp_read_system_prets(sts);
+	ret = rtl8127a_ptp_clkadj(ptp, RTL8127A_PTP_DIRECT_READ);
+	ptp_read_system_postts(sts);
+	if (!ret)
+		ret = rtl8127a_ptp_read_time(ptp, RTL8127A_PTP_CFG_TIME, ts);
+	mutex_unlock(&ptp->lock);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_settime64(struct ptp_clock_info *info,
+				  const struct timespec64 *ts)
+{
+	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
+
+	return rtl8127a_ptp_settime(ptp, ts);
+}
+
+static const struct ptp_clock_info rtl8127a_ptp_info = {
+	.owner		= THIS_MODULE,
+	.name		= "RTL8127A PHC",
+	.max_adj	= RTL8127A_PTP_MAX_ADJ,
+	.adjfine	= rtl8127a_ptp_adjfine,
+	.adjtime	= rtl8127a_ptp_adjtime,
+	.gettimex64	= rtl8127a_ptp_gettimex64,
+	.settime64	= rtl8127a_ptp_settime64,
+};
+
+int rtl8127a_ptp_config_init(struct phy_device *phydev)
+{
+	struct rtl8127a_ptp *ptp = phydev->priv;
+	struct timespec64 ts;
+
+	if (!ptp)
+		return 0;
+
+	/* (Re)start the clock from the system time whenever the PHY is
+	 * (re)initialized, e.g. each time the MAC driver brings it up.
+	 */
+	ktime_get_real_ts64(&ts);
+
+	return rtl8127a_ptp_settime(ptp, &ts);
+}
+
+int rtl8127a_ptp_probe(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct rtl8127a_ptp *ptp;
+	int ret;
+
+	ptp = devm_kzalloc(dev, sizeof(*ptp), GFP_KERNEL);
+	if (!ptp)
+		return -ENOMEM;
+
+	ret = devm_mutex_init(dev, &ptp->lock);
+	if (ret)
+		return ret;
+
+	ptp->phydev = phydev;
+	ptp->info = rtl8127a_ptp_info;
+
+	ret = rtl8127a_ptp_hwtstamp_enable(ptp, false);
+	if (ret)
+		return ret;
+
+	ptp->clock = ptp_clock_register(&ptp->info, dev);
+	if (IS_ERR(ptp->clock))
+		return PTR_ERR(ptp->clock);
+
+	phydev->priv = ptp;
+
+	return 0;
+}
+
+void rtl8127a_ptp_remove(struct phy_device *phydev)
+{
+	struct rtl8127a_ptp *ptp = phydev->priv;
+
+	if (!ptp)
+		return;
+
+	rtl8127a_ptp_hwtstamp_enable(ptp, false);
+	ptp_clock_unregister(ptp->clock);
+}
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping for RTL8127a
  2026-11-11  8:38 [PATCH net-next v2 0/2] net: phy: realtek: add PTP support for RTL8127a javen
  2026-11-11  8:38 ` [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock " javen
@ 2026-11-11  8:38 ` javen
  2026-11-15  9:05   ` netdev-bot+sashiko
  1 sibling, 1 reply; 5+ messages in thread
From: javen @ 2026-11-11  8:38 UTC (permalink / raw)
  To: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier
  Cc: netdev, linux-kernel, daniel, vladimir.oltean, Javen Xu

From: Javen Xu <javen_xu@realsil.com.cn>

Add mii_timestamper support so that PTPv2 event messages are
timestamped by the RTL8127a internal PHY. The networking core
hands the packets and the hwtstamp configuration over to the PHY.

Only one TX timestamp can be pending at a time. Since the timestamps
are read via MDIO, which may sleep, both TX and RX timestamps are
retrieved from the PTP aux worker. A pending TX timestamp is dropped
after 15 seconds.

Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
---
 drivers/net/phy/realtek/Kconfig       |  10 +-
 drivers/net/phy/realtek/realtek_ptp.c | 349 +++++++++++++++++++++++++-
 2 files changed, 352 insertions(+), 7 deletions(-)

diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig
index 93a8fb552834..ab375c42a7f2 100644
--- a/drivers/net/phy/realtek/Kconfig
+++ b/drivers/net/phy/realtek/Kconfig
@@ -16,12 +16,12 @@ config REALTEK_PHY_HWMON
 config REALTEK_PHY_PTP
 	bool "PTP support for Realtek PHYs"
 	default y
-	depends on PTP_1588_CLOCK
+	depends on PTP_1588_CLOCK && NETWORK_PHY_TIMESTAMPING
 	depends on !(REALTEK_PHY=y && PTP_1588_CLOCK=m)
 	help
-	  Optional PTP hardware clock support for the internal PHY of
-	  RTL8127A. The clock is provided by the PHY itself and can be
-	  read and adjusted through the PTP clock device, no support from
-	  the MAC driver is needed.
+	  Optional PTP hardware clock and hardware timestamping support
+	  for the internal PHY of RTL8127A. The PHY registers a PTP clock
+	  and timestamps PTPv2 event messages on behalf of the MAC, so no
+	  support from the MAC driver is needed.
 
 endif # REALTEK_PHY
diff --git a/drivers/net/phy/realtek/realtek_ptp.c b/drivers/net/phy/realtek/realtek_ptp.c
index bd5c0d6988da..5e0270762006 100644
--- a/drivers/net/phy/realtek/realtek_ptp.c
+++ b/drivers/net/phy/realtek/realtek_ptp.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- * PTP hardware clock support for the internal PHY of RTL8127A
+ * PTP hardware clock and timestamping support for the internal PHY of
+ * RTL8127A
  */
 
 #include <linux/bitfield.h>
@@ -9,7 +10,9 @@
 #include <linux/math64.h>
 #include <linux/netdevice.h>
 #include <linux/phy.h>
+#include <linux/ptp_classify.h>
 #include <linux/ptp_clock_kernel.h>
+#include <linux/skbuff.h>
 #include <linux/timekeeping.h>
 
 #include "realtek.h"
@@ -28,8 +31,19 @@
 #define RTL8127A_PTP_CLK_CFG		0xe410
 #define  RTL8127A_PTP_CLK_CFG_EXEC	BIT(0)
 #define  RTL8127A_PTP_CLK_CFG_MODE	GENMASK(3, 1)
-/* Time registers */
+#define RTL8127A_PTP_TRX_TS_STA		0xe430
+#define  RTL8127A_PTP_TRX_TS_RD		BIT(0)
+#define  RTL8127A_PTP_TRX_TS_SEL_RX	BIT(1)
+#define  RTL8127A_PTP_TRX_TS_MSGTYPE	GENMASK(3, 2)
+#define  RTL8127A_PTP_TRX_TS_EXEC	BIT(4)
+#define  RTL8127A_PTP_TX_PDLYRSP_RDY	BIT(12)
+#define  RTL8127A_PTP_TX_PDLYREQ_RDY	BIT(13)
+#define  RTL8127A_PTP_TX_DLYREQ_RDY	BIT(14)
+#define  RTL8127A_PTP_TX_SYNC_RDY	BIT(15)
+#define  RTL8127A_PTP_TX_RDY		GENMASK(15, 12)
+/* Time registers, CFG_TIME for the clock, TRX_TS_TIME for packets */
 #define RTL8127A_PTP_CFG_TIME		0xe412
+#define RTL8127A_PTP_TRX_TS_TIME	0xe446
 #define  RTL8127A_PTP_NS_LO		0x0
 #define  RTL8127A_PTP_NS_HI		0x2
 #define  RTL8127A_PTP_S_LO		0x4
@@ -41,6 +55,7 @@
 #define  RTL8127A_PTP_RATE_HI_MASK	GENMASK(21, 16)
 
 #define RTL8127A_PTP_MAX_ADJ		488281
+#define RTL8127A_PTP_TX_TIMEOUT		(15 * HZ)
 
 /* RTL8127A_PTP_CLK_CFG clock adjust modes */
 enum rtl8127a_ptp_clkadj_mode {
@@ -51,11 +66,31 @@ enum rtl8127a_ptp_clkadj_mode {
 	RTL8127A_PTP_RATE_WRITE		= 14,
 };
 
+/* ptp->state bits */
+enum rtl8127a_ptp_state {
+	RTL8127A_PTP_TX_IN_PROGRESS,
+};
+
+struct rtl8127a_ptp_skb_cb {
+	u8 msgtype;
+};
+
+#define RTL8127A_PTP_SKB_CB(skb) \
+	((struct rtl8127a_ptp_skb_cb *)(skb)->cb)
+
 struct rtl8127a_ptp {
 	struct phy_device *phydev;
+	struct mii_timestamper mii_ts;
 	struct ptp_clock_info info;
 	struct ptp_clock *clock;
 	struct mutex lock;	/* serializes PTP register access sequences */
+	enum hwtstamp_tx_types tx_type;
+	enum hwtstamp_rx_filters rx_filter;
+	bool rx_enabled;
+	struct sk_buff_head rx_queue;
+	struct sk_buff *tx_skb;
+	unsigned long tx_start;
+	unsigned long state;
 };
 
 static int rtl8127a_ptp_read(struct rtl8127a_ptp *ptp, u16 reg)
@@ -171,6 +206,64 @@ static int rtl8127a_ptp_write_time(struct rtl8127a_ptp *ptp,
 	return 0;
 }
 
+/* Latch the RX or TX timestamp of the given message type into the
+ * RTL8127A_PTP_TRX_TS_TIME registers.
+ */
+static int rtl8127a_ptp_trx_ts_cmd(struct rtl8127a_ptp *ptp, u16 cmd)
+{
+	int ret;
+
+	ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_TRX_TS_STA,
+				  RTL8127A_PTP_TRX_TS_SEL_RX |
+				  RTL8127A_PTP_TRX_TS_MSGTYPE,
+				  RTL8127A_PTP_TRX_TS_RD | cmd);
+	if (ret)
+		return ret;
+
+	ret = rtl8127a_ptp_poll(ptp, RTL8127A_PTP_TRX_TS_STA,
+				RTL8127A_PTP_TRX_TS_RD, false);
+
+	return ret < 0 ? ret : 0;
+}
+
+static int rtl8127a_ptp_egress_time(struct rtl8127a_ptp *ptp,
+				    struct timespec64 *ts)
+{
+	u8 msgtype;
+	int val;
+	int ret;
+
+	val = rtl8127a_ptp_poll(ptp, RTL8127A_PTP_TRX_TS_STA,
+				RTL8127A_PTP_TX_RDY, true);
+	if (val < 0)
+		val = 0;
+
+	switch (val & RTL8127A_PTP_TX_RDY) {
+	case RTL8127A_PTP_TX_PDLYRSP_RDY:
+		msgtype = PTP_MSGTYPE_PDELAY_RESP;
+		break;
+	case RTL8127A_PTP_TX_PDLYREQ_RDY:
+		msgtype = PTP_MSGTYPE_PDELAY_REQ;
+		break;
+	case RTL8127A_PTP_TX_DLYREQ_RDY:
+		msgtype = PTP_MSGTYPE_DELAY_REQ;
+		break;
+	case RTL8127A_PTP_TX_SYNC_RDY:
+	default:
+		msgtype = PTP_MSGTYPE_SYNC;
+		break;
+	}
+
+	ret = rtl8127a_ptp_trx_ts_cmd(ptp,
+				      FIELD_PREP(RTL8127A_PTP_TRX_TS_MSGTYPE,
+						 msgtype) |
+				      RTL8127A_PTP_TRX_TS_EXEC);
+	if (ret)
+		return ret;
+
+	return rtl8127a_ptp_read_time(ptp, RTL8127A_PTP_TRX_TS_TIME, ts);
+}
+
 static int rtl8127a_ptp_hwtstamp_enable(struct rtl8127a_ptp *ptp, bool enable)
 {
 	int ret;
@@ -306,6 +399,101 @@ static int rtl8127a_ptp_settime64(struct ptp_clock_info *info,
 	return rtl8127a_ptp_settime(ptp, ts);
 }
 
+static void rtl8127a_ptp_tx_complete(struct rtl8127a_ptp *ptp,
+				     struct skb_shared_hwtstamps *hwts)
+{
+	struct sk_buff *skb = ptp->tx_skb;
+
+	ptp->tx_skb = NULL;
+	clear_bit_unlock(RTL8127A_PTP_TX_IN_PROGRESS, &ptp->state);
+
+	if (hwts)
+		skb_complete_tx_timestamp(skb, hwts);
+	else
+		kfree_skb(skb);
+}
+
+/* Returns true if no TX timestamp is pending anymore */
+static bool rtl8127a_ptp_tx_tstamp(struct rtl8127a_ptp *ptp)
+{
+	struct skb_shared_hwtstamps hwts = {};
+	struct timespec64 ts;
+	int ret;
+
+	/* Pairs with smp_store_release() in rtl8127a_ptp_txtstamp() */
+	if (!smp_load_acquire(&ptp->tx_skb))
+		return true;
+
+	mutex_lock(&ptp->lock);
+
+	if (time_is_before_jiffies(ptp->tx_start + RTL8127A_PTP_TX_TIMEOUT)) {
+		rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR,
+				   RTL8127A_PTP_INT_TX_TS);
+		mutex_unlock(&ptp->lock);
+
+		rtl8127a_ptp_tx_complete(ptp, NULL);
+		return true;
+	}
+
+	ret = rtl8127a_ptp_read(ptp, RTL8127A_PTP_INSR);
+	if (ret < 0 || !(ret & RTL8127A_PTP_INT_TX_TS)) {
+		mutex_unlock(&ptp->lock);
+		return false;
+	}
+
+	ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR,
+				 RTL8127A_PTP_INT_TX_TS);
+	if (!ret)
+		ret = rtl8127a_ptp_egress_time(ptp, &ts);
+
+	mutex_unlock(&ptp->lock);
+
+	if (ret) {
+		rtl8127a_ptp_tx_complete(ptp, NULL);
+		return true;
+	}
+
+	hwts.hwtstamp = timespec64_to_ktime(ts);
+	rtl8127a_ptp_tx_complete(ptp, &hwts);
+
+	return true;
+}
+
+static void rtl8127a_ptp_rx_tstamp(struct rtl8127a_ptp *ptp,
+				   struct sk_buff *skb)
+{
+	u8 msgtype = RTL8127A_PTP_SKB_CB(skb)->msgtype;
+	struct timespec64 ts;
+	int ret;
+
+	mutex_lock(&ptp->lock);
+	ret = rtl8127a_ptp_trx_ts_cmd(ptp, RTL8127A_PTP_TRX_TS_SEL_RX |
+					   FIELD_PREP(RTL8127A_PTP_TRX_TS_MSGTYPE,
+						      msgtype) |
+					   RTL8127A_PTP_TRX_TS_EXEC);
+	if (!ret)
+		ret = rtl8127a_ptp_read_time(ptp, RTL8127A_PTP_TRX_TS_TIME,
+					     &ts);
+	mutex_unlock(&ptp->lock);
+
+	if (!ret)
+		skb_hwtstamps(skb)->hwtstamp = timespec64_to_ktime(ts);
+
+	netif_rx(skb);
+}
+
+static long rtl8127a_ptp_do_aux_work(struct ptp_clock_info *info)
+{
+	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
+	struct sk_buff *skb;
+
+	while ((skb = skb_dequeue(&ptp->rx_queue)))
+		rtl8127a_ptp_rx_tstamp(ptp, skb);
+
+	/* Keep polling until the pending TX timestamp is available */
+	return rtl8127a_ptp_tx_tstamp(ptp) ? -1 : 0;
+}
+
 static const struct ptp_clock_info rtl8127a_ptp_info = {
 	.owner		= THIS_MODULE,
 	.name		= "RTL8127A PHC",
@@ -314,8 +502,146 @@ static const struct ptp_clock_info rtl8127a_ptp_info = {
 	.adjtime	= rtl8127a_ptp_adjtime,
 	.gettimex64	= rtl8127a_ptp_gettimex64,
 	.settime64	= rtl8127a_ptp_settime64,
+	.do_aux_work	= rtl8127a_ptp_do_aux_work,
 };
 
+static void rtl8127a_ptp_txtstamp(struct mii_timestamper *mii_ts,
+				  struct sk_buff *skb, int type)
+{
+	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
+						mii_ts);
+
+	/* Only one TX timestamp can be pending at a time */
+	if (READ_ONCE(ptp->tx_type) != HWTSTAMP_TX_ON ||
+	    test_and_set_bit_lock(RTL8127A_PTP_TX_IN_PROGRESS, &ptp->state)) {
+		kfree_skb(skb);
+		return;
+	}
+
+	skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+	ptp->tx_start = jiffies;
+	/* Publish tx_start before tx_skb to the aux worker */
+	smp_store_release(&ptp->tx_skb, skb);
+	ptp_schedule_worker(ptp->clock, 0);
+}
+
+static bool rtl8127a_ptp_rxtstamp(struct mii_timestamper *mii_ts,
+				  struct sk_buff *skb, int type)
+{
+	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
+						mii_ts);
+	struct ptp_header *hdr;
+	u8 msgtype;
+
+	if (!READ_ONCE(ptp->rx_enabled))
+		return false;
+
+	hdr = ptp_parse_header(skb, type);
+	if (!hdr)
+		return false;
+
+	/* Only event messages are timestamped by the hardware */
+	msgtype = ptp_get_msgtype(hdr, type);
+	if (msgtype > PTP_MSGTYPE_PDELAY_RESP)
+		return false;
+
+	RTL8127A_PTP_SKB_CB(skb)->msgtype = msgtype;
+	skb_queue_tail(&ptp->rx_queue, skb);
+	ptp_schedule_worker(ptp->clock, 0);
+
+	return true;
+}
+
+static int rtl8127a_ptp_hwtstamp_get(struct mii_timestamper *mii_ts,
+				     struct kernel_hwtstamp_config *config)
+{
+	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
+						mii_ts);
+
+	config->tx_type = ptp->tx_type;
+	config->rx_filter = ptp->rx_filter;
+
+	return 0;
+}
+
+static int rtl8127a_ptp_hwtstamp_set(struct mii_timestamper *mii_ts,
+				     struct kernel_hwtstamp_config *config,
+				     struct netlink_ext_ack *extack)
+{
+	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
+						mii_ts);
+	bool enable = false;
+	bool rx_enabled;
+	int ret = 0;
+
+	if (config->flags)
+		return -EINVAL;
+
+	switch (config->tx_type) {
+	case HWTSTAMP_TX_ON:
+		enable = true;
+		break;
+	case HWTSTAMP_TX_OFF:
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	switch (config->rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		rx_enabled = false;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+	case HWTSTAMP_FILTER_PTP_V2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+		config->rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
+		rx_enabled = true;
+		enable = true;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	if (ptp->tx_type != config->tx_type ||
+	    ptp->rx_filter != config->rx_filter) {
+		WRITE_ONCE(ptp->tx_type, config->tx_type);
+		ptp->rx_filter = config->rx_filter;
+		ret = rtl8127a_ptp_hwtstamp_enable(ptp, enable);
+	}
+	WRITE_ONCE(ptp->rx_enabled, rx_enabled);
+
+	return ret;
+}
+
+static int rtl8127a_ptp_ts_info(struct mii_timestamper *mii_ts,
+				struct kernel_ethtool_ts_info *info)
+{
+	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
+						mii_ts);
+
+	info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+				SOF_TIMESTAMPING_TX_HARDWARE |
+				SOF_TIMESTAMPING_RX_HARDWARE |
+				SOF_TIMESTAMPING_RAW_HARDWARE;
+	info->phc_index = ptp_clock_index(ptp->clock);
+	info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
+	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
+			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ);
+
+	return 0;
+}
+
 int rtl8127a_ptp_config_init(struct phy_device *phydev)
 {
 	struct rtl8127a_ptp *ptp = phydev->priv;
@@ -348,6 +674,9 @@ int rtl8127a_ptp_probe(struct phy_device *phydev)
 
 	ptp->phydev = phydev;
 	ptp->info = rtl8127a_ptp_info;
+	ptp->tx_type = HWTSTAMP_TX_OFF;
+	ptp->rx_filter = HWTSTAMP_FILTER_NONE;
+	skb_queue_head_init(&ptp->rx_queue);
 
 	ret = rtl8127a_ptp_hwtstamp_enable(ptp, false);
 	if (ret)
@@ -357,7 +686,15 @@ int rtl8127a_ptp_probe(struct phy_device *phydev)
 	if (IS_ERR(ptp->clock))
 		return PTR_ERR(ptp->clock);
 
+	ptp->mii_ts.rxtstamp = rtl8127a_ptp_rxtstamp;
+	ptp->mii_ts.txtstamp = rtl8127a_ptp_txtstamp;
+	ptp->mii_ts.hwtstamp_set = rtl8127a_ptp_hwtstamp_set;
+	ptp->mii_ts.hwtstamp_get = rtl8127a_ptp_hwtstamp_get;
+	ptp->mii_ts.ts_info = rtl8127a_ptp_ts_info;
+
 	phydev->priv = ptp;
+	phydev->mii_ts = &ptp->mii_ts;
+	phydev->default_timestamp = true;
 
 	return 0;
 }
@@ -369,6 +706,14 @@ void rtl8127a_ptp_remove(struct phy_device *phydev)
 	if (!ptp)
 		return;
 
+	phydev->mii_ts = NULL;
+	phydev->default_timestamp = false;
+
 	rtl8127a_ptp_hwtstamp_enable(ptp, false);
+	/* Also stops the aux worker */
 	ptp_clock_unregister(ptp->clock);
+
+	if (ptp->tx_skb)
+		rtl8127a_ptp_tx_complete(ptp, NULL);
+	skb_queue_purge(&ptp->rx_queue);
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock for RTL8127a
  2026-11-11  8:38 ` [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock " javen
@ 2026-11-15  9:05   ` netdev-bot+sashiko
  0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15  9:05 UTC (permalink / raw)
  To: javen_xu
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier, netdev, linux-kernel, daniel,
	vladimir.oltean

Thank you for your contribution! Sashiko AI review found 6 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 4 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [Medium] rtl8127a_ptp_remove()
  (drivers/net/phy/realtek/realtek_ptp.c:365-374) unregisters the clock…
- [Medium] The patch adds a PHC whose callbacks
  (gettimex64/settime64/adjtime/adjfine) can reach the PHY's GPHY_OCP…
- [Medium] The PHC is registered at PHY probe and stays reachable from
  userspace for the whole binding.
- [Medium] The PHC is seeded in .config_init, which runs from
  phy_init_hw() before the rest of r8169's PHY bring-up.
- [Low] rtl8127a_ptp_hwtstamp_enable() ends with
  `rtl8127a_ptp_modify(ptp, 0xa640, 0, BIT(15))` (realtek_ptp.c:216),…
- [Low] In rtl8127a_ptp_gettimex64() (realtek_ptp.c:290-293),
  ptp_read_system_prets() and ptp_read_system_postts() bracket the whole…

--

net: phy: realtek: add PTP clock for RTL8127a

This patch registers a PTP hardware clock for the internal PHY of RTL8127A
(RTL8261C, PHY ID 0x001cc890). The PHY's free-running clock is read, set,
stepped and frequency-adjusted through vendor MMD registers, and a driver
mutex serializes multi-register sequences. The clock is seeded from system
time in config_init each time the PHY is initialized.

> diff --git a/drivers/net/phy/realtek/realtek_ptp.c b/drivers/net/phy/realtek/realtek_ptp.c
> new file mode 100644
> index 0000000000000..bd5c0d6988dae
> --- /dev/null
> +++ b/drivers/net/phy/realtek/realtek_ptp.c
> @@ -0,0 +1,374 @@

[ ... ]

> +static int rtl8127a_ptp_read(struct rtl8127a_ptp *ptp, u16 reg)
> +{
> +	return phy_read_mmd(ptp->phydev, MDIO_MMD_VEND2, reg);
> +}
> +
> +static int rtl8127a_ptp_write(struct rtl8127a_ptp *ptp, u16 reg, u16 val)
> +{
> +	return phy_write_mmd(ptp->phydev, MDIO_MMD_VEND2, reg, val);
> +}

[Severity: Medium]
Do these accesses need more than the MDIO bus lock to stay serialized
against r8169? The PTP accessors end up on the MAC's GPHY_OCP register:

phy_read_mmd()
  rtl822x_read_mmd()
    rtlgen_read_mmd()
      rtlgen_read_vend2()
        __mdiobus_c45_read()
          r8169_mdio_read_reg_c45()
            r8168_phy_ocp_read()

r8169 loads the RTL8127A PHY firmware through the same register, and that
path does not take the MDIO bus lock:

rtl8169_up()
  rtl8169_init_phy()
    r8169_hw_phy_config()
      rtl8127a_1_hw_phy_config()
        r8169_apply_firmware()
          rtl_fw_write_firmware()
            rtl_writephy()
              r8168g_mdio_write()
                r8168_phy_ocp_write()
                  RTL_W32(tp, GPHY_OCP, OCPAR_FLAG | (reg << 15) | data);

/dev/ptpN exists from PHY probe onward. A gettimex64, settime64, adjtime
or adjfine call could arrive from phc2sys or a chrony PHC refclock during
interface open, the reset worker, or runtime/system resume. Its GPHY_OCP
transactions could then interleave with the firmware patch writes.

Could that lose or redirect a firmware write, or hand the other
transaction's data back to the PTP code?

The unlocked firmware loader in r8169 predates this patch. This patch adds
a path that userspace polls continuously.

[ ... ]

> +static int rtl8127a_ptp_hwtstamp_enable(struct rtl8127a_ptp *ptp, bool enable)
> +{

[ ... ]

> +	if (ret)
> +		goto out;
> +
> +	ret = rtl8127a_ptp_modify(ptp, 0xa640, 0, BIT(15));

[Severity: Low]
This isn't a bug, but what does BIT(15) of 0xa640 control? The register is
outside the 0xe4xx PTP block and has no define or comment.

Both the enable and the disable branch reach this write. Probe and remove
only call rtl8127a_ptp_hwtstamp_enable(ptp, false), but they still set the
bit, and nothing ever clears it.

If the bit turns on some PHY-core timestamping function, does it stay on
after disable, remove() and driver unbind? The later hardware timestamping
patch in the series keeps the same unconditional write.

> +out:
> +	mutex_unlock(&ptp->lock);

[ ... ]

> +static int rtl8127a_ptp_gettimex64(struct ptp_clock_info *info,
> +				   struct timespec64 *ts,
> +				   struct ptp_system_timestamp *sts)
> +{
> +	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
> +	int ret;
> +
> +	mutex_lock(&ptp->lock);
> +	ptp_read_system_prets(sts);
> +	ret = rtl8127a_ptp_clkadj(ptp, RTL8127A_PTP_DIRECT_READ);
> +	ptp_read_system_postts(sts);

[Severity: Low]
Should prets/postts surround only the EXEC write that latches the counter?
rtl8127a_ptp_clkadj() first does an MDIO read of CLK_CFG inside
rtl8127a_ptp_modify(), then the EXEC write, then a read_poll_timeout()
(1us sleep, 1ms timeout) until EXEC clears.

All of that falls inside the system-time window. The window is wider than
it needs to be, and its midpoint is shifted away from the actual latch
point.

Taking prets just before the EXEC write and postts just after it, then
polling, would avoid this.

> +	if (!ret)
> +		ret = rtl8127a_ptp_read_time(ptp, RTL8127A_PTP_CFG_TIME, ts);
> +	mutex_unlock(&ptp->lock);

[ ... ]

> +int rtl8127a_ptp_config_init(struct phy_device *phydev)
> +{
> +	struct rtl8127a_ptp *ptp = phydev->priv;
> +	struct timespec64 ts;
> +
> +	if (!ptp)
> +		return 0;
> +
> +	/* (Re)start the clock from the system time whenever the PHY is
> +	 * (re)initialized, e.g. each time the MAC driver brings it up.
> +	 */
> +	ktime_get_real_ts64(&ts);
> +
> +	return rtl8127a_ptp_settime(ptp, &ts);

[Severity: Medium]
Does the time written here survive the rest of the r8169 PHY bring-up?
config_init runs from phy_init_hw(), before the firmware load and the soft
reset:

drivers/net/ethernet/realtek/r8169_main.c:rtl8169_init_phy() {
	phy_init_hw(tp->phydev);
	phy_resume(tp->phydev);
	r8169_hw_phy_config(tp, tp->phydev, tp->mac_version);
	...
	genphy_soft_reset(tp->phydev);
}

rtl8127a_1_hw_phy_config() starts with r8169_apply_firmware(), which loads
the PHY MCU patch and polls for BMCR_RESET. It then writes a number of PHY
parameters.

If the firmware load or the BMCR soft reset resets the PTP block, the
commit message would no longer hold:

  "The clock is started from the system time each time the PHY is
  initialized."

The probe-time programming in rtl8127a_ptp_hwtstamp_enable() (SYNCE_CTL,
INER, CTL, 0xa640) would be lost as well.

With the later hardware timestamping patch, the PTP_CTL/INER/GEN_CFG setup
written by rtl8127a_ptp_hwtstamp_set() is never re-applied after
rtl8169_up() either (open, reset worker, resume). hwtstamp_set() only calls
rtl8127a_ptp_hwtstamp_enable() when tx_type or rx_filter change, so
re-issuing the same configuration would not restore it.

Could hardware timestamping then stop silently after a MAC reset or
resume? The code alone doesn't show whether these registers are actually
cleared; that depends on the hardware.

> +}
> +
> +int rtl8127a_ptp_probe(struct phy_device *phydev)
> +{
> +	struct device *dev = &phydev->mdio.dev;
> +	struct rtl8127a_ptp *ptp;
> +	int ret;
> +
> +	ptp = devm_kzalloc(dev, sizeof(*ptp), GFP_KERNEL);
> +	if (!ptp)
> +		return -ENOMEM;

[ ... ]

> +	ptp->clock = ptp_clock_register(&ptp->info, dev);
> +	if (IS_ERR(ptp->clock))
> +		return PTR_ERR(ptp->clock);

[Severity: Medium]
What happens to PHC users while the r8169 PCI function is
runtime-suspended? The clock stays registered for the whole PHY binding.
r8169 schedules a runtime suspend 10s after the interface goes down or
loses carrier:

drivers/net/ethernet/realtek/r8169_main.c:rtl8169_runtime_idle() {
	...
	if (!netif_running(tp->dev) || !netif_carrier_ok(tp->dev))
		pm_schedule_suspend(device, 10000);
	...
}

In D3hot, r8168_phy_ocp_read() returns 0xffff. r8168_phy_ocp_write() times
out and logs a rtl_loop_wait error, but r8169_mdio_write_reg_c45() still
returns 0.

Every PHC operation (clock_gettime, settime, adjtime, adjfine, and
PTP_SYS_OFFSET_EXTENDED with up to 25 samples) would then send MMIO to a
suspended device. It would busy-wait through the write timeouts and the
1ms EXEC poll and then fail with -ETIMEDOUT.

Other userspace-reachable r8169 paths guard against this: the LED ops use
pm_runtime_resume_and_get() and get_stats64 checks pm_runtime_active().
This PHY-side path has no such guard.

When the link comes back, the resume path re-seeds the clock:

rtl8169_runtime_resume()
  rtl8169_up()
    rtl8169_init_phy()
      phy_init_hw()
        rtl8127a_ptp_config_init()

That steps the PHC to CLOCK_REALTIME. A ptp4l/phc2sys session would see
errors while the cable is unplugged and then a clock jump. This is still
the case at the end of the series.

> +
> +	phydev->priv = ptp;
> +
> +	return 0;
> +}
> +
> +void rtl8127a_ptp_remove(struct phy_device *phydev)
> +{
> +	struct rtl8127a_ptp *ptp = phydev->priv;
> +
> +	if (!ptp)
> +		return;
> +
> +	rtl8127a_ptp_hwtstamp_enable(ptp, false);
> +	ptp_clock_unregister(ptp->clock);
> +}

[Severity: Medium]
Should phydev->priv be cleared here? rtl8127a_ptp_probe() allocates ptp
with devm_kzalloc() on &phydev->mdio.dev. Devres frees it right after
remove() returns, but phydev->priv still points at it.

rtl8127a_ptp_config_init() and rtl8127a_ptp_remove() treat any non-NULL
priv as a live object. Consider this sequence:

  - the realtek PHY driver is unbound through sysfs while r8169 stays
    attached
  - it is rebound, and the probe fails before phydev->priv = ptp
    (devm_kzalloc() failure, an MDIO timeout in
    rtl8127a_ptp_hwtstamp_enable(), a ptp_clock_register() failure, or a
    hwmon init failure in rtl822x_probe())

phy_probe() sets phydev->drv before calling drv->probe and does not clear
it on the out_reset path. The next rtl8169_up() would then do:

rtl8169_init_phy()
  phy_init_hw()
    rtl8127a_ptp_config_init()
      rtl8127a_ptp_settime()
        mutex_lock(&ptp->lock);

Wouldn't that be a use-after-free on the devres memory? The later hardware
timestamping patch keeps the same remove() and only clears mii_ts and
default_timestamp, so the problem remains at the end of the series.

Setting phydev->priv = NULL in rtl8127a_ptp_remove() would avoid it.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261111083807.1529-1-javen_xu%40realsil.com.cn

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping for RTL8127a
  2026-11-11  8:38 ` [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping " javen
@ 2026-11-15  9:05   ` netdev-bot+sashiko
  0 siblings, 0 replies; 5+ messages in thread
From: netdev-bot+sashiko @ 2026-11-15  9:05 UTC (permalink / raw)
  To: javen_xu
  Cc: andrew, hkallweit1, linux, davem, edumazet, kuba, pabeni,
	freddy_gu, nb, maxime.chevallier, netdev, linux-kernel, daniel,
	vladimir.oltean

Thank you for your contribution! Sashiko AI review found 12 potential
issue(s) to consider.

Critical: 0 · High: 3 · Medium: 7 · Low: 2

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] rtl8127a_ptp_txtstamp()
  (drivers/net/phy/realtek/realtek_ptp.c:508-526) hands the driver's…
- [High] rtl8127a_ptp_remove() (lines 702-719) clears phydev->mii_ts with
  a plain store and immediately tears down: ptp_clock_unregister()…
- [High] rtl8127a_ptp_rxtstamp() (lines 528-553) takes ownership of every
  received PTP event packet and queues it on ptp->rx_queue with…
- [Medium] While a TX timestamp is pending, rtl8127a_ptp_do_aux_work()
  (line 494) returns 0.
- [Medium] rtl8127a_ptp_config_init() (lines 645-659) runs on every
  phy_init_hw(), for example from rtl8169_init_phy()…
- [Medium] rtl8127a_ptp_egress_time() (lines 229-265) treats a failed or
  ambiguous TX-ready poll as if a Sync timestamp were ready.
- [Medium] rtl8127a_ptp_hwtstamp_set() stores the new state
  (`WRITE_ONCE(ptp->tx_type, ...)` and `ptp->rx_filter = ...`) before…
- [Medium] RX timestamps are read late from what the register interface
  shows as one capture per message type (TRX_TS_STA SEL_RX + MSGTYPE ->…
- [Medium] rtl8127a_ptp_hwtstamp_set() reprograms the hardware without
  coordinating with an in-flight TX timestamp.
- [Medium] The driver tracks a single TX timestamp in software, but the
  PHY timestamps every outgoing event message, and nothing ties the…
- [Low] The patch changes REALTEK_PHY_PTP to `depends on PTP_1588_CLOCK
  && NETWORK_PHY_TIMESTAMPING` (drivers/net/phy/realtek/Kconfig:19).
- [Low] rtl8127a_ptp_ts_info() (lines 622-643) advertises rx_filters
  NONE, PTP_V2_EVENT, PTP_V2_L4_EVENT, PTP_V2_SYNC, PTP_V2_L4_SYNC,…

--

net: phy: realtek: add hardware timestamping for RTL8127a

This commit adds mii_timestamper support to the RTL8127A internal PHY so
that the PHY hardware timestamps PTPv2 event messages. The PTP aux worker
reads both TX and RX timestamps over MDIO. Only one TX timestamp can be
pending at a time, and it is dropped after 15 seconds.

> diff --git a/drivers/net/phy/realtek/Kconfig b/drivers/net/phy/realtek/Kconfig
> index 93a8fb552834f..ab375c42a7f22 100644
> --- a/drivers/net/phy/realtek/Kconfig
> +++ b/drivers/net/phy/realtek/Kconfig
> @@ -16,12 +16,12 @@ config REALTEK_PHY_HWMON
>  config REALTEK_PHY_PTP
>  	bool "PTP support for Realtek PHYs"
>  	default y
> -	depends on PTP_1588_CLOCK
> +	depends on PTP_1588_CLOCK && NETWORK_PHY_TIMESTAMPING

[Severity: Low]
Does this remove the existing PHC support on configs that don't enable
NETWORK_PHY_TIMESTAMPING?

NETWORK_PHY_TIMESTAMPING in net/Kconfig is a bool with no default. Its
help text says "If you are unsure how to answer this question, answer N."

Take an existing .config with PTP_1588_CLOCK=y but no
NETWORK_PHY_TIMESTAMPING. olddefconfig will now silently set
REALTEK_PHY_PTP=n, and the RTL8127A PHC goes away even though it only
needed PTP_1588_CLOCK before.

Only the mii_timestamper part seems to need PHY timestamping. Could that
part be made conditional instead? If not, could the commit message
mention that the PHC now needs this option?

[ ... ]

> diff --git a/drivers/net/phy/realtek/realtek_ptp.c b/drivers/net/phy/realtek/realtek_ptp.c
> index bd5c0d6988dae..5e02707620064 100644
> --- a/drivers/net/phy/realtek/realtek_ptp.c
> +++ b/drivers/net/phy/realtek/realtek_ptp.c

[ ... ]

> @@ -171,6 +206,64 @@ static int rtl8127a_ptp_write_time(struct rtl8127a_ptp *ptp,

[ ... ]

> +static int rtl8127a_ptp_trx_ts_cmd(struct rtl8127a_ptp *ptp, u16 cmd)
> +{
> +	int ret;
> +
> +	ret = rtl8127a_ptp_modify(ptp, RTL8127A_PTP_TRX_TS_STA,
> +				  RTL8127A_PTP_TRX_TS_SEL_RX |
> +				  RTL8127A_PTP_TRX_TS_MSGTYPE,
> +				  RTL8127A_PTP_TRX_TS_RD | cmd);

[ ... ]

> +static int rtl8127a_ptp_egress_time(struct rtl8127a_ptp *ptp,
> +				    struct timespec64 *ts)
> +{

[ ... ]

> +	val = rtl8127a_ptp_poll(ptp, RTL8127A_PTP_TRX_TS_STA,
> +				RTL8127A_PTP_TX_RDY, true);
> +	if (val < 0)
> +		val = 0;
> +
> +	switch (val & RTL8127A_PTP_TX_RDY) {

[ ... ]

> +	case RTL8127A_PTP_TX_SYNC_RDY:
> +	default:
> +		msgtype = PTP_MSGTYPE_SYNC;
> +		break;
> +	}

[Severity: Medium]
What happens here when the poll fails, or when more than one ready bit is
set?

If rtl8127a_ptp_poll() gets an MDIO error or times out after 1 ms, val
becomes 0. If several of bits 12-15 are set, none of the single-bit cases
match. Either way the code takes the default branch, latches the Sync
capture, and rtl8127a_ptp_egress_time() returns 0.

rtl8127a_ptp_tx_tstamp() then hands that value to
skb_complete_tx_timestamp() as a valid hardware timestamp, whatever the
pending skb's real message type is. For example, a Delay_Req could get
an earlier, unrelated Sync's egress time.

More than one ready bit can be set because the hardware still
timestamps frames whose clones were dropped while the slot was busy.
Should the poll failure and the ambiguous case return an error instead?
The pending skb's msgtype is also never recorded, so there is nothing to
compare against.

Also, rtl8127a_ptp_trx_ts_cmd() does a read-modify-write of TRX_TS_STA.
That writes the TX_*_RDY status bits back along with the command bits.
If those bits are write-1-to-clear, could an RX latch clear a fresh TX
ready bit?

[ ... ]

> @@ -306,6 +399,101 @@ static int rtl8127a_ptp_settime64(struct ptp_clock_info *info,

[ ... ]

> +/* Returns true if no TX timestamp is pending anymore */
> +static bool rtl8127a_ptp_tx_tstamp(struct rtl8127a_ptp *ptp)
> +{

[ ... ]

> +	if (time_is_before_jiffies(ptp->tx_start + RTL8127A_PTP_TX_TIMEOUT)) {
> +		rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR,
> +				   RTL8127A_PTP_INT_TX_TS);
> +		mutex_unlock(&ptp->lock);
> +
> +		rtl8127a_ptp_tx_complete(ptp, NULL);
> +		return true;
> +	}
> +
> +	ret = rtl8127a_ptp_read(ptp, RTL8127A_PTP_INSR);
> +	if (ret < 0 || !(ret & RTL8127A_PTP_INT_TX_TS)) {
> +		mutex_unlock(&ptp->lock);
> +		return false;
> +	}
> +
> +	ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR,
> +				 RTL8127A_PTP_INT_TX_TS);
> +	if (!ret)
> +		ret = rtl8127a_ptp_egress_time(ptp, &ts);

[Severity: Medium]
How is the TX_TS event tied to the skb in ptp->tx_skb?

The driver tracks one TX timestamp in software, but the PHY timestamps
every outgoing event message. When the slot is busy,
rtl8127a_ptp_txtstamp() frees the clone. The original frame is still
sent and timestamped, which sets its TX_*_RDY bit and INSR TX_TS.

Suppose an untracked frame raises TX_TS after the INSR clear above. The
next armed skb can see TX_TS on the worker's first pass, possibly before
its own frame has left the MAC, and get the untracked frame's timestamp.

An untracked frame of the same type can also overwrite the per-type
capture before the pending skb is latched. One of a different type
leaves a stale ready bit, which later sends rtl8127a_ptp_egress_time()
into the SYNC fallback.

The timeout path clears only INSR TX_TS, not the latched ready bits.
Nothing clears stale TX_TS or ready state when rtl8127a_ptp_txtstamp()
arms a new skb, and the skb's msgtype and sequenceId are never recorded
or compared.

Can this attach TX timestamps to the wrong packet when two event
messages go out close together? Examples are multiple PTP instances,
overlapping P2P Pdelay_Req/Resp, or ptp4l moving on after its
tx_timestamp_timeout.

[ ... ]

> +static void rtl8127a_ptp_rx_tstamp(struct rtl8127a_ptp *ptp,
> +				   struct sk_buff *skb)
> +{

[ ... ]

> +	mutex_lock(&ptp->lock);
> +	ret = rtl8127a_ptp_trx_ts_cmd(ptp, RTL8127A_PTP_TRX_TS_SEL_RX |
> +					   FIELD_PREP(RTL8127A_PTP_TRX_TS_MSGTYPE,
> +						      msgtype) |
> +					   RTL8127A_PTP_TRX_TS_EXEC);
> +	if (!ret)
> +		ret = rtl8127a_ptp_read_time(ptp, RTL8127A_PTP_TRX_TS_TIME,
> +					     &ts);
> +	mutex_unlock(&ptp->lock);
> +
> +	if (!ret)
> +		skb_hwtstamps(skb)->hwtstamp = timespec64_to_ktime(ts);

[Severity: Medium]
Can this give the skb another packet's ingress time?

The register interface appears to hold one RX capture per message type,
and the aux worker reads it here some time after the frame arrived. The
code does not check INSR RX_TS or any valid flag. It does not compare the
sequenceId, and it does not acknowledge RX_TS, even though
rtl8127a_ptp_hwtstamp_enable() unmasks it in INER.

If two event messages of the same type arrive before the worker handles
the first one, the first skb gets the second packet's time. A frame the
hardware did not capture gets a stale value, which is still reported as
a valid hardware timestamp.

rtl8127a_ptp_rxtstamp() also does not reject PTPv1 frames. For v1,
ptp_get_msgtype() returns the control field, so v1 frames with control
<= 3 are queued and latched against the v2 per-type captures.

Anyone who can send event messages to UDP port 319 can trigger this.
Could a remote sender make ptp4l's genuine Sync or Delay_Req carry the
arrival time of the sender's own packet?

I could not confirm whether the hardware keeps one register per type or
a FIFO.

> +
> +	netif_rx(skb);
> +}
> +
> +static long rtl8127a_ptp_do_aux_work(struct ptp_clock_info *info)
> +{
> +	struct rtl8127a_ptp *ptp = container_of(info, struct rtl8127a_ptp, info);
> +	struct sk_buff *skb;
> +
> +	while ((skb = skb_dequeue(&ptp->rx_queue)))
> +		rtl8127a_ptp_rx_tstamp(ptp, skb);
> +
> +	/* Keep polling until the pending TX timestamp is available */
> +	return rtl8127a_ptp_tx_tstamp(ptp) ? -1 : 0;
> +}

[Severity: Medium]
Does returning 0 here make the aux worker busy-poll MDIO?

ptp_aux_kworker() requeues the work with the returned delay:

drivers/ptp/ptp_clock.c:ptp_aux_kworker() {
    ...
	delay = info->do_aux_work(info);

	if (delay >= 0)
		kthread_queue_delayed_work(ptp->kworker, &ptp->aux_work, delay);
}

So while a TX timestamp is pending, the worker runs again immediately.
Each pass takes ptp->lock and reads RTL8127A_PTP_INSR over MDIO. That
competes with phylib, and with gettimex64/adjfine/adjtime calls from
phc2sys and ptp4l.

TX_TS may never arrive: the frame was dropped, the link is down, the
packet was not an event message, timestamping was disabled, or
rtl8127a_ptp_hwtstamp_enable() cleared INSR. In that case the polling
lasts the full 15 second RTL8127A_PTP_TX_TIMEOUT, and every other TX
timestamp request is dropped in the meantime.

Would a non-zero delay be more appropriate here? The commit message only
says:

    A pending TX timestamp is dropped after 15 seconds.

It does not mention the busy polling, or that TX timestamping is blocked
during that time.

[ ... ]

> @@ -314,8 +502,146 @@ static const struct ptp_clock_info rtl8127a_ptp_info = {

[ ... ]

> +static void rtl8127a_ptp_txtstamp(struct mii_timestamper *mii_ts,
> +				  struct sk_buff *skb, int type)
> +{
> +	struct rtl8127a_ptp *ptp = container_of(mii_ts, struct rtl8127a_ptp,
> +						mii_ts);
> +
> +	/* Only one TX timestamp can be pending at a time */
> +	if (READ_ONCE(ptp->tx_type) != HWTSTAMP_TX_ON ||
> +	    test_and_set_bit_lock(RTL8127A_PTP_TX_IN_PROGRESS, &ptp->state)) {
> +		kfree_skb(skb);
> +		return;
> +	}

[Severity: High]
Can an unprivileged sender take the only TX timestamp slot here?

skb_clone_tx_timestamp() calls ->txtstamp() for every socket-owned skb
the core classifies as PTP, whether or not a hardware timestamp was
requested. The classifier accepts any UDP datagram to destination port
319 regardless of messageType, and any L2 0x88F7 frame without the
general bit.

This check does not parse the PTP header and does not reject non-event
message types, which rtl8127a_ptp_rxtstamp() does on the RX side. It
also does not check the PTP version or SKBTX_HW_TSTAMP.

So while ptp4l runs with HWTSTAMP_TX_ON, an unprivileged local user can
send a UDP/319 datagram with a non-event messageType:

  sendto() to UDP dport 319
    rtl8169_start_xmit()
      skb_tx_timestamp()
        skb_clone_tx_timestamp()
          rtl8127a_ptp_txtstamp()   <- takes the slot

The hardware does not timestamp that datagram. Every ptp4l Sync or
Delay_Req clone sent afterwards fails test_and_set_bit_lock() and is
freed.

The hardware still timestamps ptp4l's frame and raises INSR TX_TS.
rtl8127a_ptp_tx_tstamp() then attaches that egress time to the other
user's skb and queues it on that socket's error queue.

If no later event message is sent, the slot stays held for the full 15
second timeout while the aux worker polls MDIO. Should this path check
SKBTX_HW_TSTAMP and filter with ptp_parse_header() and
ptp_get_msgtype(), as the RX side does?

[ ... ]

> +	/* Only event messages are timestamped by the hardware */
> +	msgtype = ptp_get_msgtype(hdr, type);
> +	if (msgtype > PTP_MSGTYPE_PDELAY_RESP)
> +		return false;
> +
> +	RTL8127A_PTP_SKB_CB(skb)->msgtype = msgtype;
> +	skb_queue_tail(&ptp->rx_queue, skb);
> +	ptp_schedule_worker(ptp->clock, 0);

[Severity: High]
Is anything limiting the length of ptp->rx_queue?

skb_defer_rx_timestamp() runs in the RX softirq for any frame the
classifier marks as PTP, from any sender: a UDP datagram to port 319 or
a non-general L2 0x88F7 frame. Every event message is queued here with
no length cap, drop policy or expiry.

The only consumer is rtl8127a_ptp_do_aux_work(). It handles one skb at a
time under ptp->lock: a read-modify-write of TRX_TS_STA, a poll with a
timeout of up to 1 ms, and five time register reads. Each of these goes
through r8169's OCP accessors.

While queued, these skbs bypass netdev_max_backlog and socket memory
accounting. Could a remote host flooding UDP/319 over a 10GbE link make
this queue grow without bound, so that the worker's while loop never
exits?

[ ... ]

> +	if (ptp->tx_type != config->tx_type ||
> +	    ptp->rx_filter != config->rx_filter) {
> +		WRITE_ONCE(ptp->tx_type, config->tx_type);
> +		ptp->rx_filter = config->rx_filter;
> +		ret = rtl8127a_ptp_hwtstamp_enable(ptp, enable);
> +	}
> +	WRITE_ONCE(ptp->rx_enabled, rx_enabled);

[Severity: Medium]
What happens to the cached state if rtl8127a_ptp_hwtstamp_enable()
fails?

tx_type and rx_filter are updated before the hardware is programmed.
rx_enabled is updated whatever the return value is.

rtl8127a_ptp_hwtstamp_enable() is a series of separate MDIO
read-modify-writes (SYNCE_CTL, INER, GEN_CFG, INSR, CTL, 0xa640) with no
rollback. An -ETIMEDOUT partway through can leave the block half
configured, for example with INER unmasked but CTL not enabled.

After such an error:

- rtl8127a_ptp_txtstamp() sees HWTSTAMP_TX_ON and arms TX timestamps
  that never arrive.
- rtl8127a_ptp_rxtstamp() sees rx_enabled and attaches stale captures.
- rtl8127a_ptp_hwtstamp_get() reports a configuration the hardware is
  not running.

A retry with the same configuration then matches the cached state,
skips the reprogramming and returns 0. Should the new state be stored
only after the hardware has been programmed successfully?

[Severity: Medium]
Is a pending TX timestamp handled during this reconfiguration?

Any filter change while TX is on calls rtl8127a_ptp_hwtstamp_enable(ptp,
true), which does:

	ret = rtl8127a_ptp_write(ptp, RTL8127A_PTP_INSR, 0xffff);

That clears a TX_TS event latched for ptp->tx_skb before the aux worker
reads it.

tx_type is also set to HWTSTAMP_TX_ON before the hardware is enabled. A
concurrent rtl8127a_ptp_txtstamp() can therefore arm the slot for a
frame that goes out before PTP is enabled.

Switching TX off does not cancel a pending tx_skb either. That includes
one armed by an rtl8127a_ptp_txtstamp() call that read the old tx_type.

In each case RTL8127A_PTP_TX_IN_PROGRESS stays set until the 15 second
timeout. Every new TX timestamp request is freed during that time, and
rtl8127a_ptp_do_aux_work() keeps polling INSR under ptp->lock.

[ ... ]

> +	info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_EVENT) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_EVENT) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_SYNC) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_SYNC) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_DELAY_REQ) |
> +			   BIT(HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ);

[Severity: Low]
Should the L2 filters be listed here too?

rtl8127a_ptp_hwtstamp_set() accepts HWTSTAMP_FILTER_PTP_V2_L2_EVENT,
HWTSTAMP_FILTER_PTP_V2_L2_SYNC and HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ,
and rtl8127a_ptp_rxtstamp() handles PTP_CLASS_L2 frames. ethtool -T and
the tsinfo netlink reply will not report the L2 filters as supported.

> +
> +	return 0;
> +}
> +
>  int rtl8127a_ptp_config_init(struct phy_device *phydev)
>  {
>  	struct rtl8127a_ptp *ptp = phydev->priv;

[Severity: Medium]
Should rtl8127a_ptp_config_init() also restore the timestamping
configuration?

It runs on every phy_init_hw(), for example from rtl8169_init_phy() on
open, reset and resume, but it only steps the clock again:

drivers/net/phy/realtek/realtek_ptp.c:rtl8127a_ptp_config_init() {
    ...
	/* (Re)start the clock from the system time whenever the PHY is
	 * (re)initialized, e.g. each time the MAC driver brings it up.
	 */
	ktime_get_real_ts64(&ts);

	return rtl8127a_ptp_settime(ptp, &ts);
}

That comment suggests the PTP block's state may not survive a PHY
re-init. If CTL and INER are reset as well, ptp->tx_type, ptp->rx_filter
and rtl8127a_ptp_hwtstamp_get() still report timestamping as enabled
while the hardware has it off. TX skbs then wait out the 15 second
timeout with busy polling, and RX skbs get stale captures.

Sending the same SIOCSHWTSTAMP again does not help.
rtl8127a_ptp_hwtstamp_set() only reprograms the hardware when the
requested config differs from the cached one.

Stepping the PHC here while timestamping is active also means captures
pending across the step can mix two timebases.

I could not confirm whether PHY re-init or power-down actually clears
these vendor PTP registers.

[ ... ]

> @@ -369,6 +706,14 @@ void rtl8127a_ptp_remove(struct phy_device *phydev)
>  	if (!ptp)
>  		return;
>  
> +	phydev->mii_ts = NULL;
> +	phydev->default_timestamp = false;
> +
>  	rtl8127a_ptp_hwtstamp_enable(ptp, false);
> +	/* Also stops the aux worker */
>  	ptp_clock_unregister(ptp->clock);
> +
> +	if (ptp->tx_skb)
> +		rtl8127a_ptp_tx_complete(ptp, NULL);
> +	skb_queue_purge(&ptp->rx_queue);
>  }

[Severity: High]
What stops the datapath from still using ptp after this function
returns?

phydev->mii_ts is cleared with a plain store, and teardown follows
straight away. There is no synchronize_net() or RCU grace period, and
rtnl_lock is not taken. devres frees ptp after remove returns.

phy_remove() calls drv->remove() without detaching the netdev. A sysfs
unbind of the PHY driver can therefore run this while traffic is
flowing.

skb_clone_tx_timestamp() and skb_defer_rx_timestamp() read
phydev->mii_ts without locking. A CPU that has already loaded it can
still call rtl8127a_ptp_txtstamp() or rtl8127a_ptp_rxtstamp() after this
point. Those calls could:

- call ptp_schedule_worker() on a destroyed kworker
- touch the devm-freed struct
- leak a TX clone that holds a socket reference, if it is published
  after the ptp->tx_skb check
- queue an RX skb after the purge

rtl8127a_ptp_ts_info(), rtl8127a_ptp_hwtstamp_get() and
rtl8127a_ptp_hwtstamp_set() run under rtnl, which this path does not
take. They can also call ptp_clock_index() on a freed clock.

There is a second case. If the PHY was selected as hwprov through
ethtool tsconfig, dev->hwprov is only cleared in phy_detach(), not on
driver unbind. After this remove, skb_defer_rx_timestamp() takes the
hwprov path, skips phy_is_default_hwtstamp(), and then dereferences the
now NULL mii_ts:

net/core/timestamping.c:skb_defer_rx_timestamp() {
    ...
	mii_ts = phydev->mii_ts;
	if (likely(mii_ts->rxtstamp))
		return mii_ts->rxtstamp(mii_ts, skb, type);
    ...
}

Would any received PTP frame then oops in the RX softirq?

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261111083807.1529-1-javen_xu%40realsil.com.cn

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-15  9:05 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11  8:38 [PATCH net-next v2 0/2] net: phy: realtek: add PTP support for RTL8127a javen
2026-11-11  8:38 ` [PATCH net-next v2 1/2] net: phy: realtek: add PTP clock " javen
2026-11-15  9:05   ` netdev-bot+sashiko
2026-11-11  8:38 ` [PATCH net-next v2 2/2] net: phy: realtek: add hardware timestamping " javen
2026-11-15  9:05   ` netdev-bot+sashiko

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®