[PATCH net 00/15][pull request] ice: E82x: timestamp processing logic fixes

HOTtoday

29 messages, 2 authors, 13h ago · open the first message on its own page

[PATCH net 00/15][pull request] ice: E82x: timestamp processing logic fixes

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:43

Jake Keller says:

This series contains several related fixes for the ice driver PTP logic
relating to timestamp handling and device (re)initialization.

Of particular note is some changes around the handling of timestamps that
are requested near device state changes such as administrative up/down
cycles and link change events.

The E825 device logic in the PHY has an internal counter which is used as
part of the "threshold" logic which determines when the device will trigger
an interrupt signal from a given PHY port to the MAC. This internal counter
requires some precise handling to ensure that the internal device state
remains in sync with software expectations. Otherwise, the device can be
finagled into a state where the PHY stops producing new timestamp interrupt
notifications to the MAC indefinitely. This in turn degrades the timestamp
processing latency and results in application failures for common
timestamping applications such as ptp4l.

There are four major categories of problem resolved by this series:

 * Timestamp requests made while the PHY_REG_TX_OFFSET_READY bit is cleared
   will increment the internal counter, but leave their valid bit set to 0.
   Upon read, the counter is not decremented. This leads to a desync of the
   counter and blocks the PHY interrupt.

 * Software logic for tracking timestamps incorrectly cleared in-use bits
   without waiting for completion in certain cases. If the timestamp *does*
   later complete, it leaves an "orphaned" ready bit which is not
   tracked by software. This results in the internal counter becoming
   desynced if that index is re-used.

 * The PHY timestamp memory region lacks pull-down zero-initialization at
   power on, resulting in uninitialized random data in the memory region.
   If software reads these values, an entry with its valid bit set to 1 can
   trigger a counter decrement and cause an underflow which results in the
   counter becoming desynced.

 * Timestamp request which complete near the beginning of the PHY losing
   link can become "stuck" such that the hardware logic triggered by a
   timestamp read does not activate. The memory status bit and the valid
   bit in the timestamp index are not cleared. This window where this
   may occur begins *before* the firmware notifies the driver of link loss.
   When this occurs, the driver may accidentally re-use a stale timestamp,
   and the IRQ re-trigger logic triggers a repeated IRQ "storm" that can
   consume significant excess CPU time.

The series' primary focus is towards preventing driver flows that can
trigger the above sequences. It is based on work from Przemyslaw Korba which
was previously posted at [1]. During that series development, Petr from
RedHat reported the 3rd issue mentioned above. While attempting to root
cause that issue, several other issues were uncovered and those fixes have
also been included in this series.

First, the locking around the PTP ports list in the adapter structure is
converted to use RCU primitives and a spinlock, resolving a couple of
reports from Petr about places where the original list was accessed without
lock protection. Note that an older version of this fix used an xarray
instead of the list. The xarray has more overhead and results in an
increase of ~25 microseconds to the average latency for processing Tx
timestamps. The list is simpler and avoids this overhead.

Next, the PTP reset flow is fixed to stop tearing down the Tx tracker
during a CORE or GLOBAL reset. This avoids causing Tx timestamps to break
permanently after such a reset. This issue was found by Sashiko during
review of a previous version of this series.

Next, the ice_ptp_request_ts() function is updated to sequence the marking
of the in_use bitmap in order to work properly with the lockless reader in
the IRQ thread. This issue was reported by Sashiko during review of a
previous version of this series.

Next, come two fixes for E822 hardware that were originally posted as part
of Przemyslaw Korba's work [1]. The E822-only "vernier" offset validation
work task is properly canceled during device reset, and new timestamp
requests are kept disabled until the validation task completes.

Next, Arkadiusz modifies the driver to stop pretending that the link has
gone down during ice_down(). This removes "virtual" PTP link changes that
occurred on several flows including MTU change, Eswitch setup, and others.
Now, the driver only triggers a PTP PHY timer reinitialization when the
physical PHY link has changed instead of during many other actions.

Next, the driver is modified to stop clearing the PHY_REG_TX_OFFSET_READY
bit. This bits only purpose is to tell hardware to mark any captured
timestamps as invalid. Since this also disables the necessary side effects
on read it is problematic to have cleared. According to hardware engineers,
keeping it enabled should not have any other side effects.

Next, the driver is modified to clear the PHY_REG_TX_MEMORY_STATUS by
reading each index *prior* to the PHY soft reset. This ensures that any
stale or invalid data left in the memory array is cleared, followed by the
counter being reset via the PHY soft reset procedure.

Next, the E825 timer start procedure is modified to first include a soft
reset. This ensures that upon link up the device is reconfigured from a
known-good state with its internal counter reset and everything cleared.

Next, Petr modifies the ice_ptp_flush_tx_tracker() function to wait a little
bit for any outstanding timestamps before flushing.

Next, Petr modifies the ice_ptp_process_tx_tstamp() function to avoid
releasing any index from software unless either a) it is actually completed
by hardware or b) it is timed out waiting for a full two seconds. This
closes the final gap from the second issue mentioned above. Instead of
immediately releasing the index, the software now waits until hardware has
completed it or the driver has waited long enough to be sufficiently sure
that no such timestamp will be done.

Next, the driver is modified to no longer mark timestamps as "stale" during
a clock adjust event. This avoids marking timestamps as stale unnecessarily.

Next, the ice_ptp_process_tx_tstamp() function is modified to verify
that hardware actually cleared the ready bitmap. This ensures that we do
not report false timestamps near a link down event.

Finally, Maciek adds a needed PHY recalibration for E825-C after large system
time adjustments. Without recalibration, PHY timestamps do not properly
converge to the new time, resulting in inaccurate timestamp readings.

Link: [1] https://lore.kernel.org/intel-wired-lan/20260720120151.2675206-1-przemyslaw.korba@intel.com/
---
IWL: https://lore.kernel.org/intel-wired-lan/20260825-jk-e825c-minimized-fixes-v2-0-8223f95d26e3@intel.com/

The following are changes since commit 78445023439506ebd83b86d40b1e428a3b309d4a:
  Merge tag 'net-7.3-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net
and are available in the git repository at:
  git://git.kernel.org/pub/scm/linux/kernel/git/tnguy/net-queue 100GbE

Arkadiusz Kubalewski (1):
  ice: call PTP link change only from link events

Jacob Keller (9):
  ice: use reference counting and RCU for PTP port access
  ice: fix removal of PTP timestamp tracker during reset
  ice: set in_use only after preparing Tx timestamp index
  ice: E825: stop clearing PHY_REG_TX_OFFSET_READY
  ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset
  ice: E825: perform a soft reset when starting the PHY timer
  ice: remove unnecessary discarding of timestamps after clock adjust
  ice: skip reading Tx ready bitmap on ports with no timestamps
  ice: don't clear in_use until HW clears ready bitmap

Karol Kolacinski (2):
  ice: E822: keep Tx timestamps disabled during offset calibration
  ice: E822: cancel offset verification work during reset preparation

Maciek Machnikowski (1):
  ice: Recalibrate PHY after settime64 on E825-C

Petr Oros (2):
  ice: wait for in-flight Tx timestamps before flushing the tracker
  ice: keep Tx timestamp slots tracked until completion or timeout

 drivers/net/ethernet/intel/ice/ice_adapter.c |   7 +-
 drivers/net/ethernet/intel/ice/ice_adapter.h |   6 +-
 drivers/net/ethernet/intel/ice/ice_main.c    |  12 +-
 drivers/net/ethernet/intel/ice/ice_ptp.c     | 358 +++++++++++++------
 drivers/net/ethernet/intel/ice/ice_ptp.h     |  12 +-
 drivers/net/ethernet/intel/ice/ice_ptp_hw.c  | 104 +++---
 6 files changed, 326 insertions(+), 173 deletions(-)

-- 
2.47.1

[PATCH net 01/15] ice: use reference counting and RCU for PTP port access

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:44

From: Jacob Keller <jacob.e.keller@intel.com>

The ice adapter structure maintains a list of ports associated with the
adapter. This is used for supporting PTP, where the clock owner must handle
many operations that require access to the PTP port structures of the
associated PFs.

This is implemented using a linked list and a mutex. This sort of works,
but a few places within the code do not acquire the mutex when iterating
the list. This includes ice_ptp_flush_all_tx_tracker(),
ice_ptp_restart_all_phy(), and ice_ptp_prepare_rebuild_sec().

Fixing this is tricky, especially since it is not clear if we can simply
acquire the lock around the complete iterations.

The pattern of use for the port list is read-mostly with modifications only
happening during PF initialization when elements are inserted. This
typically only happens during early boot, though a PF could in principle be
removed or loaded at arbitrary times via bind and unbind operations.

The use of a mutex does mean the driver can sleep while holding it, but it
still creates complicates with lock ordering and prevents iterating the
list in any code path that *can't* sleep.

Instead, use the RCU primitives for the port linked list, along with a
reference count on the port. The kref reference counter ensures that we can
safely acquire pointers with a guarantee of their lifetime, ensuring the
associated PF will not be removed until the reference is released.

For port iterations which are short and definitely can't sleep, wrap the
entire loop with rcu_read_lock() and rcu_read_unlock().

For longer operations, or those which might sleep, we need to close the
critical section between each loop iteration. To make this safe, start
the loop iteration with rcu_read_lock(), then acquire a reference for the
port with kref_get_unless_zero. If this returns 0, the port is already in
the process of being removed, so that port should be skipped when
iterating. Once a reference to the port is acquired, exit the RCU critical
section. Then, perform the desired operations on the port, followed by
re-entering the RCU critical section and releasing the reference with
kref_put.

The ice_ptp_release_port_rcu() function is used as the release function for
the kref_put() call. To avoid a potential infinite loop of new references,
the release function simply uses a wake_up_var() call to wake the closing
thread. The ice_ptp_cleanup_pf() function will remove the port from the linked
list using list_del_rcu, then release its primary reference, then wait for all
references to drop via wait queue. Finally synchronize_rcu() is called to
guarantee the port remains valid for at least one RCU grace period. Then PF
removal will continue.

This flow ensures that all accesses to ports via the port list will remain
valid until either the RCU critical sections end, or the references have
been dropped.

One major complication of this reference count is that ice_ptp_port is
embedded inside of other structures and not merely allocated. As a result,
we can't use the standard pattern of kfree_rcu() to just delay freeing
until references are dropped, and instead are delaying PF port teardown. If
any code path leaks the reference, the driver will be unable to teardown.
Instead, a 15 second timeout with a WARN() is used when waiting to finally
allow PF teardown to continue. This has the risk of potentially allowing
use-after-free, assuming some path really is stuck for 15 seconds. However,
this both less likely and a less bad outcome compared to blocking
indefinitely on a reference leak.

Fixes: e800654e85b5 ("ice: Use ice_adapter for PTP shared data instead of auxdev")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_adapter.c |   7 +-
 drivers/net/ethernet/intel/ice/ice_adapter.h |   6 +-
 drivers/net/ethernet/intel/ice/ice_ptp.c     | 132 ++++++++++++++-----
 drivers/net/ethernet/intel/ice/ice_ptp.h     |   4 +
 4 files changed, 106 insertions(+), 43 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.c b/drivers/net/ethernet/intel/ice/ice_adapter.c
index 2dc3629d6d0f..d1643bf8a1b5 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.c
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.c
@@ -66,18 +66,17 @@ static struct ice_adapter *ice_adapter_new(struct pci_dev *pdev)
 		mutex_init(&adapter->cpi_phy_lock[i]);
 	refcount_set(&adapter->refcount, 1);
 
-	mutex_init(&adapter->ports.lock);
-	INIT_LIST_HEAD(&adapter->ports.ports);
+	spin_lock_init(&adapter->ports.lock);
+	INIT_LIST_HEAD(&adapter->ports.list);
 
 	return adapter;
 }
 
 static void ice_adapter_free(struct ice_adapter *adapter)
 {
-	WARN_ON(!list_empty(&adapter->ports.ports));
+	WARN_ON(!list_empty(&adapter->ports.list));
 	for (int i = 0; i < ARRAY_SIZE(adapter->cpi_phy_lock); i++)
 		mutex_destroy(&adapter->cpi_phy_lock[i]);
-	mutex_destroy(&adapter->ports.lock);
 
 	kfree(adapter);
 }
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 4f695f32da3d..39923dedd534 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -19,13 +19,13 @@ struct ice_pf;
  *
  * This structure contains data used to maintain a list of adapter ports
  *
- * @ports: list of ports
+ * @list: list of ports
  * @lock: protect access to the ports list
  */
 struct ice_port_list {
-	struct list_head ports;
+	struct list_head list;
 	/* To synchronize the ports list operations */
-	struct mutex lock;
+	spinlock_t lock;
 };
 
 /**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index eaec36ab6ae3..b12181b8c843 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright (C) 2021, Intel Corporation. */
 
+#include <linux/rculist.h>
+#include <linux/wait_bit.h>
 #include "ice.h"
 #include "ice_lib.h"
 #include "ice_trace.h"
@@ -673,20 +675,33 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	pf->ptp.tx_hwtstamp_good += tstamp_good;
 }
 
+static void ice_ptp_release_port_rcu(struct kref *ref)
+{
+	wake_up_var(ref);
+}
+
 static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
 {
 	struct ice_ptp_port *port;
 
-	mutex_lock(&pf->adapter->ports.lock);
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
 		struct ice_ptp_tx *tx = &port->tx;
 
-		if (!tx || !tx->init)
+		if (!tx->init)
 			continue;
 
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+
+		rcu_read_unlock();
+
 		ice_ptp_process_tx_tstamp(tx);
+
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
 	}
-	mutex_unlock(&pf->adapter->ports.lock);
+	rcu_read_unlock();
 }
 
 /**
@@ -808,8 +823,16 @@ ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)
 {
 	struct ice_ptp_port *port;
 
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
 		ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
+	}
+	rcu_read_unlock();
 }
 
 /**
@@ -1285,12 +1308,15 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 
 	ptp_port = &pf->ptp.port;
 
+	if (!kref_get_unless_zero(&ptp_port->ref))
+		return;
+
 	/* Update cached link status for this port immediately */
 	ptp_port->link_up = linkup;
 
 	/* Skip HW writes if reset is in progress */
 	if (pf->hw.reset_ongoing)
-		return;
+		goto exit_kref_put;
 
 	if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 &&
 	    test_bit(ICE_FLAG_DPLL, pf->flags)) {
@@ -1333,17 +1359,20 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	case ICE_MAC_E810:
 	case ICE_MAC_E830:
 		/* Do not reconfigure E810 or E830 PHY */
-		return;
+		goto exit_kref_put;
 	case ICE_MAC_GENERIC:
 		ice_ptp_port_phy_restart(ptp_port);
-		return;
+		goto exit_kref_put;
 	case ICE_MAC_GENERIC_3K_E825:
 		if (linkup)
 			ice_ptp_port_phy_restart(ptp_port);
-		return;
+		goto exit_kref_put;
 	default:
 		dev_warn(ice_pf_to_dev(pf), "%s: Unknown PHY type\n", __func__);
 	}
+
+exit_kref_put:
+	kref_put(&ptp_port->ref, ice_ptp_release_port_rcu);
 }
 
 /**
@@ -1424,16 +1453,21 @@ static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)
  */
 static void ice_ptp_restart_all_phy(struct ice_pf *pf)
 {
-	struct list_head *entry;
+	struct ice_ptp_port *port;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
 
 		if (port->link_up)
 			ice_ptp_port_phy_restart(port);
+
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
 	}
+	rcu_read_unlock();
 }
 
 /**
@@ -2694,19 +2728,19 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)
 
 static bool ice_any_port_has_timestamps(struct ice_pf *pf)
 {
+	bool have_tstamps = false;
 	struct ice_ptp_port *port;
 
-	scoped_guard(mutex, &pf->adapter->ports.lock) {
-		list_for_each_entry(port, &pf->adapter->ports.ports,
-				    list_node) {
-			struct ice_ptp_tx *tx = &port->tx;
-
-			if (ice_port_has_timestamps(tx))
-				return true;
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
+		if (ice_port_has_timestamps(&port->tx)) {
+			have_tstamps = true;
+			break;
 		}
 	}
+	rcu_read_unlock();
 
-	return false;
+	return have_tstamps;
 }
 
 bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
@@ -2890,14 +2924,16 @@ void ice_ptp_queue_work(struct ice_pf *pf)
 static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
 					enum ice_reset_req reset_type)
 {
-	struct list_head *entry;
+	struct ice_ptp_port *port;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
 		struct ice_pf *peer_pf = ptp_port_to_pf(port);
 
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
+
 		if (!ice_is_primary(&peer_pf->hw)) {
 			if (rebuild) {
 				/* TODO: When implementing rebuild=true:
@@ -2909,7 +2945,11 @@ static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
 				ice_ptp_prepare_for_reset(peer_pf, reset_type);
 			}
 		}
+
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
 	}
+	rcu_read_unlock();
 }
 
 /**
@@ -3086,11 +3126,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 		return -ENODEV;
 
 	INIT_LIST_HEAD(&ptp->port.list_node);
-	mutex_lock(&pf->adapter->ports.lock);
+	kref_init(&ptp->port.ref);
 
-	list_add(&ptp->port.list_node,
-		 &pf->adapter->ports.ports);
-	mutex_unlock(&pf->adapter->ports.lock);
+	spin_lock(&pf->adapter->ports.lock);
+	list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list);
+	spin_unlock(&pf->adapter->ports.lock);
 
 	/* Seed the per-PHY Tx reference clock usage map for this port.
 	 * Only meaningful on E825 (other MAC types don't expose tx-clk
@@ -3113,12 +3153,32 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 static void ice_ptp_cleanup_pf(struct ice_pf *pf)
 {
 	struct ice_ptp *ptp = &pf->ptp;
+	struct kref *ref;
 
-	if (pf->hw.mac_type != ICE_MAC_UNKNOWN) {
-		mutex_lock(&pf->adapter->ports.lock);
-		list_del(&ptp->port.list_node);
-		mutex_unlock(&pf->adapter->ports.lock);
-	}
+	if (pf->hw.mac_type == ICE_MAC_UNKNOWN)
+		return;
+
+	/* The PF cannot be removed until there are no more remaining
+	 * outstanding references to the PTP port. To make sure this is true,
+	 * first remove the port from the list, then drop the primary
+	 * reference this PF holds on the port. Once done, wait until all
+	 * existing references are dropped. Finally, synchronize_rcu() to
+	 * ensure that all RCU critical sections that might attempt to
+	 * dereference the port are finished.
+	 */
+
+	spin_lock(&pf->adapter->ports.lock);
+	list_del_rcu(&ptp->port.list_node);
+	spin_unlock(&pf->adapter->ports.lock);
+
+	ref = &ptp->port.ref;
+	kref_put(ref, ice_ptp_release_port_rcu);
+
+	dev_WARN_ONCE(ice_pf_to_dev(pf),
+		      !wait_var_event_timeout(ref, !kref_read(ref), 15 * HZ),
+		      "Timed out waiting for port references to release. Continuing to unload anyways.");
+
+	synchronize_rcu();
 }
 
 /**
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index c4b0da7ce20e..da2003ba3bb0 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -5,6 +5,8 @@
 #define _ICE_PTP_H_
 
 #include <linux/ptp_clock_kernel.h>
+#include <linux/rculist.h>
+#include <linux/kref.h>
 #include <linux/kthread.h>
 
 #include "ice_ptp_hw.h"
@@ -138,6 +140,7 @@ struct ice_ptp_tx {
  * and determine when the port's PHY offset is valid.
  *
  * @list_node: list member structure
+ * @ref: reference counter for use with adapter ports list
  * @tx: Tx timestamp tracking for this port
  * @ov_work: delayed work task for tracking when PHY offset is valid
  * @ps_lock: mutex used to protect the overall PTP PHY start procedure
@@ -149,6 +152,7 @@ struct ice_ptp_tx {
  */
 struct ice_ptp_port {
 	struct list_head list_node;
+	struct kref ref;
 	struct ice_ptp_tx tx;
 	struct kthread_delayed_work ov_work;
 	struct mutex ps_lock; /* protects overall PTP PHY start procedure */
-- 
2.47.1

[PATCH net 02/15] ice: fix removal of PTP timestamp tracker during reset

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:46

From: Jacob Keller <jacob.e.keller@intel.com>

Commit 7a25fe5cd5fb ("ice: stop destroying and reinitalizing Tx tracker
during reset") intended to modify the PTP reset flow of the driver so that
it stopped calling ice_ptp_reset_tx_tracker() during teardown and stopped
calling ice_ptp_init_tx_*() during rebuild.

Unfortunately, the commit only removed the calls to ice_ptp_init_tx_*().
This fixed a memory leak in PF reset. However, now a CORE or GLOBAL reset
will leave the device unable to initiate Tx timestamp requests
indefinitely.

In practice the CORE and GLOBAL resets rarely happen in production
environments, which explains why this has not been caught until now.
However, it is trivial to verify by triggering the reset from userspace via
ethtool. For ice the following command will trigger a GLOBAL reset:

  $ ethtool --reset eno8303np0 irq-shared dma-shared filter-shared \
                               offload-shared ram-shared mac-shared phy-shared

This was found by Sashiko review during feedback for an unrelated change.

Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=8
Fixes: 7a25fe5cd5fb ("ice: stop destroying and reinitalizing Tx tracker during reset")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 2 --
 1 file changed, 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index b12181b8c843..142d39ee5cc5 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2979,8 +2979,6 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
 	if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
 
-	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
-
 	/* Disable periodic outputs */
 	ice_ptp_disable_all_perout(pf);
 
-- 
2.47.1

[PATCH net 03/15] ice: set in_use only after preparing Tx timestamp index

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:46

From: Jacob Keller <jacob.e.keller@intel.com>

The ice_ptp_request_ts() function is used to request a timestamp index for
use with a packet. When reserving an index, it sets the start time and
saves a pointer to the skb into the appropriate index. The function marks
the in_use bit first before doing any of these steps. The IRQ handler which
clears the timestamps reads the in_use bits uses a lockless flow for
reading the in_use bits to determine which ones are in-use. This is
necessary as actually processing a complete timestamp must be able to sleep
so we cannot hold the timestamp tracker lock over the entire sequence.
Additionally, blocking the Tx hotpath with such a lock indefinitely would
be problematic.

However, the existing flow now has a very narrow window where the IRQ
handler could see a timestamp as in-use but read a stale value for its
"start" time.

Fix this by ordering the sequence to mark the in_use bit last, and add a
memory barrier to prevent re-ordering of the previous writes to setup the
index.

This was found and reported by Sashiko while reviewing an unrelated change.

Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=7
Fixes: ea9b847cda64 ("ice: enable transmit timestamps for E810 devices")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 142d39ee5cc5..68537705e839 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2672,11 +2672,13 @@ s8 ice_ptp_request_ts(struct ice_ptp_tx *tx, struct sk_buff *skb)
 		 * a reference to the skb and the start time to allow discarding old
 		 * requests.
 		 */
-		set_bit(idx, tx->in_use);
-		clear_bit(idx, tx->stale);
 		tx->tstamps[idx].start = jiffies;
 		tx->tstamps[idx].skb = skb_get(skb);
 		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		clear_bit(idx, tx->stale);
+		/* Ensure index is setup before marking it as used */
+		smp_mb__before_atomic();
+		set_bit(idx, tx->in_use);
 		ice_trace(tx_tstamp_request, skb, idx);
 	}
 
-- 
2.47.1

[PATCH net 04/15] ice: E822: keep Tx timestamps disabled during offset calibration

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:47

From: Karol Kolacinski <redacted>

Do not clear the tx.calibrating flag immediately after starting the PHY
timer in ice_ptp_port_phy_restart(). Instead, keep Tx timestamps
disabled until the offset verification work (ice_ptp_wait_for_offsets)
has confirmed that both Tx and Rx PHY offsets are properly configured.

Previously, tx.calibrating was set to true, then immediately back to
false right after ice_start_phy_timer_e82x() returned. This allowed Tx
timestamp requests to be served during the window where offset
verification was still pending. Timestamps produced during this window
use uncalibrated PHY offsets and can produce incorrect values.

When ptp4l receives incorrect timestamps, it may reject them and wait
for the next sync interval (typically 1-2 seconds), compounding delays
during link cycling. This contributes to the time transmitter port
becoming unresponsive after repeated link down/up cycles.

Move the tx.calibrating = false to ice_ptp_wait_for_offsets(), after
both Tx and Rx offset configuration has completed successfully. This
ensures that Tx timestamps are only reported with properly calibrated
PHY offsets.

If ice_start_phy_timer_e82x() fails, do not restore calibrating to false.
The device is in a state where timestamps cannot succeed properly anyways.
A dev_err message is already logged on failure to start the timer at the
end of the function.

Log a debug message while offset calibration is still pending, including
the specific Tx/Rx error codes to aid debugging stalled calibration.
This path is expected on every routine link-up: ov_work is first queued
with no delay and the vernier offset cannot be computed until at least
one packet has been transmitted, so the first several invocations
normally land here. Use dev_dbg() rather than a rate-limited warning to
avoid emitting KERN_WARNING on every link-up during normal operation.
Log a debug message when calibration completes successfully.

Fixes: 3a7496234d17 ("ice: implement basic E822 PTP support")
Signed-off-by: Karol Kolacinski <redacted>
Reviewed-by: Aleksandr Loktionov <redacted>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 30 ++++++++++++++++++++----
 1 file changed, 25 insertions(+), 5 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 68537705e839..d018f02f700d 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1156,6 +1156,7 @@ static int ice_ptp_check_tx_fifo(struct ice_ptp_port *port)
 static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 {
 	struct ice_ptp_port *port;
+	unsigned long flags;
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	int tx_err;
@@ -1178,12 +1179,28 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 		tx_err = ice_phy_cfg_tx_offset_e82x(hw, port->port_num);
 	rx_err = ice_phy_cfg_rx_offset_e82x(hw, port->port_num);
 	if (tx_err || rx_err) {
-		/* Tx and/or Rx offset not yet configured, try again later */
+		/* Tx and/or Rx offset not yet configured, try again later.
+		 * This is expected during normal link-up: the vernier offset
+		 * calibration cannot complete until at least one packet has
+		 * been transmitted, so the first retries routinely land here.
+		 */
+		dev_dbg(ice_pf_to_dev(pf),
+			"PTP offset not yet valid for port %u (tx_err=%d rx_err=%d)\n",
+			port->port_num, tx_err, rx_err);
 		kthread_queue_delayed_work(pf->ptp.kworker,
 					   &port->ov_work,
 					   msecs_to_jiffies(100));
 		return;
 	}
+
+	/* Tx and Rx offsets are now configured, enable Tx timestamps */
+	spin_lock_irqsave(&port->tx.lock, flags);
+	port->tx.calibrating = false;
+	spin_unlock_irqrestore(&port->tx.lock, flags);
+
+	dev_dbg(ice_pf_to_dev(pf),
+		"PTP offset valid for port %u, Tx timestamps enabled\n",
+		port->port_num);
 }
 
 /**
@@ -1269,10 +1286,13 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
 		if (err)
 			break;
 
-		/* Enable Tx timestamps right away */
-		spin_lock_irqsave(&ptp_port->tx.lock, flags);
-		ptp_port->tx.calibrating = false;
-		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+		/* Do not clear calibrating flag here. Tx timestamps remain
+		 * disabled until ice_ptp_wait_for_offsets() has verified
+		 * that the Tx and Rx offset calibration has completed.
+		 * Clearing it here would allow Tx timestamps to be reported
+		 * before the PHY offset registers are configured, leading
+		 * to incorrect timestamp values.
+		 */
 
 		kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
 					   0);
-- 
2.47.1

[PATCH net 06/15] ice: call PTP link change only from link events

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:48

From: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>

Remove redundant ice_ptp_link_change() calls from ice_up_complete() and
ice_down(). These duplicate the call already made from
ice_handle_link_event(), creating three problems:

1. Double initialization on link-up: ice_handle_link_event() calls
   ice_ptp_link_change(true), then ice_up_complete() calls it again.
   The second call re-enters ice_ptp_port_phy_restart(), re-setting the
   calibrating flag and restarting the PHY timer while the first
   invocation's offset verification work (ov_work) may still be running.

2. Premature cleanup on administrative down: ice_down() calls
   ice_ptp_link_change(false) during ifconfig down or reset preparation,
   even when the physical link is still up. This clears timestamp state
   unnecessarily and can interfere with ongoing PTP operations.

3. Ordering dependency: ice_down()/ice_up_complete() are called during
   reset sequences where PTP may not be fully initialized, creating
   edge cases with partially configured state.

The link event handler is the correct and sufficient place to drive PTP
link state changes, as it reflects actual physical link transitions. Remove
the calls of ice_ptp_link_change from the ice_down()/ice_up() flows.

Initialize the link_up in ice_ptp_init() and ensure that we check and
restore the link status at the end of the rebuild flow, ensuring that we
initialize the PHY timer appropriately after a reset.

Fixes: 6b1ff5d39228 ("ice: always call ice_ptp_link_change and make it void")
Reviewed-by: Aleksandr Loktionov <redacted>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <redacted>
Signed-off-by: Petr Oros <redacted>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_main.c | 10 ++++--
 drivers/net/ethernet/intel/ice/ice_ptp.c  | 38 +++++++++++++++++------
 2 files changed, 36 insertions(+), 12 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index d88835482d3a..f32041dd8b27 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -6745,7 +6745,6 @@ static int ice_up_complete(struct ice_vsi *vsi)
 		ice_print_link_msg(vsi, true);
 		netif_tx_start_all_queues(vsi->netdev);
 		netif_carrier_on(vsi->netdev);
-		ice_ptp_link_change(pf, true);
 	}
 
 	/* Perform an initial read of the statistics registers now to
@@ -7273,7 +7272,6 @@ int ice_down(struct ice_vsi *vsi)
 
 	if (vsi->netdev) {
 		vlan_err = ice_vsi_del_vlan_zero(vsi);
-		ice_ptp_link_change(vsi->back, false);
 		netif_carrier_off(vsi->netdev);
 		netif_tx_disable(vsi->netdev);
 	}
@@ -7794,6 +7792,14 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
 
 	ice_update_pf_netdev_link(pf);
 
+	if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags) && pf->hw.port_info) {
+		bool link_up;
+
+		link_up = !!(pf->hw.port_info->phy.link_info.link_info &
+			     ICE_AQ_LINK_UP);
+		ice_ptp_link_change(pf, link_up);
+	}
+
 	/* tell the firmware we are up */
 	err = ice_send_version(pf);
 	if (err) {
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 9bf3bac552f1..4dec728e219d 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1323,9 +1323,6 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	struct ice_ptp_port *ptp_port;
 	struct ice_hw *hw = &pf->hw;
 
-	if (pf->ptp.state != ICE_PTP_READY)
-		return;
-
 	ptp_port = &pf->ptp.port;
 
 	if (!kref_get_unless_zero(&ptp_port->ref))
@@ -1334,6 +1331,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	/* Update cached link status for this port immediately */
 	ptp_port->link_up = linkup;
 
+	if (pf->ptp.state != ICE_PTP_READY)
+		goto exit_kref_put;
+
 	/* Skip HW writes if reset is in progress */
 	if (pf->hw.reset_ongoing)
 		goto exit_kref_put;
@@ -3298,9 +3298,13 @@ static int ice_ptp_init_owner(struct ice_pf *pf)
 }
 
 /**
- * ice_ptp_init_work - Initialize PTP work threads
+ * ice_ptp_init_work - Initialize the PTP kworker
  * @pf: Board private structure
  * @ptp: PF PTP structure
+ *
+ * Allocate the kworker and initialize the periodic work function. The
+ * periodic work is not queued here; the caller starts it once the PTP
+ * state is ICE_PTP_READY.
  */
 static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)
 {
@@ -3319,9 +3323,6 @@ static int ice_ptp_init_work(struct ice_pf *pf, struct ice_ptp *ptp)
 
 	ptp->kworker = kworker;
 
-	/* Start periodic work going */
-	kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0);
-
 	return 0;
 }
 
@@ -3443,6 +3444,22 @@ void ice_ptp_init(struct ice_pf *pf)
 	if (err)
 		goto err_clean_pf;
 
+	/* Seed link_up from current PHY status, since link may already be up
+	 * (e.g. after PXE boot) with no link-change edge to catch it later.
+	 */
+	if (pf->hw.port_info)
+		ptp->port.link_up =
+			!!(pf->hw.port_info->phy.link_info.link_info &
+			ICE_AQ_LINK_UP);
+
+	/* Create the kworker before restarting the PHY, which queues work on
+	 * it in the E82x restart path. This prevents concurrent link events
+	 * from reaching ice_ptp_port_phy_restart() while kworker is still NULL
+	 */
+	err = ice_ptp_init_work(pf, ptp);
+	if (err)
+		goto err_clean_pf;
+
 	/* Start the PHY timestamping block */
 	ice_ptp_reset_phy_timestamping(pf);
 
@@ -3451,9 +3468,10 @@ void ice_ptp_init(struct ice_pf *pf)
 
 	ptp->state = ICE_PTP_READY;
 
-	err = ice_ptp_init_work(pf, ptp);
-	if (err)
-		goto err_exit;
+	/* Start periodic work only after the state is READY; the worker
+	 * returns without rescheduling while the state is not READY.
+	 */
+	kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0);
 
 	dev_info(ice_pf_to_dev(pf), "PTP init successful\n");
 	return;
-- 
2.47.1

[PATCH net 05/15] ice: E822: cancel offset verification work during reset preparation

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:48

From: Karol Kolacinski <redacted>

Cancel the offset verification delayed work (ov_work) during PTP reset
preparation to prevent it from running concurrently with the reset
sequence.

Without this, ice_ptp_wait_for_offsets() can execute during a reset,
find that ice_is_reset_in_progress() is true, and re-queue itself in a
tight loop. Meanwhile, the reset path in ice_ptp_rebuild_owner() calls
ice_ptp_port_phy_restart(), which starts a new ov_work. This results in
two ov_work instances running simultaneously, racing over the PHY offset
registers and the calibrating flag.

Cancel ov_work in ice_ptp_prepare_for_reset() alongside the existing
cancellation of the Tx tracker. The comment in the existing code already
documents that ov_work interference during reset is a concern but only
cancels it during ice_ptp_release().

Fixes: 4809671015a1 ("ice: Fix E810 PTP reset flow")
Signed-off-by: Karol Kolacinski <redacted>
Reviewed-by: Aleksandr Loktionov <redacted>
Signed-off-by: Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: Przemyslaw Korba <redacted>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 8 ++++++++
 1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index d018f02f700d..9bf3bac552f1 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2998,6 +2998,14 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
 	if (reset_type == ICE_RESET_PFR)
 		return;
 
+	/* Cancel the offset verification work for E82x before releasing the
+	 * Tx tracker. If ov_work is running during reset, it may issue
+	 * sideband queue commands that will fail or timeout, and may
+	 * reference state that is being torn down.
+	 */
+	if (hw->mac_type == ICE_MAC_GENERIC)
+		kthread_cancel_delayed_work_sync(&ptp->port.ov_work);
+
 	if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
 
-- 
2.47.1

[PATCH net 07/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:49

From: Jacob Keller <jacob.e.keller@intel.com>

The ice_stop_phy_timer_eth56g() function is called by the driver for E825
devices to ensure that the PHY timer has been stopped. The equivalent
function for older E822 devices performed many steps. However, on E825 it
only clears the PHY_REG_TX_OFFSET_READY and PHY_REG_RX_OFFSET_READY bits to
indicate to HW that it should no longer treat the PHY offset as valid.

When PHY_REG_TX_OFFSET_READY is cleared, the hardware still captures Tx
timestamps, but it no longer sets the valid bit for these timestamps. This
sounds reasonable at first glance. However, this results in the internal
outstanding timestamp counter becoming out of sync.

When capturing a timestamp, hardware increments its internal counter and
sets the associated "ready" bit in the timestamp memory status. Then it
compares the timestamp count to the threshold to determine if it should
trigger an interrupt to the MAC.

Upon reading the timestamp hardware is supposed to decrement the counter,
clear the valid bit, and clear the associated bit from the memory status
register. However, it only performs these steps *if* the valid bit is set.

Since the valid bit is not set while PHY_REG_TX_OFFSET_READY is clear, the
timestamp counter is not decremented and the memory status is not cleared.
This leaves the counter out-of-sync until a PHY soft reset.

According to the hardware engineers, the PHY_REG_TX_OFFSET_READY bit has no
other effects. It only controls whether hardware captures timestamps with
the valid bit set or not. Since capturing timestamps with the valid bit
clear is problematic, they recommend simply not clearing
PHY_REG_TX_OFFSET_READY.

Note that the PHY_REG_RX_OFFSET_READY performs a similar task. However,
clearing it is fine as there is no associated timestamp counter on the Rx
side. Receive timestamps are simply inserted into the descriptor. Clearing
this register clears the valid bit for timestamps until we complete
calibration and re-enable the register.

Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Suggested-by: Maciej Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 4 ----
 1 file changed, 4 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 3a41c711e751..d48eb3c61823 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2115,10 +2115,6 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
 {
 	int err;
 
-	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_OFFSET_READY, 0);
-	if (err)
-		return err;
-
 	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);
 	if (err)
 		return err;
-- 
2.47.1

[PATCH net 08/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:50

From: Jacob Keller <jacob.e.keller@intel.com>

The current implementation of ice_ptp_reset_ts_memory_eth56g() is flawed.
It tries to clear the timestamp memory by writing to the
PHY_REG_TX_MEMORY_STATUS region. This does not work properly, as it does
not trigger appropriate PHY actions.

To clear outstanding timestamp memory, the driver must read the timestamps.
However, naively doing this as part of ice_ptp_reset_ts_memory() is
problematic. When reading the timestamp index, hardware kicks off a chain
of actions including clearing the ready bitmap index, and decrementing an
internal counter if the timestamp index was marked as valid.

This can potentially leave the internal hardware counter out of sync with
the actual number of timestamps. This occurs because the
PHY_REG_TX_MEMORY_STATUS region is not zero-initialized when the device
boots up. Instead, it is filled with garbage. On a cold power on, attempts
to read the stale data result in the hardware triggering a counter
decrement for a timestamp that never happened. This underflows the counter,
and prevents new timestamp interrupts from being triggered for real
timestamp requests.

We must read the PHY_REG_TX_MEMORY_STATUS in order to clear stale
timestamps. But doing so may cause a desync with the counter. To prevent
issues, perform this clearing always and only right before initiating a PHY
soft reset.

The soft reset will clear and reset the internal counter and the ready
bitmap. The reads to PHY_REG_TX_MEMORY_STATUS will reset the region valid
bits ensuring that no stale data is left behind. This combination ensures
that we always have a clean slate with no stale data and with the counter
properly reset to zero.

Fixes: 3ec46e157c7f ("ice: perform PHY soft reset for E825C ports at initialization")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 88 +++++++++++----------
 1 file changed, 47 insertions(+), 41 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index d48eb3c61823..a3a9f7ce04d2 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -737,24 +737,6 @@ static int ice_read_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,
 	return ice_read_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);
 }
 
-/**
- * ice_write_port_mem_eth56g - Write a PHY port memory location
- * @hw: pointer to the HW struct
- * @port: Port number to be read
- * @offset: Offset from PHY port register base
- * @val: Pointer to the value to read (out param)
- *
- * Return:
- * * %0      - success
- * * %EINVAL - invalid port number or resource type
- * * %other  - failed to write to PHY
- */
-static int ice_write_port_mem_eth56g(struct ice_hw *hw, u8 port, u16 offset,
-				     u32 val)
-{
-	return ice_write_port_eth56g(hw, port, offset, val, ETH56G_PHY_MEM_PTP);
-}
-
 /**
  * ice_write_quad_ptp_reg_eth56g - Write a PHY quad register
  * @hw: pointer to the HW struct
@@ -1139,8 +1121,8 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
  * internal PHYs of the 56G devices.
  *
  * To directly clear the contents of the timestamp block entirely, discarding
- * all timestamp data at once, software should instead use
- * ice_ptp_reset_ts_memory_quad_eth56g().
+ * all timestamp data at once, software should instead perform a PHY soft
+ * reset via ice_ptp_phy_soft_reset_eth56g().
  *
  * This function should only be called on an idx whose bit is set according to
  * ice_get_phy_tx_tstamp_ready().
@@ -1152,24 +1134,16 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
 static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 {
 	u64 unused_tstamp;
-	u16 lo_addr;
 	int err;
 
-	/* Read the timestamp register to ensure the timestamp status bit is
-	 * cleared.
+	/* Per the PHY spec, reading the timestamp memory location is what
+	 * clears the entry's valid bit and its corresponding (read-only)
+	 * ts_memory_status bit.
 	 */
 	err = ice_read_ptp_tstamp_eth56g(hw, port, idx, &unused_tstamp);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read the PHY timestamp register for port %u, idx %u, err %d\n",
 			  port, idx, err);
-	}
-
-	lo_addr = (u16)PHY_TSTAMP_L(idx);
-
-	err = ice_write_port_mem_eth56g(hw, port, lo_addr, 0);
-	if (err) {
-		ice_debug(hw, ICE_DBG_PTP, "Failed to clear low PTP timestamp register for port %u, idx %u, err %d\n",
-			  port, idx, err);
 		return err;
 	}
 
@@ -1177,19 +1151,36 @@ static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 }
 
 /**
- * ice_ptp_reset_ts_memory_eth56g - Clear all timestamps from the port block
+ * ice_ptp_clear_tx_memory_status_eth56g - Reset one port's Tx timestamp memory
  * @hw: pointer to the HW struct
+ * @port: port number to clear
+ *
+ * Fully reset a single PHY port's Tx timestamp memory. Per the PHY spec, the
+ * only way to clear a timestamp valid bit (and its read-only ts_memory_status
+ * bit) is to read the timestamp memory location, so read every entry for the
+ * port (two 32-bit reads each). This discards all timestamp data on the port,
+ * so it must only be used for a full reset; callers that must preserve
+ * in-flight timestamps clear individual indices via ice_clear_phy_tstamp().
+ *
+ * Due to interactions with an internal HW counter for the number of
+ * outstanding Tx timestamps, this *must* only be called as part of the
+ * ice_ptp_phy_soft_reset_eth56g() procedure. Otherwise, the internal counter
+ * may become out of sync and prevent new timestamp interrupts.
+ *
+ * Return: 0 on success, negative error code on failure to read the PHY.
  */
-static void ice_ptp_reset_ts_memory_eth56g(struct ice_hw *hw)
+static int ice_ptp_clear_tx_memory_status_eth56g(struct ice_hw *hw, u8 port)
 {
-	unsigned int port;
+	int err = 0;
+	u8 idx;
 
-	for (port = 0; port < hw->ptp.num_lports; port++) {
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_L,
-					 0);
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_U,
-					 0);
+	for (idx = 0; idx < INDEX_PER_PORT; idx++) {
+		err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);
+		if (err)
+			return err;
 	}
+
+	return 0;
 }
 
 /**
@@ -2290,6 +2281,7 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
  *
  * Trigger a soft reset of the ETH56G PHY by toggling the soft reset
  * bit in the PHY global register. The reset sequence consists of:
+ *   0. Reading every timestamp memory register to clear its valid bit
  *   1. Clearing the soft reset bit
  *   2. Asserting the soft reset bit
  *   3. Clearing the soft reset bit again
@@ -2298,6 +2290,12 @@ int ice_ptp_read_tx_hwtstamp_status_eth56g(struct ice_hw *hw, u32 *ts_status)
  * to settle. This provides a controlled way to reinitialize the PHY
  * without requiring a full device reset.
  *
+ * To ensure that the internal counter matches the contents of the
+ * PHY_REG_TX_MEMORY_STATUS, read every timestamp index prior to performing
+ * the soft reset. The PHY_REG_TX_MEMORY_STATUS reads ensure that the region
+ * is cleared, while the soft reset procedure ensures that the timestamp
+ * counter is reset to zero.
+ *
  * Return: 0 on success, or a negative error code on failure when
  *         reading or writing the PHY register.
  */
@@ -2306,6 +2304,13 @@ int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
 	u32 global_val;
 	int err;
 
+	err = ice_ptp_clear_tx_memory_status_eth56g(hw, port);
+	if (err) {
+		ice_debug(hw, ICE_DBG_PTP, "Failed to clear PHY_REG_TX_MEMORY_STATUS for port %d, err %d\n",
+			  port, err);
+		return err;
+	}
+
 	err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, &global_val);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read PHY_REG_GLOBAL for port %d, err %d\n",
@@ -5797,8 +5802,9 @@ void ice_ptp_reset_ts_memory(struct ice_hw *hw)
 		ice_ptp_reset_ts_memory_e82x(hw);
 		break;
 	case ICE_MAC_GENERIC_3K_E825:
-		ice_ptp_reset_ts_memory_eth56g(hw);
-		break;
+		/* E825 hardware must only reset timestamp memory as part of
+		 * the soft reset procedure.
+		 */
 	case ICE_MAC_E810:
 	default:
 		return;
-- 
2.47.1

[PATCH net 09/15] ice: E825: perform a soft reset when starting the PHY timer

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:51

From: Jacob Keller <jacob.e.keller@intel.com>

To ensure that the E825 PHY timer begins in a clean state, initiate a PHY
soft reset prior to programming the PHY. This ensures that we clear any
outstanding Tx timestamp memory, and ensures that the PHY internal state
has been completely reset.

Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp_hw.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index a3a9f7ce04d2..64e18fe1176b 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2120,9 +2120,11 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
  * @hw: pointer to the HW struct
  * @port: the PHY port to start
  *
- * Start the clock of a PHY port. This must be done as part of the flow to
- * re-calibrate Tx and Rx timestamping offsets whenever the clock time is
- * initialized or when link speed changes.
+ * Perform a PHY soft reset and then start the clock for the PHY port.
+ *
+ * This must be done as part of the flow to re-calibrate Tx and Rx
+ * timestamping offsets whenever the clock time is initialized or when link
+ * speed changes.
  *
  * Return:
  * * %0     - success
@@ -2138,6 +2140,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
 
 	tmr_idx = ice_get_ptp_src_clock_index(hw);
 
+	err = ice_ptp_phy_soft_reset_eth56g(hw, port);
+	if (err)
+		return err;
+
 	err = ice_stop_phy_timer_eth56g(hw, port, false);
 	if (err)
 		return err;
-- 
2.47.1

[PATCH net 10/15] ice: wait for in-flight Tx timestamps before flushing the tracker

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:51

From: Petr Oros <redacted>

ice_ptp_flush_tx_tracker() frees every tracked request, but a request
whose timestamp is still being captured by the PHY at that moment is
freed without touching the PHY entry. The ready bit published shortly
after has no tracked owner, and the PHY does not raise another Tx
timestamp interrupt until every outstanding ready bit is read, so
delivery for the whole quad degrades to the periodic work.

Wait up to 10 ms for in-flight captures to publish their ready bits
before flushing, so the flush clears them together with the rest.

Fixes: ea9b847cda64 ("ice: enable transmit timestamps for E810 devices")
Signed-off-by: Petr Oros <redacted>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 33 ++++++++++++++++++++++++
 1 file changed, 33 insertions(+)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 4dec728e219d..b142b779dc10 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -740,6 +740,37 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)
 	return 0;
 }
 
+static void
+ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+	unsigned long deadline = jiffies + msecs_to_jiffies(10);
+	struct ice_hw *hw = &pf->hw;
+	u64 tstamp_ready;
+	bool pending;
+	u8 idx;
+
+	if (hw->reset_ongoing)
+		return;
+
+	do {
+		if (ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready))
+			return;
+
+		pending = false;
+		for_each_set_bit(idx, tx->in_use, tx->len) {
+			if (!(tstamp_ready & BIT_ULL(idx + tx->offset)))
+				pending = true;
+		}
+		if (!pending)
+			return;
+
+		usleep_range(500, 1000);
+	} while (time_before(jiffies, deadline));
+
+	dev_dbg(ice_pf_to_dev(pf), "Timed out waiting for in-flight Tx timestamps on block %u\n",
+		tx->block);
+}
+
 /**
  * ice_ptp_flush_tx_tracker - Flush any remaining timestamps from the tracker
  * @pf: Board private structure
@@ -756,6 +787,8 @@ ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
 	int err;
 	u8 idx;
 
+	ice_ptp_wait_for_tracker_drain(pf, tx);
+
 	err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
 	if (err) {
 		dev_dbg(ice_pf_to_dev(pf), "Failed to get the Tx tstamp ready bitmap for block %u, err %d\n",
-- 
2.47.1

[PATCH net 11/15] ice: keep Tx timestamp slots tracked until completion or timeout

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:52

From: Petr Oros <redacted>

When the link goes down the processing loop drops every outstanding
request, and a request whose timestamp is not ready yet is freed
without reading the PHY slot. The hardware completes the capture a
moment later, the orphaned ready bit blocks the port interrupt until
the next link-up sweep, and the freed index can meanwhile be reused
by a new request whose slot the hardware then overwrites. Captured on
a reproducer as ready bits with no in_use owner right after a link
bounce.

Stop dropping on link down. Mark the outstanding requests stale so
their completions are read and discarded, reject new requests while
the link is down, and free a not yet ready slot only after the two
second timeout. This way an index is never reused while the hardware
can still write it and never left untracked while a completion can
still arrive.

To avoid an IRQ storm in the event that we really do have a stale packet
that is not timestamped, modify ice_ptp_tx_tstamps_pending() to ignore
stale timestamps when checking for whether to re-arm the IRQ from the
miscellaneous thread function. Instead, only check for stale packets in the
auxiliary work thread. This way we do not check in a tight loop waiting for
a timestamp that may never come.

This effectively reverts commit fcc2cef37fed ("ice/ptp: fix the PTP worker
retrying indefinitely if the link went down"), which tried to release an
index before this 2 second wait period.

Fixes: fcc2cef37fed ("ice/ptp: fix the PTP worker retrying indefinitely if the link went down")
Suggested-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Petr Oros <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_main.c |  2 +-
 drivers/net/ethernet/intel/ice/ice_ptp.c  | 44 +++++++++++------------
 drivers/net/ethernet/intel/ice/ice_ptp.h  |  8 +++--
 3 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index f32041dd8b27..d12952171a99 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -3248,7 +3248,7 @@ static irqreturn_t ice_misc_intr_thread_fn(int __always_unused irq, void *data)
 	ice_irq_dynamic_ena(hw, NULL, NULL);
 	ice_flush(hw);
 
-	if (ice_ptp_tx_tstamps_pending(pf)) {
+	if (ice_ptp_tx_tstamps_pending(pf, true)) {
 		/* If any new Tx timestamps happened while in interrupt,
 		 * re-arm the interrupt to trigger it again.
 		 */
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index b142b779dc10..299de9d49423 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -364,9 +364,12 @@ static u64 ice_ptp_extend_40b_ts(struct ice_pf *pf, u64 in_tstamp)
 static bool
 ice_ptp_is_tx_tracker_up(struct ice_ptp_tx *tx)
 {
+	struct ice_ptp_port *ptp_port =
+		container_of(tx, struct ice_ptp_port, tx);
+
 	lockdep_assert_held(&tx->lock);
 
-	return tx->init && !tx->calibrating;
+	return tx->init && !tx->calibrating && ptp_port->link_up;
 }
 
 /**
@@ -563,7 +566,6 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	u64 tstamp_ready;
-	bool link_up;
 	int err;
 	u8 idx;
 
@@ -581,14 +583,11 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 			return;
 	}
 
-	/* Drop packets if the link went down */
-	link_up = ptp_port->link_up;
-
 	for_each_set_bit(idx, tx->in_use, tx->len) {
 		struct skb_shared_hwtstamps shhwtstamps = {};
 		u8 phy_idx = idx + tx->offset;
 		u64 raw_tstamp = 0, tstamp;
-		bool drop_ts = !link_up;
+		bool drop_ts = false;
 		struct sk_buff *skb;
 
 		/* Drop packets which have waited for more than 2 seconds */
@@ -1371,6 +1370,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	if (pf->hw.reset_ongoing)
 		goto exit_kref_put;
 
+	if (!linkup)
+		ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
 	if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 &&
 	    test_bit(ICE_FLAG_DPLL, pf->flags)) {
 		int pin, err;
@@ -2767,28 +2769,29 @@ void ice_ptp_process_ts(struct ice_pf *pf)
 	}
 }
 
-static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)
+static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)
 {
-	bool more_timestamps;
+	DECLARE_BITMAP(tstamps, INDEX_PER_PORT_MAX) = {};
 
 	scoped_guard(spinlock_irqsave, &tx->lock) {
 		if (!tx->init)
 			return false;
 
-		more_timestamps = !bitmap_empty(tx->in_use, tx->len);
+		if (in_irq)
+			return bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len);
+		else
+			return !bitmap_empty(tx->in_use, tx->len);
 	}
-
-	return more_timestamps;
 }
 
-static bool ice_any_port_has_timestamps(struct ice_pf *pf)
+static bool ice_any_port_has_timestamps(struct ice_pf *pf, bool in_irq)
 {
 	bool have_tstamps = false;
 	struct ice_ptp_port *port;
 
 	rcu_read_lock();
 	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
-		if (ice_port_has_timestamps(&port->tx)) {
+		if (ice_port_has_timestamps(&port->tx, in_irq)) {
 			have_tstamps = true;
 			break;
 		}
@@ -2798,7 +2801,7 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf)
 	return have_tstamps;
 }
 
-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
 	struct ice_hw *hw = &pf->hw;
 	int ret;
@@ -2808,11 +2811,11 @@ bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
 	case ICE_PTP_TX_INTERRUPT_NONE:
 		return false;
 	case ICE_PTP_TX_INTERRUPT_SELF:
-		if (ice_port_has_timestamps(&pf->ptp.port.tx))
+		if (ice_port_has_timestamps(&pf->ptp.port.tx, in_irq))
 			return true;
 		break;
 	case ICE_PTP_TX_INTERRUPT_ALL:
-		if (ice_any_port_has_timestamps(pf))
+		if (ice_any_port_has_timestamps(pf, in_irq))
 			return true;
 		break;
 	default:
@@ -2888,7 +2891,7 @@ irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)
 		/* E830 can read timestamps in the top half using rd32() */
 		ice_ptp_process_ts(pf);
 
-		if (ice_ptp_tx_tstamps_pending(pf)) {
+		if (ice_ptp_tx_tstamps_pending(pf, true)) {
 			/* Process outstanding Tx timestamps. If there
 			 * is more work, re-arm the interrupt to trigger again.
 			 */
@@ -2918,7 +2921,6 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 {
 	struct device *dev = ice_pf_to_dev(pf);
 	struct ice_hw *hw = &pf->hw;
-	int ret;
 
 	if (!pf->ptp.port.tx.has_ready_bitmap)
 		return;
@@ -2926,11 +2928,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 	if (!ice_pf_src_tmr_owned(pf))
 		return;
 
-	ret = ice_check_phy_tx_tstamp_ready(hw);
-	if (ret < 0) {
-		dev_dbg(dev, "PTP periodic task unable to read PHY timestamp ready bitmap, err %d\n",
-			ret);
-	} else if (ret) {
+	if (ice_ptp_tx_tstamps_pending(pf, false)) {
 		dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
 
 		wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.h b/drivers/net/ethernet/intel/ice/ice_ptp.h
index da2003ba3bb0..13158a9319fb 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.h
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.h
@@ -132,6 +132,9 @@ struct ice_ptp_tx {
 #define INDEX_PER_PORT_E82X		16
 #define INDEX_PER_PORT			64
 
+/* Maximum number of timestamp indexes across all devices */
+#define INDEX_PER_PORT_MAX              INDEX_PER_PORT
+
 /**
  * struct ice_ptp_port - data used to initialize an external port for PTP
  *
@@ -316,7 +319,7 @@ void ice_ptp_req_tx_single_tstamp(struct ice_ptp_tx *tx, u8 idx);
 void ice_ptp_complete_tx_single_tstamp(struct ice_ptp_tx *tx);
 void ice_ptp_process_ts(struct ice_pf *pf);
 irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf);
-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf);
+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq);
 u64 ice_ptp_read_src_clk_reg(struct ice_pf *pf,
 			     struct ptp_system_timestamp *sts);
 
@@ -364,7 +367,8 @@ static inline irqreturn_t ice_ptp_ts_irq(struct ice_pf *pf)
 	return IRQ_HANDLED;
 }
 
-static inline bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
+static inline bool
+ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
 	return false;
 }
-- 
2.47.1

[PATCH net 13/15] ice: skip reading Tx ready bitmap on ports with no timestamps

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:53

From: Jacob Keller <jacob.e.keller@intel.com>

On E82x devices, the interrupt for Tx timestamps are handled by the clock
owner. When an interrupt with the Tx timestamp cause is fired, the clock
owner PF iterates the list of ports and checks for timestamps across all
ports.

The existing logic reads the PHY timestamp ready bitmap before iterating
the list of in-use timestamp indexes, even for ports which have no
timestamps waiting in the software timestamp tracker. This has a
significant and measurable latency impact on reporting Tx timestamps.

Check the bitmap and exit early in the event that there are no timestamps
waiting on a port. Observant reviewers may notice that the check is done
without acquiring the lock. This is fine, as the only thread that can clear
in_use bits is the miscellaneous interrupt handler. Whether the thread sees
or fails to see a new outstanding timestamp does not affect correctness,
only determining whether or not it should do extra work.

Using the ice Tx timestamp traces, with a simple ptp4l setup the average
latency appears to be around 175 to 200 microseconds with a few outliers
taking hundreds of microseconds to be reported. With the check to skip
empty bitmaps (and thus skip reading the ready bitmap for inactive ports),
the average latency drops ~50 microseconds.

Fixes: d938a8cca88a ("ice: Auxbus devices & driver for E822 TS")
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 277d9c77af1c..e060de502662 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -573,7 +573,7 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	pf = ptp_port_to_pf(ptp_port);
 	hw = &pf->hw;
 
-	if (!tx->init)
+	if (!tx->init || bitmap_empty(tx->in_use, tx->len))
 		return;
 
 	/* Read the Tx ready status first */
-- 
2.47.1

[PATCH net 12/15] ice: remove unnecessary discarding of timestamps after clock adjust

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:53

From: Jacob Keller <jacob.e.keller@intel.com>

The ice driver currently discards any outstanding timestamps that are
happening very near to a .adjtime or .settime callback. This was originally
add by commit d40fd6009332 ("ice: handle flushing stale Tx timestamps in
ice_ptp_tx_tstamp").

The original motivation for discarding timestamps was that extending an old
timestamp using the new cached value of PHC was a problem, as it could
produce incorrect results. The change did not describe what such "incorrect
results" were.

There are no such incorrect results. Extending the 32 bit timestamp with
the new time value just means that the timestamp is reported in terms of
the newly updated and adjusted system clock. This won't produce incorrect
results or problematic timestamps to applications. Either the timestamp
will be extended with the value of the PHC just prior to the time
adjustment (if the timestamp completes prior to the adjust callback), or it
will be extended using the new PHC value after the adjustment. In either
case, the resulting extended timestamp value makes sense.

The timestamp extension logic is very similar to the logic found in
timecounter_cyc2time, the primary difference being that the ice hardware
maintains the full 64 bits of nanoseconds in the MAC rather than being
maintained purely by software as in the timecounter case.

Indeed, I couldn't find an example of a driver using timecounter_cyc2time
which does discard timestamps that occur nearby a time adjustment. The ice
driver behavior of discarding such timestamps just results in failure to
deliver a Tx timestamp to userspace, resulting in applications such as
ptp4l to timeout and enter a fault state. Reporting the extended timestamp
based on the updated PHC value isn't producing "garbage" results, and
doesn't lead to incorrect behavior.

This effectively reverts commit d40fd6009332 ("ice: handle flushing stale
Tx timestamps in ice_ptp_tx_tstamp"). However, the stale logic remains, as
we now use it to inform the driver to drop timestamps which might fail due
to link down.

Fixes: d40fd6009332 ("ice: handle flushing stale Tx timestamps in ice_ptp_tx_tstamp")
Reviewed-by: Maciek Machnikowski <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 17 ++++-------------
 1 file changed, 4 insertions(+), 13 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 299de9d49423..277d9c77af1c 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -826,12 +826,10 @@ ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
  * ice_ptp_mark_tx_tracker_stale - Mark unfinished timestamps as stale
  * @tx: the tracker to mark
  *
- * Mark currently outstanding Tx timestamps as stale. This prevents sending
- * their timestamp value to the stack. This is required to prevent extending
- * the 40bit hardware timestamp incorrectly.
- *
- * This should be called when the PTP clock is modified such as after a set
- * time request.
+ * Mark currently outstanding Tx timestamps as stale. This prevents the driver
+ * from reporting the timestamp to the stack. This is called to inform the
+ * driver that a timestamp is expected to fail if it was initiated as the link
+ * went down.
  */
 static void
 ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
@@ -1049,13 +1047,6 @@ static void ice_ptp_reset_cached_phctime(struct ice_pf *pf)
 		kthread_queue_delayed_work(pf->ptp.kworker, &pf->ptp.work,
 					   msecs_to_jiffies(10));
 	}
-
-	/* Mark any outstanding timestamps as stale, since they might have
-	 * been captured in hardware before the time update. This could lead
-	 * to us extending them with the wrong cached value resulting in
-	 * incorrect timestamp values.
-	 */
-	ice_ptp_mark_tx_tracker_stale(&pf->ptp.port.tx);
 }
 
 /**
-- 
2.47.1

[PATCH net 14/15] ice: don't clear in_use until HW clears ready bitmap

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:54

From: Jacob Keller <jacob.e.keller@intel.com>

During a link down transition, the E825 PHY has a small window where it
does not properly respond to reading the PHY timestamp registers. When this
occurs, the PHY does not automatically clear the ready bitmap or the valid
bit for the timestamp. This begins happening slightly before a link
transition even before the firmware has notified the driver of the state
change.

The driver happily completes the timestamp, releasing the in_use bit. This
allows another request to reuse the bit potentially reporting an invalid
stale timestamp. Additionally, with the ready bit still set high the driver
continues to re-trigger the IRQ and check for timestamps in a tight loop,
wasting CPU cycles.

To fix this, re-read the PHY timestamp memory status after each read of a
PHY index. Double check if the hardware cleared the index properly. If it
hasn't, mark the timestamp index as stale and skip processing it.

Stale timestamps are already ignored by the ice_any_port_has_timestamps()
function. However, the ice_ptp_tx_tstamps_pending() function also checks
the ready bitmap. Instead, modify it to only check the software tracker.
Additionally, stop re-triggering the interrupt from the IRQ if the
timestamp tracker is calibrating or has the link marked as down. Continue
to check the hardware ready bitmap from the watchdog to catch cases of
unexpected timestamps.

With these changes, the timestamp processing no longer triggers a repeated
spamming of the IRQ during link down events where timestamps get stuck as
the PHY transitions to link down. Once link is restored, the PHY will be
reset and the stuck timestamps are cleared.

Measuring CPU utilization of the miscellaneous IRQ thread function during
timestamp storms near a link reset shows that this prevents the spikes
caused by the "stuck" ready bit. Without this fix, the CPU handling the IRQ
becomes slammed due to the IRQ re-triggering logic.

Measuring latency using the ice Tx timestamp traces does show that this fix
comes at a latency cost. Latency is measured using the ice Tx timestamp
traces for the request to completion time. I measured a couple of different
workloads both before and after this fix:

 * ptp4l using a profile with ~16 SYNC messages per second

    before: 159.40 microseconds mean, stdev 45.28
     after: 182.07 microseconds mean,  stdev 43.43

 * a C program generating 16 timestamp requests every 10 milliseconds on
   two different ports:

    before: 604.35 microseconds mean, stdev 345.32
     after: 990.13 microseconds mean, stdev 625.64

In the normal work flows this comes with about a 20 microsecond penalty on
the average, and the standard deviation remains approximately the same. For
heavy workloads with many more timestamps than expected for typical
applications this comes at a significant cost. This is because we handle
all timestamps in a single thread. If there are many concurrent timestamps
being requested at once, any which use the later slots on ports later in
the port list will take much longer to be processed once the interrupt is
fired. Since each timestamp now requires an additional PHY register access,
this cost is much higher in the case where the device is under unusually
heavy load.

However, *correctness* is more important than speed here. Additionally, we
still remain well below the default limit of 10 milliseconds that ptp4l
will wait before complaining about missing timestamps.

Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 59 +++++++++++++-----------
 1 file changed, 31 insertions(+), 28 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index e060de502662..832cc6ad2f90 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -620,6 +620,19 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 		if (err && !drop_ts)
 			continue;
 
+		/* verify ready bit cleared */
+		if (tx->has_ready_bitmap) {
+			err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
+			if (err || tstamp_ready & BIT_ULL(phy_idx)) {
+				spin_lock_irqsave(&tx->lock, flags);
+				if (!test_and_set_bit(idx, tx->stale))
+					dev_dbg(ice_pf_to_dev(pf), "PHY port %u failed to clear ready bit for idx %u\n",
+						ptp_port->port_num, phy_idx);
+				spin_unlock_irqrestore(&tx->lock, flags);
+				continue;
+			}
+		}
+
 		ice_trace(tx_tstamp_fw_done, tx->tstamps[idx].skb, idx);
 
 		/* For PHYs which don't implement a proper timestamp ready
@@ -2768,10 +2781,14 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)
 		if (!tx->init)
 			return false;
 
-		if (in_irq)
+		if (in_irq) {
+			if (!ice_ptp_is_tx_tracker_up(tx))
+				return false;
+
 			return bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len);
-		else
+		} else {
 			return !bitmap_empty(tx->in_use, tx->len);
+		}
 	}
 }
 
@@ -2794,41 +2811,18 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf, bool in_irq)
 
 bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
-	struct ice_hw *hw = &pf->hw;
-	int ret;
-
-	/* Check software indicator */
 	switch (pf->ptp.tx_interrupt_mode) {
 	case ICE_PTP_TX_INTERRUPT_NONE:
 		return false;
 	case ICE_PTP_TX_INTERRUPT_SELF:
-		if (ice_port_has_timestamps(&pf->ptp.port.tx, in_irq))
-			return true;
-		break;
+		return ice_port_has_timestamps(&pf->ptp.port.tx, in_irq);
 	case ICE_PTP_TX_INTERRUPT_ALL:
-		if (ice_any_port_has_timestamps(pf, in_irq))
-			return true;
-		break;
+		return ice_any_port_has_timestamps(pf, in_irq);
 	default:
 		WARN_ONCE(1, "Unexpected Tx timestamp interrupt mode %u\n",
 			  pf->ptp.tx_interrupt_mode);
-		break;
-	}
-
-	/* Check hardware indicator */
-	ret = ice_check_phy_tx_tstamp_ready(hw);
-	if (ret < 0) {
-		dev_dbg(ice_pf_to_dev(pf), "Unable to read PHY Tx timestamp ready bitmap, err %d\n",
-			ret);
-		/* Stop triggering IRQs if we're unable to read PHY */
 		return false;
 	}
-
-	/* ice_check_phy_tx_tstamp_ready() returns 1 if there are timestamps
-	 * available, 0 if there are no waiting timestamps, and a negative
-	 * value if there was an error (which we checked for above).
-	 */
-	return ret > 0;
 }
 
 /**
@@ -2912,6 +2906,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 {
 	struct device *dev = ice_pf_to_dev(pf);
 	struct ice_hw *hw = &pf->hw;
+	int ret;
 
 	if (!pf->ptp.port.tx.has_ready_bitmap)
 		return;
@@ -2919,7 +2914,15 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 	if (!ice_pf_src_tmr_owned(pf))
 		return;
 
-	if (ice_ptp_tx_tstamps_pending(pf, false)) {
+	ret = ice_check_phy_tx_tstamp_ready(hw);
+	if (ret < 0) {
+		dev_dbg(dev, "Unable to read PHY Tx timestamp ready bitmap, err %pe\n",
+			ERR_PTR(ret));
+		/* Don't trigger an IRQ if we are unable to access the PHY */
+		return;
+	}
+
+	if (ret > 0 || ice_ptp_tx_tstamps_pending(pf, false)) {
 		dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
 
 		wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
-- 
2.47.1

[PATCH net 15/15] ice: Recalibrate PHY after settime64 on E825-C

From: Tony Nguyen <anthony.l.nguyen@intel.com>
Date: 2026-09-11 00:34:55

From: Maciek Machnikowski <redacted>

The PHY on E825-C requires recalibration after large jumps of the
system time. This step is required and is done for E822 devices
previously, but was accidentally skipped due to oversight when
E825-C support was added.

Lack of recalibration will fail to converge quickly as the effective
adjustment requested is not applied properly to the PHY timestamps,
so the readings from the timestamp are incorrect.

Without the fix:
ptp4l[12591.505]: port 1 (eno8303np0): SLAVE to UNCALIBRATED on SYNCHRONIZATION_FAULT
ptp4l[12591.951]: port 1 (eno8303np0): UNCALIBRATED to SLAVE on MASTER_CLOCK_SELECTED
ptp4l[12592.027]: rms 752851973 max 1102277559 freq -84408798 +/- 53560133 delay  6009 +/- 92308
ptp4l[12593.038]: rms 91302045 max 136894784 freq +100000000 +/-   0 delay 93700 +/- 24153
ptp4l[12594.050]: rms 16036314 max 35711594 freq +47433070 +/- 46746070 delay 44899 +/- 45255
ptp4l[12595.061]: rms 5558880 max 12292103 freq -13092285 +/- 6627317 delay -13499 +/- 6560
ptp4l[12596.073]: rms 759533 max 1081638 freq +837063 +/- 802691 delay   711 +/- 979
ptp4l[12597.085]: rms 60485 max 106800 freq +146706 +/- 263535 delay   145 +/- 263
ptp4l[12598.096]: rms 16428 max 41896 freq -46707 +/- 34325 delay   -40 +/-  34
ptp4l[12599.108]: rms 3049 max 5356 freq  +5337 +/- 1730 delay     7 +/-   2
ptp4l[12600.120]: rms  284 max  381 freq   +104 +/- 759 delay     2 +/-   1
ptp4l[12601.131]: rms   42 max  119 freq   -124 +/- 143 delay     2 +/-   0
ptp4l[12602.144]: rms   11 max   25 freq    +39 +/-  12 delay     3 +/-   0
ptp4l[12603.156]: rms    2 max    4 freq    +14 +/-   5 delay     3 +/-   0
ptp4l[12604.167]: rms    1 max    3 freq    +15 +/-   5 delay     2 +/-   0
ptp4l[12605.179]: rms    1 max    4 freq    +16 +/-   5 delay     2 +/-   1
ptp4l[12606.191]: rms    1 max    3 freq    +15 +/-   5 delay     3 +/-   0
With the fix:
ptp4l[12834.266]: rms 27178238388098 max 30079328952737 freq +479164 +/- 1040869 delay     5 +/-   1
ptp4l[12834.522]: port 1 (eno8703np0): minimum delay request interval 2^-8
ptp4l[12835.315]: rms 86919 max 139746 freq +111289 +/- 539214 delay   -35 +/-  28
ptp4l[12836.376]: rms 5415 max 8884 freq   +579 +/- 22798 delay     4 +/-   5
ptp4l[12837.429]: rms  335 max  559 freq   -305 +/- 842 delay     4 +/-   1
ptp4l[12838.471]: rms   20 max   45 freq    +79 +/-  33 delay     4 +/-   0
ptp4l[12839.512]: rms    2 max    5 freq    +44 +/-   8 delay     4 +/-   0
ptp4l[12840.545]: rms    1 max    4 freq    +46 +/-   8 delay     4 +/-   0
ptp4l[12841.586]: rms    1 max    3 freq    +46 +/-   7 delay     4 +/-   0
ptp4l[12842.637]: rms    1 max    3 freq    +46 +/-   7 delay     4 +/-   0
Fixes: 7cab44f1c35f ("ice: Introduce ETH56G PHY model for E825C products")
Signed-off-by: Maciek Machnikowski <redacted>
Reviewed-by: Paul Menzel <redacted>
Reviewed-by: Aleksandr Loktionov <redacted>
Tested-by: Alexander Nowlin <redacted>
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
---
 drivers/net/ethernet/intel/ice/ice_ptp.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 832cc6ad2f90..a5efb0a95941 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2020,8 +2020,9 @@ ice_ptp_settime64(struct ptp_clock_info *info, const struct timespec64 *ts)
 	/* Reenable periodic outputs */
 	ice_ptp_enable_all_perout(pf);
 
-	/* Recalibrate and re-enable timestamp blocks for E822/E823 */
-	if (hw->mac_type == ICE_MAC_GENERIC)
+	/* Recalibrate and re-enable timestamp blocks for E822/E823/E825-C */
+	if (hw->mac_type == ICE_MAC_GENERIC ||
+	    hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_restart_all_phy(pf);
 exit:
 	if (err) {
-- 
2.47.1

Re: [PATCH net 01/15] ice: use reference counting and RCU for PTP port access

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:12

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: use reference counting and RCU for PTP port access

The adapter-wide PTP port list is converted from a mutex-protected list to
an RCU list plus a kref on each struct ice_ptp_port. Iterators either stay
inside a single RCU read section, or take a reference and drop RCU across
the per-port work. PF teardown unlinks the port, drops the primary
reference, waits (with a 15 second timeout) for the remaining references,
and then calls synchronize_rcu().

A couple of questions below about the new iteration pattern and about a
few lifetime claims in the commit message.
The kref reference counter ensures that we can
safely acquire pointers with a guarantee of their lifetime, ensuring the
associated PF will not be removed until the reference is released.
[Severity: High]

This isn't a bug introduced by this patch, but does that sentence read
wider than the change actually is?  The new kref covers the port list
only; the other cross-PF pointer on the adapter is still unprotected:

drivers/net/ethernet/intel/ice/ice_ptp.c:
static void ice_ptp_setup_adapter(struct ice_pf *pf)
{
	pf->adapter->ctrl_pf = pf;
}

drivers/net/ethernet/intel/ice/ice.h:
static inline struct ice_pf *ice_get_ctrl_pf(struct ice_pf *pf)
{
	return !pf->adapter ? NULL : pf->adapter->ctrl_pf;
}

grep over drivers/net/ethernet/intel/ice/ finds exactly one assignment of
ctrl_pf and no site that ever clears it.

If the clock owner PF is unbound while a sibling PF of the same adapter
stays bound, the ice_adapter survives on the sibling's refcount, but
ctrl_pf still points at the freed struct ice_pf.  Can a later tx-clk
change then dereference it?

drivers/net/ethernet/intel/ice/ice_txclk.c:ice_txclk_set_clk()
	mutex_lock(&ctrl_pf->dplls.lock);
	clk_in_use = ice_txclk_any_port_uses(ctrl_pf, phy, clk);

The IS_ERR_OR_NULL(ctrl_pf) checks in that file cannot detect a stale
non-NULL pointer.  Would it be worth stating in the commit message that
the new reference counting is scoped to the port list, so that ctrl_pf
isn't assumed to be covered?
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_adapter.h b/drivers/net/ethernet/intel/ice/ice_adapter.h
index 4f695f32da3d8..39923dedd534e 100644
--- a/drivers/net/ethernet/intel/ice/ice_adapter.h
+++ b/drivers/net/ethernet/intel/ice/ice_adapter.h
@@ -19,13 +19,13 @@ struct ice_pf;
  *
  * This structure contains data used to maintain a list of adapter ports
  *
- * @ports: list of ports
+ * @list: list of ports
  * @lock: protect access to the ports list
  */
 struct ice_port_list {
-	struct list_head ports;
+	struct list_head list;
 	/* To synchronize the ports list operations */
-	struct mutex lock;
+	spinlock_t lock;
 };
[Severity: Low]

This isn't a bug, but should the kernel-doc be updated along with the
locking model?  After this patch, ports.lock is taken only around
list_add_rcu() in ice_ptp_setup_pf() and list_del_rcu() in
ice_ptp_cleanup_pf(); all five traversal sites in ice_ptp.c use
rcu_read_lock() plus list_for_each_entry_rcu() and never take the lock.

The comments still say "@lock: protect access to the ports list" and "To
synchronize the ports list operations", which reads as if holding the lock
were required (or sufficient) for iteration.  Would it help to note that
the list is RCU-protected for readers, and that readers which leave the
RCU section must hold a port reference?
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index eaec36ab6ae3f..b12181b8c8434 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
[ ... ]
quoted hunk
@@ -673,20 +675,33 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	pf->ptp.tx_hwtstamp_good += tstamp_good;
 }
 
+static void ice_ptp_release_port_rcu(struct kref *ref)
+{
+	wake_up_var(ref);
+}
+
 static void ice_ptp_tx_tstamp_owner(struct ice_pf *pf)
 {
 	struct ice_ptp_port *port;
 
-	mutex_lock(&pf->adapter->ports.lock);
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
 		struct ice_ptp_tx *tx = &port->tx;
 
-		if (!tx || !tx->init)
+		if (!tx->init)
 			continue;
 
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+
+		rcu_read_unlock();
+
 		ice_ptp_process_tx_tstamp(tx);
+
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
 	}
-	mutex_unlock(&pf->adapter->ports.lock);
+	rcu_read_unlock();
 }
[Severity: High]

Is the loop advance still safe here?  The kref is held on the current port
only, and the for-loop advance reads port->list_node.next after the RCU
read section has been reopened.  list_del_rcu() only updates the
neighbours, so the removed entry's next pointer keeps pointing at its old
successor, and ice_ptp_cleanup_pf() unlinks before it drains references:

	spin_lock(&pf->adapter->ports.lock);
	list_del_rcu(&ptp->port.list_node);
	spin_unlock(&pf->adapter->ports.lock);

	ref = &ptp->port.ref;
	kref_put(ref, ice_ptp_release_port_rcu);

Can this sequence happen with two functions of the same adapter, where
port B follows port A in the list?

CPU0 (clock owner, ice_ptp_tx_tstamp_owner())
	kref_get_unless_zero(&portA->ref);
	rcu_read_unlock();
	ice_ptp_process_tx_tstamp(&portA->tx);   /* long PHY access */

CPU1 (unbind of the PF owning port B)
	ice_ptp_cleanup_pf()
	  list_del_rcu(&portB->list_node);
	  kref_put(&portB->ref, ...);            /* refcount reaches 0 */
	  wait_var_event_timeout(...)            /* returns immediately */
	  synchronize_rcu();                     /* CPU0 holds no RCU lock */
	... struct ice_pf of B (which embeds portB) is freed

CPU0
	rcu_read_lock();
	kref_put(&portA->ref, ice_ptp_release_port_rcu);
	/* loop advance: */
	pos = list_entry_rcu(portA->list_node.next, ...);  /* freed portB */
	if (!tx->init)                        /* read of freed memory */
	kref_get_unless_zero(&portB->ref)     /* cmpxchg on freed memory */

The same drop-RCU-inside-the-loop pattern appears in
ice_ptp_flush_all_tx_tracker(), ice_ptp_restart_all_phy() and
ice_ptp_prepare_rebuild_sec() below, and the windows there are wide
(ice_ptp_port_phy_restart() takes ps_lock and reprograms the PHY).

Note that ice_ptp_tx_tstamp_owner() previously held ports.lock across the
entire iteration, so this particular hazard is new for that function.
Would taking a reference on the next entry before dropping RCU, or keeping
the whole iteration inside one RCU read section, close this?
quoted hunk
 /**
@@ -808,8 +823,16 @@ ice_ptp_flush_all_tx_tracker(struct ice_pf *pf)
 {
 	struct ice_ptp_port *port;
 
-	list_for_each_entry(port, &pf->adapter->ports.ports, list_node)
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
 		ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
+	}
+	rcu_read_unlock();
 }
[Severity: Medium]

This is a pre-existing issue, not something this patch introduces, but
does the new port reference also cover the Tx tracker allocations reached
through the port?  At this commit a peer reset frees them while the port
stays linked in the list:

ice_ptp_prepare_for_reset()
	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);

ice_ptp_release_tx_tracker()
	synchronize_irq(pf->oicr_irq.virq);
	ice_ptp_flush_tx_tracker(pf, tx);
	kfree(tx->tstamps);

The synchronize_irq() is on the peer's own OICR vector, which does not
exclude the clock owner's IRQ thread in ICE_PTP_TX_INTERRUPT_ALL mode, and
the owner samples port->tx.init without tx->lock before calling
ice_ptp_process_tx_tstamp() or ice_ptp_flush_tx_tracker() on that peer
port.  Since the port is never unlinked in this path, the kref drain in
ice_ptp_cleanup_pf() does not apply.

The triggering call is removed later in the series by "ice: fix removal of
PTP timestamp tracker during reset", after which
ice_ptp_release_tx_tracker() only runs from ice_ptp_release() once
ice_ptp_cleanup_pf() has unlinked the port and drained references.
 /**
[ ... ]
quoted hunk
@@ -1424,16 +1453,21 @@ static void ice_ptp_reset_phy_timestamping(struct ice_pf *pf)
  */
 static void ice_ptp_restart_all_phy(struct ice_pf *pf)
 {
-	struct list_head *entry;
+	struct ice_ptp_port *port;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
 
 		if (port->link_up)
 			ice_ptp_port_phy_restart(port);
+
+		rcu_read_lock();
+		kref_put(&port->ref, ice_ptp_release_port_rcu);
 	}
+	rcu_read_unlock();
 }
[ ... ]
quoted hunk
@@ -2890,14 +2924,16 @@ void ice_ptp_queue_work(struct ice_pf *pf)
 static void ice_ptp_prepare_rebuild_sec(struct ice_pf *pf, bool rebuild,
 					enum ice_reset_req reset_type)
 {
-	struct list_head *entry;
+	struct ice_ptp_port *port;
 
-	list_for_each(entry, &pf->adapter->ports.ports) {
-		struct ice_ptp_port *port = list_entry(entry,
-						       struct ice_ptp_port,
-						       list_node);
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
 		struct ice_pf *peer_pf = ptp_port_to_pf(port);
 
+		if (!kref_get_unless_zero(&port->ref))
+			continue;
+		rcu_read_unlock();
+
[ ... ]
quoted hunk
@@ -3086,11 +3126,11 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 		return -ENODEV;
 
 	INIT_LIST_HEAD(&ptp->port.list_node);
-	mutex_lock(&pf->adapter->ports.lock);
+	kref_init(&ptp->port.ref);
 
-	list_add(&ptp->port.list_node,
-		 &pf->adapter->ports.ports);
-	mutex_unlock(&pf->adapter->ports.lock);
+	spin_lock(&pf->adapter->ports.lock);
+	list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list);
+	spin_unlock(&pf->adapter->ports.lock);
[Severity: Medium]

This isn't introduced by this patch, but is every successful
ice_ptp_setup_pf() matched by an ice_ptp_cleanup_pf() at this commit?  In
ice_ptp_init():

	ptp->state = ICE_PTP_READY;

	err = ice_ptp_init_work(pf, ptp);
	if (err)
		goto err_exit;
	...
err_clean_pf:
	mutex_destroy(&ptp->port.ps_lock);
	ice_ptp_cleanup_pf(pf);
err_exit:
	...
	ptp->state = ICE_PTP_UNINIT;

The err_exit path skips ice_ptp_cleanup_pf() and leaves the state at
ICE_PTP_UNINIT, and ice_ptp_release() then does:

	if (pf->ptp.state == ICE_PTP_UNINIT)
		return;

So if kthread_run_worker() in ice_ptp_init_work() fails, does the port stay
linked on adapter->ports.list with its primary reference held, inside
memory that is freed at PF removal?  That would also leave
kref_get_unless_zero() succeeding on freed memory for every peer iterating
the list, and should trip WARN_ON(!list_empty(&adapter->ports.list)) in
ice_adapter_free().

This is fixed later in the series by "ice: call PTP link change only from
link events", which moves ice_ptp_init_work() before the state assignment
and sends its failure path to err_clean_pf.
quoted hunk
 
 	/* Seed the per-PHY Tx reference clock usage map for this port.
 	 * Only meaningful on E825 (other MAC types don't expose tx-clk
@@ -3113,12 +3153,32 @@ static int ice_ptp_setup_pf(struct ice_pf *pf)
 static void ice_ptp_cleanup_pf(struct ice_pf *pf)
 {
 	struct ice_ptp *ptp = &pf->ptp;
+	struct kref *ref;
 
-	if (pf->hw.mac_type != ICE_MAC_UNKNOWN) {
-		mutex_lock(&pf->adapter->ports.lock);
-		list_del(&ptp->port.list_node);
-		mutex_unlock(&pf->adapter->ports.lock);
-	}
+	if (pf->hw.mac_type == ICE_MAC_UNKNOWN)
+		return;
+
+	/* The PF cannot be removed until there are no more remaining
+	 * outstanding references to the PTP port. To make sure this is true,
+	 * first remove the port from the list, then drop the primary
+	 * reference this PF holds on the port. Once done, wait until all
+	 * existing references are dropped. Finally, synchronize_rcu() to
+	 * ensure that all RCU critical sections that might attempt to
+	 * dereference the port are finished.
+	 */
[Severity: Low]

The commit message describes the 15 second timeout and the tradeoff it
accepts, but this comment says the PF "cannot be removed until there are no
more remaining outstanding references" and that the code will "wait until
all existing references are dropped".  Should the comment mention the
timeout, since teardown continues after the dev_WARN_ONCE() with a nonzero
reference count?
+
+	spin_lock(&pf->adapter->ports.lock);
+	list_del_rcu(&ptp->port.list_node);
+	spin_unlock(&pf->adapter->ports.lock);
+
+	ref = &ptp->port.ref;
+	kref_put(ref, ice_ptp_release_port_rcu);
+
+	dev_WARN_ONCE(ice_pf_to_dev(pf),
+		      !wait_var_event_timeout(ref, !kref_read(ref), 15 * HZ),
+		      "Timed out waiting for port references to release. Continuing to unload anyways.");
+
+	synchronize_rcu();
 }
[Severity: Low]

This is a pre-existing ordering issue, not something this patch changes,
but now that ice_ptp_cleanup_pf() is the reference/RCU barrier, would it
make sense to call it before destroying port members?  Both teardown paths
currently do the opposite:

ice_ptp_init():
err_clean_pf:
	mutex_destroy(&ptp->port.ps_lock);
	ice_ptp_cleanup_pf(pf);

ice_ptp_release():
	if (pf->ptp.state != ICE_PTP_READY) {
		mutex_destroy(&pf->ptp.port.ps_lock);
		ice_ptp_cleanup_pf(pf);

In that window the port is still published on adapter->ports.list with its
primary reference held, so a peer in ice_ptp_restart_all_phy() or
ice_ptp_prepare_rebuild_sec() can take a reference and reach
mutex_lock(&ptp_port->ps_lock) on the destroyed mutex.  With
CONFIG_DEBUG_MUTEXES that is a DEBUG_LOCKS_WARN_ON splat rather than
corruption, since mutex_destroy() only clears lock->magic, but swapping the
two calls would keep cleanup_pf() as the single teardown point.

[ ... ]
-- 
pw-bot: cr

Re: [PATCH net 02/15] ice: fix removal of PTP timestamp tracker during reset

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:15

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: fix removal of PTP timestamp tracker during reset

This drops the ice_ptp_release_tx_tracker() call from
ice_ptp_prepare_for_reset(), completing the intent of 7a25fe5cd5fb so the
Tx timestamp tracker is no longer destroyed (and never re-created) across a
CORE or GLOBAL reset.

Closes: https://sashiko.dev/#/patchset/20260821-jk-e825c-minimized-fixes-v1-0-9d0731eb4858%40intel.com?part=8
    Unfortunately, the commit only removed the calls to ice_ptp_init_tx_*().
    This fixed a memory leak in PF reset. However, now a CORE or GLOBAL reset
    will leave the device unable to initiate Tx timestamp requests
    indefinitely.
[Severity: Low]
Is the list of affected reset types complete here?  ice_ptp_prepare_for_reset()
returns early only for PFR:

	if (reset_type == ICE_RESET_PFR)
		return;

	...
	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);

so the deleted call also ran for ICE_RESET_EMPR.  ice_reset_subtask() maps
ICE_EMPR_RECV to ICE_RESET_EMPR and then calls ice_prepare_for_reset(), and
the matching ice_rebuild() -> ice_ptp_rebuild() has no ice_ptp_init_tx_*()
call either.  Should the commit message mention EMP resets as well, since
that affects stable triage?
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index b12181b8c8434..142d39ee5cc52 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2979,8 +2979,6 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
 	if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
 
-	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);
-
[Severity: High]
This call was also the only thing that cleared tx->init under tx->lock and
ran synchronize_irq() for the low latency Tx timestamp path.  With the
tracker left armed, can ice_ll_ts_intr() still run against live tracker
state in the middle of a CORE or GLOBAL reset?

The handler is gated only on tx->init:

drivers/net/ethernet/intel/ice/ice_main.c:ice_ll_ts_intr() {
	spin_lock_irqsave(&tx->lock, flags);
	if (tx->init) {
		ice_ptp_complete_tx_single_tstamp(tx);

		idx = find_next_bit_wrap(tx->in_use, tx->len,
					 tx->last_ll_ts_idx_read + 1);
		if (idx != tx->len)
			ice_ptp_req_tx_single_tstamp(tx, idx);
	}
	...
}

The LL cause lives in PFINT_SB_CTL, so ice_ptp_disable_timestamp_mode() does
not mask it (it only clears PFINT_OICR_ENA TSYN_TX), and
ice_prepare_for_reset() only does synchronize_irq(pf->oicr_irq.virq), not
pf->ll_ts_irq.

Two consequences worth checking:

ice_ptp_complete_tx_single_tstamp() reads REG_LL_PROXY_L/H of a resetting
device, so can a garbage timestamp be reported through skb_tstamp_tx() for a
still-set in_use slot?

The handler sees the pre-reset in_use bits and issues a new firmware request
via ice_ptp_req_tx_single_tstamp(), which sets:

	params->atqbal_flags |= ATQBAL_FLAGS_INTR_IN_PROGRESS;

That flag is cleared only by ice_ptp_complete_tx_single_tstamp(), i.e. by a
later LL interrupt, and nothing re-initializes it on rebuild.  If the reset
swallows the request, does every subsequent user of the LL interface block
forever in the untimed wait in ice_ptp_prep_phy_incval_ll_e810() /
ice_ptp_prep_phy_adj_ll_e810():

	err = wait_event_interruptible_locked_irq(params->atqbal_wq,
						  !(params->atqbal_flags &
						    ATQBAL_FLAGS_INTR_IN_PROGRESS));

These are reached from ice_ptp_rebuild_owner() through
ice_ptp_write_incval()/ice_ptp_write_init(), which run in the PF service task
workqueue where no signal can break the wait.

Note ice_ptp_ts_irq() checks ice_pf_state_is_nominal() and
ice_misc_intr_thread_fn() checks ice_is_reset_in_progress(), while
ice_ll_ts_intr() has no reset-state check.  Would keeping the tracker across
reset need an explicit disarm here (a state check honored by
ice_ll_ts_intr() plus synchronize_irq(pf->ll_ts_irq.virq)) rather than just
dropping the release?

[Severity: Medium]
Can the tracker allocations now leak at unload when the PTP state is not
ICE_PTP_READY?

tx->tstamps, tx->in_use and tx->stale are allocated in
ice_ptp_alloc_tx_tracker(), and after this change the only remaining free is
in the READY branch of ice_ptp_release():

	if (pf->ptp.state != ICE_PTP_READY) {
		mutex_destroy(&pf->ptp.port.ps_lock);
		ice_ptp_cleanup_pf(pf);
		if (pf->ptp.clock) {
			ptp_clock_unregister(pf->ptp.clock);
			pf->ptp.clock = NULL;
		}
		return;
	}
	...
	ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx);

ice_ptp_cleanup_pf() only unlinks the port from the adapter list and drops
the kref, so it does not touch the tracker.

If ice_ptp_rebuild_owner() fails, ice_ptp_rebuild() leaves the state at
ICE_PTP_ERROR:

err:
	ptp->state = ICE_PTP_ERROR;
	dev_err(ice_pf_to_dev(pf), "PTP reset failed %d\n", err);

and a failed ice_check_reset() leaves it at ICE_PTP_RESETTING.  A later
rmmod or unbind then takes the early-return branch above.  Does that leak the
timestamp array, both bitmaps, and any SKBs still pinned in tx->tstamps[]
along with their socket references?

[Severity: Medium]
With this call gone, the only reset-time flush left is
ice_ptp_flush_all_tx_tracker() inside ice_ptp_rebuild_owner().  What reclaims
the tracker if the reset never reaches ice_rebuild()?

drivers/net/ethernet/intel/ice/ice_main.c:ice_reset_subtask() {
		if (ice_check_reset(&pf->hw)) {
			set_bit(ICE_RESET_FAILED, pf->state);
		} else {
			...
			ice_rebuild(pf, reset_type);
	...
}

In that path ice_ptp_prepare_for_reset() has already run, so the pre-reset
tx->in_use bits stay set with skb_get() references pinned in tx->tstamps[]
and SKBTX_IN_PROGRESS still set, while the PHY timestamps were destroyed by
the reset.

The 2 second timeout drain cannot run either:

	if (pf->ptp.state != ICE_PTP_READY)
		return;

in ice_ptp_periodic_work(), and the work was cancelled in
ice_ptp_prepare_for_reset().  The OICR TSYN_TX cause was masked by
ice_ptp_disable_timestamp_mode() and is only re-enabled by
ice_ptp_restore_timestamp_mode() at the end of a completed ice_rebuild().
Combined with the non-READY branch of ice_ptp_release() above, are those SKBs
and their socket references then held for the remaining lifetime of the
driver instance?

For contrast, the narrower case of a *PTP* rebuild failure does still drain:
ice_ptp_rebuild() returns void, ice_rebuild() continues and calls
ice_ptp_restore_timestamp_mode(), so the stale entries age out via the 2 s
timeout.

[Severity: Medium]
After a failed CORE or GLOBAL PTP rebuild, does the retained tracker keep
accepting requests that can never complete?

tx->init now stays 1, and admission in ice_ptp_request_ts() consults only the
tracker:

	if (!ice_ptp_is_tx_tracker_up(tx)) {
		spin_unlock_irqrestore(&tx->lock, flags);
		return -1;
	}

ice_ptp_is_tx_tracker_up() tests tx->init && !tx->calibrating and never looks
at pf->ptp.state.  Meanwhile ice_ptp_rebuild() has set ICE_PTP_ERROR and
returned void, so ice_rebuild() reattaches the netdev and calls
ice_ptp_restore_timestamp_mode(), re-enabling the Tx timestamp cause, while
the PHY was never restarted (ice_ptp_restart_all_phy() sits after the failure
point) and:

	if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags) &&
	    pf->ptp.state == ICE_PTP_READY)
		kthread_queue_delayed_work(pf->ptp.kworker, &pf->ptp.work, 0);

in ice_ptp_queue_work() refuses to restart periodic processing.  Before this
patch tx->init had been cleared, so this error path rejected requests.  Would
each timestamp-requesting SKB now be held until the 2 second timeout drops it
and bumps tx_hwtstamp_timeouts?

[Severity: Medium]
Since peer PFs' trackers now survive CORER/GLOBR, can the clock owner's
rebuild flush race the peers?

ice_ptp_rebuild_owner() -> ice_ptp_flush_all_tx_tracker() walks every port of
the adapter:

		ice_ptp_flush_tx_tracker(ptp_port_to_pf(port), &port->tx);

and ice_ptp_flush_tx_tracker() does its PHY register work outside tx->lock:

	err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
	...
		if (!hw->reset_ongoing && (tstamp_ready & BIT_ULL(phy_idx)))
			ice_clear_phy_tstamp(hw, tx->block, phy_idx);

On E810/E830 each PF uses ICE_PTP_TX_INTERRUPT_SELF, so a peer PF that has
already finished its own ice_rebuild() (and therefore
ice_ptp_restore_timestamp_mode()) can be inside ice_ptp_process_tx_tstamp()
reading the same block:

	if (tx->has_ready_bitmap) {
		err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
	...
		err = ice_read_phy_tstamp(hw, tx->block, phy_idx, &raw_tstamp);

Before this patch the peers' trackers had been released (tx->len = 0, in_use
NULL, tx->init = 0), so the owner's flush iterated nothing and the peers could
not process.  Can a timestamp be cleared between the peer's ready-bitmap read
and its register read here, which the comment in ice_ptp_process_tx_tstamp()
warns can wedge the interrupt generation logic?  Note the teardown path
(ice_ptp_release_tx_tracker()) clears tx->init and calls synchronize_irq()
before flushing, but the rebuild flush has no equivalent quiesce for peer
PFs.

Re: [PATCH net 04/15] ice: E822: keep Tx timestamps disabled during offset calibration

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:16

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: E822: keep Tx timestamps disabled during offset calibration

This moves the clearing of port->tx.calibrating out of
ice_ptp_port_phy_restart() and into ice_ptp_wait_for_offsets(), so that Tx
timestamps stay disabled until the Tx and Rx PHY offsets have been
configured.  It also adds two dev_dbg() messages covering the pending and
the completed calibration cases.

No Link: tags present in the commit header.

A few questions below.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 68537705e8399..d018f02f700d7 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1156,6 +1156,7 @@ static int ice_ptp_check_tx_fifo(struct ice_ptp_port *port)
 static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 {
 	struct ice_ptp_port *port;
+	unsigned long flags;
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	int tx_err;
@@ -1178,12 +1179,28 @@ static void ice_ptp_wait_for_offsets(struct kthread_work *work)
 		tx_err = ice_phy_cfg_tx_offset_e82x(hw, port->port_num);
 	rx_err = ice_phy_cfg_rx_offset_e82x(hw, port->port_num);
 	if (tx_err || rx_err) {
-		/* Tx and/or Rx offset not yet configured, try again later */
+		/* Tx and/or Rx offset not yet configured, try again later.
+		 * This is expected during normal link-up: the vernier offset
+		 * calibration cannot complete until at least one packet has
+		 * been transmitted, so the first retries routinely land here.
+		 */
+		dev_dbg(ice_pf_to_dev(pf),
+			"PTP offset not yet valid for port %u (tx_err=%d rx_err=%d)\n",
+			port->port_num, tx_err, rx_err);
 		kthread_queue_delayed_work(pf->ptp.kworker,
 					   &port->ov_work,
 					   msecs_to_jiffies(100));
 		return;
 	}
+
+	/* Tx and Rx offsets are now configured, enable Tx timestamps */
+	spin_lock_irqsave(&port->tx.lock, flags);
+	port->tx.calibrating = false;
+	spin_unlock_irqrestore(&port->tx.lock, flags);
[Severity: High]
Does placing the only clear of port->tx.calibrating after the combined
"if (tx_err || rx_err)" gate make the Tx timestamp gate depend on Rx
calibration?

The two directions have independent offset-ready registers.  On the Tx side,
ice_phy_cfg_tx_offset_e82x() programs the total Tx offset and sets:

drivers/net/ethernet/intel/ice/ice_ptp_hw.c:ice_phy_cfg_tx_offset_e82x() {
	...
	err = ice_write_phy_reg_e82x(hw, port, P_REG_TX_OR, 1);
	...
}

and thereafter returns 0 immediately ("Nothing to do if we've already
programmed the offset").  Meanwhile the Rx side keeps failing until a packet
has actually been received:

drivers/net/ethernet/intel/ice/ice_ptp_hw.c:ice_phy_cfg_rx_offset_e82x() {
	...
	if (!(reg & P_REG_RX_OV_STATUS_OV_M))
		return -EBUSY;
	...
}

So in the Tx-done/Rx-pending state, ice_ptp_wait_for_offsets() loops on the
100 ms retry branch with tx_err == 0 and rx_err == -EBUSY and never reaches
the new "port->tx.calibrating = false".

Since calibrating is written in only two places after this patch (set true
in ice_ptp_port_phy_restart(), cleared here), is there anything left that
can reopen the Tx gate?  ice_ptp_is_tx_tracker_up() stays false:

drivers/net/ethernet/intel/ice/ice_ptp.c:ice_ptp_is_tx_tracker_up() {
	return tx->init && !tx->calibrating;
}

and every request is refused, with ice_tstamp() only bumping the skipped
counter:

drivers/net/ethernet/intel/ice/ice_txrx.c:ice_tstamp() {
	idx = ice_ptp_request_ts(tx_ring->tx_tstamps, skb);
	if (idx < 0) {
		tx_ring->vsi->back->ptp.tx_hwtstamp_skipped++;
		return;
	}
	...
}

This is reachable on an ordinary link-up when the link partner is silent or
passive, or in one-way/tap topologies, and it persists until the next link
transition.  Before this patch the Tx gate did not depend on the Rx side.

Would gating the clear on tx_err == 0 only, and letting the Rx offset
configuration continue to retry independently, be the intended behaviour?

[Severity: Low]
Should the surrounding documentation be updated along with the new
semantics?  The kernel-doc directly above ice_ptp_wait_for_offsets() still
says:

 * Once the offset in either direction is measured, configure the associated
 * registers with the calibrated offset values and enable timestamping. The Tx
 * and Rx directions are configured independently as soon as their associated
 * offsets are known.

and the member doc in ice_ptp.h for struct ice_ptp_tx still says:

 * @calibrating: if true, the PHY is calibrating the Tx offset. During this
 *               window, timestamps are temporarily disabled.

After the patch the flag is cleared only when ice_ptp_check_tx_fifo(),
ice_phy_cfg_tx_offset_e82x() and ice_phy_cfg_rx_offset_e82x() all succeed,
so the Tx gate now also encodes Rx state and Tx FIFO state.  The window is
also no longer temporary in all cases: on the ice_start_phy_timer_e82x()
error path ice_ptp_port_phy_restart() breaks before queueing ov_work, and
ice_ptp_port_phy_stop() cancels ov_work without restoring the flag.
quoted hunk
+
+	dev_dbg(ice_pf_to_dev(pf),
+		"PTP offset valid for port %u, Tx timestamps enabled\n",
+		port->port_num);
 }
 
 /**
@@ -1269,10 +1286,13 @@ ice_ptp_port_phy_restart(struct ice_ptp_port *ptp_port)
 		if (err)
 			break;
 
-		/* Enable Tx timestamps right away */
-		spin_lock_irqsave(&ptp_port->tx.lock, flags);
-		ptp_port->tx.calibrating = false;
-		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
+		/* Do not clear calibrating flag here. Tx timestamps remain
+		 * disabled until ice_ptp_wait_for_offsets() has verified
+		 * that the Tx and Rx offset calibration has completed.
+		 * Clearing it here would allow Tx timestamps to be reported
+		 * before the PHY offset registers are configured, leading
+		 * to incorrect timestamp values.
+		 */
[Severity: Medium]
Is the "leading to incorrect timestamp values" description in this comment,
and the matching commit message text, accurate for this window?

The commit message says:

    Timestamps produced during this window use uncalibrated PHY offsets and
    can produce incorrect values.

    When ptp4l receives incorrect timestamps, it may reject them and wait
    for the next sync interval (typically 1-2 seconds), compounding delays
    during link cycling.

During that window P_REG_TX_OR is 0.  ice_start_phy_timer_e82x() begins by
calling ice_stop_phy_timer_e82x(), whose first action is:

drivers/net/ethernet/intel/ice/ice_ptp_hw.c:ice_stop_phy_timer_e82x() {
	err = ice_write_phy_reg_e82x(hw, port, P_REG_TX_OR, 0);
	...
}

and TX_OR is only set back to 1 by ice_phy_cfg_tx_offset_e82x() once the
total Tx offset has been programmed.  The driver documents that write as
invalidating timestamps:

drivers/net/ethernet/intel/ice/ice_ptp_hw.c:ice_ptp_clear_phy_offset_ready_e82x() {
 * Clear PHY TX_/RX_OFFSET_READY registers, effectively marking all transmitted
 * and received timestamps as invalid.
	...
}

And timestamps captured without the valid bit are dropped rather than
reported:

drivers/net/ethernet/intel/ice/ice_ptp.c:ice_ptp_process_tx_tstamp() {
	...
	/* Discard any timestamp value without the valid bit set */
	if (!(raw_tstamp & ICE_PTP_TS_VALID))
		drop_ts = true;
	...
}

Is the pre-patch symptom then a missing Tx timestamp (slot held until read
or timeout, counted in tx_hwtstamp_timeouts) rather than a wrong value
delivered to ptp4l?  The sibling patch in this series ("ice: E825: stop
clearing PHY_REG_TX_OFFSET_READY") states that with the offset-ready bit
clear "the hardware still captures Tx timestamps, but it no longer sets the
valid bit", which seems to point the same way.

Could the commit message and this new comment be reworded to describe the
actual failure mode, so that anyone matching user reports against the
Fixes: tag is not misled?
 
 		kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
 					   0);

Re: [PATCH net 05/15] ice: E822: cancel offset verification work during reset preparation

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:18

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: E822: cancel offset verification work during reset preparation

This adds a kthread_cancel_delayed_work_sync() of the E82x offset
verification work (ov_work) to ice_ptp_prepare_for_reset(), so that the
work does not run while the PTP reset sequence executes.  A few questions
about the changelog and about the placement of the new cancel.

First, on the described failure mechanism:
Without this, ice_ptp_wait_for_offsets() can execute during a reset,
find that ice_is_reset_in_progress() is true, and re-queue itself in a
tight loop. Meanwhile, the reset path in ice_ptp_rebuild_owner() calls
ice_ptp_port_phy_restart(), which starts a new ov_work. This results in
two ov_work instances running simultaneously, racing over the PHY offset
registers and the calibrating flag.
[Severity: Medium]
Can this sequence happen as described?  ov_work is a single
struct kthread_delayed_work per port (ice_ptp.h, struct ice_ptp_port) run
by one per-PF kworker, so the same work item cannot execute twice at once.

The re-queue in ice_ptp_wait_for_offsets() also does not look like a tight
loop, and it returns before touching the PHY offset registers or
port->tx.calibrating:

	if (ice_is_reset_in_progress(pf->state)) {
		/* wait for device driver to complete reset */
		kthread_queue_delayed_work(pf->ptp.kworker,
					   &port->ov_work,
					   msecs_to_jiffies(100));
		return;
	}

And ice_ptp_port_phy_restart(), the only site that arms ov_work, already
cancel-syncs it under ps_lock before queueing again:

	kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
	...
	kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work, 0);

Could the changelog describe the actual window instead, for example a
worker instance that already passed the reset-in-progress check and is
mid-PHY-access when the reset starts, or one that gets re-armed after the
new cancel?

Second, on the Fixes tag:
Fixes: 4809671015a1 ("ice: Fix E810 PTP reset flow")
[Severity: Medium]
Is this the right commit to reference?  git grep ov_work at 4809671015a1
under drivers/net/ethernet/intel/ice/ returns no matches, so the E82x
offset verification work did not exist yet at that commit, and the code
being changed here is the ICE_MAC_GENERIC branch of the PTP reset flow
rather than the E810 reset flow.  Stable tooling will use this tag to pick
backport targets.

Third, on the reference to an existing cancellation:
Cancel ov_work in ice_ptp_prepare_for_reset() alongside the existing
cancellation of the Tx tracker. The comment in the existing code already
documents that ov_work interference during reset is a concern but only
cancels it during ice_ptp_release().
[Severity: Medium]
Does ice_ptp_prepare_for_reset() still release the Tx tracker in this
tree?  The ice_ptp_release_tx_tracker(pf, &pf->ptp.port.tx) call that used
to be there was removed by the earlier patch in this series, "ice: fix
removal of PTP timestamp tracker during reset", so there is nothing left
in that function to cancel ov_work "alongside".

Which existing comment documents ov_work interference during reset?  At the
series baseline the only ov_work-adjacent comments are "Start the PHY timer
in Vernier mode" and "temporarily disable Tx timestamps while calibrating
PHY offset", and neither mentions reset.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index d018f02f700d7..9bf3bac552f11 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2998,6 +2998,14 @@ void ice_ptp_prepare_for_reset(struct ice_pf *pf, enum ice_reset_req reset_type)
 	if (reset_type == ICE_RESET_PFR)
 		return;
 
+	/* Cancel the offset verification work for E82x before releasing the
+	 * Tx tracker. If ov_work is running during reset, it may issue
+	 * sideband queue commands that will fail or timeout, and may
+	 * reference state that is being torn down.
+	 */
[Severity: Medium]
Same point as above for the comment text itself: ice_ptp_prepare_for_reset()
does not release the Tx tracker anymore, so "before releasing the Tx
tracker" describes an ordering that does not exist here.  The only
remaining ice_ptp_release_tx_tracker() call is in ice_ptp_release(), and
there it runs before the ICE_MAC_GENERIC ov_work cancel reached via
ice_ptp_port_phy_stop(), i.e. the reverse order.  Would it be clearer to
drop that clause?

The second sentence also mentions sideband queue commands, but
ice_ptp_wait_for_offsets() returns as soon as ice_is_reset_in_progress() is
true, before any sideband access.  Which state that is being torn down does
the work reference?
+	if (hw->mac_type == ICE_MAC_GENERIC)
+		kthread_cancel_delayed_work_sync(&ptp->port.ov_work);
[Severity: High]
Should this cancel be taken under ptp->port.ps_lock?  Both existing
ov_work cancel sites hold it, and ice_ptp_port_phy_restart() holds it
across cancel, PHY start and re-queue:

	mutex_lock(&ptp_port->ps_lock);
	...
	kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
	...
	err = ice_start_phy_timer_e82x(hw, port);
	...
	kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work, 0);

Without ps_lock here, can ov_work be armed again right after the sync
cancel returns, so the reset sequence still runs with ov_work queued?

Two arming paths look reachable during the reset window.  One is the
PTP_CLOCK_SETTIME ioctl, which has no ptp->state or reset-in-progress
check:

ice_ptp_settime64()
	/* Recalibrate and re-enable timestamp blocks for E822/E823 */
	if (hw->mac_type == ICE_MAC_GENERIC)
		ice_ptp_restart_all_phy(pf);

The other is the clock-owner PF on a multi-PF E82x adapter, where
ice_ptp_rebuild_owner() -> ice_ptp_restart_all_phy() walks the adapter port
list and arms a peer PF's ov_work:

	list_for_each_entry_rcu(port, &pf->adapter->ports.list, list_node) {
		...
		if (port->link_up)
			ice_ptp_port_phy_restart(port);

while that peer PF's own service task is inside
ice_ptp_prepare_for_reset() on another CPU.  There is no cross-PF reset
serialization for E82x, since ice_ptp_prepare_rebuild_sec() is gated on
ICE_MAC_GENERIC_3K_E825 in the following hunk.

Would doing the cancel inside the ps_lock critical section, or simply
reusing ice_ptp_port_phy_stop(), close that window?  The ov_work callback
ice_ptp_wait_for_offsets() only takes port->tx.lock with
spin_lock_irqsave(), never ps_lock, so taking ps_lock around the sync
cancel should not deadlock.

[Severity: Low]
Separately, who re-arms ov_work for a non-owner PF after this cancel?
ice_ptp_wait_for_offsets() is the only place that programs the vernier
offsets and clears the calibrating flag:

	port->tx.calibrating = false;

and ice_ptp_is_tx_tracker_up() gates every Tx timestamp on it:

	return tx->init && !tx->calibrating;

At this commit the only re-arm during reset is ice_ptp_restart_all_phy()
from ice_ptp_rebuild_owner(), which runs on the clock-owner PF only, for
non-PFR only, for link-up ports only, and can arm a peer PF's ov_work just
before that peer cancels it here.  Note this appears to be resolved by the
next patch in the series, "ice: call PTP link change only from link
events", which adds an unconditional per-PF ice_ptp_link_change() call at
the end of ice_rebuild(); with that in place every PF re-arms its own
ov_work after its own rebuild.  Worth mentioning in the changelog that this
patch depends on that follow-up?

[Severity: Medium]
This isn't a bug introduced by this patch, but the state that
ice_ptp_prepare_for_reset() leaves behind interacts with an incomplete
teardown path.  ice_ptp_release() early-returns whenever
pf->ptp.state is neither ICE_PTP_UNINIT nor ICE_PTP_READY:

	if (pf->ptp.state != ICE_PTP_READY) {
		mutex_destroy(&pf->ptp.port.ps_lock);
		ice_ptp_cleanup_pf(pf);
		if (pf->ptp.clock) {
			ptp_clock_unregister(pf->ptp.clock);
			pf->ptp.clock = NULL;
		}
		return;
	}

ice_ptp_cleanup_pf() only removes the port from the adapter list and drops
the kref, so unloading the driver while the state is ICE_PTP_RESETTING
(set here) or ICE_PTP_ERROR (set on the ice_ptp_rebuild() failure path)
leaks the kthread worker created by kthread_run_worker() in
ice_ptp_init_work() and the tx->tstamps allocation from
ice_ptp_alloc_tx_tracker(), and does not sync-cancel ptp->work or
port.ov_work before the devm-allocated ice_pf embedding them goes away.
That branch also leaves state non-UNINIT, unlike the invariant the comment
above ice_ptp_release() describes.
 	if (ice_pf_src_tmr_owned(pf) && hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_prepare_rebuild_sec(pf, false, reset_type);
 
[ ... ]

Re: [PATCH net 06/15] ice: call PTP link change only from link events

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:19

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: call PTP link change only from link events

The patch drops the ice_ptp_link_change() calls from ice_up_complete() and
ice_down() so that only the admin queue link event handler drives PTP link
state, seeds ptp_port->link_up during ice_ptp_init(), and re-checks the link
state at the end of ice_rebuild(). The PTP kworker creation is also moved
earlier in ice_ptp_init(), with the periodic work queued only once the state
is ICE_PTP_READY.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index d88835482d3aa..f32041dd8b274 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
[ ... ]
quoted hunk
@@ -7273,7 +7272,6 @@ int ice_down(struct ice_vsi *vsi)
 
 	if (vsi->netdev) {
 		vlan_err = ice_vsi_del_vlan_zero(vsi);
-		ice_ptp_link_change(vsi->back, false);
 		netif_carrier_off(vsi->netdev);
 		netif_tx_disable(vsi->netdev);
 	}
[ ... ]
quoted hunk
@@ -7794,6 +7792,14 @@ static void ice_rebuild(struct ice_pf *pf, enum ice_reset_req reset_type)
 
 	ice_update_pf_netdev_link(pf);
 
+	if (test_bit(ICE_FLAG_PTP_SUPPORTED, pf->flags) && pf->hw.port_info) {
+		bool link_up;
+
+		link_up = !!(pf->hw.port_info->phy.link_info.link_info &
+			     ICE_AQ_LINK_UP);
+		ice_ptp_link_change(pf, link_up);
+	}
+
[Severity: High]
Does this re-create, in the reset path, the same double PHY
re-initialization that the changelog describes as problem 1?

With the ice_ptp_link_change(vsi->back, false) call gone from ice_down(),
nothing clears ptp_port->link_up during reset preparation.  The only
remaining writers of that field are the store in ice_ptp_link_change() and
the new seed in ice_ptp_init().

ice_prepare_for_reset() calls ice_pf_dis_all_vsi() -> ice_down() before
ice_ptp_prepare_for_reset(), so previously link_up was cleared while the
state was still ICE_PTP_READY and the local port was skipped here:

ice_ptp_restart_all_phy()
	if (port->link_up)
		ice_ptp_port_phy_restart(port);

Now link_up survives the reset, so ice_rebuild() -> ice_ptp_rebuild() ->
ice_ptp_rebuild_owner() -> ice_ptp_restart_all_phy() does a real restart of
the local port, and then the block added above calls
ice_ptp_link_change(pf, true), which re-enters ice_ptp_port_phy_restart() a
second time within the same ice_rebuild().

For ICE_MAC_GENERIC that second entry does:

ice_ptp_port_phy_restart()
	kthread_cancel_delayed_work_sync(&ptp_port->ov_work);
	...
	ptp_port->tx.calibrating = true;
	...
	err = ice_start_phy_timer_e82x(hw, port);

which cancels the offset verification work queued milliseconds earlier by
the first restart, re-asserts calibrating and restarts the timer.  On
ICE_MAC_GENERIC_3K_E825 it re-runs ice_start_phy_timer_eth56g(), i.e. a PHY
soft reset, timer stop, incval reprogram, PHC resync and clearing/setting of
the offset-ready bits.

Neither guard in ice_ptp_link_change() short-circuits this call:
ice_reset_subtask() clears pf->hw.reset_ongoing right before ice_rebuild(),
and ice_ptp_rebuild() has already set the state to ICE_PTP_READY earlier in
ice_rebuild().

One more question on the same block: ice_ptp_link_change() discards the
return value of ice_ptp_port_phy_restart(), and the E825 restart begins by
stopping the timer.  If the redundant second attempt fails, can it leave a
previously working PHY timer stopped, at a point where
ice_update_pf_netdev_link() has already turned the carrier on and woken the
Tx queues?

Would it be cleaner to have exactly one driver of the post-reset PHY
restart, either ice_ptp_restart_all_phy() or this new block, rather than
both?
quoted hunk
 	/* tell the firmware we are up */
 	err = ice_send_version(pf);
 	if (err) {
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 9bf3bac552f11..4dec728e219d8 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -1323,9 +1323,6 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	struct ice_ptp_port *ptp_port;
 	struct ice_hw *hw = &pf->hw;
 
-	if (pf->ptp.state != ICE_PTP_READY)
-		return;
-
 	ptp_port = &pf->ptp.port;
 
 	if (!kref_get_unless_zero(&ptp_port->ref))
@@ -1334,6 +1331,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	/* Update cached link status for this port immediately */
 	ptp_port->link_up = linkup;
 
+	if (pf->ptp.state != ICE_PTP_READY)
+		goto exit_kref_put;
+
[Severity: High]
Can moving the state check below the store make a half-initialized port
visible to ice_ptp_restart_all_phy()?

ice_ptp_setup_pf() publishes the port before the rest of PTP init runs:

	kref_init(&ptp->port.ref);

	spin_lock(&pf->adapter->ports.lock);
	list_add_rcu(&ptp->port.list_node, &pf->adapter->ports.list);
	spin_unlock(&pf->adapter->ports.lock);

After that, and while pf->ptp.state is still ICE_PTP_INITIALIZING, a link
event reaching ice_ptp_link_change() now sets ptp_port->link_up = true
before returning.  At that point ice_ptp_init_port() may not yet have run
mutex_init(&ptp_port->ps_lock) or spin_lock_init(&tx->lock), and
pf->ptp.kworker is still NULL.

ice_ptp_restart_all_phy() gates only on the kref and the cached flag:

		if (!kref_get_unless_zero(&port->ref))
			continue;
		rcu_read_unlock();

		if (port->link_up)
			ice_ptp_port_phy_restart(port);

and it is reachable from ice_ptp_settime64(), which does no ptp->state check
and ends with:

	/* Recalibrate and re-enable timestamp blocks for E822/E823 */
	if (hw->mac_type == ICE_MAC_GENERIC)
		ice_ptp_restart_all_phy(pf);

The PHC character device already exists at that point, because
ice_ptp_init_owner() -> ice_ptp_create_clock() runs earlier in
ice_ptp_init(), so a clock_settime() on the owner PF, or a reset of the
owner PF concurrent with the probe of a peer PF, can reach it.

On ICE_MAC_GENERIC the restart then does:

		kthread_queue_delayed_work(pf->ptp.kworker, &ptp_port->ov_work,
					   0);

with pf->ptp.kworker == NULL, and kthread_queue_delayed_work() starts with
raw_spin_lock_irqsave(&worker->lock, flags).

Before this patch link_up stayed false for the whole of ice_ptp_init()
because ice_ptp_link_change() returned before the store while the state was
not READY, so the port->link_up guard skipped the initializing port.  Should
link_up only be published after the kworker exists and after the port locks
are initialized?
 	/* Skip HW writes if reset is in progress */
 	if (pf->hw.reset_ongoing)
 		goto exit_kref_put;
[ ... ]
quoted hunk
@@ -3443,6 +3444,22 @@ void ice_ptp_init(struct ice_pf *pf)
 	if (err)
 		goto err_clean_pf;
 
+	/* Seed link_up from current PHY status, since link may already be up
+	 * (e.g. after PXE boot) with no link-change edge to catch it later.
+	 */
+	if (pf->hw.port_info)
+		ptp->port.link_up =
+			!!(pf->hw.port_info->phy.link_info.link_info &
+			ICE_AQ_LINK_UP);
[Severity: Medium]
Is ptp_port->link_up now written from two contexts without any
synchronization?

This seed runs on the probe thread, while ice_ptp_link_change() writes the
same plain bool from the service task via the admin queue link event
handler, and after this patch that store happens before the ICE_PTP_READY
bail-out.  ice_init() clears ICE_SERVICE_DIS and arms pf->serv_tmr before
ice_init_features() calls ice_ptp_init(), so both writers can run
concurrently.

There is no lock, and no WRITE_ONCE/READ_ONCE pairing, between the probe
thread's read of phy.link_info and its store to link_up, so a link
transition processed inside that window is silently dropped.

Link events are edge triggered:

ice_link_event()
	if (link_up == old_link && link_speed == old_link_speed)
		return 0;

so if the surviving value is false while the link is physically up,
ice_ptp_reset_phy_timestamping() -> ice_ptp_port_phy_restart() takes:

	if (!ptp_port->link_up)
		return ice_ptp_port_phy_stop(ptp_port);

and nothing later corrects it.  On E82x the vernier timer would then never
be started and timestamping would stay off until the link physically
toggles.  Note also that ice_ptp_port_phy_restart() reads link_up before
taking ptp_port->ps_lock, so ps_lock does not cover this field.
+
+	/* Create the kworker before restarting the PHY, which queues work on
+	 * it in the E82x restart path. This prevents concurrent link events
+	 * from reaching ice_ptp_port_phy_restart() while kworker is still NULL
+	 */
+	err = ice_ptp_init_work(pf, ptp);
+	if (err)
+		goto err_clean_pf;
+
[Severity: Medium]
This isn't a bug introduced by this patch, but since the unwind path is
being reshuffled here, should err_clean_pf also release the Tx timestamp
tracker?

By the time this new goto err_clean_pf can be taken, ice_ptp_init_port() ->
ice_ptp_init_tx()/ice_ptp_init_tx_e82x() -> ice_ptp_alloc_tx_tracker() has
already allocated:

	tstamps = kzalloc_objs(*tstamps, tx->len);
	in_use = bitmap_zalloc(tx->len, GFP_KERNEL);
	stale = bitmap_zalloc(tx->len, GFP_KERNEL);

but the unwind only does:

err_clean_pf:
	mutex_destroy(&ptp->port.ps_lock);
	ice_ptp_cleanup_pf(pf);

ice_ptp_cleanup_pf() only unlinks the port from the adapter list and drops
the kref, and err_exit then sets ptp->state = ICE_PTP_UNINIT, which makes
ice_ptp_release() return at its first check, so
ice_ptp_release_tx_tracker() never runs.  The same leak existed on the old
err_exit path when ice_ptp_init_work() failed, so this predates the patch;
the trigger is a kthread_run_worker() failure in ice_ptp_init_work().
quoted hunk
 	/* Start the PHY timestamping block */
 	ice_ptp_reset_phy_timestamping(pf);
 
@@ -3451,9 +3468,10 @@ void ice_ptp_init(struct ice_pf *pf)
 
 	ptp->state = ICE_PTP_READY;
 
-	err = ice_ptp_init_work(pf, ptp);
-	if (err)
-		goto err_exit;
+	/* Start periodic work only after the state is READY; the worker
+	 * returns without rescheduling while the state is not READY.
+	 */
+	kthread_queue_delayed_work(ptp->kworker, &ptp->work, 0);
 
 	dev_info(ice_pf_to_dev(pf), "PTP init successful\n");
 	return;

Re: [PATCH net 07/15] ice: E825: stop clearing PHY_REG_TX_OFFSET_READY

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:20

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
    ice: E825: stop clearing PHY_REG_TX_OFFSET_READY

This drops the PHY_REG_TX_OFFSET_READY = 0 write from
ice_stop_phy_timer_eth56g() on E825 devices. Clearing that bit left hardware
capturing Tx timestamps without the valid bit, which desynchronized the
internal outstanding timestamp counter until a PHY soft reset. Only
PHY_REG_RX_OFFSET_READY is cleared now.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index 3a41c711e751b..d48eb3c618231 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
@@ -2115,10 +2115,6 @@ int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
 {
 	int err;
 
-	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_OFFSET_READY, 0);
-	if (err)
-		return err;
-
[Severity: High]
With this write gone, is anything left that suppresses Tx timestamp
reporting while the E825 PHY is being re-calibrated?

ice_start_phy_timer_eth56g() begins by calling the stop helper and only
asserts Tx readiness at the very end:

	err = ice_stop_phy_timer_eth56g(hw, port, false);
	...
	err = ice_phy_cfg_parpcs_eth56g(hw, port);
	err = ice_phy_cfg_ptp_1step_eth56g(hw, port);
	err = ice_phy_cfg_mac_eth56g(hw, port);	/* PHY_REG_TOTAL_TX_OFFSET_L/U */
	...
	err = ice_write_40b_ptp_reg_eth56g(hw, port, PHY_REG_TIMETUS_L, incval);
	err = ice_ptp_one_port_cmd(hw, port, ICE_PTP_INIT_INCVAL);
	...
	err = ice_sync_phy_timer_eth56g(hw, port);
	...
	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_OFFSET_READY, 1);

Across that whole window the Tx offset registers are being reprogrammed and
the port timer has not yet been re-synced to the PHC, but per the commit
message hardware now marks captures valid, so this check in
ice_ptp_process_tx_tstamp() no longer drops them:

		/* Discard any timestamp value without the valid bit set */
		if (!(raw_tstamp & ICE_PTP_TS_VALID))
			drop_ts = true;

The E82x branch of ice_ptp_port_phy_restart() gates this in software:

		spin_lock_irqsave(&ptp_port->tx.lock, flags);
		ptp_port->tx.calibrating = true;
		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);
		...
		/* Do not clear calibrating flag here. Tx timestamps remain
		 * disabled until ice_ptp_wait_for_offsets() has verified
		 * that the Tx and Rx offset calibration has completed.
		 * Clearing it here would allow Tx timestamps to be reported
		 * before the PHY offset registers are configured, leading
		 * to incorrect timestamp values.
		 */

while the E825 branch does not:

	case ICE_MAC_GENERIC_3K_E825:
		err = ice_start_phy_timer_eth56g(hw, port);
		break;

Since ice_ptp_is_tx_tracker_up() only tests tx->init && !tx->calibrating,
new requests keep being accepted during the restart, and the restart path
holds only ps_lock while the reader takes tx->lock, so the two do not
exclude each other. Should the E825 case set tx.calibrating around
ice_start_phy_timer_eth56g() to replace the removed hardware gate?

The same exposure looks unbounded on the early-return paths of
ice_start_phy_timer_eth56g(), for example the ice_ptp_lock() failure:

	if (!ice_ptp_lock(hw)) {
		dev_err(ice_hw_to_dev(hw), "Failed to acquire PTP semaphore\n");
		return -EBUSY;
	}

because PHY_REG_TX_OFFSET_READY is only re-asserted on the success path.
The reachable trigger is a link up or link speed change
(ice_ptp_link_change() -> ice_ptp_port_phy_restart()) concurrent with any
SO_TIMESTAMPING application. Could the commit message also mention this
change in reporting behaviour during calibration?
 	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);
 	if (err)
 		return err;
[Severity: Low]
The kernel-doc and the debug print of ice_stop_phy_timer_eth56g() now
describe more than the body does:

 * Stop the clock of a PHY port. This must be done as part of the flow to
 * re-calibrate Tx and Rx timestamping offsets whenever the clock time is
 * initialized or when link speed changes.

	ice_debug(hw, ICE_DBG_PTP, "Disabled clock on PHY port %u\n", port);

After this change the body only clears PHY_REG_RX_OFFSET_READY, so no Tx
offset is invalidated and no clock enable bit is touched, while
ice_start_phy_timer_eth56g() still asserts both PHY_REG_TX_OFFSET_READY and
PHY_REG_RX_OFFSET_READY. ice_ptp_port_phy_stop() is likewise documented as
"Stop timestamping for a PHY port" while Tx timestamping stays enabled for
ICE_MAC_GENERIC_3K_E825. Should the comment and the log message be updated
to say only the Rx offset-ready bit is cleared?

[Severity: Low]
This isn't a bug introduced by this patch, but with the body reduced to a
single register write the unused soft_reset parameter becomes quite visible:

 * @soft_reset: if true, hold the SOFT_RESET bit of PHY_REG_PS

ice_stop_phy_timer_eth56g() never reads soft_reset, and PHY_REG_PS appears
nowhere in drivers/net/ethernet/intel/ice/ outside this comment; the E82x
counterpart ice_stop_phy_timer_e82x() does honour the flag via P_REG_PS and
P_REG_PS_SFT_RESET_M. Both call sites are therefore equivalent:
ice_ptp_port_phy_stop() passes true for ICE_MAC_GENERIC_3K_E825 and
ice_start_phy_timer_eth56g() passes false. Would it make sense to drop the
parameter, or to document that it is ignored on ETH56G?

Re: [PATCH net 08/15] ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:21

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: E825: clear PHY_REG_TX_MEMORY_STATUS prior to soft reset

This replaces the register-write based ice_ptp_reset_ts_memory_eth56g()
with a read of every Tx timestamp memory entry for a port, and moves that
clearing into ice_ptp_phy_soft_reset_eth56g() so it always happens right
before the PHY soft reset. ice_ptp_reset_ts_memory() now does nothing for
E825.

Two questions below.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index d48eb3c618231..a3a9f7ce04d2b 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
[ ... ]
quoted hunk
@@ -1139,8 +1121,8 @@ static int ice_read_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx,
  * internal PHYs of the 56G devices.
  *
  * To directly clear the contents of the timestamp block entirely, discarding
- * all timestamp data at once, software should instead use
- * ice_ptp_reset_ts_memory_quad_eth56g().
+ * all timestamp data at once, software should instead perform a PHY soft
+ * reset via ice_ptp_phy_soft_reset_eth56g().
  *
  * This function should only be called on an idx whose bit is set according to
  * ice_get_phy_tx_tstamp_ready().
[Severity: Low]

This isn't a bug, but should the rest of the kernel-doc for
ice_clear_ptp_tstamp_eth56g() be refreshed too?

The summary above still says "Read and then forcibly clear the timestamp
index", and the return list still says "%other - failed to write to PHY",
but the ice_write_port_mem_eth56g() call is removed below, so the body only
performs two PHY reads and no write can fail.

The retained line

  * This function should only be called on an idx whose bit is set according to
  * ice_get_phy_tx_tstamp_ready().

is also the contract repeated by the exported wrapper ice_clear_phy_tstamp(),
yet the new ice_ptp_clear_tx_memory_status_eth56g() calls this helper for
every index regardless of the ready bitmap. Since reading an index whose
valid bit is set is exactly what triggers the internal counter decrement
described in the commit message, is the precondition still meant to apply,
or should the doc say the unconditional sweep is allowed only when paired
with the soft reset?
quoted hunk
@@ -1152,24 +1134,16 @@ static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 {
 	u64 unused_tstamp;
-	u16 lo_addr;
 	int err;
 
-	/* Read the timestamp register to ensure the timestamp status bit is
-	 * cleared.
+	/* Per the PHY spec, reading the timestamp memory location is what
+	 * clears the entry's valid bit and its corresponding (read-only)
+	 * ts_memory_status bit.
 	 */
 	err = ice_read_ptp_tstamp_eth56g(hw, port, idx, &unused_tstamp);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read the PHY timestamp register for port %u, idx %u, err %d\n",
 			  port, idx, err);
-	}
-
-	lo_addr = (u16)PHY_TSTAMP_L(idx);
-
-	err = ice_write_port_mem_eth56g(hw, port, lo_addr, 0);
-	if (err) {
-		ice_debug(hw, ICE_DBG_PTP, "Failed to clear low PTP timestamp register for port %u, idx %u, err %d\n",
-			  port, idx, err);
 		return err;
 	}
 
[ ... ]
quoted hunk
@@ -1177,19 +1151,36 @@ static int ice_clear_ptp_tstamp_eth56g(struct ice_hw *hw, u8 port, u8 idx)
 }
 
 /**
- * ice_ptp_reset_ts_memory_eth56g - Clear all timestamps from the port block
+ * ice_ptp_clear_tx_memory_status_eth56g - Reset one port's Tx timestamp memory
  * @hw: pointer to the HW struct
+ * @port: port number to clear
+ *
+ * Fully reset a single PHY port's Tx timestamp memory. Per the PHY spec, the
+ * only way to clear a timestamp valid bit (and its read-only ts_memory_status
+ * bit) is to read the timestamp memory location, so read every entry for the
+ * port (two 32-bit reads each). This discards all timestamp data on the port,
+ * so it must only be used for a full reset; callers that must preserve
+ * in-flight timestamps clear individual indices via ice_clear_phy_tstamp().
+ *
+ * Due to interactions with an internal HW counter for the number of
+ * outstanding Tx timestamps, this *must* only be called as part of the
+ * ice_ptp_phy_soft_reset_eth56g() procedure. Otherwise, the internal counter
+ * may become out of sync and prevent new timestamp interrupts.
+ *
+ * Return: 0 on success, negative error code on failure to read the PHY.
  */
-static void ice_ptp_reset_ts_memory_eth56g(struct ice_hw *hw)
+static int ice_ptp_clear_tx_memory_status_eth56g(struct ice_hw *hw, u8 port)
 {
-	unsigned int port;
+	int err = 0;
+	u8 idx;
 
-	for (port = 0; port < hw->ptp.num_lports; port++) {
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_L,
-					 0);
-		ice_write_ptp_reg_eth56g(hw, port, PHY_REG_TX_MEMORY_STATUS_U,
-					 0);
+	for (idx = 0; idx < INDEX_PER_PORT; idx++) {
+		err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);
+		if (err)
+			return err;
 	}
+
+	return 0;
 }
[Severity: Medium]

Can this fail-fast return leave the internal timestamp counter underflowed
for this port?

The reads are the side-effecting operation: each read of an entry whose
valid bit is set clears that bit and decrements the PHY's outstanding
timestamp counter. If ice_clear_ptp_tstamp_eth56g() fails at, say, idx 30,
the decrements for idx 0..29 have already been applied, and this function
returns before the soft reset that is the only thing which re-zeroes the
counter.

The read path goes through the sideband queue:

  ice_clear_ptp_tstamp_eth56g()
    ice_read_ptp_tstamp_eth56g()
      ice_read_port_mem_eth56g() -> ice_read_phy_eth56g() -> ice_sbq_rw_reg()

so a transient sideband failure is enough to hit this.

Would it be better to log the failing index but continue the sweep, and
still drive the soft reset before propagating the error?
quoted hunk
@@ -2306,6 +2304,13 @@ int ice_ptp_phy_soft_reset_eth56g(struct ice_hw *hw, u8 port)
 	u32 global_val;
 	int err;
 
+	err = ice_ptp_clear_tx_memory_status_eth56g(hw, port);
+	if (err) {
+		ice_debug(hw, ICE_DBG_PTP, "Failed to clear PHY_REG_TX_MEMORY_STATUS for port %d, err %d\n",
+			  port, err);
+		return err;
+	}
+
 	err = ice_read_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, &global_val);
 	if (err) {
 		ice_debug(hw, ICE_DBG_PTP, "Failed to read PHY_REG_GLOBAL for port %d, err %d\n",
[Severity: Medium]

Same concern at this early return, and at the two error returns that follow
it. Once ice_ptp_clear_tx_memory_status_eth56g() has consumed entries, a
failed ice_read_ptp_reg_eth56g(PHY_REG_GLOBAL) or a failed first
PHY_REG_GLOBAL write also returns without ever asserting
PHY_REG_GLOBAL_SOFT_RESET_M, so the clear happens without its paired reset.

Isn't that the exact condition the new kernel-doc warns about?

  * Due to interactions with an internal HW counter for the number of
  * outstanding Tx timestamps, this *must* only be called as part of the
  * ice_ptp_phy_soft_reset_eth56g() procedure.

The error also propagates out of the per-port loop in
ice_ptp_init_phc_e825c():

  for (int port = 0; port < hw->ptp.num_lports; port++) {
          err = ice_ptp_phy_soft_reset_eth56g(hw, port);
          if (err) {
                  ...
                  return err;
          }
  }

so the remaining ports get neither the clear nor the reset. A later
successful soft reset would repair the state, but should the reset still be
driven for the port that already had its entries read?

This was checked at the end of the series and the fail-fast return and the
early return here are both still present, so no later patch in the series
changes this.

Re: [PATCH net 09/15] ice: E825: perform a soft reset when starting the PHY timer

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:23

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: E825: perform a soft reset when starting the PHY timer

This adds a call to ice_ptp_phy_soft_reset_eth56g() at the top of
ice_start_phy_timer_eth56g() so that the E825 PHY PTP block is reset before
it is reprogrammed. The kernel-doc for the function is updated to mention
the reset.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
index a3a9f7ce04d2b..64e18fe1176b1 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp_hw.c
[ ... ]
quoted hunk
@@ -2138,6 +2140,10 @@ int ice_start_phy_timer_eth56g(struct ice_hw *hw, u8 port)
 
 	tmr_idx = ice_get_ptp_src_clock_index(hw);
 
+	err = ice_ptp_phy_soft_reset_eth56g(hw, port);
+	if (err)
+		return err;
+
[Severity: Medium]
Should the software Tx timestamp tracker be reconciled here?

ice_ptp_phy_soft_reset_eth56g() calls
ice_ptp_clear_tx_memory_status_eth56g(), whose kernel-doc says:

  * for the port (two 32-bit reads each). This discards all timestamp data on the port,
  * so it must only be used for a full reset; callers that must preserve
  * in-flight timestamps clear individual indices via ice_clear_phy_tstamp().

This now runs on an ordinary link-up path:

  ice_ptp_link_change(true)
    -> ice_ptp_port_phy_restart()
      -> case ICE_MAC_GENERIC_3K_E825: ice_start_phy_timer_eth56g()
        -> ice_ptp_phy_soft_reset_eth56g()

The ICE_MAC_GENERIC branch of ice_ptp_port_phy_restart() sets
ptp_port->tx.calibrating under tx->lock, and ice_ptp_rebuild_owner() does:

  /* Flush software tracking of any outstanding timestamps since we're
   * about to flush the PHY timestamp block.
   */
  ice_ptp_flush_all_tx_tracker(pf);

The ICE_MAC_GENERIC_3K_E825 branch does neither, so indices still set in
tx->in_use keep their skb references while the hardware timestamp memory
and ready bits are wiped underneath them. Those entries are then only
resolved by the 2 second timeout in ice_ptp_process_tx_tstamp(), bumping
tx_hwtstamp_timeouts. Later patches in the series ("ice: keep Tx timestamp
slots tracked until completion or timeout" and "ice: don't clear in_use
until HW clears ready bitmap") make the periodic work reap those slots, so
the effect is bounded rather than permanent, but should the E825 branch
still gate or flush the tracker around the reset?

[Severity: Medium]
Can this leave the PHY parked in soft reset?

ice_ptp_phy_soft_reset_eth56g() asserts the bit and then has several
error returns before the final de-assert:

  global_val |= PHY_REG_GLOBAL_SOFT_RESET_M;
  err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_GLOBAL, global_val);
  if (err) {
          ice_debug(...);
          return err;
  }
  usleep_range(5000, 6000);

  global_val &= ~PHY_REG_GLOBAL_SOFT_RESET_M;
  ...

If a sideband access fails after the assert (queue full, timeout, or a
concurrent device reset returning -EBUSY/-EIO/-ETIMEDOUT), the new early
return here skips ice_stop_phy_timer_eth56g(),
ice_phy_cfg_parpcs_eth56g(), ice_phy_cfg_ptp_1step_eth56g(),
ice_phy_cfg_mac_eth56g(), the incval programming, the timer sync and the
TX/RX_OFFSET_READY writes.

ice_ptp_port_phy_restart() only logs the failure:

  if (err)
          dev_err(ice_pf_to_dev(pf), "PTP failed to set PHY port %d up, err %d\n",
                  port, err);

  mutex_unlock(&ptp_port->ps_lock);

There is no ov_work retry for ICE_MAC_GENERIC_3K_E825, and
ice_ptp_link_change() only calls the restart on a link-up transition, so
the port stays with SOFT_RESET asserted and stale offset-ready bits until
the next link-up or a rebuild. Would it be better to de-assert the bit on
the error paths inside ice_ptp_phy_soft_reset_eth56g()?

[Severity: Medium]
What serialises this reset against the Tx timestamp reader on the other PF?

The reset holds the block in reset across two settling windows:

  usleep_range(5000, 6000);

  global_val |= PHY_REG_GLOBAL_SOFT_RESET_M;
  ...
  usleep_range(5000, 6000);

The writer side holds only its own ptp_port->ps_lock:

  ice_ptp_link_change() -> ice_ptp_port_phy_restart() -> ice_start_phy_timer_eth56g()

The reader side runs on the source-timer-owning PF:

  ice_ptp_process_ts() -> ice_ptp_tx_tstamp_owner() -> ice_ptp_process_tx_tstamp()
      ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
      ...
      ice_read_phy_tstamp(hw, tx->block, phy_idx, &raw_tstamp);

and for E825 that PF processes all ports, including the peer PF's port:

  if (ice_pf_src_tmr_owned(pf))
          pf->ptp.tx_interrupt_mode = ICE_PTP_TX_INTERRUPT_ALL;

ice_ptp_process_tx_tstamp() takes only tx->lock around the bitmap/skb
update, never ps_lock, and the E825 branch of ice_ptp_port_phy_restart()
does not set tx.calibrating. So can ice_read_phy_tstamp() land on a PHY
PTP block that another PF is holding mid-reset, and if such a read returns
a word with ICE_PTP_TS_VALID set, would it be extended with the cached PHC
and reported to userspace as a real hardware timestamp?

[Severity: Medium]
Does PHY_REG_TS_INT_CONFIG survive this reset?

Everything else in the per-lane PTP block is reprogrammed below this point
(PAR/PCS, 1-step, MAC/XIF, TIMETUS incval, timer sync, TX/RX_OFFSET_READY),
but PHY_REG_TS_INT_CONFIG (Tx timestamp interrupt enable plus threshold, in
the same per-lane block as PHY_REG_GLOBAL) is not. Its only writer is
ice_phy_cfg_intr_eth56g() via ice_ptp_cfg_phy_interrupt(), reached only from
ice_ptp_init_owner() and ice_ptp_rebuild_owner(), and it is a
read-modify-write, so a value returned to defaults cannot be recovered.

The existing ordering in ice_ptp_init_phc_e825c() is reset first:

  /* Soft reset all ports, to ensure everything is at a clean state */
  for (int port = 0; port < hw->ptp.num_lports; port++) {
          err = ice_ptp_phy_soft_reset_eth56g(hw, port);

whereas ice_ptp_rebuild_owner() now configures the interrupt and then
resets every port:

  err = ice_ptp_cfg_phy_interrupt(pf, true, 1);
  if (err)
          return err;

  ice_ptp_restart_all_phy(pf);

Given the commit message says the reset "ensures that the PHY internal
state has been completely reset", could this disarm the port's Tx timestamp
threshold interrupt after the first link-up while software still believes
it is enabled? The software fallback ice_ptp_maybe_trigger_tx_interrupt()
only runs on the source-timer-owning PF. Commit 3ec46e157c7f describes the
reset's effects without mentioning configuration registers, so this may
well be fine; could the commit message state whether
PHY_REG_TS_INT_CONFIG is preserved, or should
ice_phy_cfg_intr_eth56g() be re-applied after the reset?
 	err = ice_stop_phy_timer_eth56g(hw, port, false);
[Severity: Low]
This is a pre-existing issue, not introduced by this patch, but the new
juxtaposition makes it more visible. ice_stop_phy_timer_eth56g() documents
a parameter and an action it does not implement:

  * @soft_reset: if true, hold the SOFT_RESET bit of PHY_REG_PS
  *
  * Stop the clock of a PHY port. ...
  int ice_stop_phy_timer_eth56g(struct ice_hw *hw, u8 port, bool soft_reset)
  {
          err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);

soft_reset is never read, so ice_ptp_port_phy_stop() passing true gets no
soft reset:

  case ICE_MAC_GENERIC_3K_E825:
          err = ice_stop_phy_timer_eth56g(hw, port, true);

Now that the soft reset is done explicitly right before a call that passes
false, could the parameter be dropped and the summary line corrected?
 	if (err)
 		return err;

Re: [PATCH net 10/15] ice: wait for in-flight Tx timestamps before flushing the tracker

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:24

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
Here is the report:

commit fea79aeeb67734d73dbd19b0b88b152cd016c86e
Author: Petr Oros [off-list ref]

ice: wait for in-flight Tx timestamps before flushing the tracker

This adds ice_ptp_wait_for_tracker_drain(), called from
ice_ptp_flush_tx_tracker(), which polls the PHY Tx timestamp ready bitmap
for up to 10 ms so that in-flight captures publish their ready bits before
the tracker entries are freed and the ready bits are cleared.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 4dec728e219d8..b142b779dc105 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -740,6 +740,37 @@ ice_ptp_alloc_tx_tracker(struct ice_ptp_tx *tx)
 	return 0;
 }
 
+static void
+ice_ptp_wait_for_tracker_drain(struct ice_pf *pf, struct ice_ptp_tx *tx)
+{
+	unsigned long deadline = jiffies + msecs_to_jiffies(10);
[Severity: Low]
Is the 10 ms budget the commit message describes actually available at every
HZ setting?  With CONFIG_HZ=100, msecs_to_jiffies(10) is 1, so the deadline
is a single tick ahead and the remaining fraction of the current tick is
arbitrary.

Since the loop body only sleeps 500-1000 us before re-testing
time_before(jiffies, deadline), can the whole wait end after one poll of
roughly 500 us?

Would a microsecond-resolution deadline (ktime_get() plus ktime_add_us(), or
read_poll_timeout()) match the documented budget better here?
+	struct ice_hw *hw = &pf->hw;
+	u64 tstamp_ready;
+	bool pending;
+	u8 idx;
+
+	if (hw->reset_ongoing)
+		return;
+
+	do {
+		if (ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready))
+			return;
+
+		pending = false;
+		for_each_set_bit(idx, tx->in_use, tx->len) {
+			if (!(tstamp_ready & BIT_ULL(idx + tx->offset)))
+				pending = true;
+		}
+		if (!pending)
+			return;
[Severity: High]
Should this read of the ready bitmap be gated on tx->has_ready_bitmap?

On ICE_MAC_E810 the flag is cleared in ice_ptp_init_tx():

	/* The E810 PHY does not provide a timestamp ready bitmap. Instead,
	 * verify new timestamps against cached copy of the last read
	 * timestamp.
	 */
	tx->has_ready_bitmap = pf->hw.mac_type != ICE_MAC_E810;

and the backing helper in ice_ptp_hw.c is a stub:

	static int
	ice_get_phy_tx_tstamp_ready_e810(struct ice_hw *hw, u8 port, u64 *tstamp_ready)
	{
		*tstamp_ready = 0xFFFFFFFFFFFFFFFF;
		return 0;
	}

With every bit set, doesn't the for_each_set_bit() loop above leave pending
false on the first iteration, so the helper returns immediately without ever
sleeping?  That would make the wait dead code on E810, which is the device
named by the Fixes tag ea9b847cda64 ("ice: enable transmit timestamps for
E810 devices").

The other consumer of the same register in ice_ptp_process_tx_tstamp() does
gate both the read and the per-index test:

	if (tx->has_ready_bitmap) {
		err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
		if (err)
			return;
	}
	...
		if (tx->has_ready_bitmap &&
		    !(tstamp_ready & BIT_ULL(phy_idx))) {

and the kernel-doc in ice_ptp.h states the register is only valid when the
flag is set:

 * @has_ready_bitmap: if true, the hardware has a valid Tx timestamp ready
 *                    bitmap register. If false, fall back to verifying new
 *                    timestamp values against previously cached copy.

Would it make sense to either skip the wait when
!tx->has_ready_bitmap and use the cached-timestamp fallback on E810, or
adjust the Fixes tag and commit message to describe which devices are
actually covered?

Also, a device with has_ready_bitmap == 0 whose stub returned zeros instead
of all-ones would make this loop burn the entire budget on every flush,
wouldn't it?
+
+		usleep_range(500, 1000);
+	} while (time_before(jiffies, deadline));
+
+	dev_dbg(ice_pf_to_dev(pf), "Timed out waiting for in-flight Tx timestamps on block %u\n",
+		tx->block);
+}
+
[ ... ]

Re: [PATCH net 11/15] ice: keep Tx timestamp slots tracked until completion or timeout

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:25

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: keep Tx timestamp slots tracked until completion or timeout

Instead of dropping every outstanding Tx timestamp request when the link
goes down, the outstanding requests are marked stale, new requests are
rejected while the link is down, and a not yet ready slot is only released
by the two second timeout in ice_ptp_process_tx_tstamp().
ice_ptp_tx_tstamps_pending() gains an in_irq argument so the interrupt
re-arm path ignores stale slots while the periodic worker still sees them.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_main.c b/drivers/net/ethernet/intel/ice/ice_main.c
index f32041dd8b274..d12952171a991 100644
--- a/drivers/net/ethernet/intel/ice/ice_main.c
+++ b/drivers/net/ethernet/intel/ice/ice_main.c
@@ -3248,7 +3248,7 @@ static irqreturn_t ice_misc_intr_thread_fn(int __always_unused irq, void *data)
 	ice_irq_dynamic_ena(hw, NULL, NULL);
 	ice_flush(hw);
 
-	if (ice_ptp_tx_tstamps_pending(pf)) {
+	if (ice_ptp_tx_tstamps_pending(pf, true)) {
 		/* If any new Tx timestamps happened while in interrupt,
 		 * re-arm the interrupt to trigger it again.
 		 */
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index b142b779dc105..299de9d494233 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -364,9 +364,12 @@ static u64 ice_ptp_extend_40b_ts(struct ice_pf *pf, u64 in_tstamp)
 static bool
 ice_ptp_is_tx_tracker_up(struct ice_ptp_tx *tx)
 {
+	struct ice_ptp_port *ptp_port =
+		container_of(tx, struct ice_ptp_port, tx);
+
 	lockdep_assert_held(&tx->lock);
 
-	return tx->init && !tx->calibrating;
+	return tx->init && !tx->calibrating && ptp_port->link_up;
 }
[Severity: Medium]
With link_up now part of the tracker-up test, is the E825 PHY restart path
still covered?

ice_ptp_link_change() sets ptp_port->link_up = true before it reaches
ice_ptp_port_phy_restart(), and for ICE_MAC_GENERIC_3K_E825 that function
calls ice_start_phy_timer_eth56g() without setting tx.calibrating:

	case ICE_MAC_GENERIC:
		...
		ptp_port->tx.calibrating = true;
		...
	case ICE_MAC_GENERIC_3K_E825:
		err = ice_start_phy_timer_eth56g(hw, port);
		break;

So during the E825 restart ice_ptp_is_tx_tracker_up() returns true
(tx->init && !tx->calibrating && ptp_port->link_up), and
ice_ptp_request_ts() keeps handing out indices while
ice_start_phy_timer_eth56g() walks the Tx timestamp memory and soft resets
the PHY.

The window is also reachable while the carrier stays up: ice_link_event()
continues past

	if (link_up == old_link && link_speed == old_link_speed)
		return 0;

on a speed-only change and calls ice_ptp_link_change(pf, link_up) while the
netdev is transmitting.

Are the captures requested in that window simply lost until the two second
timeout, and should tx.calibrating be set around the E825 restart as it is
for the E82X case?
quoted hunk
@@ -563,7 +566,6 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 	struct ice_pf *pf;
 	struct ice_hw *hw;
 	u64 tstamp_ready;
-	bool link_up;
 	int err;
 	u8 idx;
 
@@ -581,14 +583,11 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 			return;
 	}
 
-	/* Drop packets if the link went down */
-	link_up = ptp_port->link_up;
-
 	for_each_set_bit(idx, tx->in_use, tx->len) {
 		struct skb_shared_hwtstamps shhwtstamps = {};
 		u8 phy_idx = idx + tx->offset;
 		u64 raw_tstamp = 0, tstamp;
-		bool drop_ts = !link_up;
+		bool drop_ts = false;
 		struct sk_buff *skb;
 
 		/* Drop packets which have waited for more than 2 seconds */
@@ -1371,6 +1370,9 @@ void ice_ptp_link_change(struct ice_pf *pf, bool linkup)
 	if (pf->hw.reset_ongoing)
 		goto exit_kref_put;
 
+	if (!linkup)
+		ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);
+
[Severity: High]
Who runs the processing pass that applies the two second timeout on E810,
and on PFs that are not the source timer owner?

After this hunk the stale slots are only released from inside
ice_ptp_process_tx_tstamp(), by

		if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ)) {
			drop_ts = true;

which requires something to schedule another pass. The candidates all look
closed on those configurations:

ice_misc_intr_thread_fn() re-arm now passes in_irq = true, and
ice_port_has_timestamps(tx, true) returns
bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len), which is 0 for an
all-stale tracker. The hardware fall-through in
ice_ptp_tx_tstamps_pending() does not help on E810 either, since
ice_check_phy_tx_tstamp_ready_e810() is:

	static int ice_check_phy_tx_tstamp_ready_e810(struct ice_hw *hw)
	{
		return 0;
	}

The replacement periodic sweep is not reached on those devices, see the
early returns in ice_ptp_maybe_trigger_tx_interrupt() quoted in the last
hunk below.

No hardware TSYN_TX interrupt can arrive either, because the packets whose
captures are outstanding were not transmitted, and
ice_ptp_is_tx_tracker_up() now rejects new requests while link_up is false.

Does this mean the index and the skb reference taken in
ice_ptp_request_ts() by

		tx->tstamps[idx].skb = skb_get(skb);
		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;

stay held for the entire link-down period?

There is a second effect worth checking. Requests are still accepted
between the physical link loss and the firmware link event, so the tracker
can fill up (tx->len is INDEX_PER_PORT, 64, on E810). If every index is
in_use and stale, does ice_ptp_request_ts() then return -1 for every packet
after link-up, so no timestamp is requested, no TSYN_TX interrupt is
generated, no processing pass ever runs, and Tx timestamping for that port
stays dead until a PF reset or driver reload? ice_ptp_flush_tx_tracker() is
only reached from ice_ptp_release_tx_tracker() and the clock-owner rebuild
path, and ice_ptp_link_change() returns early for E810 and E830 without
flushing.

For reference, the E810 low latency read sub-path does not appear affected:
ice_ll_ts_intr() -> ice_ptp_req_tx_single_tstamp() keeps walking in_use on
every LL_TS interrupt, so the timeout still fires there.
 	if (hw->mac_type == ICE_MAC_GENERIC_3K_E825 &&
 	    test_bit(ICE_FLAG_DPLL, pf->flags)) {
 		int pin, err;
[ ... ]
quoted hunk
@@ -2767,28 +2769,29 @@ void ice_ptp_process_ts(struct ice_pf *pf)
 	}
 }
 
-static bool ice_port_has_timestamps(struct ice_ptp_tx *tx)
+static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)
 {
-	bool more_timestamps;
+	DECLARE_BITMAP(tstamps, INDEX_PER_PORT_MAX) = {};
 
 	scoped_guard(spinlock_irqsave, &tx->lock) {
 		if (!tx->init)
 			return false;
 
-		more_timestamps = !bitmap_empty(tx->in_use, tx->len);
+		if (in_irq)
+			return bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len);
+		else
+			return !bitmap_empty(tx->in_use, tx->len);
 	}
-
-	return more_timestamps;
 }
[ ... ]
quoted hunk
-bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
+bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
 	struct ice_hw *hw = &pf->hw;
 	int ret;
@@ -2808,11 +2811,11 @@ bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf)
 	case ICE_PTP_TX_INTERRUPT_NONE:
 		return false;
 	case ICE_PTP_TX_INTERRUPT_SELF:
-		if (ice_port_has_timestamps(&pf->ptp.port.tx))
+		if (ice_port_has_timestamps(&pf->ptp.port.tx, in_irq))
 			return true;
 		break;
 	case ICE_PTP_TX_INTERRUPT_ALL:
-		if (ice_any_port_has_timestamps(pf))
+		if (ice_any_port_has_timestamps(pf, in_irq))
 			return true;
 		break;
 	default:
[Severity: Low]
This is a pre-existing issue and is not introduced by this patch, and on
closer reading it looks like a non-issue here, but it is worth noting for
completeness.

The commit message says the in_irq filtering avoids an IRQ storm, while
after the switch above ice_ptp_tx_tstamps_pending() still falls through to
the unconditional hardware check:

	/* Check hardware indicator */
	ret = ice_check_phy_tx_tstamp_ready(hw);

A PHY ready bit with no in_use owner would therefore still make
ice_misc_intr_thread_fn() and the ICE_MAC_E830 branch of ice_ptp_ts_irq()
re-arm PFINT_OICR(TSYN_TX) regardless of in_irq. Such an unowned ready bit
can be produced by the timeout path in ice_ptp_process_tx_tstamp(), which
clears in_use without reading or clearing the PHY slot:

		if (tx->has_ready_bitmap &&
		    !(tstamp_ready & BIT_ULL(phy_idx))) {
			if (drop_ts)
				goto skip_ts_read;

That release-without-read is unchanged from before this patch and is
described as intentional in the ice_ptp_process_tx_tstamp() kernel-doc. For
the case the commit message actually describes, a stale packet that is
never timestamped, no ready bit is ever set, so the hardware check returns
0 and the stated goal holds. The remaining hardware check is also removed
later in this series by "ice: don't clear in_use until HW clears ready
bitmap", which makes ice_ptp_tx_tstamps_pending() consult only the software
tracker.
quoted hunk
@@ -2918,7 +2921,6 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 {
 	struct device *dev = ice_pf_to_dev(pf);
 	struct ice_hw *hw = &pf->hw;
-	int ret;
 
 	if (!pf->ptp.port.tx.has_ready_bitmap)
 		return;
@@ -2926,11 +2928,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 	if (!ice_pf_src_tmr_owned(pf))
 		return;
 
-	ret = ice_check_phy_tx_tstamp_ready(hw);
-	if (ret < 0) {
-		dev_dbg(dev, "PTP periodic task unable to read PHY timestamp ready bitmap, err %d\n",
-			ret);
-	} else if (ret) {
+	if (ice_ptp_tx_tstamps_pending(pf, false)) {
[Severity: High]
Is the new in_irq = false check reachable on the devices that need it?

ice_ptp_init_tx() sets:

	tx->has_ready_bitmap = pf->hw.mac_type != ICE_MAC_E810;

so the first early return above skips every E810 PF, and the
ice_pf_src_tmr_owned() return skips PFs that are not the source timer
owner, including ICE_PTP_TX_INTERRUPT_SELF PFs on E830. Combined with the
suppressed re-arm in ice_misc_intr_thread_fn(), does anything on those
devices ever call ice_ptp_process_tx_tstamp() again so the two second
timeout can release the stale slots?

Would moving the ice_ptp_tx_tstamps_pending(pf, false) check ahead of the
has_ready_bitmap and ice_pf_src_tmr_owned() early returns give the
software tracker a guaranteed sweep on all configurations?

Re: [PATCH net 12/15] ice: remove unnecessary discarding of timestamps after clock adjust

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:27

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: remove unnecessary discarding of timestamps after clock adjust

This drops the ice_ptp_mark_tx_tracker_stale() call from
ice_ptp_reset_cached_phctime(), so outstanding Tx timestamps are no longer
discarded when the PHC is adjusted or set.  The stale bit itself is kept and
is now only used by the link-down path.  The kernel-doc of
ice_ptp_mark_tx_tracker_stale() is updated to describe the new meaning.

A couple of questions below.
    The original motivation for discarding timestamps was that extending an old
    timestamp using the new cached value of PHC was a problem, as it could
    produce incorrect results. The change did not describe what such "incorrect
    results" were.

    There are no such incorrect results. Extending the 32 bit timestamp with
    the new time value just means that the timestamp is reported in terms of
    the newly updated and adjusted system clock.
[Severity: High]
Does this hold for the .settime path too, or only for the atomic .adjtime
path?

ice_ptp_adjtime() only uses the atomic ice_ptp_write_adj() path for
|delta| <= S32_MAX:

	if (delta > S32_MAX || delta < S32_MIN) {
		dev_dbg(dev, "delta = %lld, adjtime non-atomic\n", delta);

Larger deltas, and every clock_settime() on /dev/ptpN, take
ice_ptp_adjtime_nonatomic() -> ice_ptp_settime64() -> ice_ptp_write_init()
-> ice_ptp_init_time(), which replaces the 64-bit source timer with an
arbitrary new value rather than shifting it by a bounded amount.

The PHY Tx timestamp memory keeps only the captured low 32 bits of nominal
nanoseconds, and ice_ptp_settime64() only restarts the PHY blocks on E82X:

	/* Recalibrate and re-enable timestamp blocks for E822/E823 */
	if (hw->mac_type == ICE_MAC_GENERIC)
		ice_ptp_restart_all_phy(pf);

So on ICE_MAC_E810 / ICE_MAC_E830 a capture taken just before the step is
still in the PHY bank with its ready bit set when
ice_ptp_process_tx_tstamp() runs after the cache was replaced.  With the
stale bit no longer set, that slot passes the drop checks and
ice_ptp_extend_40b_ts() extends the pre-step low 32 bits with the post-step
READ_ONCE(pf->ptp.cached_phc_time).

Isn't the value produced by ice_ptp_extend_32b_ts() then the alias of the
real event time nearest the new clock, i.e. off by (step mod 2^32), which is
up to about +/- 2.147 s and can even be dated after the new PHC value?  The
same function documents that this is outside its stated assumption:

 * 2) assume that the in_tstamp was captured 2^31 nanoseconds (~2.1
 *    seconds) before or after the PHC time was captured.

Separately, is the reverse branch safe when the clock is set to a small
absolute value (for example phc_ctl set 0)?

	if (delta > (U32_MAX / 2)) {
		/* reverse the delta calculation here */
		delta = (phc_time_lo - in_tstamp);
		ns = cached_phc_time - delta;

With cached_phc_time smaller than delta this u64 subtraction wraps, and
ns_to_ktime() then reports a negative hardware timestamp.  Previously the
stale bit hid this case; now it is reachable.

In both cases the result is counted in pf->ptp.tx_hwtstamp_good and handed
to skb_tstamp_tx() rather than dropped.  Would it make sense to keep
discarding outstanding captures for the settime and large-adjtime paths, or
alternatively to restrict the claim in the commit message to the atomic
adjustment case and note the trade-off (a wrong timestamp delivered instead
of no timestamp)?
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 299de9d494233..277d9c77af1cb 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -826,12 +826,10 @@ ice_ptp_flush_tx_tracker(struct ice_pf *pf, struct ice_ptp_tx *tx)
  * ice_ptp_mark_tx_tracker_stale - Mark unfinished timestamps as stale
  * @tx: the tracker to mark
  *
- * Mark currently outstanding Tx timestamps as stale. This prevents sending
- * their timestamp value to the stack. This is required to prevent extending
- * the 40bit hardware timestamp incorrectly.
- *
- * This should be called when the PTP clock is modified such as after a set
- * time request.
+ * Mark currently outstanding Tx timestamps as stale. This prevents the driver
+ * from reporting the timestamp to the stack. This is called to inform the
+ * driver that a timestamp is expected to fail if it was initiated as the link
+ * went down.
  */
 static void
 ice_ptp_mark_tx_tracker_stale(struct ice_ptp_tx *tx)
[Severity: Low]
This kernel-doc is updated, but two other comment blocks still describe the
removed behaviour.  Should they be updated in the same patch?

The kernel-doc of ice_ptp_reset_cached_phctime(), the function being changed
here, still says:

 * This function must be called when the cached PHC time is no longer valid,
 * such as after a time adjustment. It marks any currently outstanding Tx
 * timestamps as stale and updates the cached PHC time for both the PF and Rx
 * rings.

And the kernel-doc of ice_ptp_process_tx_tstamp() still says the opposite of
the reasoning in this commit message:

 * In cases where the PTP hardware clock was directly adjusted, some
 * timestamps may not be able to safely use the timestamp extension math. In
 * this case, software will set the stale bit for any outstanding Tx
 * timestamps when the clock is adjusted. Then this function will discard
 * those captured timestamps instead of sending them to the stack.

After this patch the only caller of ice_ptp_mark_tx_tracker_stale() is the
link-down branch in ice_ptp_link_change():

	if (!linkup)
		ice_ptp_mark_tx_tracker_stale(&ptp_port->tx);

so nothing sets the stale bit on a clock adjustment any more.  Both comments
are still present unchanged at the end of the series.
quoted hunk
 static void
@@ -1049,13 +1047,6 @@ static void ice_ptp_reset_cached_phctime(struct ice_pf *pf)
 		kthread_queue_delayed_work(pf->ptp.kworker, &pf->ptp.work,
 					   msecs_to_jiffies(10));
 	}
-
-	/* Mark any outstanding timestamps as stale, since they might have
-	 * been captured in hardware before the time update. This could lead
-	 * to us extending them with the wrong cached value resulting in
-	 * incorrect timestamp values.
-	 */
-	ice_ptp_mark_tx_tracker_stale(&pf->ptp.port.tx);
 }

 /**

Re: [PATCH net 14/15] ice: don't clear in_use until HW clears ready bitmap

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:28

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: don't clear in_use until HW clears ready bitmap

On E825 the PHY can leave the Tx timestamp ready bit and valid bit set
around a link down transition, so this change re-reads the ready bitmap
after each PHY timestamp read and marks the index stale if it is still
set. It also moves the hardware ready bitmap check out of
ice_ptp_tx_tstamps_pending() into the watchdog and stops re-arming the
interrupt while the tracker is calibrating or link is down.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index e060de5026621..832cc6ad2f90c 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -620,6 +620,19 @@ static void ice_ptp_process_tx_tstamp(struct ice_ptp_tx *tx)
 		if (err && !drop_ts)
 			continue;
 
+		/* verify ready bit cleared */
+		if (tx->has_ready_bitmap) {
+			err = ice_get_phy_tx_tstamp_ready(hw, tx->block, &tstamp_ready);
+			if (err || tstamp_ready & BIT_ULL(phy_idx)) {
+				spin_lock_irqsave(&tx->lock, flags);
+				if (!test_and_set_bit(idx, tx->stale))
+					dev_dbg(ice_pf_to_dev(pf), "PHY port %u failed to clear ready bit for idx %u\n",
+						ptp_port->port_num, phy_idx);
+				spin_unlock_irqrestore(&tx->lock, flags);
+				continue;
+			}
+		}
+
[Severity: High]
Should this new gate honour drop_ts the way the ready-bit gate just above
it does?

The earlier gate in ice_ptp_process_tx_tstamp() jumps to skip_ts_read when
the 2 second deadline has already passed:

	if (tx->has_ready_bitmap &&
	    !(tstamp_ready & BIT_ULL(phy_idx))) {
		if (drop_ts)
			goto skip_ts_read;

		continue;
	}

The new block instead does a plain continue, so for an index whose ready
bit stays set the skip_ts_read block is never reached:

skip_ts_read:
		spin_lock_irqsave(&tx->lock, flags);
		...
		clear_bit(idx, tx->in_use);
		skb = tx->tstamps[idx].skb;
		tx->tstamps[idx].skb = NULL;

That is the only place in this function that clears in_use, detaches the
SKB and later calls dev_kfree_skb_any() on the reference taken by
skb_get() in ice_ptp_request_ts(). Does this mean the 2 second timeout
reclaim no longer works for exactly the stuck-ready-bit case the patch
targets, holding up to INDEX_PER_PORT (64) SKBs per port, each with
SKBTX_IN_PROGRESS still set and each pinning its socket, for as long as
the condition lasts?

On this path ice_ptp_link_change() only calls
ice_ptp_mark_tx_tracker_stale() for ICE_MAC_GENERIC_3K_E825 and does not
flush, and ice_ptp_flush_tx_tracker() is only reached from
ice_ptp_release_tx_tracker() and ice_ptp_flush_all_tx_tracker(), so is
recovery dependent on a link-up event that may never arrive while a cable
stays unplugged?

There is a second effect from the same continue. Because the entry is
never released and tx->tstamps[idx].start is never refreshed, this block
in the same loop runs again on every pass:

		if (time_is_before_jiffies(tx->tstamps[idx].start + 2 * HZ)) {
			drop_ts = true;

			/* Count the number of Tx timestamps that timed out */
			pf->ptp.tx_hwtstamp_timeouts++;
		}

With the watchdog seeing ice_check_phy_tx_tstamp_ready() > 0 and
re-triggering the IRQ every 500 ms, does tx_hwtstamp_timeouts grow by
roughly two per second per stuck index without any SKB actually being
discarded? That counter is user visible both as ethtool -S
tx_hwtstamp_timeouts in ice_gstrings_pf_stats and as the standard
ts_stats lost field filled in by ice_ptp_get_ts_stats(), and it is
documented as the number of Tx skbs discarded with no time stamp.
quoted hunk
 		ice_trace(tx_tstamp_fw_done, tx->tstamps[idx].skb, idx);
 
 		/* For PHYs which don't implement a proper timestamp ready
@@ -2768,10 +2781,14 @@ static bool ice_port_has_timestamps(struct ice_ptp_tx *tx, bool in_irq)
 		if (!tx->init)
 			return false;
 
-		if (in_irq)
+		if (in_irq) {
+			if (!ice_ptp_is_tx_tracker_up(tx))
+				return false;
+
 			return bitmap_andnot(tstamps, tx->in_use, tx->stale, tx->len);
-		else
+		} else {
 			return !bitmap_empty(tx->in_use, tx->len);
+		}
 	}
 }
 
@@ -2794,41 +2811,18 @@ static bool ice_any_port_has_timestamps(struct ice_pf *pf, bool in_irq)
 
 bool ice_ptp_tx_tstamps_pending(struct ice_pf *pf, bool in_irq)
 {
-	struct ice_hw *hw = &pf->hw;
-	int ret;
-
-	/* Check software indicator */
 	switch (pf->ptp.tx_interrupt_mode) {
 	case ICE_PTP_TX_INTERRUPT_NONE:
 		return false;
 	case ICE_PTP_TX_INTERRUPT_SELF:
-		if (ice_port_has_timestamps(&pf->ptp.port.tx, in_irq))
-			return true;
-		break;
+		return ice_port_has_timestamps(&pf->ptp.port.tx, in_irq);
 	case ICE_PTP_TX_INTERRUPT_ALL:
-		if (ice_any_port_has_timestamps(pf, in_irq))
-			return true;
-		break;
+		return ice_any_port_has_timestamps(pf, in_irq);
 	default:
 		WARN_ONCE(1, "Unexpected Tx timestamp interrupt mode %u\n",
 			  pf->ptp.tx_interrupt_mode);
-		break;
-	}
-
-	/* Check hardware indicator */
-	ret = ice_check_phy_tx_tstamp_ready(hw);
-	if (ret < 0) {
-		dev_dbg(ice_pf_to_dev(pf), "Unable to read PHY Tx timestamp ready bitmap, err %d\n",
-			ret);
-		/* Stop triggering IRQs if we're unable to read PHY */
 		return false;
 	}
-
-	/* ice_check_phy_tx_tstamp_ready() returns 1 if there are timestamps
-	 * available, 0 if there are no waiting timestamps, and a negative
-	 * value if there was an error (which we checked for above).
-	 */
-	return ret > 0;
 }
[Severity: High]
After this change, which code path still consults the hardware ready
bitmap for an E830 PF that does not own the source timer?

ice_ptp_init_tx_interrupt_mode() assigns ICE_PTP_TX_INTERRUPT_SELF to
E810 and E830 PFs in the default arm of its switch, owner or not. The
hardware check has moved into ice_ptp_maybe_trigger_tx_interrupt(), which
returns before reaching it:

	if (!ice_pf_src_tmr_owned(pf))
		return;

	ret = ice_check_phy_tx_tstamp_ready(hw);

So for a non-owner E830 PF, both remaining re-arm sites,
ice_ptp_ts_irq() (case ICE_MAC_E830) and ice_misc_intr_thread_fn(), now
decide purely from the software tracker, and that decision was further
narrowed by the new ice_ptp_is_tx_tracker_up() early return and by
masking stale bits with bitmap_andnot().

The commit message says:

    Continue to check the hardware ready bitmap from the watchdog to catch
    cases of unexpected timestamps.

Does that hold only for clock-owner PFs? The kernel-doc of
ice_ptp_maybe_trigger_tx_interrupt() states the hardware will not
interrupt again until all current timestamp data is read, so does a valid
bit that software never reads block all further Tx timestamp interrupts on
that PF until a reset?

Two ways such an orphaned bit looks reachable on E830:

  - link down runs ice_ptp_mark_tx_tracker_stale(), which ORs stale over
    in_use and clears link_up, so the in_irq check returns false while the
    hardware bits for in-flight captures get set and are never read.

  - ice_ptp_flush_tx_tracker() clears in_use for an entry whose capture
    completes after the 10 ms drain wait, and ice_clear_phy_tstamp() has
    no E830 case (-EOPNOTSUPP), so the valid bit stays set with no
    software owner.

E810 looks unaffected since ice_check_phy_tx_tstamp_ready_e810() always
returns 0, and E82x uses the ALL/NONE modes where the owner still runs the
watchdog.
quoted hunk
 
 /**
@@ -2912,6 +2906,7 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 {
 	struct device *dev = ice_pf_to_dev(pf);
 	struct ice_hw *hw = &pf->hw;
+	int ret;
 
 	if (!pf->ptp.port.tx.has_ready_bitmap)
 		return;
@@ -2919,7 +2914,15 @@ static void ice_ptp_maybe_trigger_tx_interrupt(struct ice_pf *pf)
 	if (!ice_pf_src_tmr_owned(pf))
 		return;
 
-	if (ice_ptp_tx_tstamps_pending(pf, false)) {
+	ret = ice_check_phy_tx_tstamp_ready(hw);
+	if (ret < 0) {
+		dev_dbg(dev, "Unable to read PHY Tx timestamp ready bitmap, err %pe\n",
+			ERR_PTR(ret));
+		/* Don't trigger an IRQ if we are unable to access the PHY */
+		return;
+	}
+
+	if (ret > 0 || ice_ptp_tx_tstamps_pending(pf, false)) {
 		dev_dbg(dev, "PTP periodic task detected waiting timestamps. Triggering Tx timestamp interrupt now.\n");
 
 		wr32(hw, PFINT_OICR, PFINT_OICR_TSYN_TX_M);
[Severity: Medium]
One more question on a related patch in this series rather than on this
hunk. The patch "ice: fix removal of PTP timestamp tracker during reset"
dropped the ice_ptp_release_tx_tracker() call from
ice_ptp_prepare_for_reset(), so the tracker now survives a reset.

If ice_ptp_rebuild() then fails, it takes the err label:

err:
	ptp->state = ICE_PTP_ERROR;
	dev_err(ice_pf_to_dev(pf), "PTP reset failed %d\n", err);

A later ice_ptp_rebuild() cannot recover, since the state is neither
READY nor RESETTING and it returns -EINVAL. On unload, ice_ptp_release()
takes the abbreviated branch:

	if (pf->ptp.state != ICE_PTP_READY) {
		mutex_destroy(&pf->ptp.port.ps_lock);
		ice_ptp_cleanup_pf(pf);
		if (pf->ptp.clock) {
			ptp_clock_unregister(pf->ptp.clock);
			pf->ptp.clock = NULL;
		}
		return;
	}

which never calls ice_ptp_release_tx_tracker(). Does this leak the
tstamps array and the two bitmaps allocated in
ice_ptp_alloc_tx_tracker(), along with any SKB references still held in
tx->tstamps[].skb, since ice_ptp_flush_tx_tracker() is not reached
either? Would releasing the tracker in the non-READY branch of
ice_ptp_release(), or on the ice_ptp_rebuild() error path, be
appropriate?

Re: [PATCH net 15/15] ice: Recalibrate PHY after settime64 on E825-C

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-09-16 01:12:29

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: Recalibrate PHY after settime64 on E825-C

The patch extends the post-settime PHY recalibration path in
ice_ptp_settime64() so that it also runs for ICE_MAC_GENERIC_3K_E825,
not only for ICE_MAC_GENERIC (E822/E823). The stated motivation is that
the E825-C PHY needs recalibration after large system time jumps,
otherwise PHY timestamps read back incorrectly.
quoted hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_ptp.c b/drivers/net/ethernet/intel/ice/ice_ptp.c
index 832cc6ad2f90c..a5efb0a95941e 100644
--- a/drivers/net/ethernet/intel/ice/ice_ptp.c
+++ b/drivers/net/ethernet/intel/ice/ice_ptp.c
@@ -2020,8 +2020,9 @@ ice_ptp_settime64(struct ptp_clock_info *info, const struct timespec64 *ts)
 	/* Reenable periodic outputs */
 	ice_ptp_enable_all_perout(pf);
 
-	/* Recalibrate and re-enable timestamp blocks for E822/E823 */
-	if (hw->mac_type == ICE_MAC_GENERIC)
+	/* Recalibrate and re-enable timestamp blocks for E822/E823/E825-C */
+	if (hw->mac_type == ICE_MAC_GENERIC ||
+	    hw->mac_type == ICE_MAC_GENERIC_3K_E825)
 		ice_ptp_restart_all_phy(pf);
[Severity: High]

The E82X sequence has two halves: timestamps are invalidated before the
time is written, and the PHY is recalibrated afterwards.  Only the second
half is being extended to E825-C here.  Earlier in ice_ptp_settime64() the
invalidation is still E82X only:

	if (hw->mac_type == ICE_MAC_GENERIC) {
		err = ice_ptp_clear_phy_offset_ready_e82x(hw);
		if (err)
			dev_warn(ice_pf_to_dev(pf), "Failed to mark timestamps as invalid before settime\n");
	}

Should something equivalent run for ICE_MAC_GENERIC_3K_E825?  There is no
eth56g counterpart of ice_ptp_clear_phy_offset_ready_e82x() in the tree, and
ice_stop_phy_timer_eth56g() clears only PHY_REG_RX_OFFSET_READY, so
PHY_REG_TX_OFFSET_READY stays at 1 for the whole restart window while the
PHY timer is being re-initialised.

Related to that, the ICE_MAC_GENERIC_3K_E825 branch of
ice_ptp_port_phy_restart() does not quiesce the Tx tracker at all:

	case ICE_MAC_GENERIC_3K_E825:
		err = ice_start_phy_timer_eth56g(hw, port);
		break;

whereas the ICE_MAC_GENERIC branch does:

		spin_lock_irqsave(&ptp_port->tx.lock, flags);
		ptp_port->tx.calibrating = true;
		spin_unlock_irqrestore(&ptp_port->tx.lock, flags);

tx.calibrating is only ever set in that E82X branch and only ever cleared in
ice_ptp_wait_for_offsets(), so on E825-C ice_ptp_is_tx_tracker_up() keeps
returning true (link_up is true, which is why the port was picked in
ice_ptp_restart_all_phy()):

	return tx->init && !tx->calibrating && ptp_port->link_up;

Does that mean Tx timestamp requests keep being armed and reported while
ice_start_phy_timer_eth56g() is soft-resetting the PTP block, reprogramming
PHY_REG_TIMETUS/INCVAL and re-running ice_sync_phy_timer_eth56g()?  The
comment in the ICE_MAC_GENERIC branch describes that state as producing bad
values:

		/* Do not clear calibrating flag here. Tx timestamps remain
		 * disabled until ice_ptp_wait_for_offsets() has verified
		 * that the Tx and Rx offset calibration has completed.
		 * Clearing it here would allow Tx timestamps to be reported
		 * before the PHY offset registers are configured, leading
		 * to incorrect timestamp values.
		 */

One more effect of the same window: the call chain
ice_start_phy_timer_eth56g() -> ice_ptp_phy_soft_reset_eth56g() ->
ice_ptp_clear_tx_memory_status_eth56g() clears every hardware Tx timestamp
entry:

	for (idx = 0; idx < INDEX_PER_PORT; idx++) {
		err = ice_clear_ptp_tstamp_eth56g(hw, port, idx);

but the software tracker keeps its in_use bits and skbs, since nothing calls
ice_ptp_mark_tx_tracker_stale() on this path.  Can those already-outstanding
requests then only complete via the 2 second timeout?

[Severity: Medium]

This is also the first E825-C path that can reach ice_ptp_port_phy_stop(),
because ice_ptp_link_change() only calls ice_ptp_port_phy_restart() on a
link-up transition for this MAC type:

	case ICE_MAC_GENERIC_3K_E825:
		if (linkup)
			ice_ptp_port_phy_restart(ptp_port);
		goto exit_kref_put;

ice_ptp_port_phy_restart() tests link_up outside ps_lock:

	if (!ptp_port->link_up)
		return ice_ptp_port_phy_stop(ptp_port);

	mutex_lock(&ptp_port->ps_lock);

and ice_ptp_link_change() stores that field with no lock held:

	/* Update cached link status for this port immediately */
	ptp_port->link_up = linkup;

Can a link bounce concurrent with a settime invert the decision here?

CPU0 (ice_ptp_settime64 -> ice_ptp_restart_all_phy)
	if (port->link_up)                /* true */
		ice_ptp_port_phy_restart(port);
	   -> !ptp_port->link_up          /* link just went down */
	   -> ice_ptp_port_phy_stop()     /* waits on ps_lock */

CPU1 (link comes back up)
	ice_ptp_link_change()
	   ptp_port->link_up = true;
	   ice_ptp_port_phy_restart()
	      -> ice_start_phy_timer_eth56g()   /* holds ps_lock, takes ms */

CPU0 then acquires ps_lock and issues ice_stop_phy_timer_eth56g(hw, port,
true), which does:

	err = ice_write_ptp_reg_eth56g(hw, port, PHY_REG_RX_OFFSET_READY, 0);

Since the E825-C link handler only restarts on link-up, would this leave the
port with the link up but Rx offsets marked not ready, with no recovery until
the next link flap?

The unlocked check itself predates this change on the E82x path, but the
E825-C path that can now hit it is new here.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help